From 798db6a063a1165a8eab46db94164c9f763f3dd4 Mon Sep 17 00:00:00 2001 From: Geno Date: Fri, 20 Jan 2017 14:38:13 +0100 Subject: [PATCH] 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 --- meshviewer/meshviewer.go | 57 ++++++++++++++++++++------- models/config.go | 15 ++++---- models/nodes.go | 83 ++++++++++++++++++++++------------------ models/nodes_test.go | 2 +- models/stats_test.go | 12 ++++-- 5 files changed, 108 insertions(+), 61 deletions(-) diff --git a/meshviewer/meshviewer.go b/meshviewer/meshviewer.go index 899b9e3..a1249dc 100644 --- a/meshviewer/meshviewer.go +++ b/meshviewer/meshviewer.go @@ -1,8 +1,6 @@ package meshviewer import ( - "sync" - "github.com/FreifunkBremen/respond-collector/data" "github.com/FreifunkBremen/respond-collector/jsontime" ) @@ -22,23 +20,31 @@ 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 { - NodeId string `json:"node_id"` - Clients uint32 `json:"clients"` - RootFsUsage float64 `json:"rootfs_usage,omitempty"` - LoadAverage float64 `json:"loadavg,omitempty"` - Memory data.Memory `json:"memory,omitempty"` - Uptime float64 `json:"uptime,omitempty"` - Idletime float64 `json:"idletime,omitempty"` - Gateway string `json:"gateway,omitempty"` + NodeId string `json:"node_id"` + Clients uint32 `json:"clients"` + RootFsUsage float64 `json:"rootfs_usage,omitempty"` + LoadAverage float64 `json:"loadavg,omitempty"` + MemoryUsage float64 `json:"memory_usage,omitempty"` + Uptime float64 `json:"uptime,omitempty"` + Idletime float64 `json:"idletime,omitempty"` + Gateway string `json:"gateway,omitempty"` Processes struct { Total uint32 `json:"total"` Running uint32 `json:"running"` @@ -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, + } +} diff --git a/models/config.go b/models/config.go index 89b5901..1b24c87 100644 --- a/models/config.go +++ b/models/config.go @@ -26,13 +26,14 @@ type Config struct { } `yaml:"api"` } `yaml:"webserver"` Nodes struct { - Enable bool `yaml:"enable"` - NodesPath string `yaml:"nodes_path"` - NodesMiniPath string `yaml:"nodesmini_path"` - GraphsPath string `yaml:"graphs_path"` - AliasesPath string `yaml:"aliases_path"` - SaveInterval int `yaml:"saveinterval"` // Save nodes every n seconds - MaxAge int `yaml:"max_age"` // Remove nodes after n days of inactivity + Enable bool `yaml:"enable"` + 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 + MaxAge int `yaml:"max_age"` // Remove nodes after n days of inactivity } `yaml:"nodes"` Influxdb struct { Enable bool `yaml:"enable"` diff --git a/models/nodes.go b/models/nodes.go index 939c3bb..e964801 100644 --- a/models/nodes.go +++ b/models/nodes.go @@ -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{ - Firstseen: nodeOrigin.Firstseen, - Lastseen: nodeOrigin.Lastseen, - Flags: nodeOrigin.Flags, - Nodeinfo: nodeOrigin.Nodeinfo, - } - meshviewerNodes.List[nodeID] = 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" diff --git a/models/nodes_test.go b/models/nodes_test.go index b5874e4..165056d 100644 --- a/models/nodes_test.go +++ b/models/nodes_test.go @@ -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() diff --git a/models/stats_test.go b/models/stats_test.go index 9283585..00af87f 100644 --- a/models/stats_test.go +++ b/models/stats_test.go @@ -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 {