Export multiple versions of JSON for different Meshviewers (#16)
* multi json output format * fix memory usage in older JSON * meshviewer versions add detailed comments * some beautiful fixes in modes/nodes
This commit is contained in:
parent
e586dad6d5
commit
798db6a063
|
@ -1,8 +1,6 @@
|
|||
package meshviewer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/FreifunkBremen/respond-collector/data"
|
||||
"github.com/FreifunkBremen/respond-collector/jsontime"
|
||||
)
|
||||
|
@ -22,12 +20,20 @@ type Flags struct {
|
|||
Gateway bool `json:"gateway"`
|
||||
}
|
||||
|
||||
// Nodes struct: cache DB of Node's structs
|
||||
type Nodes struct {
|
||||
// NodesV1 struct, to support legacy meshviewer (which are in master branch)
|
||||
// i.e. https://github.com/ffnord/meshviewer/tree/master
|
||||
type NodesV1 struct {
|
||||
Version int `json:"version"`
|
||||
Timestamp jsontime.Time `json:"timestamp"`
|
||||
List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NodesV2 struct, to support new version of meshviewer (which are in legacy develop branch or newer)
|
||||
// i.e. https://github.com/ffnord/meshviewer/tree/dev or https://github.com/ffrgb/meshviewer/tree/develop
|
||||
type NodesV2 struct {
|
||||
Version int `json:"version"`
|
||||
Timestamp jsontime.Time `json:"timestamp"`
|
||||
List []*Node `json:"nodes"` // the current nodemap, as array
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
|
@ -35,7 +41,7 @@ type Statistics struct {
|
|||
Clients uint32 `json:"clients"`
|
||||
RootFsUsage float64 `json:"rootfs_usage,omitempty"`
|
||||
LoadAverage float64 `json:"loadavg,omitempty"`
|
||||
Memory data.Memory `json:"memory,omitempty"`
|
||||
MemoryUsage float64 `json:"memory_usage,omitempty"`
|
||||
Uptime float64 `json:"uptime,omitempty"`
|
||||
Idletime float64 `json:"idletime,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
|
@ -52,3 +58,28 @@ type Statistics struct {
|
|||
MgmtRx *data.Traffic `json:"mgmt_rx"`
|
||||
} `json:"traffic,omitempty"`
|
||||
}
|
||||
|
||||
func NewStatistics(stats *data.Statistics) *Statistics {
|
||||
total := stats.Clients.Total
|
||||
if total == 0 {
|
||||
total = stats.Clients.Wifi24 + stats.Clients.Wifi5
|
||||
}
|
||||
/* The Meshviewer could not handle absolute memory output
|
||||
* calc the used memory as a float witch 100% equal 1.0
|
||||
*/
|
||||
memoryUsage := (float64(stats.Memory.Total) - float64(stats.Memory.Free)) / float64(stats.Memory.Total)
|
||||
|
||||
return &Statistics{
|
||||
NodeId: stats.NodeId,
|
||||
Gateway: stats.Gateway,
|
||||
RootFsUsage: stats.RootFsUsage,
|
||||
LoadAverage: stats.LoadAverage,
|
||||
MemoryUsage: memoryUsage,
|
||||
Uptime: stats.Uptime,
|
||||
Idletime: stats.Idletime,
|
||||
Processes: stats.Processes,
|
||||
MeshVpn: stats.MeshVpn,
|
||||
Traffic: stats.Traffic,
|
||||
Clients: total,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ type Config struct {
|
|||
} `yaml:"webserver"`
|
||||
Nodes struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
NodesPath string `yaml:"nodes_path"`
|
||||
NodesMiniPath string `yaml:"nodesmini_path"`
|
||||
NodesDynamicPath string `yaml:"nodes_path"`
|
||||
NodesV1Path string `yaml:"nodesv1_path"`
|
||||
NodesV2Path string `yaml:"nodesv2_path"`
|
||||
GraphsPath string `yaml:"graphs_path"`
|
||||
AliasesPath string `yaml:"aliases_path"`
|
||||
SaveInterval int `yaml:"saveinterval"` // Save nodes every n seconds
|
||||
|
|
|
@ -28,11 +28,14 @@ func NewNodes(config *Config) *Nodes {
|
|||
config: config,
|
||||
}
|
||||
|
||||
if config.Nodes.NodesPath != "" {
|
||||
if config.Nodes.NodesDynamicPath != "" {
|
||||
nodes.load()
|
||||
}
|
||||
|
||||
nodes.Version = 2
|
||||
/**
|
||||
* Version '-1' because the nodes.json would not be defined,
|
||||
* it would be change with the change of the respondd application on gluon
|
||||
*/
|
||||
nodes.Version = -1
|
||||
return nodes
|
||||
}
|
||||
|
||||
|
@ -83,51 +86,53 @@ func (nodes *Nodes) Update(nodeID string, res *data.ResponseData) *Node {
|
|||
return node
|
||||
}
|
||||
|
||||
// GetNodesMini get meshviewer valide JSON
|
||||
func (nodes *Nodes) GetNodesMini() *meshviewer.Nodes {
|
||||
meshviewerNodes := &meshviewer.Nodes{
|
||||
// GetNodesV1 transform data to legacy meshviewer
|
||||
func (nodes *Nodes) GetNodesV1() *meshviewer.NodesV1 {
|
||||
meshviewerNodes := &meshviewer.NodesV1{
|
||||
Version: 1,
|
||||
List: make(map[string]*meshviewer.Node),
|
||||
Timestamp: nodes.Timestamp,
|
||||
}
|
||||
|
||||
for nodeID := range nodes.List {
|
||||
node, _ := meshviewerNodes.List[nodeID]
|
||||
nodeOrigin := nodes.List[nodeID]
|
||||
|
||||
if nodeOrigin.Statistics == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
node = &meshviewer.Node{
|
||||
node := &meshviewer.Node{
|
||||
Firstseen: nodeOrigin.Firstseen,
|
||||
Lastseen: nodeOrigin.Lastseen,
|
||||
Flags: nodeOrigin.Flags,
|
||||
Nodeinfo: nodeOrigin.Nodeinfo,
|
||||
}
|
||||
node.Statistics = meshviewer.NewStatistics(nodeOrigin.Statistics)
|
||||
meshviewerNodes.List[nodeID] = node
|
||||
}
|
||||
return meshviewerNodes
|
||||
}
|
||||
|
||||
// Calculate Total
|
||||
total := nodeOrigin.Statistics.Clients.Total
|
||||
if total == 0 {
|
||||
total = nodeOrigin.Statistics.Clients.Wifi24 + nodeOrigin.Statistics.Clients.Wifi5
|
||||
// GetNodesV2 transform data to modern meshviewers
|
||||
func (nodes *Nodes) GetNodesV2() *meshviewer.NodesV2 {
|
||||
meshviewerNodes := &meshviewer.NodesV2{
|
||||
Version: 2,
|
||||
Timestamp: nodes.Timestamp,
|
||||
}
|
||||
for nodeID := range nodes.List {
|
||||
|
||||
node.Statistics = &meshviewer.Statistics{
|
||||
NodeId: nodeOrigin.Statistics.NodeId,
|
||||
Gateway: nodeOrigin.Statistics.Gateway,
|
||||
RootFsUsage: nodeOrigin.Statistics.RootFsUsage,
|
||||
LoadAverage: nodeOrigin.Statistics.LoadAverage,
|
||||
Memory: nodeOrigin.Statistics.Memory,
|
||||
Uptime: nodeOrigin.Statistics.Uptime,
|
||||
Idletime: nodeOrigin.Statistics.Idletime,
|
||||
Processes: nodeOrigin.Statistics.Processes,
|
||||
MeshVpn: nodeOrigin.Statistics.MeshVpn,
|
||||
Traffic: nodeOrigin.Statistics.Traffic,
|
||||
Clients: total,
|
||||
nodeOrigin := nodes.List[nodeID]
|
||||
if nodeOrigin.Statistics == nil {
|
||||
continue
|
||||
}
|
||||
node := &meshviewer.Node{
|
||||
Firstseen: nodeOrigin.Firstseen,
|
||||
Lastseen: nodeOrigin.Lastseen,
|
||||
Flags: nodeOrigin.Flags,
|
||||
Nodeinfo: nodeOrigin.Nodeinfo,
|
||||
}
|
||||
node.Statistics = meshviewer.NewStatistics(nodeOrigin.Statistics)
|
||||
meshviewerNodes.List = append(meshviewerNodes.List, node)
|
||||
}
|
||||
return meshviewerNodes
|
||||
}
|
||||
|
@ -172,9 +177,9 @@ func (nodes *Nodes) expire() {
|
|||
}
|
||||
|
||||
func (nodes *Nodes) load() {
|
||||
path := nodes.config.Nodes.NodesPath
|
||||
path := nodes.config.Nodes.NodesDynamicPath
|
||||
|
||||
if f, err := os.Open(path); err == nil {
|
||||
if f, err := os.Open(path); err == nil { // transform data to legacy meshviewer
|
||||
if err := json.NewDecoder(f).Decode(nodes); err == nil {
|
||||
log.Println("loaded", len(nodes.List), "nodes")
|
||||
} else {
|
||||
|
@ -191,15 +196,19 @@ func (nodes *Nodes) save() {
|
|||
defer nodes.RUnlock()
|
||||
|
||||
// serialize nodes
|
||||
save(nodes, nodes.config.Nodes.NodesPath)
|
||||
save(nodes.GetNodesMini(), nodes.config.Nodes.NodesMiniPath)
|
||||
save(nodes, nodes.config.Nodes.NodesDynamicPath)
|
||||
if path := nodes.config.Nodes.NodesV1Path; path != "" {
|
||||
save(nodes.GetNodesV1(), path)
|
||||
}
|
||||
if path := nodes.config.Nodes.NodesV2Path; path != "" {
|
||||
save(nodes.GetNodesV2(), path)
|
||||
}
|
||||
|
||||
if path := nodes.config.Nodes.GraphsPath; path != "" {
|
||||
save(nodes.BuildGraph(), path)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshals the input and writes it into the given file
|
||||
func save(input interface{}, outputFile string) {
|
||||
tmpFile := outputFile + ".tmp"
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestLoadAndSave(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
config := &Config{}
|
||||
config.Nodes.NodesPath = "testdata/nodes.json"
|
||||
config.Nodes.NodesDynamicPath = "testdata/nodes.json"
|
||||
|
||||
nodes := NewNodes(config)
|
||||
nodes.load()
|
||||
|
|
|
@ -25,11 +25,17 @@ func TestGlobalStats(t *testing.T) {
|
|||
assert.EqualValues(1, stats.Firmwares["2016.1.6+entenhausen1"])
|
||||
}
|
||||
|
||||
func TestNodesMini(t *testing.T) {
|
||||
mini := createTestNodes().GetNodesMini()
|
||||
func TestNodesV1(t *testing.T) {
|
||||
nodes := createTestNodes().GetNodesV1()
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.Equal(2, len(mini.List))
|
||||
assert.Equal(2, len(nodes.List))
|
||||
}
|
||||
func TestNodesV2(t *testing.T) {
|
||||
nodes := createTestNodes().GetNodesV2()
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.Equal(2, len(nodes.List))
|
||||
}
|
||||
|
||||
func createTestNodes() *Nodes {
|
||||
|
|
Loading…
Reference in New Issue