Add new experimental metrics (#4)

This commit is contained in:
Tomy Guichard 2023-04-21 18:58:34 +02:00 committed by GitHub
parent 4bac289600
commit 136f95a9b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 592 additions and 18 deletions

106
pkg/bitrate/bitrate.go Normal file
View 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
View 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
View 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
}