initial commit
This commit is contained in:
commit
7241c4c499
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
omada-bandwidth
|
39
client_traffic.go
Normal file
39
client_traffic.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
omadamodel "git.tordarus.net/tordarus/omada-api/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TrafficByClient = map[ClientUniqueID]TrafficRate{}
|
||||||
|
|
||||||
|
func CalculateClientTraffic(site *omadamodel.Site, client *omadamodel.Client, duration time.Duration) TrafficRate {
|
||||||
|
uniqueID := ClientUniqueID{
|
||||||
|
SiteID: site.ID,
|
||||||
|
ClientMac: client.MacAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
trafficTotal := TrafficRate{
|
||||||
|
Received: uint64(client.TrafficDownBytes),
|
||||||
|
Transferred: uint64(client.TrafficUpBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
TrafficByClient[uniqueID] = trafficTotal
|
||||||
|
}()
|
||||||
|
|
||||||
|
lastTotal, ok := TrafficByClient[uniqueID]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return TrafficRate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
trafficInDur := trafficTotal.Sub(lastTotal)
|
||||||
|
seconds := duration.Seconds()
|
||||||
|
|
||||||
|
return TrafficRate{
|
||||||
|
Received: uint64(float64(trafficInDur.Received) / seconds),
|
||||||
|
Transferred: uint64(float64(trafficInDur.Transferred) / seconds),
|
||||||
|
}
|
||||||
|
}
|
15
go.mod
Normal file
15
go.mod
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module git.tordarus.net/tordarus/omada-bandwidth
|
||||||
|
|
||||||
|
go 1.23.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.tordarus.net/tordarus/channel v0.1.18
|
||||||
|
git.tordarus.net/tordarus/omada-api v0.0.1
|
||||||
|
git.tordarus.net/tordarus/slices v0.0.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.milar.in/milarin/gmath v0.0.6 // indirect
|
||||||
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5 // indirect
|
||||||
|
git.tordarus.net/tordarus/ezhttp v0.0.3 // indirect
|
||||||
|
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
git.milar.in/milarin/gmath v0.0.6 h1:NNtlh4PLsQT/hqq3F87Xm3u0PLapgBU/L2mN9mb8SPs=
|
||||||
|
git.milar.in/milarin/gmath v0.0.6/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE=
|
||||||
|
git.tordarus.net/tordarus/channel v0.1.18 h1:/9BDbkyXbVpFB+dQbToniX6g/ApBnzjslYt4NiycMQo=
|
||||||
|
git.tordarus.net/tordarus/channel v0.1.18/go.mod h1:8/dWFTdGO7g4AeSZ7cF6GerkGbe9c4dBVMVDBxOd9m4=
|
||||||
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5 h1:rKNDX/YGunqg8TEU6q1rgS2BcDKVmUW2cg61JOE/wws=
|
||||||
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5/go.mod h1:/qVGwrEmqtIrZyuuoIQl4vquSkPWUNJmlGNedDrdYfg=
|
||||||
|
git.tordarus.net/tordarus/ezhttp v0.0.3 h1:K6IlLmqkAFUF68HJsOTKcP3ejco7qfm+MuEagohoouo=
|
||||||
|
git.tordarus.net/tordarus/ezhttp v0.0.3/go.mod h1:Zq9o0Hibny61GqSCwJHa0PfGjVoUFv/zt2PjiQHXvmY=
|
||||||
|
git.tordarus.net/tordarus/omada-api v0.0.1 h1:w4WozETL00JygidOXGkGs9UxUQhUNlNnlfqWiYYriHo=
|
||||||
|
git.tordarus.net/tordarus/omada-api v0.0.1/go.mod h1:Ufp8hdXMyrXK7JFHq4WL1WSIlr9L6rhOBfQnqWNpXyM=
|
||||||
|
git.tordarus.net/tordarus/slices v0.0.12 h1:/0GDo1oGTIZB4+Nr/0dxUf7Z3mE8H8Ew+orFedGbxb8=
|
||||||
|
git.tordarus.net/tordarus/slices v0.0.12/go.mod h1:bu97dfPWRJ8iqwijWcpzRnFkry4sfY6mc+/bxLai4fU=
|
87
main.go
Normal file
87
main.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/gmath"
|
||||||
|
"git.tordarus.net/tordarus/channel"
|
||||||
|
"git.tordarus.net/tordarus/envvars"
|
||||||
|
omadaapi "git.tordarus.net/tordarus/omada-api"
|
||||||
|
omadamodel "git.tordarus.net/tordarus/omada-api/model"
|
||||||
|
"git.tordarus.net/tordarus/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ( // flags
|
||||||
|
FlagSiteNames = envvars.StringSlice("SITES", ",", []string{})
|
||||||
|
FlagOmadaURL = envvars.String("OMADA_URL", "http://localhost:8088")
|
||||||
|
FlagOmadaID = envvars.String("OMADA_ID", "")
|
||||||
|
FlagOmadaClientID = envvars.String("OMADA_CLIENT_ID", "")
|
||||||
|
FlagOmadaClientSecret = envvars.String("OMADA_CLIENT_SECRET", "")
|
||||||
|
FlagOmadaUsername = envvars.String("OMADA_USERNAME", "")
|
||||||
|
FlagOmadaPassword = envvars.String("OMADA_PASSWORD", "")
|
||||||
|
FlagRefreshInterval = envvars.Duration("REFRESH_INTERVAL", 30*time.Second)
|
||||||
|
)
|
||||||
|
|
||||||
|
var MinRefreshInterval = 30 * time.Second
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
FlagRefreshInterval = gmath.Max(FlagRefreshInterval, MinRefreshInterval)
|
||||||
|
|
||||||
|
api, err := omadaapi.NewApi(omadaapi.ApiConfig{
|
||||||
|
BasePath: FlagOmadaURL,
|
||||||
|
OmadaID: FlagOmadaID,
|
||||||
|
ClientID: FlagOmadaClientID,
|
||||||
|
ClientSecret: FlagOmadaClientSecret,
|
||||||
|
Username: FlagOmadaUsername,
|
||||||
|
Password: FlagOmadaPassword,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sites := slices.Filter(channel.ToSlice(api.GetSites()), FilterSitesByName(FlagSiteNames...))
|
||||||
|
|
||||||
|
ticker := time.NewTicker(FlagRefreshInterval)
|
||||||
|
|
||||||
|
CalculateSiteTraffic(api, sites, 0)
|
||||||
|
|
||||||
|
lastTick := time.Now()
|
||||||
|
for now := range ticker.C {
|
||||||
|
trafficStats := CalculateSiteTraffic(api, sites, now.Sub(lastTick))
|
||||||
|
fmt.Println(trafficStats)
|
||||||
|
lastTick = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalculateSiteTraffic(api *omadaapi.Api, sites []*omadamodel.Site, duration time.Duration) *TrafficStats {
|
||||||
|
siteTraffics := map[string]*SiteTraffic{}
|
||||||
|
|
||||||
|
for _, site := range sites {
|
||||||
|
trafficByClient := map[string]TrafficRate{}
|
||||||
|
|
||||||
|
clients := channel.ToSlice(api.GetClients(site.ID))
|
||||||
|
for _, client := range clients {
|
||||||
|
traffic := CalculateClientTraffic(site, client, duration)
|
||||||
|
trafficByClient[client.Name] = traffic
|
||||||
|
}
|
||||||
|
|
||||||
|
siteTraffics[site.Name] = NewSiteTraffic(trafficByClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewTrafficStats(siteTraffics)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterSitesByName(allowedSiteNames ...string) func(site *omadamodel.Site) bool {
|
||||||
|
if len(allowedSiteNames) == 0 {
|
||||||
|
return func(site *omadamodel.Site) bool { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
siteNames := slices.ToStructMap(allowedSiteNames)
|
||||||
|
|
||||||
|
return func(site *omadamodel.Site) bool {
|
||||||
|
_, ok := siteNames[site.Name]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
}
|
92
types.go
Normal file
92
types.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/gmath"
|
||||||
|
omadamodel "git.tordarus.net/tordarus/omada-api/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientUniqueID struct {
|
||||||
|
SiteID omadamodel.SiteID
|
||||||
|
ClientMac string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrafficRate struct {
|
||||||
|
Received uint64 `json:"received"` // bytes per second
|
||||||
|
Transferred uint64 `json:"transferred"` // bytes per second
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr TrafficRate) Add(o TrafficRate) TrafficRate {
|
||||||
|
return TrafficRate{
|
||||||
|
Received: tr.Received + o.Received,
|
||||||
|
Transferred: tr.Transferred + o.Transferred,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr TrafficRate) Sub(o TrafficRate) TrafficRate {
|
||||||
|
return TrafficRate{
|
||||||
|
Received: tr.Received - o.Received,
|
||||||
|
Transferred: tr.Transferred - o.Transferred,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SiteTraffic struct {
|
||||||
|
Total TrafficRate `json:"total"`
|
||||||
|
Clients map[string]TrafficRate `json:"clients"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSiteTraffic(clients map[string]TrafficRate) *SiteTraffic {
|
||||||
|
total := TrafficRate{}
|
||||||
|
for _, rate := range clients {
|
||||||
|
total = total.Add(rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SiteTraffic{
|
||||||
|
Total: total,
|
||||||
|
Clients: clients,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrafficStats struct {
|
||||||
|
Total TrafficRate `json:"total"`
|
||||||
|
Sites map[string]*SiteTraffic `json:"sites"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrafficStats(sites map[string]*SiteTraffic) *TrafficStats {
|
||||||
|
total := TrafficRate{}
|
||||||
|
for _, siteTraffic := range sites {
|
||||||
|
total = total.Add(siteTraffic.Total)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TrafficStats{
|
||||||
|
Total: total,
|
||||||
|
Sites: sites,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr TrafficRate) String() string {
|
||||||
|
return fmt.Sprintf("Rx: %s | Tx: %s", FormatBytes(tr.Received), FormatBytes(tr.Transferred))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stats TrafficStats) String() string {
|
||||||
|
data, _ := json.MarshalIndent(stats, "", "\t")
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatBytes[T gmath.Integer](bytes T) string {
|
||||||
|
value := float64(bytes)
|
||||||
|
|
||||||
|
if value >= 1000000000000 {
|
||||||
|
return fmt.Sprintf("%.02fT", value/1000000000000)
|
||||||
|
} else if value >= 1000000000 {
|
||||||
|
return fmt.Sprintf("%.02fG", value/1000000000)
|
||||||
|
} else if value >= 1000000 {
|
||||||
|
return fmt.Sprintf("%.02fM", value/1000000)
|
||||||
|
} else if value >= 1000 {
|
||||||
|
return fmt.Sprintf("%.02fK", value/1000)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%.02fB", value)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user