Add new experimental metrics (#4)
This commit is contained in:
parent
4bac289600
commit
136f95a9b7
12 changed files with 592 additions and 18 deletions
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…
Add table
Add a link
Reference in a new issue