mirror of
https://github.com/unpoller/unpoller.git
synced 2026-09-30 03:21:28 +02:00
feat: Add DHCP lease metrics export to Prometheus
- Add DHCP lease fetching in inputunifi collector - Create promunifi/dhcp_leases.go with network-level and per-lease metrics - Network-level metrics: pool_size, active_leases, utilization_percent, free_percent, available_ips - Per-lease metrics: is_static, lease_end, lease_start, lease_time - Separate network-level pool metrics from per-lease metrics
This commit is contained in:
@@ -182,6 +182,14 @@ func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
|
||||
u.LogDebugf("Found %d SpeedTests entries", len(m.SpeedTests))
|
||||
}
|
||||
|
||||
// Get DHCP leases with associations
|
||||
if m.DHCPLeases, err = c.Unifi.GetActiveDHCPLeasesWithAssociations(sites); err != nil {
|
||||
// Don't fail collection if DHCP leases fail - older controllers may not have this endpoint
|
||||
u.LogDebugf("unifi.GetActiveDHCPLeasesWithAssociations(%s): %v (continuing)", c.URL, err)
|
||||
} else {
|
||||
u.LogDebugf("Found %d DHCPLeases entries", len(m.DHCPLeases))
|
||||
}
|
||||
|
||||
return u.augmentMetrics(c, m), nil
|
||||
}
|
||||
|
||||
@@ -295,12 +303,12 @@ func (u *InputUnifi) augmentMetrics(c *Controller, metrics *Metrics) *poller.Met
|
||||
client.ApName = devices[client.ApMac]
|
||||
client.GwName = devices[client.GwMac]
|
||||
client.RadioDescription = bssdIDs[client.Bssid] + client.RadioProto
|
||||
|
||||
|
||||
// Apply site name override for clients if configured
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(client.SiteName) {
|
||||
client.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
|
||||
m.Clients = append(m.Clients, client)
|
||||
}
|
||||
|
||||
@@ -313,12 +321,12 @@ func (u *InputUnifi) augmentMetrics(c *Controller, metrics *Metrics) *poller.Met
|
||||
|
||||
client.Name = RedactNamePII(client.Name, c.HashPII, c.DropPII)
|
||||
client.MAC = RedactMacPII(client.MAC, c.HashPII, c.DropPII)
|
||||
|
||||
|
||||
// Apply site name override for DPI clients if configured
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(client.SiteName) {
|
||||
client.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
|
||||
m.ClientsDPI = append(m.ClientsDPI, client)
|
||||
}
|
||||
|
||||
@@ -367,6 +375,14 @@ func (u *InputUnifi) augmentMetrics(c *Controller, metrics *Metrics) *poller.Met
|
||||
m.CountryTraffic = append(m.CountryTraffic, traffic)
|
||||
}
|
||||
|
||||
for _, lease := range metrics.DHCPLeases {
|
||||
// Apply site name override for DHCP leases if configured
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(lease.SiteName) {
|
||||
lease.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
m.DHCPLeases = append(m.DHCPLeases, lease)
|
||||
}
|
||||
|
||||
// Apply default_site_name_override to all metrics if configured.
|
||||
// This must be done AFTER all metrics are added to m, so everything is included.
|
||||
// This allows us to use the console name for Cloud Gateways while keeping
|
||||
@@ -462,6 +478,15 @@ func applySiteNameOverride(m *poller.Metrics, overrideName string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply to DHCP leases
|
||||
for i := range m.DHCPLeases {
|
||||
if lease, ok := m.DHCPLeases[i].(*unifi.DHCPLease); ok {
|
||||
if isDefaultSiteName(lease.SiteName) {
|
||||
lease.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is a helper function for augmentMetrics.
|
||||
|
||||
@@ -86,6 +86,7 @@ type Metrics struct {
|
||||
RogueAPs []*unifi.RogueAP
|
||||
SpeedTests []*unifi.SpeedTestResult
|
||||
Devices *unifi.Devices
|
||||
DHCPLeases []*unifi.DHCPLease
|
||||
}
|
||||
|
||||
func init() { // nolint: gochecknoinits
|
||||
|
||||
@@ -88,6 +88,7 @@ type Metrics struct {
|
||||
RogueAPs []any
|
||||
SpeedTests []any
|
||||
CountryTraffic []any
|
||||
DHCPLeases []any
|
||||
}
|
||||
|
||||
// Events defines the type for log entries.
|
||||
|
||||
@@ -269,6 +269,7 @@ func AppendMetrics(existing *Metrics, m *Metrics) *Metrics {
|
||||
existing.Clients = append(existing.Clients, m.Clients...)
|
||||
existing.Devices = append(existing.Devices, m.Devices...)
|
||||
existing.CountryTraffic = append(existing.CountryTraffic, m.CountryTraffic...)
|
||||
existing.DHCPLeases = append(existing.DHCPLeases, m.DHCPLeases...)
|
||||
|
||||
return existing
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package promunifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -11,6 +10,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
promver "github.com/prometheus/common/version"
|
||||
@@ -46,6 +47,7 @@ type promUnifi struct {
|
||||
RogueAP *rogueap
|
||||
SpeedTest *speedtest
|
||||
CountryTraffic *ucountrytraffic
|
||||
DHCPLease *dhcplease
|
||||
// This interface is passed to the Collect() method. The Collect method uses
|
||||
// this interface to retrieve the latest UniFi measurements and export them.
|
||||
Collector poller.Collect
|
||||
@@ -205,6 +207,7 @@ func (u *promUnifi) Run(c poller.Collect) error {
|
||||
u.RogueAP = descRogueAP(u.Namespace + "_rogueap_")
|
||||
u.SpeedTest = descSpeedTest(u.Namespace + "_speedtest_")
|
||||
u.CountryTraffic = descCountryTraffic(u.Namespace + "_countrytraffic_")
|
||||
u.DHCPLease = descDHCPLease(u.Namespace + "_")
|
||||
|
||||
mux := http.NewServeMux()
|
||||
promver.Version = version.Version
|
||||
@@ -288,7 +291,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} {
|
||||
for _, f := range []any{u.Client, u.Device, u.UAP, u.USG, u.USW, u.PDU, u.Site, u.SpeedTest, u.DHCPLease} {
|
||||
v := reflect.Indirect(reflect.ValueOf(f))
|
||||
|
||||
// Loop each struct member and send it to the provided channel.
|
||||
@@ -411,6 +414,24 @@ func (u *promUnifi) loopExports(r report) {
|
||||
u.exportCountryTraffic(r, ct)
|
||||
}
|
||||
|
||||
// Export network-level pool metrics first (once per network)
|
||||
dhcpLeases := make([]*unifi.DHCPLease, 0, len(m.DHCPLeases))
|
||||
for _, lease := range m.DHCPLeases {
|
||||
if l, ok := lease.(*unifi.DHCPLease); ok {
|
||||
dhcpLeases = append(dhcpLeases, l)
|
||||
}
|
||||
}
|
||||
if len(dhcpLeases) > 0 {
|
||||
u.exportDHCPNetworkPool(r, dhcpLeases)
|
||||
}
|
||||
|
||||
// Export per-lease metrics
|
||||
for _, lease := range m.DHCPLeases {
|
||||
if l, ok := lease.(*unifi.DHCPLease); ok {
|
||||
u.exportDHCPLease(r, l)
|
||||
}
|
||||
}
|
||||
|
||||
u.exportClientDPItotals(r, appTotal, catTotal)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
type dhcplease struct {
|
||||
// Network-level pool metrics (exported once per network)
|
||||
ActiveLeases *prometheus.Desc
|
||||
PoolSize *prometheus.Desc
|
||||
UtilizationPercent *prometheus.Desc
|
||||
FreePercent *prometheus.Desc
|
||||
AvailableIPs *prometheus.Desc
|
||||
// Per-lease metrics
|
||||
LeaseStart *prometheus.Desc
|
||||
LeaseEnd *prometheus.Desc
|
||||
LeaseTime *prometheus.Desc
|
||||
IsStatic *prometheus.Desc
|
||||
}
|
||||
|
||||
func descDHCPLease(ns string) *dhcplease {
|
||||
// Network-level labels (for pool metrics)
|
||||
networkLabels := []string{
|
||||
"network",
|
||||
"network_id",
|
||||
"site_name",
|
||||
"source",
|
||||
}
|
||||
// Per-lease labels
|
||||
leaseLabels := []string{
|
||||
"ip",
|
||||
"mac",
|
||||
"hostname",
|
||||
"network",
|
||||
"network_id",
|
||||
"client_name",
|
||||
"site_name",
|
||||
"source",
|
||||
}
|
||||
nd := prometheus.NewDesc
|
||||
|
||||
return &dhcplease{
|
||||
ActiveLeases: nd(ns+"dhcp_active_leases", "Number of active DHCP leases for this network", networkLabels, nil),
|
||||
PoolSize: nd(ns+"dhcp_pool_size", "Total number of IPs in DHCP pool range", networkLabels, nil),
|
||||
UtilizationPercent: nd(ns+"dhcp_utilization_percent", "DHCP pool utilization percentage (used)", networkLabels, nil),
|
||||
FreePercent: nd(ns+"dhcp_free_percent", "DHCP pool free percentage (available)", networkLabels, nil),
|
||||
AvailableIPs: nd(ns+"dhcp_available_ips", "Number of available IPs in DHCP pool", networkLabels, nil),
|
||||
LeaseStart: nd(ns+"dhcp_lease_start", "DHCP lease start timestamp", leaseLabels, nil),
|
||||
LeaseEnd: nd(ns+"dhcp_lease_end", "DHCP lease end timestamp", leaseLabels, nil),
|
||||
LeaseTime: nd(ns+"dhcp_lease_time", "DHCP lease duration in seconds", leaseLabels, nil),
|
||||
IsStatic: nd(ns+"dhcp_is_static", "Whether this is a static DHCP lease (1) or dynamic (0)", leaseLabels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportDHCPLease(r report, l *unifi.DHCPLease) {
|
||||
// Per-lease labels
|
||||
leaseLabels := []string{
|
||||
l.IP,
|
||||
l.Mac,
|
||||
l.Hostname,
|
||||
l.Network,
|
||||
l.NetworkID,
|
||||
l.ClientName,
|
||||
l.SiteName,
|
||||
l.SourceName,
|
||||
}
|
||||
|
||||
// Convert FlexBool to float64 (1.0 for true, 0.0 for false)
|
||||
isStaticVal := 0.0
|
||||
if l.IsStatic.Val {
|
||||
isStaticVal = 1.0
|
||||
}
|
||||
|
||||
metrics := []*metric{
|
||||
{u.DHCPLease.IsStatic, gauge, isStaticVal, leaseLabels},
|
||||
}
|
||||
|
||||
// Add lease time metrics if available
|
||||
if l.LeaseStart.Val > 0 {
|
||||
metrics = append(metrics, &metric{u.DHCPLease.LeaseStart, gauge, l.LeaseStart.Val, leaseLabels})
|
||||
}
|
||||
|
||||
if l.LeaseEnd.Val > 0 {
|
||||
metrics = append(metrics, &metric{u.DHCPLease.LeaseEnd, gauge, l.LeaseEnd.Val, leaseLabels})
|
||||
}
|
||||
|
||||
if l.LeaseTime.Val > 0 {
|
||||
metrics = append(metrics, &metric{u.DHCPLease.LeaseTime, gauge, l.LeaseTime.Val, leaseLabels})
|
||||
}
|
||||
|
||||
r.send(metrics)
|
||||
}
|
||||
|
||||
// exportDHCPNetworkPool exports network-level DHCP pool metrics (once per network).
|
||||
func (u *promUnifi) exportDHCPNetworkPool(r report, leases []*unifi.DHCPLease) {
|
||||
// Group leases by network_id to export pool metrics once per network
|
||||
networkMetrics := make(map[string]*networkPoolData)
|
||||
|
||||
for _, lease := range leases {
|
||||
if lease.NetworkTableEntry == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
networkID := lease.NetworkID
|
||||
if networkID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the first lease for each network to get pool data
|
||||
if _, exists := networkMetrics[networkID]; !exists {
|
||||
poolSize := lease.GetPoolSize()
|
||||
if poolSize > 0 {
|
||||
networkMetrics[networkID] = &networkPoolData{
|
||||
Network: lease.Network,
|
||||
NetworkID: networkID,
|
||||
SiteName: lease.SiteName,
|
||||
SourceName: lease.SourceName,
|
||||
PoolSize: poolSize,
|
||||
ActiveLeases: lease.GetActiveLeaseCount(),
|
||||
Utilization: lease.GetUtilizationPercentage(),
|
||||
FreePercent: 100.0 - lease.GetUtilizationPercentage(),
|
||||
AvailableIPs: lease.GetAvailableIPs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export metrics for each unique network
|
||||
for _, data := range networkMetrics {
|
||||
networkLabels := []string{
|
||||
data.Network,
|
||||
data.NetworkID,
|
||||
data.SiteName,
|
||||
data.SourceName,
|
||||
}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.DHCPLease.PoolSize, gauge, float64(data.PoolSize), networkLabels},
|
||||
{u.DHCPLease.ActiveLeases, gauge, float64(data.ActiveLeases), networkLabels},
|
||||
{u.DHCPLease.UtilizationPercent, gauge, data.Utilization, networkLabels},
|
||||
{u.DHCPLease.FreePercent, gauge, data.FreePercent, networkLabels},
|
||||
{u.DHCPLease.AvailableIPs, gauge, float64(data.AvailableIPs), networkLabels},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type networkPoolData struct {
|
||||
Network string
|
||||
NetworkID string
|
||||
SiteName string
|
||||
SourceName string
|
||||
PoolSize int
|
||||
ActiveLeases int
|
||||
Utilization float64
|
||||
FreePercent float64
|
||||
AvailableIPs int
|
||||
}
|
||||
Reference in New Issue
Block a user