Merge pull request #14 from FreifunkBremen/measurements
Add measurements for models and firmwares
This commit is contained in:
commit
da9d2cf8c6
@ -12,9 +12,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MeasurementNode = "node" // Measurement for per-node statistics
|
||||
MeasurementGlobal = "global" // Measurement for summarized global statistics
|
||||
batchMaxSize = 500
|
||||
MeasurementNode = "node" // Measurement for per-node statistics
|
||||
MeasurementGlobal = "global" // Measurement for summarized global statistics
|
||||
MeasurementFirmware = "firmware" // Measurement for firmware statistics
|
||||
MeasurementModel = "model" // Measurement for model statistics
|
||||
batchMaxSize = 500
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
@ -65,6 +67,23 @@ func (db *DB) AddPoint(name string, tags imodels.Tags, fields imodels.Fields, ti
|
||||
db.points <- point
|
||||
}
|
||||
|
||||
// Saves the values of a CounterMap in the database.
|
||||
// The key are used as 'value' tag.
|
||||
// The value is used as 'counter' field.
|
||||
func (db *DB) AddCounterMap(name string, m models.CounterMap) {
|
||||
now := time.Now()
|
||||
for key, count := range m {
|
||||
db.AddPoint(
|
||||
name,
|
||||
imodels.Tags{
|
||||
imodels.Tag{Key: []byte("value"), Value: []byte(key)},
|
||||
},
|
||||
imodels.Fields{"count": count},
|
||||
now,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Add data for a single node
|
||||
func (db *DB) Add(nodeId string, node *models.Node) {
|
||||
tags, fields := node.ToInflux()
|
||||
|
@ -1,31 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/respond-collector/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToInflux(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
node := Node{
|
||||
Statistics: &data.Statistics{
|
||||
NodeId: "foobar",
|
||||
LoadAverage: 0.5,
|
||||
},
|
||||
Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "nobody",
|
||||
},
|
||||
},
|
||||
Neighbours: &data.Neighbours{},
|
||||
}
|
||||
|
||||
tags, fields := node.ToInflux()
|
||||
|
||||
assert.Equal("foobar", tags.GetString("nodeid"))
|
||||
assert.Equal("nobody", tags.GetString("owner"))
|
||||
assert.Equal(0.5, fields["load"])
|
||||
}
|
@ -21,14 +21,6 @@ type Nodes struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type GlobalStats struct {
|
||||
Nodes uint32
|
||||
Clients uint32
|
||||
ClientsWifi uint32
|
||||
ClientsWifi24 uint32
|
||||
ClientsWifi5 uint32
|
||||
}
|
||||
|
||||
// NewNodes create Nodes structs
|
||||
func NewNodes(config *Config) *Nodes {
|
||||
nodes := &Nodes{
|
||||
@ -207,36 +199,6 @@ func (nodes *Nodes) save() {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns global statistics for InfluxDB
|
||||
func (nodes *Nodes) GlobalStats() (result *GlobalStats) {
|
||||
result = &GlobalStats{}
|
||||
nodes.Lock()
|
||||
for _, node := range nodes.List {
|
||||
if node.Flags.Online {
|
||||
result.Nodes += 1
|
||||
if stats := node.Statistics; stats != nil {
|
||||
result.Clients += stats.Clients.Total
|
||||
result.ClientsWifi24 += stats.Clients.Wifi24
|
||||
result.ClientsWifi5 += stats.Clients.Wifi5
|
||||
result.ClientsWifi += stats.Clients.Wifi
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Returns fields for InfluxDB
|
||||
func (stats *GlobalStats) Fields() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"nodes": stats.Nodes,
|
||||
"clients.total": stats.Clients,
|
||||
"clients.wifi": stats.ClientsWifi,
|
||||
"clients.wifi24": stats.ClientsWifi24,
|
||||
"clients.wifi5": stats.ClientsWifi5,
|
||||
}
|
||||
}
|
||||
|
||||
// Marshals the input and writes it into the given file
|
||||
func save(input interface{}, outputFile string) {
|
||||
tmpFile := outputFile + ".tmp"
|
||||
|
@ -74,28 +74,25 @@ func TestUpdateNodes(t *testing.T) {
|
||||
assert.Equal(1, len(nodes.List))
|
||||
}
|
||||
|
||||
func TestGlobalStats(t *testing.T) {
|
||||
stats := createTestNodes().GlobalStats()
|
||||
|
||||
func TestToInflux(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.EqualValues(uint32(1), stats.Nodes)
|
||||
assert.EqualValues(uint32(23), stats.Clients)
|
||||
}
|
||||
|
||||
func TestNodesMini(t *testing.T) {
|
||||
mini := createTestNodes().GetNodesMini()
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.Equal(1, len(mini.List))
|
||||
}
|
||||
|
||||
func createTestNodes() *Nodes {
|
||||
nodes := NewNodes(&Config{})
|
||||
|
||||
res := &data.ResponseData{
|
||||
Statistics: &data.Statistics{},
|
||||
node := Node{
|
||||
Statistics: &data.Statistics{
|
||||
NodeId: "foobar",
|
||||
LoadAverage: 0.5,
|
||||
},
|
||||
Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "nobody",
|
||||
},
|
||||
},
|
||||
Neighbours: &data.Neighbours{},
|
||||
}
|
||||
res.Statistics.Clients.Total = 23
|
||||
nodes.Update("abcdef012345", res)
|
||||
return nodes
|
||||
|
||||
tags, fields := node.ToInflux()
|
||||
|
||||
assert.Equal("foobar", tags.GetString("nodeid"))
|
||||
assert.Equal("nobody", tags.GetString("owner"))
|
||||
assert.Equal(0.5, fields["load"])
|
||||
}
|
||||
|
66
models/stats.go
Normal file
66
models/stats.go
Normal file
@ -0,0 +1,66 @@
|
||||
package models
|
||||
|
||||
type CounterMap map[string]uint32
|
||||
|
||||
type GlobalStats struct {
|
||||
Clients uint32
|
||||
ClientsWifi uint32
|
||||
ClientsWifi24 uint32
|
||||
ClientsWifi5 uint32
|
||||
Gateways uint32
|
||||
Nodes uint32
|
||||
|
||||
Firmwares CounterMap
|
||||
Models CounterMap
|
||||
}
|
||||
|
||||
// Returns global statistics for InfluxDB
|
||||
func NewGlobalStats(nodes *Nodes) (result *GlobalStats) {
|
||||
result = &GlobalStats{
|
||||
Firmwares: make(CounterMap),
|
||||
Models: make(CounterMap),
|
||||
}
|
||||
|
||||
nodes.Lock()
|
||||
for _, node := range nodes.List {
|
||||
if node.Flags.Online {
|
||||
result.Nodes += 1
|
||||
if stats := node.Statistics; stats != nil {
|
||||
result.Clients += stats.Clients.Total
|
||||
result.ClientsWifi24 += stats.Clients.Wifi24
|
||||
result.ClientsWifi5 += stats.Clients.Wifi5
|
||||
result.ClientsWifi += stats.Clients.Wifi
|
||||
}
|
||||
if node.Flags.Gateway {
|
||||
result.Gateways += 1
|
||||
}
|
||||
if info := node.Nodeinfo; info != nil {
|
||||
result.Models.Increment(info.Hardware.Model)
|
||||
result.Firmwares.Increment(info.Software.Firmware.Release)
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Increment counter in the map by one
|
||||
// if the value is not empty
|
||||
func (m CounterMap) Increment(key string) {
|
||||
if key != "" {
|
||||
val := m[key]
|
||||
m[key] = val + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Returns fields for InfluxDB
|
||||
func (stats *GlobalStats) Fields() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"nodes": stats.Nodes,
|
||||
"gateways": stats.Gateways,
|
||||
"clients.total": stats.Clients,
|
||||
"clients.wifi": stats.ClientsWifi,
|
||||
"clients.wifi24": stats.ClientsWifi24,
|
||||
"clients.wifi5": stats.ClientsWifi5,
|
||||
}
|
||||
}
|
76
models/stats_test.go
Normal file
76
models/stats_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/respond-collector/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGlobalStats(t *testing.T) {
|
||||
stats := NewGlobalStats(createTestNodes())
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.EqualValues(1, stats.Gateways)
|
||||
assert.EqualValues(3, stats.Nodes)
|
||||
assert.EqualValues(25, stats.Clients)
|
||||
|
||||
// check models
|
||||
assert.EqualValues(2, len(stats.Models))
|
||||
assert.EqualValues(2, stats.Models["TP-Link 841"])
|
||||
assert.EqualValues(1, stats.Models["Xeon Multi-Core"])
|
||||
|
||||
// check firmwares
|
||||
assert.EqualValues(1, len(stats.Firmwares))
|
||||
assert.EqualValues(1, stats.Firmwares["2016.1.6+entenhausen1"])
|
||||
}
|
||||
|
||||
func TestNodesMini(t *testing.T) {
|
||||
mini := createTestNodes().GetNodesMini()
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.Equal(2, len(mini.List))
|
||||
}
|
||||
|
||||
func createTestNodes() *Nodes {
|
||||
nodes := NewNodes(&Config{})
|
||||
|
||||
nodeData := &data.ResponseData{
|
||||
Statistics: &data.Statistics{
|
||||
Clients: data.Clients{
|
||||
Total: 23,
|
||||
},
|
||||
},
|
||||
NodeInfo: &data.NodeInfo{
|
||||
Hardware: data.Hardware{
|
||||
Model: "TP-Link 841",
|
||||
},
|
||||
},
|
||||
}
|
||||
nodeData.NodeInfo.Software.Firmware.Release = "2016.1.6+entenhausen1"
|
||||
nodes.Update("abcdef012345", nodeData)
|
||||
|
||||
nodes.Update("112233445566", &data.ResponseData{
|
||||
Statistics: &data.Statistics{
|
||||
Clients: data.Clients{
|
||||
Total: 2,
|
||||
},
|
||||
},
|
||||
NodeInfo: &data.NodeInfo{
|
||||
Hardware: data.Hardware{
|
||||
Model: "TP-Link 841",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
nodes.Update("0xdeadbeef0x", &data.ResponseData{
|
||||
NodeInfo: &data.NodeInfo{
|
||||
VPN: true,
|
||||
Hardware: data.Hardware{
|
||||
Model: "Xeon Multi-Core",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
@ -56,6 +56,10 @@ func NewCollector(db *database.DB, nodes *models.Nodes, interval time.Duration,
|
||||
go collector.receiver()
|
||||
go collector.parser()
|
||||
|
||||
if collector.db != nil {
|
||||
go collector.globalStatsWorker()
|
||||
}
|
||||
|
||||
// Run senders
|
||||
go func() {
|
||||
collector.sendOnce() // immediately
|
||||
@ -97,11 +101,6 @@ func (coll *Collector) sender() {
|
||||
case <-coll.stop:
|
||||
return
|
||||
case <-coll.ticker.C:
|
||||
// save global statistics
|
||||
if coll.db != nil {
|
||||
coll.db.AddPoint(database.MeasurementGlobal, nil, coll.nodes.GlobalStats().Fields(), time.Now())
|
||||
}
|
||||
|
||||
// send the multicast packet to request per-node statistics
|
||||
coll.sendOnce()
|
||||
}
|
||||
@ -172,3 +171,24 @@ func (coll *Collector) receiver() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (coll *Collector) globalStatsWorker() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
for {
|
||||
select {
|
||||
case <-coll.stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
coll.saveGlobalStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// saves global statistics
|
||||
func (coll *Collector) saveGlobalStats() {
|
||||
stats := models.NewGlobalStats(coll.nodes)
|
||||
|
||||
coll.db.AddPoint(database.MeasurementGlobal, nil, stats.Fields(), time.Now())
|
||||
coll.db.AddCounterMap(database.MeasurementFirmware, stats.Firmwares)
|
||||
coll.db.AddCounterMap(database.MeasurementModel, stats.Models)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user