Compare commits
4 commits
3-livebox-
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
4a5496d239 | ||
|
5abe206e27 | ||
|
e348d3871b | ||
|
136f95a9b7 |
19 changed files with 831 additions and 46 deletions
40
README.md
40
README.md
|
@ -7,11 +7,24 @@ FTTH subscription.
|
|||
|
||||
This exporter currently exposes the following metrics:
|
||||
|
||||
| Name | Type | Description | Labels |
|
||||
| -------------------------- | ----- | ---------------------------------- | --------- |
|
||||
| livebox_interface_rx_mbits | gauge | Received 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 |
|
||||
| Name | Type | Description | Labels | Experimental |
|
||||
| --------------------------------------- | ----- | ------------------------------------------------- | --------- | ------------ |
|
||||
| livebox_interface_rx_mbits | gauge | Received Mbits per second | interface | No |
|
||||
| livebox_interface_tx_mbits | gauge | Transmitted Mbits per second | interface | No |
|
||||
| livebox_devices_total | gauge | The total number of active devices | type | No |
|
||||
| livebox_deviceinfo_reboots_total | gauge | Number of Livebox reboots | | No |
|
||||
| livebox_deviceinfo_uptime_seconds_total | gauge | Livebox current uptime | | No |
|
||||
| livebox_deviceinfo_memory_total_bytes | gauge | Livebox system total memory | | No |
|
||||
| livebox_deviceinfo_memory_usage_bytes | gauge | Livebox system used memory | | No |
|
||||
| livebox_interface_homelan_rx_mbits | gauge | Received Mbits per second | interface | Yes |
|
||||
| livebox_interface_homelan_tx_mbits | gauge | Transmitted Mbits per second | interface | Yes |
|
||||
| livebox_interface_netdev_rx_mbits | gauge | Received Mbits per second | interface | Yes |
|
||||
| livebox_interface_netdev_tx_mbits | gauge | Transmitted Mbits per second | interface | Yes |
|
||||
| livebox_wan_rx_mbits | gauge | Received Mbits per second on the WAN interface | | Yes |
|
||||
| livebox_wan_tx_mbits | gauge | Transmitted Mbits per second on the WAN interface | | Yes |
|
||||
|
||||
Experimental metrics are not enabled by default, use the `-experimental`
|
||||
command-line option to enable them.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -19,16 +32,19 @@ This exporter currently exposes the following metrics:
|
|||
|
||||
The exporter accepts the following command-line options:
|
||||
|
||||
| Name | Description | Default value |
|
||||
| ------------------- | ----------------- | ------------- |
|
||||
| --polling-frequency | Polling frequency | 30 |
|
||||
| --listen | Listening address | :8080 |
|
||||
| Name | Description | Default value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| -polling-frequency | Polling frequency | 30 |
|
||||
| -listen | Listening address | :8080 |
|
||||
| -experimental | Comma separated list of experimental metrics to enable (available metrics: livebox_interface_homelan,livebox_interface_netdev,livebox_wan) | |
|
||||
|
||||
The exporter reads the following environment variables:
|
||||
|
||||
| Name | Description | Default value |
|
||||
| -------------- | --------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| ADMIN_PASSWORD | Password of the Livebox "admin" user. The exporter will exit if this environment variable is not defined. | |
|
||||
| Name | Description | Default value |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
| ADMIN_PASSWORD | Password of the Livebox "admin" user. The exporter will exit if this environment variable is not defined. | |
|
||||
| LIVEBOX_ADDRESS | Address of the Livebox. | `http://192.168.1.1` |
|
||||
| LIVEBOX_CACERT | Optional path to a PEM-encoded CA certificate file on the local disk. | |
|
||||
|
||||
### Docker
|
||||
|
||||
|
|
|
@ -2,5 +2,5 @@ apiVersion: v2
|
|||
name: livebox-exporter
|
||||
description: A prometheus exporter for Livebox
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "v0.1.0"
|
||||
version: 0.3.0
|
||||
appVersion: "v0.3.0"
|
||||
|
|
10
charts/livebox-exporter/templates/configmap.yaml
Normal file
10
charts/livebox-exporter/templates/configmap.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{- if .Values.livebox.caCert }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "livebox-exporter.fullname" . }}
|
||||
labels:
|
||||
{{- include "livebox-exporter.labels" . | nindent 4 }}
|
||||
data:
|
||||
ca.crt: {{ toYaml .Values.livebox.caCert | indent 2 }}
|
||||
{{- end }}
|
|
@ -41,13 +41,26 @@ spec:
|
|||
- name: ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
{{- if .Values.adminPassword.secretKeyRef }}
|
||||
name: {{ .Values.adminPassword.secretKeyRef.name}}
|
||||
key: {{ .Values.adminPassword.secretKeyRef.key }}
|
||||
{{- if .Values.livebox.adminPassword.secretKeyRef }}
|
||||
name: {{ .Values.livebox.adminPassword.secretKeyRef.name}}
|
||||
key: {{ .Values.livebox.adminPassword.secretKeyRef.key }}
|
||||
{{- else }}
|
||||
name: {{ include "livebox-exporter.fullname" . }}-admin
|
||||
key: password
|
||||
{{- end }}
|
||||
{{- with .Values.livebox.address }}
|
||||
- name: LIVEBOX_ADDRESS
|
||||
value: {{ . }}
|
||||
{{- end }}
|
||||
{{- if .Values.livebox.caCert }}
|
||||
- name: LIVEBOX_CACERT
|
||||
value: /etc/livebox/certs/ca.crt
|
||||
{{- end }}
|
||||
{{- if .Values.livebox.caCert }}
|
||||
volumeMounts:
|
||||
- name: livebox-crt
|
||||
mountPath: /etc/livebox/certs
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
|
@ -60,3 +73,9 @@ spec:
|
|||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.livebox.caCert }}
|
||||
volumes:
|
||||
- name: livebox-crt
|
||||
configMap:
|
||||
name: {{ include "livebox-exporter.fullname" . }}
|
||||
{{- end }}
|
|
@ -1,4 +1,4 @@
|
|||
{{- if not .Values.adminPassword.secretKeyRef }}
|
||||
{{- if not .Values.livebox.adminPassword.secretKeyRef }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
|
@ -7,5 +7,5 @@ metadata:
|
|||
{{- include "livebox-exporter.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
data:
|
||||
password: {{ .Values.adminPassword.value | b64enc }}
|
||||
password: {{ .Values.livebox.adminPassword.value | b64enc }}
|
||||
{{- end }}
|
|
@ -1,7 +1,3 @@
|
|||
# Default values for livebox-exporter.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
|
@ -10,12 +6,19 @@ image:
|
|||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
adminPassword:
|
||||
secretKeyRef: {}
|
||||
# name: ""
|
||||
# key: ""
|
||||
livebox:
|
||||
adminPassword:
|
||||
secretKeyRef: {}
|
||||
# name: ""
|
||||
# key: ""
|
||||
|
||||
value: "changeme"
|
||||
value: "changeme"
|
||||
|
||||
# Address of the Livebox. If empty the exporter will use its own default value.
|
||||
address: ""
|
||||
|
||||
# CA cert of the Livebox.
|
||||
caCert: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
|
|
5
go.mod
5
go.mod
|
@ -3,8 +3,9 @@ module github.com/Tomy2e/livebox-exporter
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7
|
||||
github.com/Tomy2e/livebox-api-client v0.0.0-20230524112450-31caca47cbd8
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
||||
golang.org/x/sync v0.1.0
|
||||
)
|
||||
|
||||
|
@ -16,6 +17,6 @@ require (
|
|||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -33,8 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7 h1:aX04myQJxWqjP1I1S8jiE8fKyXSu4CHKMBOCUMTwlCI=
|
||||
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7/go.mod h1:3uvJQHRP5V3BKPTzWCOmUMxZrPbJNl45Wu7ueX9L8QM=
|
||||
github.com/Tomy2e/livebox-api-client v0.0.0-20230524112450-31caca47cbd8 h1:rVK1emWX3JW3nNhLdl76M4RJgmMj/hcgQVOJmrh2r9Y=
|
||||
github.com/Tomy2e/livebox-api-client v0.0.0-20230524112450-31caca47cbd8/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-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
|
@ -218,6 +218,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -325,8 +327,8 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
101
internal/collector/deviceinfo.go
Normal file
101
internal/collector/deviceinfo.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var _ prometheus.Collector = &DeviceInfo{}
|
||||
|
||||
// DeviceInfo implements a prometheus Collector that returns Livebox specific metrics.
|
||||
type DeviceInfo struct {
|
||||
client *livebox.Client
|
||||
numberOfRebootsMetric *prometheus.Desc
|
||||
uptimeMetric *prometheus.Desc
|
||||
memoryTotalMetric *prometheus.Desc
|
||||
memoryUsageMetric *prometheus.Desc
|
||||
}
|
||||
|
||||
// NewDeviceInfo returns a new DeviceInfo collector using the specified client.
|
||||
func NewDeviceInfo(client *livebox.Client) *DeviceInfo {
|
||||
return &DeviceInfo{
|
||||
client: client,
|
||||
numberOfRebootsMetric: prometheus.NewDesc(
|
||||
"livebox_deviceinfo_reboots_total",
|
||||
"Number of Livebox reboots.",
|
||||
nil, nil,
|
||||
),
|
||||
uptimeMetric: prometheus.NewDesc(
|
||||
"livebox_deviceinfo_uptime_seconds_total",
|
||||
"Livebox current uptime.",
|
||||
nil, nil,
|
||||
),
|
||||
memoryTotalMetric: prometheus.NewDesc(
|
||||
"livebox_deviceinfo_memory_total_bytes",
|
||||
"Livebox system total memory.",
|
||||
nil, nil,
|
||||
),
|
||||
memoryUsageMetric: prometheus.NewDesc(
|
||||
"livebox_deviceinfo_memory_usage_bytes",
|
||||
"Livebox system used memory.",
|
||||
nil, nil,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Describe currently does nothing.
|
||||
func (d *DeviceInfo) Describe(c chan<- *prometheus.Desc) {}
|
||||
|
||||
func (d *DeviceInfo) deviceInfo(c chan<- prometheus.Metric) {
|
||||
var deviceInfo struct {
|
||||
Status struct {
|
||||
NumberOfReboots float64 `json:"NumberOfReboots"`
|
||||
UpTime float64 `json:"UpTime"`
|
||||
} `json:"status"`
|
||||
}
|
||||
if err := d.client.Request(context.TODO(), request.New("DeviceInfo", "get", nil), &deviceInfo); err != nil {
|
||||
log.Printf("WARN: DeviceInfo collector failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c <- prometheus.MustNewConstMetric(d.numberOfRebootsMetric, prometheus.GaugeValue, deviceInfo.Status.NumberOfReboots)
|
||||
c <- prometheus.MustNewConstMetric(d.uptimeMetric, prometheus.GaugeValue, deviceInfo.Status.UpTime)
|
||||
}
|
||||
|
||||
func (d *DeviceInfo) memoryStatus(c chan<- prometheus.Metric) {
|
||||
var memoryStatus struct {
|
||||
Status struct {
|
||||
Total float64 `json:"Total"`
|
||||
Free float64 `json:"Free"`
|
||||
} `json:"status"`
|
||||
}
|
||||
if err := d.client.Request(context.TODO(), request.New("DeviceInfo.MemoryStatus", "get", nil), &memoryStatus); err != nil {
|
||||
log.Printf("WARN: MemoryStatus collector failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c <- prometheus.MustNewConstMetric(d.memoryTotalMetric, prometheus.GaugeValue, 1000*memoryStatus.Status.Total)
|
||||
c <- prometheus.MustNewConstMetric(d.memoryUsageMetric, prometheus.GaugeValue, 1000*(memoryStatus.Status.Total-memoryStatus.Status.Free))
|
||||
}
|
||||
|
||||
// Collect collects all DeviceInfo metrics.
|
||||
func (d *DeviceInfo) Collect(c chan<- prometheus.Metric) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
d.deviceInfo(c)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
d.memoryStatus(c)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
9
internal/poller/common.go
Normal file
9
internal/poller/common.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package poller
|
||||
|
||||
import "math"
|
||||
|
||||
const maxMbits = 2150
|
||||
|
||||
func sanitizeMbits(mbits float64) float64 {
|
||||
return math.Min(mbits, maxMbits)
|
||||
}
|
|
@ -13,12 +13,12 @@ var _ Poller = &DevicesTotal{}
|
|||
|
||||
// DevicesTotal allows to poll the total number of active devices.
|
||||
type DevicesTotal struct {
|
||||
client livebox.Client
|
||||
client *livebox.Client
|
||||
devicesTotal *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewDevicesTotal returns a new DevicesTotal poller.
|
||||
func NewDevicesTotal(client livebox.Client) *DevicesTotal {
|
||||
func NewDevicesTotal(client *livebox.Client) *DevicesTotal {
|
||||
return &DevicesTotal{
|
||||
client: client,
|
||||
devicesTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||
"github.com/Tomy2e/livebox-exporter/pkg/bitrate"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
|
@ -14,12 +15,12 @@ var _ Poller = &InterfaceMbits{}
|
|||
// InterfaceMbits allows to poll the current bandwidth usage on the Livebox
|
||||
// interfaces.
|
||||
type InterfaceMbits struct {
|
||||
client livebox.Client
|
||||
client *livebox.Client
|
||||
txMbits, rxMbits *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewInterfaceMbits returns a new InterfaceMbits poller.
|
||||
func NewInterfaceMbits(client livebox.Client) *InterfaceMbits {
|
||||
func NewInterfaceMbits(client *livebox.Client) *InterfaceMbits {
|
||||
return &InterfaceMbits{
|
||||
client: client,
|
||||
txMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
|
@ -44,10 +45,6 @@ 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 {
|
||||
|
@ -87,10 +84,10 @@ func (im *InterfaceMbits) Poll(ctx context.Context) error {
|
|||
|
||||
im.rxMbits.
|
||||
With(prometheus.Labels{"interface": iface}).
|
||||
Set(bitsPer30SecsToMbitsPerSec(rxCounter))
|
||||
Set(bitrate.BitsPer30SecsToMbits(rxCounter))
|
||||
im.txMbits.
|
||||
With(prometheus.Labels{"interface": iface}).
|
||||
Set(bitsPer30SecsToMbitsPerSec(txCounter))
|
||||
Set(bitrate.BitsPer30SecsToMbits(txCounter))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
110
internal/poller/interface_homelan.go
Normal file
110
internal/poller/interface_homelan.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||
"github.com/Tomy2e/livebox-exporter/pkg/bitrate"
|
||||
exporterLivebox "github.com/Tomy2e/livebox-exporter/pkg/livebox"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// InterfaceHomeLanMbitsMinDelay set the minimum delay between each poll.
|
||||
// Polling must only be done once every 30 seconds as Livebox updates data
|
||||
// only every 30 seconds.
|
||||
const InterfaceHomeLanMbitsMinDelay = 30 * time.Second
|
||||
|
||||
var _ Poller = &InterfaceHomeLanMbits{}
|
||||
|
||||
// InterfaceHomeLanMbits is an experimental poller to get the current bandwidth
|
||||
// usage on the Livebox interfaces.
|
||||
type InterfaceHomeLanMbits struct {
|
||||
client *livebox.Client
|
||||
interfaces []*exporterLivebox.Interface
|
||||
bitrate *bitrate.Bitrate
|
||||
txMbits, rxMbits *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewInterfaceHomeLanMbits returns a new InterfaceMbits poller.
|
||||
func NewInterfaceHomeLanMbits(client *livebox.Client, interfaces []*exporterLivebox.Interface) *InterfaceHomeLanMbits {
|
||||
return &InterfaceHomeLanMbits{
|
||||
client: client,
|
||||
interfaces: interfaces,
|
||||
bitrate: bitrate.New(InterfaceHomeLanMbitsMinDelay),
|
||||
txMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "livebox_interface_homelan_tx_mbits",
|
||||
Help: "Transmitted Mbits per second.",
|
||||
}, []string{
|
||||
// Name of the interface.
|
||||
"interface",
|
||||
}),
|
||||
rxMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "livebox_interface_homelan_rx_mbits",
|
||||
Help: "Received Mbits per second.",
|
||||
}, []string{
|
||||
// Name of the interface.
|
||||
"interface",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Collectors returns all metrics.
|
||||
func (im *InterfaceHomeLanMbits) Collectors() []prometheus.Collector {
|
||||
return []prometheus.Collector{
|
||||
im.txMbits,
|
||||
im.rxMbits,
|
||||
}
|
||||
}
|
||||
|
||||
// Poll polls the current bandwidth usage.
|
||||
func (im *InterfaceHomeLanMbits) Poll(ctx context.Context) error {
|
||||
for _, itf := range im.interfaces {
|
||||
// Enforce InterfaceHomeLanMbitsMinDelay.
|
||||
if !im.bitrate.ShouldMeasure(itf.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
var stats struct {
|
||||
Status struct {
|
||||
BytesReceived uint64 `json:"BytesReceived"`
|
||||
BytesSent uint64 `json:"BytesSent"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
if err := im.client.Request(ctx, request.New(
|
||||
fmt.Sprintf("HomeLan.Interface.%s.Stats", itf.Name),
|
||||
"get",
|
||||
nil,
|
||||
), &stats); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counters := &bitrate.Counters{
|
||||
Tx: stats.Status.BytesSent,
|
||||
Rx: stats.Status.BytesReceived,
|
||||
}
|
||||
|
||||
if !itf.IsWAN() {
|
||||
counters.Swap()
|
||||
}
|
||||
|
||||
bitrates := im.bitrate.Measure(itf.Name, counters)
|
||||
|
||||
if bitrates.Rx != nil && !bitrates.Rx.Reset {
|
||||
im.rxMbits.
|
||||
With(prometheus.Labels{"interface": itf.Name}).
|
||||
Set(sanitizeMbits(bitrates.Rx.Value))
|
||||
}
|
||||
|
||||
if bitrates.Tx != nil && !bitrates.Tx.Reset {
|
||||
im.txMbits.
|
||||
With(prometheus.Labels{"interface": itf.Name}).
|
||||
Set(sanitizeMbits(bitrates.Tx.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
99
internal/poller/interface_netdev.go
Normal file
99
internal/poller/interface_netdev.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||
"github.com/Tomy2e/livebox-exporter/pkg/bitrate"
|
||||
exporterLivebox "github.com/Tomy2e/livebox-exporter/pkg/livebox"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var _ Poller = &InterfaceNetDevMbits{}
|
||||
|
||||
// InterfaceNetDevMbits is an experimental poller to get the current bandwidth
|
||||
// usage on the Livebox interfaces.
|
||||
type InterfaceNetDevMbits struct {
|
||||
client *livebox.Client
|
||||
interfaces []*exporterLivebox.Interface
|
||||
bitrate *bitrate.Bitrate
|
||||
txMbits, rxMbits *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewInterfaceNetDevMbits returns a new InterfaceNetDevMbits poller.
|
||||
func NewInterfaceNetDevMbits(client *livebox.Client, interfaces []*exporterLivebox.Interface) *InterfaceNetDevMbits {
|
||||
return &InterfaceNetDevMbits{
|
||||
client: client,
|
||||
interfaces: interfaces,
|
||||
bitrate: bitrate.New(0),
|
||||
txMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "livebox_interface_netdev_tx_mbits",
|
||||
Help: "Transmitted Mbits per second.",
|
||||
}, []string{
|
||||
// Name of the interface.
|
||||
"interface",
|
||||
}),
|
||||
rxMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "livebox_interface_netdev_rx_mbits",
|
||||
Help: "Received Mbits per second.",
|
||||
}, []string{
|
||||
// Name of the interface.
|
||||
"interface",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Collectors returns all metrics.
|
||||
func (im *InterfaceNetDevMbits) Collectors() []prometheus.Collector {
|
||||
return []prometheus.Collector{
|
||||
im.txMbits,
|
||||
im.rxMbits,
|
||||
}
|
||||
}
|
||||
|
||||
// Poll polls the current bandwidth usage.
|
||||
func (im *InterfaceNetDevMbits) Poll(ctx context.Context) error {
|
||||
for _, itf := range im.interfaces {
|
||||
var stats struct {
|
||||
Status struct {
|
||||
RxBytes uint64 `json:"RxBytes"`
|
||||
TxBytes uint64 `json:"TxBytes"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
if err := im.client.Request(ctx, request.New(
|
||||
fmt.Sprintf("NeMo.Intf.%s", itf.Name),
|
||||
"getNetDevStats",
|
||||
nil,
|
||||
), &stats); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counters := &bitrate.Counters{
|
||||
Tx: stats.Status.TxBytes,
|
||||
Rx: stats.Status.RxBytes,
|
||||
}
|
||||
|
||||
if !itf.IsWAN() {
|
||||
counters.Swap()
|
||||
}
|
||||
|
||||
bitrates := im.bitrate.Measure(itf.Name, counters)
|
||||
|
||||
if bitrates.Rx != nil && !bitrates.Rx.Reset {
|
||||
im.rxMbits.
|
||||
With(prometheus.Labels{"interface": itf.Name}).
|
||||
Set(sanitizeMbits(bitrates.Rx.Value))
|
||||
}
|
||||
|
||||
if bitrates.Tx != nil && !bitrates.Tx.Reset {
|
||||
im.txMbits.
|
||||
With(prometheus.Labels{"interface": itf.Name}).
|
||||
Set(sanitizeMbits(bitrates.Tx.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
81
internal/poller/wan.go
Normal file
81
internal/poller/wan.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||
"github.com/Tomy2e/livebox-exporter/pkg/bitrate"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var _ Poller = &WANMbits{}
|
||||
|
||||
// WANMbits is an experimental poller to get the current bandwidth usage on the
|
||||
// WAN interface of the Livebox.
|
||||
type WANMbits struct {
|
||||
client *livebox.Client
|
||||
bitrate *bitrate.Bitrate
|
||||
txMbits, rxMbits prometheus.Gauge
|
||||
}
|
||||
|
||||
// NewWANMbits returns a new WANMbits poller.
|
||||
func NewWANMbits(client *livebox.Client) *WANMbits {
|
||||
return &WANMbits{
|
||||
client: client,
|
||||
bitrate: bitrate.New(InterfaceHomeLanMbitsMinDelay),
|
||||
txMbits: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "livebox_wan_tx_mbits",
|
||||
Help: "Transmitted Mbits per second on the WAN interface.",
|
||||
}),
|
||||
rxMbits: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "livebox_wan_rx_mbits",
|
||||
Help: "Received Mbits per second on the WAN interface.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Collectors returns all metrics.
|
||||
func (im *WANMbits) Collectors() []prometheus.Collector {
|
||||
return []prometheus.Collector{
|
||||
im.txMbits,
|
||||
im.rxMbits,
|
||||
}
|
||||
}
|
||||
|
||||
// Poll polls the current bandwidth usage on the WAN interface.
|
||||
func (im *WANMbits) Poll(ctx context.Context) error {
|
||||
var stats struct {
|
||||
Status struct {
|
||||
BytesReceived uint64 `json:"BytesReceived"`
|
||||
BytesSent uint64 `json:"BytesSent"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
if err := im.client.Request(
|
||||
ctx,
|
||||
request.New("HomeLan", "getWANCounters", nil),
|
||||
&stats,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counters := &bitrate.Counters{
|
||||
Tx: stats.Status.BytesSent,
|
||||
Rx: stats.Status.BytesReceived,
|
||||
}
|
||||
|
||||
counters.Swap()
|
||||
|
||||
bitrates := im.bitrate.Measure("WAN", counters)
|
||||
|
||||
if bitrates.Rx != nil && !bitrates.Rx.Reset {
|
||||
im.rxMbits.Set(sanitizeMbits(bitrates.Rx.Value))
|
||||
}
|
||||
|
||||
if bitrates.Tx != nil && !bitrates.Tx.Reset {
|
||||
im.txMbits.Set(sanitizeMbits(bitrates.Tx.Value))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
160
main.go
160
main.go
|
@ -2,25 +2,158 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-exporter/internal/collector"
|
||||
"github.com/Tomy2e/livebox-exporter/internal/poller"
|
||||
exporterLivebox "github.com/Tomy2e/livebox-exporter/pkg/livebox"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const defaultPollingFrequency = 30
|
||||
|
||||
const (
|
||||
ExperimentalMetricsInterfaceHomeLan = "livebox_interface_homelan"
|
||||
ExperimentalMetricsInterfaceNetDev = "livebox_interface_netdev"
|
||||
ExperimentalMetricsWAN = "livebox_wan"
|
||||
)
|
||||
|
||||
var experimentalMetrics = []string{
|
||||
ExperimentalMetricsInterfaceHomeLan,
|
||||
ExperimentalMetricsInterfaceNetDev,
|
||||
ExperimentalMetricsWAN,
|
||||
}
|
||||
|
||||
func parseExperimentalFlag(
|
||||
ctx context.Context,
|
||||
client *livebox.Client,
|
||||
experimental string,
|
||||
pollingFrequency *uint,
|
||||
) (pollers []poller.Poller) {
|
||||
if experimental == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
interfaces []*exporterLivebox.Interface
|
||||
err error
|
||||
|
||||
enabled = make(map[string]bool)
|
||||
)
|
||||
|
||||
for _, exp := range strings.Split(experimental, ",") {
|
||||
exp = strings.TrimSpace(exp)
|
||||
|
||||
if !slices.Contains(experimentalMetrics, exp) {
|
||||
log.Printf("WARN: Unknown experimental metrics: %s", exp)
|
||||
continue
|
||||
}
|
||||
|
||||
if enabled[exp] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Discover interfaces for experimental pollers that require interfaces.
|
||||
switch exp {
|
||||
case ExperimentalMetricsInterfaceHomeLan, ExperimentalMetricsInterfaceNetDev:
|
||||
if interfaces == nil {
|
||||
interfaces, err = exporterLivebox.DiscoverInterfaces(ctx, client)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to discover Livebox interfaces: %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch exp {
|
||||
case ExperimentalMetricsInterfaceHomeLan:
|
||||
pollers = append(pollers, poller.NewInterfaceHomeLanMbits(client, interfaces))
|
||||
case ExperimentalMetricsInterfaceNetDev:
|
||||
pollers = append(pollers, poller.NewInterfaceNetDevMbits(client, interfaces))
|
||||
|
||||
if *pollingFrequency > 5 {
|
||||
log.Printf(
|
||||
"WARN: The %s experimental metrics require a lower polling frequency, "+
|
||||
"setting polling frequency to 5 seconds\n",
|
||||
ExperimentalMetricsInterfaceNetDev,
|
||||
)
|
||||
*pollingFrequency = 5
|
||||
}
|
||||
case ExperimentalMetricsWAN:
|
||||
pollers = append(pollers, poller.NewWANMbits(client))
|
||||
}
|
||||
|
||||
log.Printf("INFO: enabled experimental metrics: %s\n", exp)
|
||||
enabled[exp] = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getHTTPClient() (*http.Client, error) {
|
||||
liveboxCACertPath := os.Getenv("LIVEBOX_CACERT")
|
||||
|
||||
if liveboxCACertPath == "" {
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
// Get the SystemCertPool, continue with an empty pool on error.
|
||||
rootCAs, _ := x509.SystemCertPool()
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
|
||||
certs, err := ioutil.ReadFile(liveboxCACertPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read livebox CA cert: %w", err)
|
||||
}
|
||||
|
||||
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
|
||||
return nil, errors.New("no livebox CA cert was successfully added")
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isFatalError(err error) bool {
|
||||
if errors.Is(err, livebox.ErrInvalidPassword) {
|
||||
return true
|
||||
}
|
||||
|
||||
var certError *tls.CertificateVerificationError
|
||||
if errors.As(err, &certError) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func main() {
|
||||
pollingFrequency := flag.Uint("polling-frequency", defaultPollingFrequency, "Polling frequency")
|
||||
listen := flag.String("listen", ":8080", "Listening address")
|
||||
experimental := flag.String("experimental", "", fmt.Sprintf(
|
||||
"Comma separated list of experimental metrics to enable (available metrics: %s)",
|
||||
strings.Join(experimentalMetrics, ","),
|
||||
))
|
||||
flag.Parse()
|
||||
|
||||
adminPassword := os.Getenv("ADMIN_PASSWORD")
|
||||
|
@ -28,16 +161,37 @@ func main() {
|
|||
log.Fatal("ADMIN_PASSWORD environment variable must be set")
|
||||
}
|
||||
|
||||
liveboxAddress := os.Getenv("LIVEBOX_ADDRESS")
|
||||
if liveboxAddress == "" {
|
||||
liveboxAddress = livebox.DefaultAddress
|
||||
}
|
||||
|
||||
httpClient, err := getHTTPClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
client, err := livebox.NewClient(
|
||||
adminPassword,
|
||||
livebox.WithAddress(liveboxAddress),
|
||||
livebox.WithHTTPClient(httpClient),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create Livebox client: %v", err)
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
registry = prometheus.NewRegistry()
|
||||
client = livebox.NewClient(adminPassword)
|
||||
pollers = poller.Pollers{
|
||||
poller.NewDevicesTotal(client),
|
||||
poller.NewInterfaceMbits(client),
|
||||
}
|
||||
)
|
||||
|
||||
// Add experimental pollers.
|
||||
pollers = append(pollers, parseExperimentalFlag(ctx, client, *experimental, pollingFrequency)...)
|
||||
|
||||
registry.MustRegister(
|
||||
append(
|
||||
pollers.Collectors(),
|
||||
|
@ -46,10 +200,12 @@ func main() {
|
|||
)...,
|
||||
)
|
||||
|
||||
registry.MustRegister(collector.NewDeviceInfo(client))
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if err := pollers.Poll(ctx); err != nil {
|
||||
if errors.Is(err, livebox.ErrInvalidPassword) {
|
||||
if isFatalError(err) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
106
pkg/bitrate/bitrate.go
Normal file
106
pkg/bitrate/bitrate.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package bitrate
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Bitrate allows calculating bitrates for a set of network interfaces.
|
||||
// This implementation is not thread-safe.
|
||||
type Bitrate struct {
|
||||
measures map[string]*measure
|
||||
minDelayBetweenMeasures time.Duration
|
||||
}
|
||||
|
||||
// New returns a new bitrate measurer.
|
||||
func New(minDelayBetweenMeasures time.Duration) *Bitrate {
|
||||
return &Bitrate{
|
||||
measures: make(map[string]*measure),
|
||||
minDelayBetweenMeasures: minDelayBetweenMeasures,
|
||||
}
|
||||
}
|
||||
|
||||
// mesure saves the counter values at a specific point in time.
|
||||
type measure struct {
|
||||
Counters
|
||||
Last time.Time
|
||||
}
|
||||
|
||||
// Counters contain Tx and Rx counters for a network interface.
|
||||
type Counters struct {
|
||||
Tx, Rx uint64
|
||||
}
|
||||
|
||||
// Swap swaps Tx and Rx counters.
|
||||
func (c *Counters) Swap() {
|
||||
c.Rx, c.Tx = c.Tx, c.Rx
|
||||
}
|
||||
|
||||
// Bitrates for Tx and Rx.
|
||||
type Bitrates struct {
|
||||
// Tx bitrate, can be nil if not available.
|
||||
Tx *BitrateSpec
|
||||
// Rx bitrate, can be nil if not available.
|
||||
Rx *BitrateSpec
|
||||
}
|
||||
|
||||
// BitrateSpec contains the value of the bitrate
|
||||
type BitrateSpec struct {
|
||||
// Value of the bitrate (in Mbit/s). Will be 0 if Reset is true.
|
||||
Value float64
|
||||
// Reset is true when the counter was reset.
|
||||
Reset bool
|
||||
}
|
||||
|
||||
// ShouldMeasure returns true if a measure should be done.
|
||||
func (b *Bitrate) ShouldMeasure(name string) bool {
|
||||
last, ok := b.measures[name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return time.Now().Sub(last.Last) > b.minDelayBetweenMeasures
|
||||
}
|
||||
|
||||
// Measure saves the current measure and returns the current RX/TX bitrates.
|
||||
func (b *Bitrate) Measure(name string, current *Counters) *Bitrates {
|
||||
br := &Bitrates{}
|
||||
|
||||
last, ok := b.measures[name]
|
||||
|
||||
// Only calculate bitrates if there is a previous measure.
|
||||
if ok && !last.Last.IsZero() {
|
||||
elapsed := time.Now().Sub(last.Last)
|
||||
|
||||
if elapsed.Seconds() > 0 {
|
||||
diff := current.Rx - last.Rx
|
||||
if diff >= 0 {
|
||||
br.Rx = &BitrateSpec{
|
||||
Value: BytesPerSecToMbits(float64(diff) / elapsed.Seconds()),
|
||||
}
|
||||
} else {
|
||||
br.Rx = &BitrateSpec{
|
||||
Reset: true,
|
||||
}
|
||||
}
|
||||
|
||||
diff = current.Tx - last.Tx
|
||||
if diff >= 0 {
|
||||
br.Tx = &BitrateSpec{
|
||||
Value: BytesPerSecToMbits(float64(diff) / elapsed.Seconds()),
|
||||
}
|
||||
} else {
|
||||
br.Tx = &BitrateSpec{
|
||||
Reset: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save this measure as the latest.
|
||||
b.measures[name] = &measure{
|
||||
Counters: *current,
|
||||
Last: time.Now(),
|
||||
}
|
||||
|
||||
return br
|
||||
}
|
11
pkg/bitrate/conversion.go
Normal file
11
pkg/bitrate/conversion.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package bitrate
|
||||
|
||||
// BitsPer30SecsToMbits converts bits/30secs to Mbit/s.
|
||||
func BitsPer30SecsToMbits(v int) float64 {
|
||||
return float64(v) / 30000000
|
||||
}
|
||||
|
||||
// BytesPerSecToMbits converts B/s to Mbit/s.
|
||||
func BytesPerSecToMbits(bytes float64) float64 {
|
||||
return bytes * 8 / 1000000
|
||||
}
|
64
pkg/livebox/discovery.go
Normal file
64
pkg/livebox/discovery.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package livebox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Tomy2e/livebox-api-client"
|
||||
"github.com/Tomy2e/livebox-api-client/api/request"
|
||||
)
|
||||
|
||||
// Interface is a Livebox Network interface.
|
||||
type Interface struct {
|
||||
Name string
|
||||
Flags string
|
||||
}
|
||||
|
||||
// IsWAN returns true if this interface is a WAN interface.
|
||||
func (i *Interface) IsWAN() bool {
|
||||
return strings.Contains(i.Flags, "wan")
|
||||
}
|
||||
|
||||
// IsWLAN returns true if this interface is a WLAN interface.
|
||||
func (i *Interface) IsWLAN() bool {
|
||||
return strings.Contains(i.Flags, "wlanvap")
|
||||
}
|
||||
|
||||
// DiscoverInterfaces discovers network interfaces on the Livebox.
|
||||
func DiscoverInterfaces(ctx context.Context, client *livebox.Client) ([]*Interface, error) {
|
||||
var mibs struct {
|
||||
Status struct {
|
||||
Base map[string]struct {
|
||||
Flags string `json:"flags"`
|
||||
} `json:"base"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
if err := client.Request(
|
||||
ctx,
|
||||
request.New("NeMo.Intf.data", "getMIBs", map[string]interface{}{
|
||||
"traverse": "all",
|
||||
"flag": "statmon && !vlan",
|
||||
}),
|
||||
&mibs,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("failed to discover interfaces: %w", err)
|
||||
}
|
||||
|
||||
if len(mibs.Status.Base) == 0 {
|
||||
return nil, errors.New("no interfaces found")
|
||||
}
|
||||
|
||||
itfs := make([]*Interface, 0, len(mibs.Status.Base))
|
||||
|
||||
for itf, val := range mibs.Status.Base {
|
||||
itfs = append(itfs, &Interface{
|
||||
Name: itf,
|
||||
Flags: val.Flags,
|
||||
})
|
||||
}
|
||||
|
||||
return itfs, nil
|
||||
}
|
Loading…
Reference in a new issue