mirror of
https://github.com/unpoller/unpoller.git
synced 2026-03-31 06:33:57 -04:00
Bumps github.com/unpoller/unifi/v5 to v5.23.0 which adds
GetTopology() fetching vertices (devices/clients) and edges
(wired/wireless connections) from /proxy/network/v2/api/site/{site}/topology.
Changes across the stack:
- poller.Metrics: add Topologies []any field + AppendMetrics support
- inputunifi: collect topology per-site (non-fatal on older controllers),
pass through augmentMetrics with site name override support
- promunifi: new topology.go with summary, connection-type, link-quality,
and band-distribution gauges
- influxunifi: new topology.go with topology_summary and topology_edge
measurements
- datadogunifi: new topology.go with equivalent Datadog gauges
- otelunifi: new topology.go with OpenTelemetry gauge observations
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ type promUnifi struct {
|
||||
WAN *wan
|
||||
Controller *controller
|
||||
FirewallPolicy *firewallpolicy
|
||||
Topology *topology
|
||||
// controllerUp tracks per-controller poll success (1) or failure (0).
|
||||
controllerUp *prometheus.GaugeVec
|
||||
// This interface is passed to the Collect() method. The Collect method uses
|
||||
@@ -217,6 +218,7 @@ func (u *promUnifi) Run(c poller.Collect) error {
|
||||
u.WAN = descWAN(u.Namespace + "_")
|
||||
u.Controller = descController(u.Namespace + "_")
|
||||
u.FirewallPolicy = descFirewallPolicy(u.Namespace + "_")
|
||||
u.Topology = descTopology(u.Namespace + "_")
|
||||
u.controllerUp = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: u.Namespace + "_controller_up",
|
||||
Help: "Whether the last poll of the UniFi controller succeeded (1) or failed (0).",
|
||||
@@ -305,7 +307,7 @@ func (t *target) Describe(ch chan<- *prometheus.Desc) {
|
||||
// Describe satisfies the prometheus Collector. This returns all of the
|
||||
// metric descriptions that this packages produces.
|
||||
func (u *promUnifi) Describe(ch chan<- *prometheus.Desc) {
|
||||
for _, f := range []any{u.Client, u.Device, u.UAP, u.USG, u.USW, u.PDU, u.Site, u.SpeedTest, u.DHCPLease, u.WAN, u.FirewallPolicy} {
|
||||
for _, f := range []any{u.Client, u.Device, u.UAP, u.USG, u.USW, u.PDU, u.Site, u.SpeedTest, u.DHCPLease, u.WAN, u.FirewallPolicy, u.Topology} {
|
||||
v := reflect.Indirect(reflect.ValueOf(f))
|
||||
|
||||
// Loop each struct member and send it to the provided channel.
|
||||
@@ -482,6 +484,12 @@ func (u *promUnifi) loopExports(r report) {
|
||||
|
||||
u.exportFirewallPolicies(r, firewallPolicies)
|
||||
|
||||
for _, t := range m.Topologies {
|
||||
if topo, ok := t.(*unifi.Topology); ok {
|
||||
u.exportTopology(r, topo)
|
||||
}
|
||||
}
|
||||
|
||||
u.exportClientDPItotals(r, appTotal, catTotal)
|
||||
}
|
||||
|
||||
|
||||
121
pkg/promunifi/topology.go
Normal file
121
pkg/promunifi/topology.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
type topology struct {
|
||||
// Summary metrics
|
||||
VerticesTotal *prometheus.Desc
|
||||
EdgesTotal *prometheus.Desc
|
||||
DevicesTotal *prometheus.Desc
|
||||
ClientsTotal *prometheus.Desc
|
||||
HasUnknownSwitch *prometheus.Desc
|
||||
|
||||
// Connection type metrics
|
||||
ConnectionsWired *prometheus.Desc
|
||||
ConnectionsWireless *prometheus.Desc
|
||||
ConnectionsByBand *prometheus.Desc
|
||||
|
||||
// Link quality metrics
|
||||
LinkExperienceScore *prometheus.Desc
|
||||
LinkRateMbps *prometheus.Desc
|
||||
WiredFullDuplex *prometheus.Desc
|
||||
}
|
||||
|
||||
func descTopology(ns string) *topology {
|
||||
siteLabels := []string{"site_name", "source"}
|
||||
linkLabels := []string{"uplink_mac", "downlink_mac", "link_type", "site_name", "source"}
|
||||
bandLabels := []string{"band", "site_name", "source"}
|
||||
|
||||
nd := prometheus.NewDesc
|
||||
|
||||
return &topology{
|
||||
VerticesTotal: nd(ns+"topology_vertices_total", "Total vertices in topology", siteLabels, nil),
|
||||
EdgesTotal: nd(ns+"topology_edges_total", "Total edges/connections in topology", siteLabels, nil),
|
||||
DevicesTotal: nd(ns+"topology_devices_total", "UniFi devices in topology", siteLabels, nil),
|
||||
ClientsTotal: nd(ns+"topology_clients_total", "Clients in topology", siteLabels, nil),
|
||||
HasUnknownSwitch: nd(ns+"topology_has_unknown_switch", "Unknown switch detected in topology (1/0)", siteLabels, nil),
|
||||
ConnectionsWired: nd(ns+"topology_connections_wired", "Number of wired connections", siteLabels, nil),
|
||||
ConnectionsWireless: nd(ns+"topology_connections_wireless", "Number of wireless connections", siteLabels, nil),
|
||||
ConnectionsByBand: nd(ns+"topology_connections_by_band", "Number of wireless connections by radio band", bandLabels, nil),
|
||||
LinkExperienceScore: nd(ns+"topology_link_experience_score", "Link experience score (0-100)", linkLabels, nil),
|
||||
LinkRateMbps: nd(ns+"topology_link_rate_mbps", "Link rate in Mbps", linkLabels, nil),
|
||||
WiredFullDuplex: nd(ns+"topology_wired_full_duplex", "Number of full-duplex wired links", siteLabels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportTopology(r report, t *unifi.Topology) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
siteLabels := []string{t.SiteName, t.SourceName}
|
||||
|
||||
var (
|
||||
devices int
|
||||
clients int
|
||||
wired int
|
||||
wireless int
|
||||
fullDuplex int
|
||||
bandCounts = make(map[string]int)
|
||||
unknownSwitch float64
|
||||
)
|
||||
|
||||
if t.HasUnknownSwitch {
|
||||
unknownSwitch = 1
|
||||
}
|
||||
|
||||
for i := range t.Vertices {
|
||||
switch t.Vertices[i].Type {
|
||||
case "DEVICE":
|
||||
devices++
|
||||
case "CLIENT":
|
||||
clients++
|
||||
}
|
||||
}
|
||||
|
||||
for i := range t.Edges {
|
||||
e := &t.Edges[i]
|
||||
linkLabels := []string{e.UplinkMac, e.DownlinkMac, e.Type, t.SiteName, t.SourceName}
|
||||
|
||||
switch e.Type {
|
||||
case "WIRED":
|
||||
wired++
|
||||
|
||||
if e.Duplex == "FULL_DUPLEX" {
|
||||
fullDuplex++
|
||||
}
|
||||
|
||||
if e.RateMbps.Val > 0 {
|
||||
r.send([]*metric{{u.Topology.LinkRateMbps, gauge, e.RateMbps.Val, linkLabels}})
|
||||
}
|
||||
case "WIRELESS":
|
||||
wireless++
|
||||
|
||||
if e.RadioBand != "" {
|
||||
bandCounts[e.RadioBand]++
|
||||
}
|
||||
|
||||
if e.ExperienceScore.Val > 0 {
|
||||
r.send([]*metric{{u.Topology.LinkExperienceScore, gauge, e.ExperienceScore.Val, linkLabels}})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.Topology.VerticesTotal, gauge, float64(len(t.Vertices)), siteLabels},
|
||||
{u.Topology.EdgesTotal, gauge, float64(len(t.Edges)), siteLabels},
|
||||
{u.Topology.DevicesTotal, gauge, float64(devices), siteLabels},
|
||||
{u.Topology.ClientsTotal, gauge, float64(clients), siteLabels},
|
||||
{u.Topology.HasUnknownSwitch, gauge, unknownSwitch, siteLabels},
|
||||
{u.Topology.ConnectionsWired, gauge, float64(wired), siteLabels},
|
||||
{u.Topology.ConnectionsWireless, gauge, float64(wireless), siteLabels},
|
||||
{u.Topology.WiredFullDuplex, gauge, float64(fullDuplex), siteLabels},
|
||||
})
|
||||
|
||||
for band, count := range bandCounts {
|
||||
r.send([]*metric{{u.Topology.ConnectionsByBand, gauge, float64(count), []string{band, t.SiteName, t.SourceName}}})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user