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
|
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,12 +20,20 @@ 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 {
|
||||||
|
@ -35,7 +41,7 @@ type Statistics struct {
|
||||||
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"`
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,8 +27,9 @@ type Config struct {
|
||||||
} `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"`
|
||||||
|
NodesV2Path string `yaml:"nodesv2_path"`
|
||||||
GraphsPath string `yaml:"graphs_path"`
|
GraphsPath string `yaml:"graphs_path"`
|
||||||
AliasesPath string `yaml:"aliases_path"`
|
AliasesPath string `yaml:"aliases_path"`
|
||||||
SaveInterval int `yaml:"saveinterval"` // Save nodes every n seconds
|
SaveInterval int `yaml:"saveinterval"` // Save nodes every n seconds
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
node.Statistics = meshviewer.NewStatistics(nodeOrigin.Statistics)
|
||||||
meshviewerNodes.List[nodeID] = node
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Statistics = &meshviewer.Statistics{
|
// GetNodesV2 transform data to modern meshviewers
|
||||||
NodeId: nodeOrigin.Statistics.NodeId,
|
func (nodes *Nodes) GetNodesV2() *meshviewer.NodesV2 {
|
||||||
Gateway: nodeOrigin.Statistics.Gateway,
|
meshviewerNodes := &meshviewer.NodesV2{
|
||||||
RootFsUsage: nodeOrigin.Statistics.RootFsUsage,
|
Version: 2,
|
||||||
LoadAverage: nodeOrigin.Statistics.LoadAverage,
|
Timestamp: nodes.Timestamp,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
for nodeID := range nodes.List {
|
||||||
|
|
||||||
|
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
|
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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue