From 6a135c60a79dc65d5c16b47e0db96e151d1145c1 Mon Sep 17 00:00:00 2001 From: Cody Lee Date: Tue, 9 Dec 2025 10:20:23 -0600 Subject: [PATCH] Enhance UBB device support with comprehensive Prometheus metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change significantly improves UniFi Building Bridge (UBB) device support by adding comprehensive Prometheus metric exports. UBB devices are point-to-point wireless bridges with dual radios: - wifi0: 5GHz radio (802.11ac) - terra2/wlan0/ad: 60GHz radio (802.11ad - Terragraph/WiGig) Changes: - Added exportUBBstats() to export UBB-specific statistics separated by radio (total, wifi0, terra2, user-wifi0, user-terra2) - Added exportP2Pstats() to export point-to-point link metrics (rx_rate, tx_rate, throughput) - Added VAP (Virtual Access Point) table export via existing exportVAPtable() - Added Radio table export via existing exportRADtable() to capture 60GHz radio metrics - Added link quality metrics (link_quality, link_quality_current, link_capacity) - Added comprehensive comments documenting UBB device characteristics and 60GHz band support The implementation reuses existing UAP metric descriptors where appropriate, allowing UBB metrics to be collected alongside UAP metrics in Prometheus with proper labeling for differentiation. Requires: unpoller/unifi#169 (UBB type definition fixes) Fixes: #409 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- go.mod | 2 + pkg/promunifi/ubb.go | 153 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 144 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index a5c3bfcb..9a0be485 100644 --- a/go.mod +++ b/go.mod @@ -50,3 +50,5 @@ require ( // for local iterative development only // replace github.com/unpoller/unifi/v5 => ../unifi + +replace github.com/unpoller/unifi/v5 => ../unifi diff --git a/pkg/promunifi/ubb.go b/pkg/promunifi/ubb.go index 037d2a3d..5de32358 100644 --- a/pkg/promunifi/ubb.go +++ b/pkg/promunifi/ubb.go @@ -4,33 +4,164 @@ import ( "github.com/unpoller/unifi/v5" ) -// exportUBB is a collection of stats from UBB. +// exportUBB is a collection of stats from UBB (UniFi Building Bridge). +// UBB devices are point-to-point wireless bridges with dual radios: +// - wifi0: 5GHz radio (802.11ac) +// - terra2/wlan0/ad: 60GHz radio (802.11ad - Terragraph/WiGig) func (u *promUnifi) exportUBB(r report, d *unifi.UBB) { if !d.Adopted.Val || d.Locating.Val { return } - //var sw *unifi.Bb - //if d.Stat != nil { - // sw = d.Stat.Bb - //} - // unsure of what to do with this yet. - labels := []string{d.Type, d.SiteName, d.Name, d.SourceName} infoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID} - // Shared data (all devices do this). + + // Export UBB-specific stats if available + u.exportUBBstats(r, labels, d.Stat) + + // Export VAP table (Virtual Access Point table - wireless interface stats) + u.exportVAPtable(r, labels, d.VapTable) + + // Export Radio tables (includes 5GHz wifi0 and 60GHz terra2/ad radios) + u.exportRADtable(r, labels, d.RadioTable, d.RadioTableStats) + + // Shared device stats u.exportBYTstats(r, labels, d.TxBytes, d.RxBytes) if d.SysStats != nil && d.SystemStats != nil { u.exportSYSstats(r, labels, *d.SysStats, *d.SystemStats) } - // Dream Machine System Data. + // Device info, uptime, and temperature r.send([]*metric{ {u.Device.Info, gauge, 1.0, append(labels, infoLabels...)}, {u.Device.Uptime, gauge, d.Uptime, labels}, + {u.Device.Temperature, gauge, d.GeneralTemperature.Val, append(labels, d.Name, "general")}, }) - // temperature - r.send([]*metric{{u.Device.Temperature, gauge, d.GeneralTemperature.Val, append(labels, d.Name, "general")}}) + // UBB-specific metrics + if d.P2PStats != nil { + u.exportP2Pstats(r, labels, d.P2PStats) + } + + // Link quality metrics for point-to-point links + r.send([]*metric{ + {u.Device.Counter, gauge, d.LinkQuality.Val, append(labels, "link_quality")}, + {u.Device.Counter, gauge, d.LinkQualityCurrent.Val, append(labels, "link_quality_current")}, + {u.Device.Counter, gauge, d.LinkCapacity.Val, append(labels, "link_capacity")}, + }) +} + +// exportUBBstats exports UBB-specific stats from the Bb structure. +// This includes separate metrics for wifi0 (5GHz) and terra2 (60GHz) radios. +func (u *promUnifi) exportUBBstats(r report, labels []string, stat *unifi.UBBStat) { + if stat == nil || stat.Bb == nil { + return + } + + bb := stat.Bb + + // Export aggregated stats (total across both radios) + labelTotal := []string{"total", labels[1], labels[2], labels[3]} + r.send([]*metric{ + {u.UAP.ApRxPackets, counter, bb.RxPackets, labelTotal}, + {u.UAP.ApRxBytes, counter, bb.RxBytes, labelTotal}, + {u.UAP.ApRxErrors, counter, bb.RxErrors, labelTotal}, + {u.UAP.ApRxDropped, counter, bb.RxDropped, labelTotal}, + {u.UAP.ApRxCrypts, counter, bb.RxCrypts, labelTotal}, + {u.UAP.ApRxFrags, counter, bb.RxFrags, labelTotal}, + {u.UAP.ApTxPackets, counter, bb.TxPackets, labelTotal}, + {u.UAP.ApTxBytes, counter, bb.TxBytes, labelTotal}, + {u.UAP.ApTxErrors, counter, bb.TxErrors, labelTotal}, + {u.UAP.ApTxDropped, counter, bb.TxDropped, labelTotal}, + {u.UAP.ApTxRetries, counter, bb.TxRetries, labelTotal}, + {u.UAP.WifiTxAttempts, counter, bb.WifiTxAttempts, labelTotal}, + {u.UAP.MacFilterRejections, counter, bb.MacFilterRejections, labelTotal}, + {u.UAP.ApWifiTxDropped, counter, bb.WifiTxDropped, labelTotal}, + }) + + // Export wifi0 radio stats (5GHz) + labelWifi0 := []string{"wifi0", labels[1], labels[2], labels[3]} + r.send([]*metric{ + {u.UAP.ApRxPackets, counter, bb.Wifi0RxPackets, labelWifi0}, + {u.UAP.ApRxBytes, counter, bb.Wifi0RxBytes, labelWifi0}, + {u.UAP.ApRxErrors, counter, bb.Wifi0RxErrors, labelWifi0}, + {u.UAP.ApRxDropped, counter, bb.Wifi0RxDropped, labelWifi0}, + {u.UAP.ApRxCrypts, counter, bb.Wifi0RxCrypts, labelWifi0}, + {u.UAP.ApRxFrags, counter, bb.Wifi0RxFrags, labelWifi0}, + {u.UAP.ApTxPackets, counter, bb.Wifi0TxPackets, labelWifi0}, + {u.UAP.ApTxBytes, counter, bb.Wifi0TxBytes, labelWifi0}, + {u.UAP.ApTxErrors, counter, bb.Wifi0TxErrors, labelWifi0}, + {u.UAP.ApTxDropped, counter, bb.Wifi0TxDropped, labelWifi0}, + {u.UAP.ApTxRetries, counter, bb.Wifi0TxRetries, labelWifi0}, + {u.UAP.WifiTxAttempts, counter, bb.Wifi0WifiTxAttempts, labelWifi0}, + {u.UAP.MacFilterRejections, counter, bb.Wifi0MacFilterRejections, labelWifi0}, + {u.UAP.ApWifiTxDropped, counter, bb.Wifi0WifiTxDropped, labelWifi0}, + }) + + // Export terra2 radio stats (60GHz - 802.11ad) + labelTerra2 := []string{"terra2", labels[1], labels[2], labels[3]} + r.send([]*metric{ + {u.UAP.ApRxPackets, counter, bb.Terra2RxPackets, labelTerra2}, + {u.UAP.ApRxBytes, counter, bb.Terra2RxBytes, labelTerra2}, + {u.UAP.ApRxErrors, counter, bb.Terra2RxErrors, labelTerra2}, + {u.UAP.ApRxDropped, counter, bb.Terra2RxDropped, labelTerra2}, + {u.UAP.ApRxCrypts, counter, bb.Terra2RxCrypts, labelTerra2}, + {u.UAP.ApRxFrags, counter, bb.Terra2RxFrags, labelTerra2}, + {u.UAP.ApTxPackets, counter, bb.Terra2TxPackets, labelTerra2}, + {u.UAP.ApTxBytes, counter, bb.Terra2TxBytes, labelTerra2}, + {u.UAP.ApTxErrors, counter, bb.Terra2TxErrors, labelTerra2}, + {u.UAP.ApTxDropped, counter, bb.Terra2TxDropped, labelTerra2}, + {u.UAP.ApTxRetries, counter, bb.Terra2TxRetries, labelTerra2}, + {u.UAP.WifiTxAttempts, counter, bb.Terra2WifiTxAttempts, labelTerra2}, + {u.UAP.MacFilterRejections, counter, bb.Terra2MacFilterRejections, labelTerra2}, + {u.UAP.ApWifiTxDropped, counter, bb.Terra2WifiTxDropped, labelTerra2}, + }) + + // Export user stats for wifi0 + labelUserWifi0 := []string{"user-wifi0", labels[1], labels[2], labels[3]} + r.send([]*metric{ + {u.UAP.ApRxPackets, counter, bb.UserWifi0RxPackets, labelUserWifi0}, + {u.UAP.ApRxBytes, counter, bb.UserWifi0RxBytes, labelUserWifi0}, + {u.UAP.ApRxErrors, counter, bb.UserWifi0RxErrors, labelUserWifi0}, + {u.UAP.ApRxDropped, counter, bb.UserWifi0RxDropped, labelUserWifi0}, + {u.UAP.ApRxCrypts, counter, bb.UserWifi0RxCrypts, labelUserWifi0}, + {u.UAP.ApRxFrags, counter, bb.UserWifi0RxFrags, labelUserWifi0}, + {u.UAP.ApTxPackets, counter, bb.UserWifi0TxPackets, labelUserWifi0}, + {u.UAP.ApTxBytes, counter, bb.UserWifi0TxBytes, labelUserWifi0}, + {u.UAP.ApTxErrors, counter, bb.UserWifi0TxErrors, labelUserWifi0}, + {u.UAP.ApTxDropped, counter, bb.UserWifi0TxDropped, labelUserWifi0}, + {u.UAP.ApTxRetries, counter, bb.UserWifi0TxRetries, labelUserWifi0}, + {u.UAP.WifiTxAttempts, counter, bb.UserWifi0WifiTxAttempts, labelUserWifi0}, + {u.UAP.MacFilterRejections, counter, bb.UserWifi0MacFilterRejections, labelUserWifi0}, + {u.UAP.ApWifiTxDropped, counter, bb.UserWifi0WifiTxDropped, labelUserWifi0}, + }) + + // Export user stats for terra2 (60GHz) + labelUserTerra2 := []string{"user-terra2", labels[1], labels[2], labels[3]} + r.send([]*metric{ + {u.UAP.ApRxPackets, counter, bb.UserTerra2RxPackets, labelUserTerra2}, + {u.UAP.ApRxBytes, counter, bb.UserTerra2RxBytes, labelUserTerra2}, + {u.UAP.ApRxErrors, counter, bb.UserTerra2RxErrors, labelUserTerra2}, + {u.UAP.ApRxDropped, counter, bb.UserTerra2RxDropped, labelUserTerra2}, + {u.UAP.ApRxCrypts, counter, bb.UserTerra2RxCrypts, labelUserTerra2}, + {u.UAP.ApRxFrags, counter, bb.UserTerra2RxFrags, labelUserTerra2}, + {u.UAP.ApTxPackets, counter, bb.UserTerra2TxPackets, labelUserTerra2}, + {u.UAP.ApTxBytes, counter, bb.UserTerra2TxBytes, labelUserTerra2}, + {u.UAP.ApTxErrors, counter, bb.UserTerra2TxErrors, labelUserTerra2}, + {u.UAP.ApTxDropped, counter, bb.UserTerra2TxDropped, labelUserTerra2}, + {u.UAP.ApTxRetries, counter, bb.UserTerra2TxRetries, labelUserTerra2}, + {u.UAP.WifiTxAttempts, counter, bb.UserTerra2WifiTxAttempts, labelUserTerra2}, + {u.UAP.MacFilterRejections, counter, bb.UserTerra2MacFilterRejections, labelUserTerra2}, + {u.UAP.ApWifiTxDropped, counter, bb.UserTerra2WifiTxDropped, labelUserTerra2}, + }) +} + +// exportP2Pstats exports point-to-point link statistics for UBB devices. +func (u *promUnifi) exportP2Pstats(r report, labels []string, p2p *unifi.P2PStats) { + r.send([]*metric{ + {u.Device.Counter, gauge, p2p.RXRate.Val, append(labels, "p2p_rx_rate")}, + {u.Device.Counter, gauge, p2p.TXRate.Val, append(labels, "p2p_tx_rate")}, + {u.Device.Counter, gauge, p2p.Throughput.Val, append(labels, "p2p_throughput")}, + }) }