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
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,
}
}

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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 {