mirror of
https://github.com/unpoller/unpoller.git
synced 2026-03-31 06:33:57 -04:00
fix: use v2 traffic API as DPI fallback for Network 9.1+ firmware (#985)
The legacy /stat/stadpi and /stat/sitedpi endpoints return empty data on UniFi Network 9.1+ (issue #834). The v2 /traffic endpoint already existed in the unifi library and in the collector, but was only called when both SaveTraffic and SaveDPI were enabled — most users only set SaveDPI=true and never saw any data. - Remove the SaveTraffic gate on GetClientTraffic; call it whenever SaveDPI is enabled, treating it as a DPI data source - Downgrade GetClientTraffic errors to debug-log so old firmware that lacks the v2 endpoint continues to use the legacy API without error - Add convertToSiteDPI to aggregate per-client v2 data into per-site DPITable entries, filling SitesDPI when the legacy endpoint is empty - Legacy API results are preserved; v2 data only supplements sites not already covered, so old-firmware users are unaffected Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -152,16 +152,23 @@ func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
|
||||
u.LogDebugf("Found %d CountryTraffic entries", len(m.CountryTraffic))
|
||||
}
|
||||
|
||||
if c.SaveTraffic != nil && *c.SaveTraffic && c.SaveDPI != nil && *c.SaveDPI {
|
||||
if c.SaveDPI != nil && *c.SaveDPI {
|
||||
// Supplement DPI data with the v2 traffic API, which works on newer firmware
|
||||
// (Network 9.1+) where the legacy /stat/stadpi and /stat/sitedpi endpoints
|
||||
// return empty results. GetClientTraffic is called regardless of SaveTraffic
|
||||
// because it provides DPI-equivalent per-client app/category breakdowns.
|
||||
clientUsageByApp, err := c.Unifi.GetClientTraffic(sites, &tp, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unifi.GetClientTraffic(%s): %w", c.URL, err)
|
||||
u.LogDebugf("unifi.GetClientTraffic(%s): %v (legacy DPI endpoints will be used if available)", c.URL, err)
|
||||
} else {
|
||||
u.LogDebugf("Found %d ClientUsageByApp entries", len(clientUsageByApp))
|
||||
b4 := len(m.ClientsDPI)
|
||||
u.convertToClientDPI(clientUsageByApp, m)
|
||||
u.LogDebugf("Added %d ClientDPI entries from v2 traffic API for a total of %d", len(m.ClientsDPI)-b4, len(m.ClientsDPI))
|
||||
b4Sites := len(m.SitesDPI)
|
||||
u.convertToSiteDPI(clientUsageByApp, m)
|
||||
u.LogDebugf("Added %d SitesDPI entries from v2 traffic API for a total of %d", len(m.SitesDPI)-b4Sites, len(m.SitesDPI))
|
||||
}
|
||||
|
||||
u.LogDebugf("Found %d ClientUsageByApp entries", len(clientUsageByApp))
|
||||
b4 := len(m.ClientsDPI)
|
||||
u.convertToClientDPI(clientUsageByApp, m)
|
||||
u.LogDebugf("Added %d ClientDPI entries for a total of %d", len(m.ClientsDPI)-b4, len(m.ClientsDPI))
|
||||
}
|
||||
|
||||
// Get all the points.
|
||||
@@ -358,6 +365,99 @@ func (u *InputUnifi) convertToClientDPI(clientUsageByApp []*unifi.ClientUsageByA
|
||||
}
|
||||
}
|
||||
|
||||
// convertToSiteDPI aggregates v2 client traffic data into per-site DPITable entries.
|
||||
// It only adds a site entry if the site doesn't already have one from the legacy API,
|
||||
// so old-firmware users are unaffected.
|
||||
func (u *InputUnifi) convertToSiteDPI(clientUsageByApp []*unifi.ClientUsageByApp, metrics *Metrics) {
|
||||
// Build a set of sites already covered by the legacy API.
|
||||
existing := make(map[string]bool)
|
||||
|
||||
for _, s := range metrics.SitesDPI {
|
||||
existing[s.SiteName] = true
|
||||
}
|
||||
|
||||
type appKey struct {
|
||||
App int
|
||||
Cat int
|
||||
}
|
||||
|
||||
type siteAgg struct {
|
||||
byApp map[appKey]*unifi.DPIData
|
||||
byCat map[int]*unifi.DPIData
|
||||
sourceName string
|
||||
}
|
||||
|
||||
siteMap := make(map[string]*siteAgg)
|
||||
|
||||
for _, client := range clientUsageByApp {
|
||||
siteName := client.TrafficSite.SiteName
|
||||
if existing[siteName] {
|
||||
continue
|
||||
}
|
||||
|
||||
agg, ok := siteMap[siteName]
|
||||
if !ok {
|
||||
agg = &siteAgg{
|
||||
byApp: make(map[appKey]*unifi.DPIData),
|
||||
byCat: make(map[int]*unifi.DPIData),
|
||||
sourceName: client.TrafficSite.SourceName,
|
||||
}
|
||||
siteMap[siteName] = agg
|
||||
}
|
||||
|
||||
for _, app := range client.UsageByApp {
|
||||
k := appKey{App: app.Application, Cat: app.Category}
|
||||
|
||||
if d, ok := agg.byApp[k]; ok {
|
||||
d.RxBytes.Val += float64(app.BytesReceived)
|
||||
d.TxBytes.Val += float64(app.BytesTransmitted)
|
||||
} else {
|
||||
agg.byApp[k] = &unifi.DPIData{
|
||||
App: u.intToFlexInt(app.Application),
|
||||
Cat: u.intToFlexInt(app.Category),
|
||||
RxBytes: u.int64ToFlexInt(app.BytesReceived),
|
||||
RxPackets: u.int64ToFlexInt(0),
|
||||
TxBytes: u.int64ToFlexInt(app.BytesTransmitted),
|
||||
TxPackets: u.int64ToFlexInt(0),
|
||||
}
|
||||
}
|
||||
|
||||
if d, ok := agg.byCat[app.Category]; ok {
|
||||
d.RxBytes.Val += float64(app.BytesReceived)
|
||||
d.TxBytes.Val += float64(app.BytesTransmitted)
|
||||
} else {
|
||||
agg.byCat[app.Category] = &unifi.DPIData{
|
||||
App: u.intToFlexInt(16777215), // unknown app — category aggregate
|
||||
Cat: u.intToFlexInt(app.Category),
|
||||
RxBytes: u.int64ToFlexInt(app.BytesReceived),
|
||||
RxPackets: u.int64ToFlexInt(0),
|
||||
TxBytes: u.int64ToFlexInt(app.BytesTransmitted),
|
||||
TxPackets: u.int64ToFlexInt(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for siteName, agg := range siteMap {
|
||||
byApp := make([]unifi.DPIData, 0, len(agg.byApp))
|
||||
for _, d := range agg.byApp {
|
||||
byApp = append(byApp, *d)
|
||||
}
|
||||
|
||||
byCat := make([]unifi.DPIData, 0, len(agg.byCat))
|
||||
for _, d := range agg.byCat {
|
||||
byCat = append(byCat, *d)
|
||||
}
|
||||
|
||||
metrics.SitesDPI = append(metrics.SitesDPI, &unifi.DPITable{
|
||||
ByApp: byApp,
|
||||
ByCat: byCat,
|
||||
SiteName: siteName,
|
||||
SourceName: agg.sourceName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// augmentMetrics is our middleware layer between collecting metrics and writing them.
|
||||
// This is where we can manipuate the returned data or make arbitrary decisions.
|
||||
// This method currently adds parent device names to client metrics and hashes PII.
|
||||
|
||||
Reference in New Issue
Block a user