Add livebox_devices_total metric
This commit is contained in:
parent
3b01929958
commit
5d2b77a38c
7 changed files with 239 additions and 72 deletions
|
@ -7,10 +7,11 @@ FTTH subscription.
|
||||||
|
|
||||||
This exporter currently exposes the following metrics:
|
This exporter currently exposes the following metrics:
|
||||||
|
|
||||||
| Name | Type | Description | Labels |
|
| Name | Type | Description | Labels |
|
||||||
| -------------------------- | ----- | ---------------------------- | --------- |
|
| -------------------------- | ----- | ---------------------------------- | --------- |
|
||||||
| livebox_interface_rx_mbits | gauge | Received Mbits per second | interface |
|
| livebox_interface_rx_mbits | gauge | Received Mbits per second | interface |
|
||||||
| livebox_interface_tx_mbits | gauge | Transmitted Mbits per second | interface |
|
| livebox_interface_tx_mbits | gauge | Transmitted Mbits per second | interface |
|
||||||
|
| livebox_devices_total | gauge | The total number of active devices | type |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -3,8 +3,9 @@ module github.com/Tomy2e/livebox-exporter
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Tomy2e/livebox-api-client v0.0.0-20211112014400-2ac195fb56d7
|
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.11.0
|
||||||
|
golang.org/x/sync v0.1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -1,6 +1,6 @@
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/Tomy2e/livebox-api-client v0.0.0-20211112014400-2ac195fb56d7 h1:7rtUzWI31OT0PE51M1wP1IQNV2PwMXv5kTRmUDyS2QU=
|
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7 h1:aX04myQJxWqjP1I1S8jiE8fKyXSu4CHKMBOCUMTwlCI=
|
||||||
github.com/Tomy2e/livebox-api-client v0.0.0-20211112014400-2ac195fb56d7/go.mod h1:3uvJQHRP5V3BKPTzWCOmUMxZrPbJNl45Wu7ueX9L8QM=
|
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7/go.mod h1:3uvJQHRP5V3BKPTzWCOmUMxZrPbJNl45Wu7ueX9L8QM=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
@ -104,6 +104,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
63
internal/poller/devices.go
Normal file
63
internal/poller/devices.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package poller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Tomy2e/livebox-api-client"
|
||||||
|
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Poller = &DevicesTotal{}
|
||||||
|
|
||||||
|
// DevicesTotal allows to poll the total number of active devices.
|
||||||
|
type DevicesTotal struct {
|
||||||
|
client livebox.Client
|
||||||
|
devicesTotal *prometheus.GaugeVec
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDevicesTotal returns a new DevicesTotal poller.
|
||||||
|
func NewDevicesTotal(client livebox.Client) *DevicesTotal {
|
||||||
|
return &DevicesTotal{
|
||||||
|
client: client,
|
||||||
|
devicesTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "livebox_devices_total",
|
||||||
|
Help: "The total number of active devices",
|
||||||
|
}, []string{
|
||||||
|
// Type of the device (dongle, ethernet, printer or wifi).
|
||||||
|
"type",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collectors returns all metrics.
|
||||||
|
func (dt *DevicesTotal) Collectors() []prometheus.Collector {
|
||||||
|
return []prometheus.Collector{dt.devicesTotal}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll polls the current number of active devices.
|
||||||
|
func (dt *DevicesTotal) Poll(ctx context.Context) error {
|
||||||
|
var devices struct {
|
||||||
|
Status map[string][]struct{} `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dt.client.Request(ctx, request.New("Devices", "get", map[string]interface{}{
|
||||||
|
"expression": map[string]string{
|
||||||
|
"ethernet": "not interface and not self and eth and .Active==true",
|
||||||
|
"wifi": "not interface and not self and wifi and .Active==true",
|
||||||
|
"printer": "printer and .Active==true",
|
||||||
|
"dongle": "usb && wwan and .Active==true",
|
||||||
|
},
|
||||||
|
}), &devices); err != nil {
|
||||||
|
return fmt.Errorf("failed to get active devices: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for t, d := range devices.Status {
|
||||||
|
dt.devicesTotal.
|
||||||
|
With(prometheus.Labels{"type": t}).
|
||||||
|
Set(float64(len(d)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
97
internal/poller/interface.go
Normal file
97
internal/poller/interface.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package poller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Tomy2e/livebox-api-client"
|
||||||
|
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Poller = &InterfaceMbits{}
|
||||||
|
|
||||||
|
// InterfaceMbits allows to poll the current bandwidth usage on the Livebox
|
||||||
|
// interfaces.
|
||||||
|
type InterfaceMbits struct {
|
||||||
|
client livebox.Client
|
||||||
|
txMbits, rxMbits *prometheus.GaugeVec
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInterfaceMbits returns a new InterfaceMbits poller.
|
||||||
|
func NewInterfaceMbits(client livebox.Client) *InterfaceMbits {
|
||||||
|
return &InterfaceMbits{
|
||||||
|
client: client,
|
||||||
|
txMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "livebox_interface_tx_mbits",
|
||||||
|
Help: "Transmitted Mbits per second.",
|
||||||
|
}, []string{
|
||||||
|
// Name of the interface.
|
||||||
|
"interface",
|
||||||
|
}),
|
||||||
|
rxMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "livebox_interface_rx_mbits",
|
||||||
|
Help: "Received Mbits per second.",
|
||||||
|
}, []string{
|
||||||
|
// Name of the interface.
|
||||||
|
"interface",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collectors returns all metrics.
|
||||||
|
func (im *InterfaceMbits) Collectors() []prometheus.Collector {
|
||||||
|
return []prometheus.Collector{im.txMbits, im.rxMbits}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitsPer30SecsToMbitsPerSec(v int) float64 {
|
||||||
|
return float64(v) / 30000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll polls the current bandwidth usage.
|
||||||
|
func (im *InterfaceMbits) Poll(ctx context.Context) error {
|
||||||
|
var counters struct {
|
||||||
|
Status map[string]struct {
|
||||||
|
Traffic []struct {
|
||||||
|
Timestamp int `json:"Timestamp"`
|
||||||
|
RxCounter int `json:"Rx_Counter"`
|
||||||
|
TxCounter int `json:"Tx_Counter"`
|
||||||
|
} `json:"Traffic"`
|
||||||
|
} `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request latest rx/tx counters.
|
||||||
|
if err := im.client.Request(
|
||||||
|
ctx,
|
||||||
|
request.New(
|
||||||
|
"HomeLan",
|
||||||
|
"getResults",
|
||||||
|
map[string]interface{}{
|
||||||
|
"Seconds": 0,
|
||||||
|
"NumberOfReadings": 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
&counters,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("failed to get interfaces: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for iface, traffic := range counters.Status {
|
||||||
|
rxCounter := 0
|
||||||
|
txCounter := 0
|
||||||
|
|
||||||
|
if len(traffic.Traffic) > 0 {
|
||||||
|
rxCounter = traffic.Traffic[0].RxCounter
|
||||||
|
txCounter = traffic.Traffic[0].TxCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
im.rxMbits.
|
||||||
|
With(prometheus.Labels{"interface": iface}).
|
||||||
|
Set(bitsPer30SecsToMbitsPerSec(rxCounter))
|
||||||
|
im.txMbits.
|
||||||
|
With(prometheus.Labels{"interface": iface}).
|
||||||
|
Set(bitsPer30SecsToMbitsPerSec(txCounter))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
41
internal/poller/poller.go
Normal file
41
internal/poller/poller.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package poller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Poller is an interface that allows polling a system and updating Prometheus
|
||||||
|
// metrics.
|
||||||
|
type Poller interface {
|
||||||
|
Poll(ctx context.Context) error
|
||||||
|
Collectors() []prometheus.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pollers is a list of pollers.
|
||||||
|
type Pollers []Poller
|
||||||
|
|
||||||
|
// Collectors returns the collectors of all pollers.
|
||||||
|
func (p Pollers) Collectors() (c []prometheus.Collector) {
|
||||||
|
for _, poller := range p {
|
||||||
|
c = append(c, poller.Collectors()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll runs all pollers in parallel.
|
||||||
|
func (p Pollers) Poll(ctx context.Context) error {
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
for _, poller := range p {
|
||||||
|
poller := poller
|
||||||
|
eg.Go(func() error {
|
||||||
|
return poller.Poll(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
|
}
|
92
main.go
92
main.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -9,35 +10,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Tomy2e/livebox-api-client"
|
"github.com/Tomy2e/livebox-api-client"
|
||||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
"github.com/Tomy2e/livebox-exporter/internal/poller"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPollingFrequency = 30
|
const defaultPollingFrequency = 30
|
||||||
|
|
||||||
var (
|
|
||||||
rxMbits = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Name: "livebox_interface_rx_mbits",
|
|
||||||
Help: "Received Mbits per second.",
|
|
||||||
}, []string{
|
|
||||||
// Name of the interface.
|
|
||||||
"interface",
|
|
||||||
})
|
|
||||||
txMbits = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Name: "livebox_interface_tx_mbits",
|
|
||||||
Help: "Transmitted Mbits per second.",
|
|
||||||
}, []string{
|
|
||||||
// Name of the interface.
|
|
||||||
"interface",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func bitsPer30SecsToMbitsPerSec(v int) float64 {
|
|
||||||
return float64(v) / 30000000
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
pollingFrequency := flag.Uint("polling-frequency", defaultPollingFrequency, "Polling frequency")
|
pollingFrequency := flag.Uint("polling-frequency", defaultPollingFrequency, "Polling frequency")
|
||||||
listen := flag.String("listen", ":8080", "Listening address")
|
listen := flag.String("listen", ":8080", "Listening address")
|
||||||
|
@ -48,59 +28,41 @@ func main() {
|
||||||
log.Fatal("ADMIN_PASSWORD environment variable must be set")
|
log.Fatal("ADMIN_PASSWORD environment variable must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
var (
|
||||||
client := livebox.NewClient(adminPassword)
|
ctx = context.Background()
|
||||||
|
registry = prometheus.NewRegistry()
|
||||||
|
client = livebox.NewClient(adminPassword)
|
||||||
|
pollers = poller.Pollers{
|
||||||
|
poller.NewDevicesTotal(client),
|
||||||
|
poller.NewInterfaceMbits(client),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
registry.MustRegister(
|
||||||
|
append(
|
||||||
|
pollers.Collectors(),
|
||||||
|
collectors.NewGoCollector(),
|
||||||
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||||
|
)...,
|
||||||
|
)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var counters struct {
|
if err := pollers.Poll(ctx); err != nil {
|
||||||
Status map[string]struct {
|
if errors.Is(err, livebox.ErrInvalidPassword) {
|
||||||
Traffic []struct {
|
log.Fatal(err)
|
||||||
Timestamp int `json:"Timestamp"`
|
|
||||||
RxCounter int `json:"Rx_Counter"`
|
|
||||||
TxCounter int `json:"Tx_Counter"`
|
|
||||||
} `json:"Traffic"`
|
|
||||||
} `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request latest rx/tx counters.
|
|
||||||
if err := client.Request(
|
|
||||||
ctx,
|
|
||||||
request.New(
|
|
||||||
"HomeLan",
|
|
||||||
"getResults",
|
|
||||||
map[string]interface{}{
|
|
||||||
"Seconds": 0,
|
|
||||||
"NumberOfReadings": 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
&counters,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatalf("Request to Livebox API failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for iface, traffic := range counters.Status {
|
|
||||||
rxCounter := 0
|
|
||||||
txCounter := 0
|
|
||||||
|
|
||||||
if len(traffic.Traffic) > 0 {
|
|
||||||
rxCounter = traffic.Traffic[0].RxCounter
|
|
||||||
txCounter = traffic.Traffic[0].TxCounter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rxMbits.
|
log.Printf("WARN: polling failed: %s\n", err)
|
||||||
With(prometheus.Labels{"interface": iface}).
|
|
||||||
Set(bitsPer30SecsToMbitsPerSec(rxCounter))
|
|
||||||
txMbits.
|
|
||||||
With(prometheus.Labels{"interface": iface}).
|
|
||||||
Set(bitsPer30SecsToMbitsPerSec(txCounter))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Duration(*pollingFrequency) * time.Second)
|
time.Sleep(time.Duration(*pollingFrequency) * time.Second)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
http.Handle("/metrics", promhttp.Handler())
|
http.Handle("/metrics", promhttp.InstrumentMetricHandler(
|
||||||
|
registry, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
|
||||||
|
))
|
||||||
log.Printf("Listening on %s\n", *listen)
|
log.Printf("Listening on %s\n", *listen)
|
||||||
log.Fatal(http.ListenAndServe(*listen, nil))
|
log.Fatal(http.ListenAndServe(*listen, nil))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue