mirror of
https://github.com/unpoller/unpoller.git
synced 2026-03-31 06:24:19 -04:00
feat(devices): add UDB (UniFi Device Bridge) support (#968)
Adds metrics export for UDB devices (UDB-Switch, UDB-Pro, UDB-Pro-Sector) to all output backends. UDB-Switch is a hybrid device combining PoE switch ports with WiFi 7 wireless bridge capability (5GHz + 6GHz radios). - pkg/promunifi/udb.go: Prometheus metrics exporter for UDB - pkg/influxunifi/udb.go: InfluxDB batch exporter for UDB - pkg/datadogunifi/udb.go: Datadog batch exporter for UDB - Wire UDB into switchExport in all three output plugins - Add UDB to inputunifi device collection and site name override - Update integration test expectations for InfluxDB and Datadog - Fix addUBB() bug: was incorrectly incrementing UCI counter Resolves #947 Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -343,6 +343,8 @@ func (u *DatadogUnifi) switchExport(r report, v any) { //nolint:cyclop
|
||||
u.batchUBB(r, v)
|
||||
case *unifi.UCI:
|
||||
u.batchUCI(r, v)
|
||||
case *unifi.UDB:
|
||||
u.batchUDB(r, v)
|
||||
case *unifi.Site:
|
||||
u.reportSite(r, v)
|
||||
case *unifi.Client:
|
||||
|
||||
@@ -507,6 +507,51 @@ gauges:
|
||||
- unifi.uci.stat_rx_packets
|
||||
- unifi.uci.stat_tx_dropped
|
||||
- unifi.uci.state
|
||||
- unifi.udb.bytes
|
||||
- unifi.udb.cpu
|
||||
- unifi.udb.fan_level
|
||||
- unifi.udb.general_temperature
|
||||
- unifi.udb.guest_num_sta
|
||||
- unifi.udb.guest_wlan_num_sta
|
||||
- unifi.udb.last_seen
|
||||
- unifi.udb.loadavg_1
|
||||
- unifi.udb.loadavg_5
|
||||
- unifi.udb.loadavg_15
|
||||
- unifi.udb.mem
|
||||
- unifi.udb.mem_buffer
|
||||
- unifi.udb.mem_total
|
||||
- unifi.udb.mem_used
|
||||
- unifi.udb.memory
|
||||
- unifi.udb.network
|
||||
- unifi.udb.num_sta
|
||||
- unifi.udb.probe
|
||||
- unifi.udb.rx_bytes
|
||||
- unifi.udb.satisfaction
|
||||
- unifi.udb.stat_bytes
|
||||
- unifi.udb.stat_rx_bytes
|
||||
- unifi.udb.stat_rx_crypts
|
||||
- unifi.udb.stat_rx_dropped
|
||||
- unifi.udb.stat_rx_errors
|
||||
- unifi.udb.stat_rx_frags
|
||||
- unifi.udb.stat_rx_packets
|
||||
- unifi.udb.stat_tx_bytes
|
||||
- unifi.udb.stat_tx_dropped
|
||||
- unifi.udb.stat_tx_errors
|
||||
- unifi.udb.stat_tx_packets
|
||||
- unifi.udb.stat_tx_retries
|
||||
- unifi.udb.state
|
||||
- unifi.udb.sys
|
||||
- unifi.udb.system_uptime
|
||||
- unifi.udb.total_max_power
|
||||
- unifi.udb.tx_bytes
|
||||
- unifi.udb.upgradeable
|
||||
- unifi.udb.uplink_latency
|
||||
- unifi.udb.uplink_max_speed
|
||||
- unifi.udb.uplink_speed
|
||||
- unifi.udb.uplink_uptime
|
||||
- unifi.udb.uptime
|
||||
- unifi.udb.user_num_sta
|
||||
- unifi.udb.user_wlan_num_sta
|
||||
counts:
|
||||
- unifi.collector.num_devices
|
||||
- unifi.collector.num_errors
|
||||
|
||||
68
pkg/datadogunifi/udb.go
Normal file
68
pkg/datadogunifi/udb.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package datadogunifi
|
||||
|
||||
import "github.com/unpoller/unifi/v5"
|
||||
|
||||
// udbT is used as a name for printed/logged counters.
|
||||
const udbT = item("UDB")
|
||||
|
||||
// batchUDB generates datapoints for UDB (UniFi Device Bridge) devices.
|
||||
// UDB-Switch is a hybrid device combining switch ports with WiFi 7
|
||||
// wireless bridge capability.
|
||||
func (u *DatadogUnifi) batchUDB(r report, s *unifi.UDB) {
|
||||
if !s.Adopted.Val || s.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
"ip": s.IP,
|
||||
})
|
||||
|
||||
data := CombineFloat64(
|
||||
u.batchUSWstat(s.Stat.Sw),
|
||||
u.batchSysStats(s.SysStats, s.SystemStats),
|
||||
map[string]float64{
|
||||
"guest_num_sta": s.GuestNumSta.Val,
|
||||
"bytes": s.Bytes.Val,
|
||||
"fan_level": s.FanLevel.Val,
|
||||
"general_temperature": s.GeneralTemperature.Val,
|
||||
"last_seen": s.LastSeen.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"uptime": s.Uptime.Val,
|
||||
"state": s.State.Val,
|
||||
"user_num_sta": s.UserNumSta.Val,
|
||||
"num_sta": s.NumSta.Val,
|
||||
"upgradeable": boolToFloat64(s.Upgradable.Val),
|
||||
"guest_wlan_num_sta": s.GuestWlanNumSta.Val,
|
||||
"user_wlan_num_sta": s.UserWlanNumSta.Val,
|
||||
"satisfaction": s.Satisfaction.Val,
|
||||
"total_max_power": s.TotalMaxPower.Val,
|
||||
"uplink_speed": s.Uplink.Speed.Val,
|
||||
"uplink_max_speed": s.Uplink.MaxSpeed.Val,
|
||||
"uplink_latency": s.Uplink.Latency.Val,
|
||||
"uplink_uptime": s.Uplink.Uptime.Val,
|
||||
})
|
||||
|
||||
r.addCount(udbT)
|
||||
|
||||
metricName := metricNamespace("udb")
|
||||
|
||||
reportGaugeForFloat64Map(r, metricName, data, tags)
|
||||
|
||||
// Port table (reuse USW function)
|
||||
u.batchPortTable(r, tags, s.PortTable)
|
||||
|
||||
// Radio table (reuse UAP functions)
|
||||
u.processRadTable(r, tags, s.RadioTable, s.RadioTableStats)
|
||||
|
||||
// VAP table (reuse UAP function)
|
||||
u.processVAPTable(r, tags, s.VapTable)
|
||||
}
|
||||
@@ -454,6 +454,8 @@ func (u *InfluxUnifi) switchExport(r report, v any) { //nolint:cyclop
|
||||
u.batchUBB(r, v)
|
||||
case *unifi.UCI:
|
||||
u.batchUCI(r, v)
|
||||
case *unifi.UDB:
|
||||
u.batchUDB(r, v)
|
||||
case *unifi.UDM:
|
||||
u.batchUDM(r, v)
|
||||
case *unifi.Site:
|
||||
|
||||
@@ -581,6 +581,64 @@ points:
|
||||
tx_bytes: float
|
||||
uptime: float
|
||||
version: string
|
||||
udb:
|
||||
tags:
|
||||
- mac
|
||||
- model
|
||||
- name
|
||||
- serial
|
||||
- site_name
|
||||
- source
|
||||
- type
|
||||
- version
|
||||
fields:
|
||||
bytes: float
|
||||
cpu: float
|
||||
fan_level: float
|
||||
general_temperature: float
|
||||
guest-num_sta: float
|
||||
guest-wlan-num_sta: float
|
||||
ip: string
|
||||
last_seen: float
|
||||
loadavg_1: float
|
||||
loadavg_5: float
|
||||
loadavg_15: float
|
||||
mem: float
|
||||
mem_buffer: float
|
||||
mem_total: float
|
||||
mem_used: float
|
||||
num_sta: float
|
||||
rx_bytes: float
|
||||
satisfaction: float
|
||||
stat_bytes: float
|
||||
stat_rx_bytes: float
|
||||
stat_rx_crypts: float
|
||||
stat_rx_dropped: float
|
||||
stat_rx_errors: float
|
||||
stat_rx_frags: float
|
||||
stat_rx_packets: float
|
||||
stat_tx_bytes: float
|
||||
stat_tx_dropped: float
|
||||
stat_tx_errors: float
|
||||
stat_tx_packets: float
|
||||
stat_tx_retries: float
|
||||
state: float
|
||||
system_uptime: float
|
||||
temp_cpu: float
|
||||
temp_memory: float
|
||||
temp_network: float
|
||||
temp_probe: float
|
||||
temp_sys: float
|
||||
total_max_power: float
|
||||
tx_bytes: float
|
||||
upgradeable: bool
|
||||
uplink_latency: float
|
||||
uplink_max_speed: float
|
||||
uplink_speed: float
|
||||
uplink_uptime: float
|
||||
uptime: float
|
||||
user-num_sta: float
|
||||
user-wlan-num_sta: float
|
||||
unifi_alarm:
|
||||
tags:
|
||||
- action
|
||||
|
||||
65
pkg/influxunifi/udb.go
Normal file
65
pkg/influxunifi/udb.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package influxunifi
|
||||
|
||||
import "github.com/unpoller/unifi/v5"
|
||||
|
||||
// udbT is used as a name for printed/logged counters.
|
||||
const udbT = item("UDB")
|
||||
|
||||
// batchUDB generates datapoints for UDB (UniFi Device Bridge) devices.
|
||||
// UDB-Switch is a hybrid device combining switch ports with WiFi 7
|
||||
// wireless bridge capability.
|
||||
func (u *InfluxUnifi) batchUDB(r report, s *unifi.UDB) {
|
||||
if !s.Adopted.Val || s.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
|
||||
fields := Combine(
|
||||
u.batchUSWstat(s.Stat.Sw),
|
||||
u.batchSysStats(s.SysStats, s.SystemStats),
|
||||
map[string]any{
|
||||
"guest-num_sta": s.GuestNumSta.Val,
|
||||
"ip": s.IP,
|
||||
"bytes": s.Bytes.Val,
|
||||
"fan_level": s.FanLevel.Val,
|
||||
"general_temperature": s.GeneralTemperature.Val,
|
||||
"last_seen": s.LastSeen.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"uptime": s.Uptime.Val,
|
||||
"state": s.State.Val,
|
||||
"user-num_sta": s.UserNumSta.Val,
|
||||
"num_sta": s.NumSta.Val,
|
||||
"upgradeable": s.Upgradable.Val,
|
||||
"guest-wlan-num_sta": s.GuestWlanNumSta.Val,
|
||||
"user-wlan-num_sta": s.UserWlanNumSta.Val,
|
||||
"satisfaction": s.Satisfaction.Val,
|
||||
"total_max_power": s.TotalMaxPower.Val,
|
||||
"uplink_speed": s.Uplink.Speed.Val,
|
||||
"uplink_max_speed": s.Uplink.MaxSpeed.Val,
|
||||
"uplink_latency": s.Uplink.Latency.Val,
|
||||
"uplink_uptime": s.Uplink.Uptime.Val,
|
||||
})
|
||||
|
||||
r.addCount(udbT)
|
||||
r.send(&metric{Table: "udb", Tags: tags, Fields: fields})
|
||||
|
||||
// Port table (reuse USW function)
|
||||
u.batchPortTable(r, tags, s.PortTable)
|
||||
|
||||
// Radio table (reuse UAP functions)
|
||||
u.processRadTable(r, tags, s.RadioTable, s.RadioTableStats)
|
||||
|
||||
// VAP table (reuse UAP function)
|
||||
u.processVAPTable(r, tags, s.VapTable)
|
||||
}
|
||||
@@ -174,10 +174,10 @@ func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
|
||||
return nil, fmt.Errorf("unifi.GetDevices(%s): %w", c.URL, err)
|
||||
}
|
||||
|
||||
u.LogDebugf("Found %d UBB, %d UXG, %d PDU, %d UCI, %d UAP %d USG %d USW %d UDM devices",
|
||||
u.LogDebugf("Found %d UBB, %d UXG, %d PDU, %d UCI, %d UDB, %d UAP %d USG %d USW %d UDM devices",
|
||||
len(m.Devices.UBBs), len(m.Devices.UXGs),
|
||||
len(m.Devices.PDUs), len(m.Devices.UCIs),
|
||||
len(m.Devices.UAPs), len(m.Devices.USGs),
|
||||
len(m.Devices.UDBs), len(m.Devices.UAPs), len(m.Devices.USGs),
|
||||
len(m.Devices.USWs), len(m.Devices.UDMs))
|
||||
|
||||
// Get speed test results for all WANs
|
||||
@@ -486,6 +486,10 @@ func applySiteNameOverride(m *poller.Metrics, overrideName string) {
|
||||
if isDefaultSiteName(d.SiteName) {
|
||||
d.SiteName = overrideName
|
||||
}
|
||||
case *unifi.UDB:
|
||||
if isDefaultSiteName(d.SiteName) {
|
||||
d.SiteName = overrideName
|
||||
}
|
||||
case *unifi.PDU:
|
||||
if isDefaultSiteName(d.SiteName) {
|
||||
d.SiteName = overrideName
|
||||
@@ -595,6 +599,15 @@ func extractDevices(metrics *Metrics) (*poller.Metrics, map[string]string, map[s
|
||||
m.Devices = append(m.Devices, r)
|
||||
}
|
||||
|
||||
for _, r := range metrics.Devices.UDBs {
|
||||
devices[r.Mac] = r.Name
|
||||
m.Devices = append(m.Devices, r)
|
||||
|
||||
for _, v := range r.VapTable {
|
||||
bssdIDs[v.Bssid] = fmt.Sprintf("%s %s %s:", r.Name, v.Radio, v.RadioName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range metrics.Devices.PDUs {
|
||||
devices[r.Mac] = r.Name
|
||||
m.Devices = append(m.Devices, r)
|
||||
|
||||
@@ -101,6 +101,7 @@ type Report struct {
|
||||
UXG int // Total count of UXG devices.
|
||||
UBB int // Total count of UBB devices.
|
||||
UCI int // Total count of UCI devices.
|
||||
UDB int // Total count of UDB devices.
|
||||
Metrics *poller.Metrics // Metrics collected and recorded.
|
||||
Elapsed time.Duration // Duration elapsed collecting and exporting.
|
||||
Fetch time.Duration // Duration elapsed making controller requests.
|
||||
@@ -479,6 +480,9 @@ func (u *promUnifi) switchExport(r report, v any) {
|
||||
case *unifi.UCI:
|
||||
r.addUCI()
|
||||
u.exportUCI(r, v)
|
||||
case *unifi.UDB:
|
||||
r.addUDB()
|
||||
u.exportUDB(r, v)
|
||||
case *unifi.UDM:
|
||||
r.addUDM()
|
||||
u.exportUDM(r, v)
|
||||
|
||||
@@ -23,6 +23,7 @@ type report interface {
|
||||
addUXG()
|
||||
addUBB()
|
||||
addUCI()
|
||||
addUDB()
|
||||
addUSG()
|
||||
addUAP()
|
||||
addUSW()
|
||||
@@ -111,13 +112,17 @@ func (r *Report) addUXG() {
|
||||
}
|
||||
|
||||
func (r *Report) addUBB() {
|
||||
r.UCI++
|
||||
r.UBB++
|
||||
}
|
||||
|
||||
func (r *Report) addUCI() {
|
||||
r.UCI++
|
||||
}
|
||||
|
||||
func (r *Report) addUDB() {
|
||||
r.UDB++
|
||||
}
|
||||
|
||||
// close is not part of the interface.
|
||||
func (r *Report) close() {
|
||||
r.wg.Wait()
|
||||
|
||||
53
pkg/promunifi/udb.go
Normal file
53
pkg/promunifi/udb.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package promunifi
|
||||
|
||||
import "github.com/unpoller/unifi/v5"
|
||||
|
||||
// exportUDB exports metrics for UDB (UniFi Device Bridge) devices.
|
||||
// The UDB range includes UDB-Switch, UDB-Pro, UDB-Pro-Sector.
|
||||
// UDB-Switch is a hybrid device combining switch ports (8 PoE ports)
|
||||
// with WiFi 7 wireless bridge capability (5GHz + 6GHz radios).
|
||||
func (u *promUnifi) exportUDB(r report, d *unifi.UDB) {
|
||||
if !d.Adopted.Val || d.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
baseLabels := []string{d.Type, d.SiteName, d.Name, d.SourceName}
|
||||
baseInfoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID}
|
||||
|
||||
u.exportWithTags(r, d.Tags, func(tagLabels []string) {
|
||||
tag := tagLabels[0]
|
||||
labels := append(baseLabels, tag)
|
||||
infoLabels := append(baseInfoLabels, tag)
|
||||
|
||||
// Export switch stats (reuse USW functions)
|
||||
u.exportUSWstats(r, labels, d.Stat.Sw)
|
||||
u.exportPRTtable(r, labels, d.PortTable)
|
||||
|
||||
// Export wireless stats (reuse UAP functions)
|
||||
u.exportVAPtable(r, labels, d.VapTable)
|
||||
u.exportRADtable(r, labels, d.RadioTable, d.RadioTableStats)
|
||||
|
||||
// Common device stats
|
||||
u.exportBYTstats(r, labels, d.TxBytes, d.RxBytes)
|
||||
u.exportSYSstats(r, labels, d.SysStats, d.SystemStats)
|
||||
u.exportSTAcount(r, labels, d.UserNumSta, d.GuestNumSta)
|
||||
|
||||
r.send([]*metric{
|
||||
{u.Device.Info, gauge, 1.0, append(baseLabels, infoLabels...)},
|
||||
{u.Device.Uptime, gauge, d.Uptime, labels},
|
||||
{u.Device.Upgradeable, gauge, d.Upgradable.Val, labels},
|
||||
})
|
||||
|
||||
if d.HasTemperature.Val {
|
||||
r.send([]*metric{{u.Device.Temperature, gauge, d.GeneralTemperature, append(labels, "general", "board")}})
|
||||
}
|
||||
|
||||
if d.HasFan.Val {
|
||||
r.send([]*metric{{u.Device.FanLevel, gauge, d.FanLevel, labels}})
|
||||
}
|
||||
|
||||
if d.TotalMaxPower.Txt != "" {
|
||||
r.send([]*metric{{u.Device.TotalMaxPower, gauge, d.TotalMaxPower, labels}})
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user