Compare commits
No commits in common. "main" and "v0.0.1" have entirely different histories.
@ -14,10 +14,10 @@ func CalculateClientTraffic(site *omadamodel.Site, client *omadamodel.Client, du
|
|||||||
ClientMac: client.MacAddress,
|
ClientMac: client.MacAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
trafficTotal := MakeTrafficRate(
|
trafficTotal := TrafficRate{
|
||||||
uint64(client.TrafficDownBytes),
|
Received: uint64(client.TrafficDownBytes),
|
||||||
uint64(client.TrafficUpBytes),
|
Transferred: uint64(client.TrafficUpBytes),
|
||||||
)
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
TrafficByClient[uniqueID] = trafficTotal
|
TrafficByClient[uniqueID] = trafficTotal
|
||||||
@ -26,14 +26,14 @@ func CalculateClientTraffic(site *omadamodel.Site, client *omadamodel.Client, du
|
|||||||
lastTotal, ok := TrafficByClient[uniqueID]
|
lastTotal, ok := TrafficByClient[uniqueID]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return MakeTrafficRate(0, 0)
|
return TrafficRate{}
|
||||||
}
|
}
|
||||||
|
|
||||||
trafficInDur := trafficTotal.Sub(lastTotal)
|
trafficInDur := trafficTotal.Sub(lastTotal)
|
||||||
seconds := duration.Seconds()
|
seconds := duration.Seconds()
|
||||||
|
|
||||||
return MakeTrafficRate(
|
return TrafficRate{
|
||||||
uint64(float64(trafficInDur.Received)/seconds),
|
Received: uint64(float64(trafficInDur.Received) / seconds),
|
||||||
uint64(float64(trafficInDur.Transferred)/seconds),
|
Transferred: uint64(float64(trafficInDur.Transferred) / seconds),
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
8
go.mod
8
go.mod
@ -3,11 +3,11 @@ module git.tordarus.net/tordarus/omada-bandwidth
|
|||||||
go 1.23.5
|
go 1.23.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.tordarus.net/tordarus/channel v0.1.19
|
git.tordarus.net/tordarus/channel v0.1.18
|
||||||
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5
|
||||||
git.tordarus.net/tordarus/gmath v0.0.7
|
git.tordarus.net/tordarus/gmath v0.0.7
|
||||||
git.tordarus.net/tordarus/omada-api v0.0.10
|
git.tordarus.net/tordarus/omada-api v0.0.1
|
||||||
git.tordarus.net/tordarus/slices v0.0.14
|
git.tordarus.net/tordarus/slices v0.0.13
|
||||||
)
|
)
|
||||||
|
|
||||||
require git.tordarus.net/tordarus/ezhttp v0.0.9 // indirect
|
require git.tordarus.net/tordarus/ezhttp v0.0.3 // indirect
|
||||||
|
16
go.sum
16
go.sum
@ -1,12 +1,12 @@
|
|||||||
git.tordarus.net/tordarus/channel v0.1.19 h1:d9xnSwFyvBh4B1/82mt0A7Gpm2nIZJTc+9ceJMIOu5Q=
|
git.tordarus.net/tordarus/channel v0.1.18 h1:/9BDbkyXbVpFB+dQbToniX6g/ApBnzjslYt4NiycMQo=
|
||||||
git.tordarus.net/tordarus/channel v0.1.19/go.mod h1:8/dWFTdGO7g4AeSZ7cF6GerkGbe9c4dBVMVDBxOd9m4=
|
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 h1:rKNDX/YGunqg8TEU6q1rgS2BcDKVmUW2cg61JOE/wws=
|
||||||
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5/go.mod h1:/qVGwrEmqtIrZyuuoIQl4vquSkPWUNJmlGNedDrdYfg=
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5/go.mod h1:/qVGwrEmqtIrZyuuoIQl4vquSkPWUNJmlGNedDrdYfg=
|
||||||
git.tordarus.net/tordarus/ezhttp v0.0.9 h1:YwdQ4YcJwvpMw5CX5NcCEM23XQL+WCz5nWuc2dzX/84=
|
git.tordarus.net/tordarus/ezhttp v0.0.3 h1:K6IlLmqkAFUF68HJsOTKcP3ejco7qfm+MuEagohoouo=
|
||||||
git.tordarus.net/tordarus/ezhttp v0.0.9/go.mod h1:Zq9o0Hibny61GqSCwJHa0PfGjVoUFv/zt2PjiQHXvmY=
|
git.tordarus.net/tordarus/ezhttp v0.0.3/go.mod h1:Zq9o0Hibny61GqSCwJHa0PfGjVoUFv/zt2PjiQHXvmY=
|
||||||
git.tordarus.net/tordarus/gmath v0.0.7 h1:tR48idt9AUL0r556ww3ZxByTKJEr6NWCTlhl2ihzYxQ=
|
git.tordarus.net/tordarus/gmath v0.0.7 h1:tR48idt9AUL0r556ww3ZxByTKJEr6NWCTlhl2ihzYxQ=
|
||||||
git.tordarus.net/tordarus/gmath v0.0.7/go.mod h1:mO7aPlvNrGVE9UFXEuuACjZgMDsM63l3OcQy6xSQnoE=
|
git.tordarus.net/tordarus/gmath v0.0.7/go.mod h1:mO7aPlvNrGVE9UFXEuuACjZgMDsM63l3OcQy6xSQnoE=
|
||||||
git.tordarus.net/tordarus/omada-api v0.0.10 h1:SUxpG6HTE2meEyaEXorympNPqOQxRviBCKoOQ9fKlgY=
|
git.tordarus.net/tordarus/omada-api v0.0.1 h1:w4WozETL00JygidOXGkGs9UxUQhUNlNnlfqWiYYriHo=
|
||||||
git.tordarus.net/tordarus/omada-api v0.0.10/go.mod h1:+VjFQyRiDp8fKbRB6E3zfkEPm00ffnidQ1+mfVC2KUI=
|
git.tordarus.net/tordarus/omada-api v0.0.1/go.mod h1:Ufp8hdXMyrXK7JFHq4WL1WSIlr9L6rhOBfQnqWNpXyM=
|
||||||
git.tordarus.net/tordarus/slices v0.0.14 h1:Jy1VRMs777WewJ7mxTgjyQIMm/Zr+co18/XoQ01YZ3A=
|
git.tordarus.net/tordarus/slices v0.0.13 h1:S/6PtIxAtERwlyQvZAZZaq6RVo2kLrAsrA+19ThRRvY=
|
||||||
git.tordarus.net/tordarus/slices v0.0.14/go.mod h1:RgE7A1aSAezIvPUgcbUuMHu0q4xGKoRevT+DC0eJmwI=
|
git.tordarus.net/tordarus/slices v0.0.13/go.mod h1:RgE7A1aSAezIvPUgcbUuMHu0q4xGKoRevT+DC0eJmwI=
|
||||||
|
70
main.go
70
main.go
@ -9,7 +9,7 @@ import (
|
|||||||
"git.tordarus.net/tordarus/channel"
|
"git.tordarus.net/tordarus/channel"
|
||||||
"git.tordarus.net/tordarus/envvars"
|
"git.tordarus.net/tordarus/envvars"
|
||||||
"git.tordarus.net/tordarus/gmath"
|
"git.tordarus.net/tordarus/gmath"
|
||||||
omada "git.tordarus.net/tordarus/omada-api"
|
omadaapi "git.tordarus.net/tordarus/omada-api"
|
||||||
omadamodel "git.tordarus.net/tordarus/omada-api/model"
|
omadamodel "git.tordarus.net/tordarus/omada-api/model"
|
||||||
"git.tordarus.net/tordarus/slices"
|
"git.tordarus.net/tordarus/slices"
|
||||||
)
|
)
|
||||||
@ -17,14 +17,13 @@ import (
|
|||||||
var ( // flags
|
var ( // flags
|
||||||
FlagSiteNames = envvars.StringSlice("SITES", ",", []string{})
|
FlagSiteNames = envvars.StringSlice("SITES", ",", []string{})
|
||||||
|
|
||||||
FlagOmadaURL = envvars.String("OMADA_URL", "http://localhost:8088")
|
FlagOmadaURL = envvars.String("OMADA_URL", "http://localhost:8088")
|
||||||
FlagOmadaID = envvars.String("OMADA_ID", "")
|
FlagOmadaID = envvars.String("OMADA_ID", "")
|
||||||
FlagOmadaClientID = envvars.String("OMADA_CLIENT_ID", "")
|
FlagOmadaClientID = envvars.String("OMADA_CLIENT_ID", "")
|
||||||
FlagOmadaClientSecret = envvars.String("OMADA_CLIENT_SECRET", "")
|
FlagOmadaClientSecret = envvars.String("OMADA_CLIENT_SECRET", "")
|
||||||
FlagOmadaUsername = envvars.String("OMADA_USERNAME", "")
|
FlagOmadaUsername = envvars.String("OMADA_USERNAME", "")
|
||||||
FlagOmadaPassword = envvars.String("OMADA_PASSWORD", "")
|
FlagOmadaPassword = envvars.String("OMADA_PASSWORD", "")
|
||||||
FlagRefreshInterval = envvars.Duration("REFRESH_INTERVAL", 30*time.Second)
|
FlagRefreshInterval = envvars.Duration("REFRESH_INTERVAL", 30*time.Second)
|
||||||
FlagRefreshBeforeExpiration = envvars.Duration("REFRESH_BEFORE", time.Minute)
|
|
||||||
|
|
||||||
FlagHttpInterface = envvars.String("HTTP_INTERFACE", "")
|
FlagHttpInterface = envvars.String("HTTP_INTERFACE", "")
|
||||||
FlagHttpPort = envvars.Uint16("HTTP_PORT", 8080)
|
FlagHttpPort = envvars.Uint16("HTTP_PORT", 8080)
|
||||||
@ -37,7 +36,7 @@ var CurrentTraffic = &TrafficStats{}
|
|||||||
func main() {
|
func main() {
|
||||||
FlagRefreshInterval = gmath.Max(FlagRefreshInterval, MinRefreshInterval)
|
FlagRefreshInterval = gmath.Max(FlagRefreshInterval, MinRefreshInterval)
|
||||||
|
|
||||||
api, err := omada.NewApi(omada.ApiConfig{
|
api, err := omadaapi.NewApi(omadaapi.ApiConfig{
|
||||||
BasePath: FlagOmadaURL,
|
BasePath: FlagOmadaURL,
|
||||||
OmadaID: FlagOmadaID,
|
OmadaID: FlagOmadaID,
|
||||||
ClientID: FlagOmadaClientID,
|
ClientID: FlagOmadaClientID,
|
||||||
@ -50,37 +49,38 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go StartHttpServer()
|
go func() {
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := json.NewEncoder(w).Encode(CurrentTraffic); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
sites := slices.Ref(slices.Filter(
|
http.ListenAndServe(fmt.Sprintf("%s:%d", FlagHttpInterface, FlagHttpPort), nil)
|
||||||
channel.ToSlice(channel.Map(api.GetSites(), channel.Result[omadamodel.Site].GetUnsafe)),
|
}()
|
||||||
FilterSitesByName(FlagSiteNames...),
|
|
||||||
))
|
sites := slices.Filter(channel.ToSlice(api.GetSites()), FilterSitesByName(FlagSiteNames...))
|
||||||
|
|
||||||
ticker := time.NewTicker(FlagRefreshInterval)
|
ticker := time.NewTicker(FlagRefreshInterval)
|
||||||
|
|
||||||
lastTick := time.Now()
|
|
||||||
CalculateSiteTraffic(api, sites, 0)
|
CalculateSiteTraffic(api, sites, 0)
|
||||||
|
|
||||||
|
lastTick := time.Now()
|
||||||
for now := range ticker.C {
|
for now := range ticker.C {
|
||||||
CurrentTraffic = CalculateSiteTraffic(api, sites, now.Sub(lastTick))
|
CurrentTraffic = CalculateSiteTraffic(api, sites, now.Sub(lastTick))
|
||||||
lastTick = now
|
lastTick = now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalculateSiteTraffic(api *omada.Api, sites []*omadamodel.Site, duration time.Duration) *TrafficStats {
|
func CalculateSiteTraffic(api *omadaapi.Api, sites []*omadamodel.Site, duration time.Duration) *TrafficStats {
|
||||||
siteTraffics := map[string]*SiteTraffic{}
|
siteTraffics := map[string]*SiteTraffic{}
|
||||||
|
|
||||||
for _, site := range sites {
|
for _, site := range sites {
|
||||||
trafficByClient := map[string]TrafficRate{}
|
trafficByClient := map[string]TrafficRate{}
|
||||||
|
|
||||||
clientRequests := channel.ToSlice(api.GetClients(site.ID))
|
clients := channel.ToSlice(api.GetClients(site.ID))
|
||||||
for _, clientReq := range clientRequests {
|
for _, client := range clients {
|
||||||
client, err := clientReq.Get()
|
traffic := CalculateClientTraffic(site, client, duration)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
traffic := CalculateClientTraffic(site, &client, duration)
|
|
||||||
trafficByClient[client.Name] = traffic
|
trafficByClient[client.Name] = traffic
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,29 +90,15 @@ func CalculateSiteTraffic(api *omada.Api, sites []*omadamodel.Site, duration tim
|
|||||||
return NewTrafficStats(siteTraffics)
|
return NewTrafficStats(siteTraffics)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FilterSitesByName(allowedSiteNames ...string) func(site omadamodel.Site) bool {
|
func FilterSitesByName(allowedSiteNames ...string) func(site *omadamodel.Site) bool {
|
||||||
if len(allowedSiteNames) == 0 {
|
if len(allowedSiteNames) == 0 {
|
||||||
return func(site omadamodel.Site) bool { return true }
|
return func(site *omadamodel.Site) bool { return true }
|
||||||
}
|
}
|
||||||
|
|
||||||
siteNames := slices.ToStructMap(allowedSiteNames)
|
siteNames := slices.ToStructMap(allowedSiteNames)
|
||||||
|
|
||||||
return func(site omadamodel.Site) bool {
|
return func(site *omadamodel.Site) bool {
|
||||||
_, ok := siteNames[site.Name]
|
_, ok := siteNames[site.Name]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHttpServer() {
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if err := json.NewEncoder(w).Encode(CurrentTraffic); err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
connString := fmt.Sprintf("%s:%d", FlagHttpInterface, FlagHttpPort)
|
|
||||||
|
|
||||||
if err := http.ListenAndServe(connString, nil); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
48
types.go
48
types.go
@ -16,32 +16,20 @@ type ClientUniqueID struct {
|
|||||||
type TrafficRate struct {
|
type TrafficRate struct {
|
||||||
Received uint64 `json:"received"` // bytes per second
|
Received uint64 `json:"received"` // bytes per second
|
||||||
Transferred uint64 `json:"transferred"` // bytes per second
|
Transferred uint64 `json:"transferred"` // bytes per second
|
||||||
|
|
||||||
ReceivedFormatted string `json:"received_fmt"`
|
|
||||||
TransferredFormatted string `json:"transferred_fmt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeTrafficRate(received, transferred uint64) TrafficRate {
|
|
||||||
return TrafficRate{
|
|
||||||
Received: received,
|
|
||||||
Transferred: transferred,
|
|
||||||
ReceivedFormatted: FormatBytesPerSecond(received),
|
|
||||||
TransferredFormatted: FormatBytesPerSecond(transferred),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr TrafficRate) Add(o TrafficRate) TrafficRate {
|
func (tr TrafficRate) Add(o TrafficRate) TrafficRate {
|
||||||
return MakeTrafficRate(
|
return TrafficRate{
|
||||||
tr.Received+o.Received,
|
Received: tr.Received + o.Received,
|
||||||
tr.Transferred+o.Transferred,
|
Transferred: tr.Transferred + o.Transferred,
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr TrafficRate) Sub(o TrafficRate) TrafficRate {
|
func (tr TrafficRate) Sub(o TrafficRate) TrafficRate {
|
||||||
return MakeTrafficRate(
|
return TrafficRate{
|
||||||
tr.Received-o.Received,
|
Received: tr.Received - o.Received,
|
||||||
tr.Transferred-o.Transferred,
|
Transferred: tr.Transferred - o.Transferred,
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SiteTraffic struct {
|
type SiteTraffic struct {
|
||||||
@ -50,7 +38,7 @@ type SiteTraffic struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSiteTraffic(clients map[string]TrafficRate) *SiteTraffic {
|
func NewSiteTraffic(clients map[string]TrafficRate) *SiteTraffic {
|
||||||
total := MakeTrafficRate(0, 0)
|
total := TrafficRate{}
|
||||||
for _, rate := range clients {
|
for _, rate := range clients {
|
||||||
total = total.Add(rate)
|
total = total.Add(rate)
|
||||||
}
|
}
|
||||||
@ -67,7 +55,7 @@ type TrafficStats struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTrafficStats(sites map[string]*SiteTraffic) *TrafficStats {
|
func NewTrafficStats(sites map[string]*SiteTraffic) *TrafficStats {
|
||||||
total := MakeTrafficRate(0, 0)
|
total := TrafficRate{}
|
||||||
for _, siteTraffic := range sites {
|
for _, siteTraffic := range sites {
|
||||||
total = total.Add(siteTraffic.Total)
|
total = total.Add(siteTraffic.Total)
|
||||||
}
|
}
|
||||||
@ -79,7 +67,7 @@ func NewTrafficStats(sites map[string]*SiteTraffic) *TrafficStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tr TrafficRate) String() string {
|
func (tr TrafficRate) String() string {
|
||||||
return fmt.Sprintf("Rx: %s | Tx: %s", FormatBytesPerSecond(tr.Received), FormatBytesPerSecond(tr.Transferred))
|
return fmt.Sprintf("Rx: %s | Tx: %s", FormatBytes(tr.Received), FormatBytes(tr.Transferred))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stats TrafficStats) String() string {
|
func (stats TrafficStats) String() string {
|
||||||
@ -87,16 +75,18 @@ func (stats TrafficStats) String() string {
|
|||||||
return string(data)
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatBytesPerSecond[T gmath.Integer](bytes T) string {
|
func FormatBytes[T gmath.Integer](bytes T) string {
|
||||||
value := float64(bytes)
|
value := float64(bytes)
|
||||||
|
|
||||||
if value >= 1000000000 {
|
if value >= 1000000000000 {
|
||||||
return fmt.Sprintf("%.1f GB/s", value/1000000000)
|
return fmt.Sprintf("%.02fT", value/1000000000000)
|
||||||
|
} else if value >= 1000000000 {
|
||||||
|
return fmt.Sprintf("%.02fG", value/1000000000)
|
||||||
} else if value >= 1000000 {
|
} else if value >= 1000000 {
|
||||||
return fmt.Sprintf("%.1f MB/s", value/1000000)
|
return fmt.Sprintf("%.02fM", value/1000000)
|
||||||
} else if value >= 1000 {
|
} else if value >= 1000 {
|
||||||
return fmt.Sprintf("%.0f kB/s", value/1000)
|
return fmt.Sprintf("%.02fK", value/1000)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("%.0f B/s", value)
|
return fmt.Sprintf("%.02fB", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user