mirror of
https://github.com/unpoller/unpoller.git
synced 2026-09-30 11:36:41 +02:00
Exposes the uplink medium (wire vs wireless) and link speed for UniFi access
points so users can detect when an AP downgrades from gigabit to fast ethernet,
which was the original ask in #988. UAPs previously had zero uplink coverage
in any output plugin; now influxunifi, datadogunifi, and promunifi all report
uplink_type, uplink_speed, uplink_max_speed, and related fields.
Also brings Prometheus to parity with Influx/Datadog by emitting uplink
metrics for USW, UBB, and UDB devices (previously only USG/UDM/UXG had them
in promunifi). A new exportDeviceUplink helper in promunifi/usg.go reuses
the existing unpoller_device_uplink_* descriptors to avoid descriptor
collision (per c48b9917).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
244 lines
8.3 KiB
Go
244 lines
8.3 KiB
Go
package datadogunifi
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/unpoller/unifi/v5"
|
|
)
|
|
|
|
// uapT is used as a name for printed/logged counters.
|
|
const uapT = item("UAP")
|
|
|
|
// batchRogueAP generates metric points for neighboring access points.
|
|
func (u *DatadogUnifi) batchRogueAP(r report, s *unifi.RogueAP) {
|
|
tags := cleanTags(map[string]string{
|
|
"security": s.Security,
|
|
"oui": s.Oui,
|
|
"band": s.Band,
|
|
"mac": s.Bssid,
|
|
"ap_mac": s.ApMac,
|
|
"radio": s.Radio,
|
|
"radio_name": s.RadioName,
|
|
"site_name": s.SiteName,
|
|
"name": s.Essid,
|
|
"source": s.SourceName,
|
|
})
|
|
|
|
data := map[string]float64{
|
|
"age": s.Age.Val,
|
|
"bw": s.Bw.Val,
|
|
"center_freq": s.CenterFreq.Val,
|
|
"channel": float64(s.Channel),
|
|
"freq": s.Freq.Val,
|
|
"noise": s.Noise.Val,
|
|
"rssi": s.Rssi.Val,
|
|
"rssi_age": s.RssiAge.Val,
|
|
"signal": s.Signal.Val,
|
|
}
|
|
|
|
metricName := metricNamespace("uap_rogue")
|
|
|
|
reportGaugeForFloat64Map(r, metricName, data, tags)
|
|
}
|
|
|
|
// batchUAP generates Wireless-Access-Point datapoints for Datadog.
|
|
// These points can be passed directly to datadog.
|
|
func (u *DatadogUnifi) batchUAP(r report, s *unifi.UAP) {
|
|
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,
|
|
"uplink_type": s.Uplink.Type,
|
|
})
|
|
data := CombineFloat64(
|
|
u.processUAPstats(s.Stat.Ap),
|
|
u.batchSysStats(s.SysStats, s.SystemStats),
|
|
)
|
|
data["bytes"] = s.Bytes.Val
|
|
data["last_seen"] = s.LastSeen.Val
|
|
data["rx_bytes"] = s.RxBytes.Val
|
|
data["tx_bytes"] = s.TxBytes.Val
|
|
data["uptime"] = s.Uptime.Val
|
|
data["user_num_sta"] = s.UserNumSta.Val
|
|
data["guest_num_sta"] = s.GuestNumSta.Val
|
|
data["num_sta"] = s.NumSta.Val
|
|
data["upgradeable"] = s.Upgradable.Float64()
|
|
data["adopted"] = s.Adopted.Float64()
|
|
data["locating"] = s.Locating.Float64()
|
|
data["uplink_speed"] = s.Uplink.Speed.Val
|
|
data["uplink_max_speed"] = s.Uplink.MaxSpeed.Val
|
|
data["uplink_up"] = s.Uplink.Up.Float64()
|
|
data["uplink_full_duplex"] = s.Uplink.FullDuplex.Float64()
|
|
data["uplink_num_port"] = float64(s.Uplink.NumPort)
|
|
data["uplink_rx_bytes"] = s.Uplink.RxBytes.Val
|
|
data["uplink_tx_bytes"] = s.Uplink.TxBytes.Val
|
|
|
|
r.addCount(uapT)
|
|
|
|
metricName := metricNamespace("uap")
|
|
|
|
reportGaugeForFloat64Map(r, metricName, data, tags)
|
|
|
|
u.processVAPTable(r, tags, s.VapTable)
|
|
u.batchPortTable(r, tags, s.PortTable)
|
|
}
|
|
|
|
func (u *DatadogUnifi) processUAPstats(ap *unifi.Ap) map[string]float64 {
|
|
if ap == nil {
|
|
return map[string]float64{}
|
|
}
|
|
|
|
// Accumulative Statistics.
|
|
return map[string]float64{
|
|
"stat_user-rx_packets": ap.UserRxPackets.Val,
|
|
"stat_guest-rx_packets": ap.GuestRxPackets.Val,
|
|
"stat_rx_packets": ap.RxPackets.Val,
|
|
"stat_user-rx_bytes": ap.UserRxBytes.Val,
|
|
"stat_guest-rx_bytes": ap.GuestRxBytes.Val,
|
|
"stat_rx_bytes": ap.RxBytes.Val,
|
|
"stat_user-rx_errors": ap.UserRxErrors.Val,
|
|
"stat_guest-rx_errors": ap.GuestRxErrors.Val,
|
|
"stat_rx_errors": ap.RxErrors.Val,
|
|
"stat_user-rx_dropped": ap.UserRxDropped.Val,
|
|
"stat_guest-rx_dropped": ap.GuestRxDropped.Val,
|
|
"stat_rx_dropped": ap.RxDropped.Val,
|
|
"stat_user-rx_crypts": ap.UserRxCrypts.Val,
|
|
"stat_guest-rx_crypts": ap.GuestRxCrypts.Val,
|
|
"stat_rx_crypts": ap.RxCrypts.Val,
|
|
"stat_user-rx_frags": ap.UserRxFrags.Val,
|
|
"stat_guest-rx_frags": ap.GuestRxFrags.Val,
|
|
"stat_rx_frags": ap.RxFrags.Val,
|
|
"stat_user-tx_packets": ap.UserTxPackets.Val,
|
|
"stat_guest-tx_packets": ap.GuestTxPackets.Val,
|
|
"stat_tx_packets": ap.TxPackets.Val,
|
|
"stat_user-tx_bytes": ap.UserTxBytes.Val,
|
|
"stat_guest-tx_bytes": ap.GuestTxBytes.Val,
|
|
"stat_tx_bytes": ap.TxBytes.Val,
|
|
"stat_user-tx_errors": ap.UserTxErrors.Val,
|
|
"stat_guest-tx_errors": ap.GuestTxErrors.Val,
|
|
"stat_tx_errors": ap.TxErrors.Val,
|
|
"stat_user-tx_dropped": ap.UserTxDropped.Val,
|
|
"stat_guest-tx_dropped": ap.GuestTxDropped.Val,
|
|
"stat_tx_dropped": ap.TxDropped.Val,
|
|
"stat_user-tx_retries": ap.UserTxRetries.Val,
|
|
"stat_guest-tx_retries": ap.GuestTxRetries.Val,
|
|
}
|
|
}
|
|
|
|
// processVAPTable creates points for Wifi Radios. This works with several types of UAP-capable devices.
|
|
func (u *DatadogUnifi) processVAPTable(r report, t map[string]string, vt unifi.VapTable) { // nolint: funlen
|
|
for _, s := range vt {
|
|
tags := map[string]string{
|
|
"device_name": t["name"],
|
|
"site_name": t["site_name"],
|
|
"source": t["source"],
|
|
"ap_mac": s.ApMac,
|
|
"bssid": s.Bssid,
|
|
"id": s.ID,
|
|
"name": s.Name,
|
|
"radio_name": s.RadioName,
|
|
"radio": s.Radio,
|
|
"essid": s.Essid,
|
|
"site_id": s.SiteID,
|
|
"usage": s.Usage,
|
|
"state": s.State,
|
|
"is_guest": s.IsGuest.Txt,
|
|
}
|
|
data := map[string]float64{
|
|
"ccq": float64(s.Ccq),
|
|
"mac_filter_rejections": float64(s.MacFilterRejections),
|
|
"num_satisfaction_sta": s.NumSatisfactionSta.Val,
|
|
"avg_client_signal": s.AvgClientSignal.Val,
|
|
"satisfaction": s.Satisfaction.Val,
|
|
"satisfaction_now": s.SatisfactionNow.Val,
|
|
"num_sta": float64(s.NumSta),
|
|
"channel": s.Channel.Val,
|
|
"rx_bytes": s.RxBytes.Val,
|
|
"rx_crypts": s.RxCrypts.Val,
|
|
"rx_dropped": s.RxDropped.Val,
|
|
"rx_errors": s.RxErrors.Val,
|
|
"rx_frags": s.RxFrags.Val,
|
|
"rx_nwids": s.RxNwids.Val,
|
|
"rx_packets": s.RxPackets.Val,
|
|
"tx_bytes": s.TxBytes.Val,
|
|
"tx_dropped": s.TxDropped.Val,
|
|
"tx_errors": s.TxErrors.Val,
|
|
"tx_packets": s.TxPackets.Val,
|
|
"tx_power": s.TxPower.Val,
|
|
"tx_retries": s.TxRetries.Val,
|
|
"tx_combined_retries": s.TxCombinedRetries.Val,
|
|
"tx_data_mpdu_bytes": s.TxDataMpduBytes.Val,
|
|
"tx_rts_retries": s.TxRtsRetries.Val,
|
|
"tx_success": s.TxSuccess.Val,
|
|
"tx_total": s.TxTotal.Val,
|
|
"tx_tcp_goodbytes": s.TxTCPStats.Goodbytes.Val,
|
|
"tx_tcp_lat_avg": s.TxTCPStats.LatAvg.Val,
|
|
"tx_tcp_lat_max": s.TxTCPStats.LatMax.Val,
|
|
"tx_tcp_lat_min": s.TxTCPStats.LatMin.Val,
|
|
"rx_tcp_goodbytes": s.RxTCPStats.Goodbytes.Val,
|
|
"rx_tcp_lat_avg": s.RxTCPStats.LatAvg.Val,
|
|
"rx_tcp_lat_max": s.RxTCPStats.LatMax.Val,
|
|
"rx_tcp_lat_min": s.RxTCPStats.LatMin.Val,
|
|
"wifi_tx_latency_mov_avg": s.WifiTxLatencyMov.Avg.Val,
|
|
"wifi_tx_latency_mov_max": s.WifiTxLatencyMov.Max.Val,
|
|
"wifi_tx_latency_mov_min": s.WifiTxLatencyMov.Min.Val,
|
|
"wifi_tx_latency_mov_total": s.WifiTxLatencyMov.Total.Val,
|
|
"wifi_tx_latency_mov_cuont": s.WifiTxLatencyMov.TotalCount.Val,
|
|
}
|
|
|
|
metricName := metricNamespace("uap_vaps")
|
|
|
|
reportGaugeForFloat64Map(r, metricName, data, tags)
|
|
}
|
|
}
|
|
|
|
func (u *DatadogUnifi) processRadTable(r report, t map[string]string, rt unifi.RadioTable, rts unifi.RadioTableStats) {
|
|
for _, p := range rt {
|
|
tags := map[string]string{
|
|
"device_name": t["name"],
|
|
"site_name": t["site_name"],
|
|
"source": t["source"],
|
|
"channel": p.Channel.Txt,
|
|
"radio": p.Radio,
|
|
"ht": p.Ht.Txt,
|
|
}
|
|
data := map[string]float64{
|
|
"current_antenna_gain": p.CurrentAntennaGain.Val,
|
|
"max_txpower": p.MaxTxpower.Val,
|
|
"min_txpower": p.MinTxpower.Val,
|
|
"nss": p.Nss.Val,
|
|
"radio_caps": p.RadioCaps.Val,
|
|
}
|
|
|
|
for _, t := range rts {
|
|
if strings.EqualFold(t.Name, p.Name) {
|
|
data["ast_be_xmit"] = t.AstBeXmit.Val
|
|
data["channel"] = t.Channel.Val
|
|
data["cu_self_rx"] = t.CuSelfRx.Val
|
|
data["cu_self_tx"] = t.CuSelfTx.Val
|
|
data["cu_total"] = t.CuTotal.Val
|
|
data["ext_channel"] = t.Extchannel.Val
|
|
data["gain"] = t.Gain.Val
|
|
data["guest_num_sta"] = t.GuestNumSta.Val
|
|
data["num_sta"] = t.NumSta.Val
|
|
data["tx_packets"] = t.TxPackets.Val
|
|
data["tx_power"] = t.TxPower.Val
|
|
data["tx_retries"] = t.TxRetries.Val
|
|
data["user_num_sta"] = t.UserNumSta.Val
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
metricName := metricNamespace("uap_radios")
|
|
|
|
reportGaugeForFloat64Map(r, metricName, data, tags)
|
|
}
|
|
}
|