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:
Geno 2017-01-20 14:38:13 +01:00 committed by Julian K
parent e586dad6d5
commit 798db6a063
5 changed files with 108 additions and 61 deletions

View File

@ -1,8 +1,6 @@
package meshviewer package meshviewer
import ( import (
"sync"
"github.com/FreifunkBremen/respond-collector/data" "github.com/FreifunkBremen/respond-collector/data"
"github.com/FreifunkBremen/respond-collector/jsontime" "github.com/FreifunkBremen/respond-collector/jsontime"
) )
@ -22,23 +20,31 @@ type Flags struct {
Gateway bool `json:"gateway"` Gateway bool `json:"gateway"`
} }
// Nodes struct: cache DB of Node's structs // NodesV1 struct, to support legacy meshviewer (which are in master branch)
type Nodes struct { // i.e. https://github.com/ffnord/meshviewer/tree/master
type NodesV1 struct {
Version int `json:"version"` Version int `json:"version"`
Timestamp jsontime.Time `json:"timestamp"` Timestamp jsontime.Time `json:"timestamp"`
List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID 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 { type Statistics struct {
NodeId string `json:"node_id"` NodeId string `json:"node_id"`
Clients uint32 `json:"clients"` Clients uint32 `json:"clients"`
RootFsUsage float64 `json:"rootfs_usage,omitempty"` RootFsUsage float64 `json:"rootfs_usage,omitempty"`
LoadAverage float64 `json:"loadavg,omitempty"` LoadAverage float64 `json:"loadavg,omitempty"`
Memory data.Memory `json:"memory,omitempty"` MemoryUsage float64 `json:"memory_usage,omitempty"`
Uptime float64 `json:"uptime,omitempty"` Uptime float64 `json:"uptime,omitempty"`
Idletime float64 `json:"idletime,omitempty"` Idletime float64 `json:"idletime,omitempty"`
Gateway string `json:"gateway,omitempty"` Gateway string `json:"gateway,omitempty"`
Processes struct { Processes struct {
Total uint32 `json:"total"` Total uint32 `json:"total"`
Running uint32 `json:"running"` Running uint32 `json:"running"`
@ -52,3 +58,28 @@ type Statistics struct {
MgmtRx *data.Traffic `json:"mgmt_rx"` MgmtRx *data.Traffic `json:"mgmt_rx"`
} `json:"traffic,omitempty"` } `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,
}
}

View File

@ -26,13 +26,14 @@ type Config struct {
} `yaml:"api"` } `yaml:"api"`
} `yaml:"webserver"` } `yaml:"webserver"`
Nodes struct { Nodes struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
NodesPath string `yaml:"nodes_path"` NodesDynamicPath string `yaml:"nodes_path"`
NodesMiniPath string `yaml:"nodesmini_path"` NodesV1Path string `yaml:"nodesv1_path"`
GraphsPath string `yaml:"graphs_path"` NodesV2Path string `yaml:"nodesv2_path"`
AliasesPath string `yaml:"aliases_path"` GraphsPath string `yaml:"graphs_path"`
SaveInterval int `yaml:"saveinterval"` // Save nodes every n seconds AliasesPath string `yaml:"aliases_path"`
MaxAge int `yaml:"max_age"` // Remove nodes after n days of inactivity SaveInterval int `yaml:"saveinterval"` // Save nodes every n seconds
MaxAge int `yaml:"max_age"` // Remove nodes after n days of inactivity
} `yaml:"nodes"` } `yaml:"nodes"`
Influxdb struct { Influxdb struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`

View File

@ -28,11 +28,14 @@ func NewNodes(config *Config) *Nodes {
config: config, config: config,
} }
if config.Nodes.NodesPath != "" { if config.Nodes.NodesDynamicPath != "" {
nodes.load() 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 return nodes
} }
@ -83,51 +86,53 @@ func (nodes *Nodes) Update(nodeID string, res *data.ResponseData) *Node {
return node return node
} }
// GetNodesMini get meshviewer valide JSON // GetNodesV1 transform data to legacy meshviewer
func (nodes *Nodes) GetNodesMini() *meshviewer.Nodes { func (nodes *Nodes) GetNodesV1() *meshviewer.NodesV1 {
meshviewerNodes := &meshviewer.Nodes{ meshviewerNodes := &meshviewer.NodesV1{
Version: 1, Version: 1,
List: make(map[string]*meshviewer.Node), List: make(map[string]*meshviewer.Node),
Timestamp: nodes.Timestamp, Timestamp: nodes.Timestamp,
} }
for nodeID := range nodes.List { for nodeID := range nodes.List {
node, _ := meshviewerNodes.List[nodeID]
nodeOrigin := nodes.List[nodeID] nodeOrigin := nodes.List[nodeID]
if nodeOrigin.Statistics == nil { if nodeOrigin.Statistics == nil {
continue continue
} }
if node == nil { node := &meshviewer.Node{
node = &meshviewer.Node{ Firstseen: nodeOrigin.Firstseen,
Firstseen: nodeOrigin.Firstseen, Lastseen: nodeOrigin.Lastseen,
Lastseen: nodeOrigin.Lastseen, Flags: nodeOrigin.Flags,
Flags: nodeOrigin.Flags, Nodeinfo: nodeOrigin.Nodeinfo,
Nodeinfo: nodeOrigin.Nodeinfo,
}
meshviewerNodes.List[nodeID] = node
} }
node.Statistics = meshviewer.NewStatistics(nodeOrigin.Statistics)
meshviewerNodes.List[nodeID] = node
}
return meshviewerNodes
}
// Calculate Total // GetNodesV2 transform data to modern meshviewers
total := nodeOrigin.Statistics.Clients.Total func (nodes *Nodes) GetNodesV2() *meshviewer.NodesV2 {
if total == 0 { meshviewerNodes := &meshviewer.NodesV2{
total = nodeOrigin.Statistics.Clients.Wifi24 + nodeOrigin.Statistics.Clients.Wifi5 Version: 2,
} Timestamp: nodes.Timestamp,
}
for nodeID := range nodes.List {
node.Statistics = &meshviewer.Statistics{ nodeOrigin := nodes.List[nodeID]
NodeId: nodeOrigin.Statistics.NodeId, if nodeOrigin.Statistics == nil {
Gateway: nodeOrigin.Statistics.Gateway, continue
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,
} }
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 return meshviewerNodes
} }
@ -172,9 +177,9 @@ func (nodes *Nodes) expire() {
} }
func (nodes *Nodes) load() { 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 { if err := json.NewDecoder(f).Decode(nodes); err == nil {
log.Println("loaded", len(nodes.List), "nodes") log.Println("loaded", len(nodes.List), "nodes")
} else { } else {
@ -191,15 +196,19 @@ func (nodes *Nodes) save() {
defer nodes.RUnlock() defer nodes.RUnlock()
// serialize nodes // serialize nodes
save(nodes, nodes.config.Nodes.NodesPath) save(nodes, nodes.config.Nodes.NodesDynamicPath)
save(nodes.GetNodesMini(), nodes.config.Nodes.NodesMiniPath) 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 != "" { if path := nodes.config.Nodes.GraphsPath; path != "" {
save(nodes.BuildGraph(), path) save(nodes.BuildGraph(), path)
} }
} }
// Marshals the input and writes it into the given file
func save(input interface{}, outputFile string) { func save(input interface{}, outputFile string) {
tmpFile := outputFile + ".tmp" tmpFile := outputFile + ".tmp"

View File

@ -47,7 +47,7 @@ func TestLoadAndSave(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
config := &Config{} config := &Config{}
config.Nodes.NodesPath = "testdata/nodes.json" config.Nodes.NodesDynamicPath = "testdata/nodes.json"
nodes := NewNodes(config) nodes := NewNodes(config)
nodes.load() nodes.load()

View File

@ -25,11 +25,17 @@ func TestGlobalStats(t *testing.T) {
assert.EqualValues(1, stats.Firmwares["2016.1.6+entenhausen1"]) assert.EqualValues(1, stats.Firmwares["2016.1.6+entenhausen1"])
} }
func TestNodesMini(t *testing.T) { func TestNodesV1(t *testing.T) {
mini := createTestNodes().GetNodesMini() nodes := createTestNodes().GetNodesV1()
assert := assert.New(t) 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 { func createTestNodes() *Nodes {