move pkgs, update readme, add two more metrics to influx client

This commit is contained in:
davidnewhall2
2019-11-30 01:45:42 -08:00
parent d052e699db
commit ebe05ec3e5
36 changed files with 22 additions and 16 deletions
+4
View File
@@ -0,0 +1,4 @@
# influx
This package provides the methods to turn UniFi measurements into influx
data-points with appropriate tags and fields.
+111
View File
@@ -0,0 +1,111 @@
package influxunifi
import (
"time"
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// ClientPoints generates Unifi Client datapoints for InfluxDB.
// These points can be passed directly to influx.
func ClientPoints(c *unifi.Client, now time.Time) ([]*influx.Point, error) {
tags := map[string]string{
"id": c.ID,
"mac": c.Mac,
"user_id": c.UserID,
"site_id": c.SiteID,
"site_name": c.SiteName,
"network_id": c.NetworkID,
"usergroup_id": c.UserGroupID,
"ap_mac": c.ApMac,
"gw_mac": c.GwMac,
"sw_mac": c.SwMac,
"ap_name": c.ApName,
"gw_name": c.GwName,
"sw_name": c.SwName,
"oui": c.Oui,
"radio_name": c.RadioName,
"radio": c.Radio,
"radio_proto": c.RadioProto,
"name": c.Name,
"fixed_ip": c.FixedIP,
"sw_port": c.SwPort.Txt,
"os_class": c.OsClass.Txt,
"os_name": c.OsName.Txt,
"dev_cat": c.DevCat.Txt,
"dev_id": c.DevID.Txt,
"dev_vendor": c.DevVendor.Txt,
"dev_family": c.DevFamily.Txt,
"is_11r": c.Is11R.Txt,
"is_wired": c.IsWired.Txt,
"is_guest": c.IsGuest.Txt,
"is_guest_by_uap": c.IsGuestByUAP.Txt,
"is_guest_by_ugw": c.IsGuestByUGW.Txt,
"is_guest_by_usw": c.IsGuestByUSW.Txt,
"noted": c.Noted.Txt,
"powersave_enabled": c.PowersaveEnabled.Txt,
"qos_policy_applied": c.QosPolicyApplied.Txt,
"use_fixedip": c.UseFixedIP.Txt,
"channel": c.Channel.Txt,
"vlan": c.Vlan.Txt,
}
fields := map[string]interface{}{
"anomalies": c.Anomalies,
"ip": c.IP,
"essid": c.Essid,
"bssid": c.Bssid,
"radio_desc": c.RadioDescription,
"satisfaction": c.Satisfaction.Val,
"hostname": c.Hostname,
"dpi_stats_last_updated": c.DpiStatsLastUpdated,
"last_seen_by_uap": c.LastSeenByUAP,
"last_seen_by_ugw": c.LastSeenByUGW,
"last_seen_by_usw": c.LastSeenByUSW,
"uptime_by_uap": c.UptimeByUAP,
"uptime_by_ugw": c.UptimeByUGW,
"uptime_by_usw": c.UptimeByUSW,
"assoc_time": c.AssocTime,
"bytes_r": c.BytesR,
"ccq": c.Ccq,
"first_seen": c.FirstSeen,
"idle_time": c.IdleTime,
"last_seen": c.LastSeen,
"latest_assoc_time": c.LatestAssocTime,
"network": c.Network,
"noise": c.Noise,
"note": c.Note,
"roam_count": c.RoamCount,
"rssi": c.Rssi,
"rx_bytes": c.RxBytes,
"rx_bytes_r": c.RxBytesR,
"rx_packets": c.RxPackets,
"rx_rate": c.RxRate,
"signal": c.Signal,
"tx_bytes": c.TxBytes,
"tx_bytes_r": c.TxBytesR,
"tx_packets": c.TxPackets,
"tx_retries": c.TxRetries,
"tx_power": c.TxPower,
"tx_rate": c.TxRate,
"uptime": c.Uptime,
"wifi_tx_attempts": c.WifiTxAttempts,
"wired-rx_bytes": c.WiredRxBytes,
"wired-rx_bytes-r": c.WiredRxBytesR,
"wired-rx_packets": c.WiredRxPackets,
"wired-tx_bytes": c.WiredTxBytes,
"wired-tx_bytes-r": c.WiredTxBytesR,
"wired-tx_packets": c.WiredTxPackets,
"dpi_app": c.DpiStats.App.Val,
"dpi_cat": c.DpiStats.Cat.Val,
"dpi_rx_bytes": c.DpiStats.RxBytes.Val,
"dpi_rx_packets": c.DpiStats.RxPackets.Val,
"dpi_tx_bytes": c.DpiStats.TxBytes.Val,
"dpi_tx_packets": c.DpiStats.TxPackets.Val,
}
pt, err := influx.NewPoint("clients", tags, fields, now)
if err != nil {
return nil, err
}
return []*influx.Point{pt}, nil
}
+44
View File
@@ -0,0 +1,44 @@
package influxunifi
import (
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// IDSPoints generates intrusion detection datapoints for InfluxDB.
// These points can be passed directly to influx.
func IDSPoints(i *unifi.IDS) ([]*influx.Point, error) {
tags := map[string]string{
"in_iface": i.InIface,
"event_type": i.EventType,
"proto": i.Proto,
"app_proto": i.AppProto,
"usgip": i.Usgip,
"country_code": i.SrcipGeo.CountryCode,
"country_name": i.SrcipGeo.CountryName,
"region": i.SrcipGeo.Region,
"city": i.SrcipGeo.City,
"postal_code": i.SrcipGeo.PostalCode,
"srcipASN": i.SrcipASN,
"usgipASN": i.UsgipASN,
"alert_category": i.InnerAlertCategory,
"subsystem": i.Subsystem,
"catname": i.Catname,
}
fields := map[string]interface{}{
"event_type": i.EventType,
"proto": i.Proto,
"app_proto": i.AppProto,
"usgip": i.Usgip,
"country_name": i.SrcipGeo.CountryName,
"city": i.SrcipGeo.City,
"postal_code": i.SrcipGeo.PostalCode,
"srcipASN": i.SrcipASN,
"usgipASN": i.UsgipASN,
}
pt, err := influx.NewPoint("intrusion_detect", tags, fields, i.Datetime)
if err != nil {
return nil, err
}
return []*influx.Point{pt}, nil
}
+69
View File
@@ -0,0 +1,69 @@
// Package influx provides the methods to turn UniFi measurements into influx
// data-points with appropriate tags and fields.
package influxunifi
import (
"github.com/davidnewhall/unifi-poller/pkg/metrics"
client "github.com/influxdata/influxdb1-client/v2"
)
// Metrics contains all the data from the controller and an influx endpoint to send it to.
type Metrics struct {
*metrics.Metrics
client.BatchPoints
}
// ProcessPoints batches all device and client data into influxdb data points.
// Call this after you've collected all the data you care about.
// This function is sorta weird and returns a slice of errors. The reasoning is
// that some points may process while others fail, so we attempt to process them
// all. This is (usually) run in a loop, so we can't really exit on error,
// we just log the errors and tally them on a counter. In reality, this never
// returns any errors because we control the data going in; cool right? But we
// still check&log it in case the data going is skewed up and causes errors!
func (m *Metrics) ProcessPoints() []error {
errs := []error{}
processPoints := func(m *Metrics, p []*client.Point, err error) {
switch {
case err != nil:
errs = append(errs, err)
case p == nil:
default:
m.AddPoints(p)
}
}
for _, asset := range m.Sites {
pts, err := SitePoints(asset, m.TS)
processPoints(m, pts, err)
}
for _, asset := range m.Clients {
pts, err := ClientPoints(asset, m.TS)
processPoints(m, pts, err)
}
for _, asset := range m.IDSList {
pts, err := IDSPoints(asset) // no m.TS.
processPoints(m, pts, err)
}
if m.Devices == nil {
return errs
}
for _, asset := range m.Devices.UAPs {
pts, err := UAPPoints(asset, m.TS)
processPoints(m, pts, err)
}
for _, asset := range m.Devices.USGs {
pts, err := USGPoints(asset, m.TS)
processPoints(m, pts, err)
}
for _, asset := range m.Devices.USWs {
pts, err := USWPoints(asset, m.TS)
processPoints(m, pts, err)
}
for _, asset := range m.Devices.UDMs {
pts, err := UDMPoints(asset, m.TS)
processPoints(m, pts, err)
}
return errs
}
+83
View File
@@ -0,0 +1,83 @@
package influxunifi
import (
"strings"
"time"
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// SitePoints generates Unifi Sites' datapoints for InfluxDB.
// These points can be passed directly to influx.
func SitePoints(u *unifi.Site, now time.Time) ([]*influx.Point, error) {
points := []*influx.Point{}
for _, s := range u.Health {
tags := map[string]string{
"id": u.ID,
"name": u.Name,
"site_name": u.SiteName,
"desc": u.Desc,
"status": s.Status,
"subsystem": s.Subsystem,
"wan_ip": s.WanIP,
"netmask": s.Netmask,
"gw_name": s.GwName,
"gw_mac": s.GwMac,
"gw_version": s.GwVersion,
"speedtest_status": s.SpeedtestStatus,
"lan_ip": s.LanIP,
"remote_user_enabled": s.RemoteUserEnabled.Txt,
"site_to_site_enabled": s.SiteToSiteEnabled.Txt,
"nameservers": strings.Join(s.Nameservers, ","),
"gateways": strings.Join(s.Gateways, ","),
"num_new_alarms": u.NumNewAlarms.Txt,
"attr_hidden_id": u.AttrHiddenID,
"attr_no_delete": u.AttrNoDelete.Txt,
}
fields := map[string]interface{}{
"attr_hidden_id": u.AttrHiddenID,
"attr_no_delete": u.AttrNoDelete.Val,
"num_user": s.NumUser.Val,
"num_guest": s.NumGuest.Val,
"num_iot": s.NumIot.Val,
"tx_bytes-r": s.TxBytesR.Val,
"rx_bytes-r": s.RxBytesR.Val,
"status": s.Status,
"num_ap": s.NumAp.Val,
"num_adopted": s.NumAdopted.Val,
"num_disabled": s.NumDisabled.Val,
"num_disconnected": s.NumDisconnected.Val,
"num_pending": s.NumPending.Val,
"num_gw": s.NumGw.Val,
"wan_ip": s.WanIP,
"num_sta": s.NumSta.Val,
"gw_cpu": s.GwSystemStats.CPU.Val,
"gw_mem": s.GwSystemStats.Mem.Val,
"gw_uptime": s.GwSystemStats.Uptime.Val,
"latency": s.Latency.Val,
"uptime": s.Uptime.Val,
"drops": s.Drops.Val,
"xput_up": s.XputUp.Val,
"xput_down": s.XputDown.Val,
"speedtest_ping": s.SpeedtestPing.Val,
"speedtest_lastrun": s.SpeedtestLastrun.Val,
"num_sw": s.NumSw.Val,
"remote_user_num_active": s.RemoteUserNumActive.Val,
"remote_user_num_inactive": s.RemoteUserNumInactive.Val,
"remote_user_rx_bytes": s.RemoteUserRxBytes.Val,
"remote_user_tx_bytes": s.RemoteUserTxBytes.Val,
"remote_user_rx_packets": s.RemoteUserRxPackets.Val,
"remote_user_tx_packets": s.RemoteUserTxPackets.Val,
"num_new_alarms": u.NumNewAlarms.Val,
"nameservers": len(s.Nameservers),
"gateways": len(s.Gateways),
}
pt, err := influx.NewPoint("subsystems", tags, fields, time.Now())
if err != nil {
return points, err
}
points = append(points, pt)
}
return points, nil
}
+214
View File
@@ -0,0 +1,214 @@
package influxunifi
import (
"time"
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// UAPPoints generates Wireless-Access-Point datapoints for InfluxDB.
// These points can be passed directly to influx.
func UAPPoints(u *unifi.UAP, now time.Time) ([]*influx.Point, error) {
if u.Stat.Ap == nil {
u.Stat.Ap = &unifi.Ap{}
}
tags := map[string]string{
"id": u.ID,
"ip": u.IP,
"mac": u.Mac,
"device_type": u.Stat.Ap.O,
"device_oid": u.Stat.Ap.Oid,
"device_ap": u.Stat.Ap.Ap,
"site_id": u.SiteID,
"site_name": u.SiteName,
"name": u.Name,
"adopted": u.Adopted.Txt,
"cfgversion": u.Cfgversion,
"config_network_ip": u.ConfigNetwork.IP,
"config_network_type": u.ConfigNetwork.Type,
"connect_request_ip": u.ConnectRequestIP,
"device_id": u.DeviceID,
"has_eth1": u.HasEth1.Txt,
"inform_ip": u.InformIP,
"known_cfgversion": u.KnownCfgversion,
"model": u.Model,
"serial": u.Serial,
"type": u.Type,
}
fields := map[string]interface{}{
"ip": u.IP,
"bytes": u.Bytes.Val,
"last_seen": u.LastSeen.Val,
"rx_bytes": u.RxBytes.Val,
"tx_bytes": u.TxBytes.Val,
"uptime": u.Uptime.Val,
"state": u.State,
"user-num_sta": int(u.UserNumSta.Val),
"guest-num_sta": int(u.GuestNumSta.Val),
"num_sta": u.NumSta.Val,
"version": u.Version,
"loadavg_1": u.SysStats.Loadavg1.Val,
"loadavg_5": u.SysStats.Loadavg5.Val,
"loadavg_15": u.SysStats.Loadavg15.Val,
"mem_buffer": u.SysStats.MemBuffer.Val,
"mem_total": u.SysStats.MemTotal.Val,
"mem_used": u.SysStats.MemUsed.Val,
"cpu": u.SystemStats.CPU.Val,
"mem": u.SystemStats.Mem.Val,
"system_uptime": u.SystemStats.Uptime.Val,
// Accumulative Statistics.
"stat_user-rx_packets": u.Stat.Ap.UserRxPackets.Val,
"stat_guest-rx_packets": u.Stat.Ap.GuestRxPackets.Val,
"stat_rx_packets": u.Stat.Ap.RxPackets.Val,
"stat_user-rx_bytes": u.Stat.Ap.UserRxBytes.Val,
"stat_guest-rx_bytes": u.Stat.Ap.GuestRxBytes.Val,
"stat_rx_bytes": u.Stat.Ap.RxBytes.Val,
"stat_user-rx_errors": u.Stat.Ap.UserRxErrors.Val,
"stat_guest-rx_errors": u.Stat.Ap.GuestRxErrors.Val,
"stat_rx_errors": u.Stat.Ap.RxErrors.Val,
"stat_user-rx_dropped": u.Stat.Ap.UserRxDropped.Val,
"stat_guest-rx_dropped": u.Stat.Ap.GuestRxDropped.Val,
"stat_rx_dropped": u.Stat.Ap.RxDropped.Val,
"stat_user-rx_crypts": u.Stat.Ap.UserRxCrypts.Val,
"stat_guest-rx_crypts": u.Stat.Ap.GuestRxCrypts.Val,
"stat_rx_crypts": u.Stat.Ap.RxCrypts.Val,
"stat_user-rx_frags": u.Stat.Ap.UserRxFrags.Val,
"stat_guest-rx_frags": u.Stat.Ap.GuestRxFrags.Val,
"stat_rx_frags": u.Stat.Ap.RxFrags.Val,
"stat_user-tx_packets": u.Stat.Ap.UserTxPackets.Val,
"stat_guest-tx_packets": u.Stat.Ap.GuestTxPackets.Val,
"stat_tx_packets": u.Stat.Ap.TxPackets.Val,
"stat_user-tx_bytes": u.Stat.Ap.UserTxBytes.Val,
"stat_guest-tx_bytes": u.Stat.Ap.GuestTxBytes.Val,
"stat_tx_bytes": u.Stat.Ap.TxBytes.Val,
"stat_user-tx_errors": u.Stat.Ap.UserTxErrors.Val,
"stat_guest-tx_errors": u.Stat.Ap.GuestTxErrors.Val,
"stat_tx_errors": u.Stat.Ap.TxErrors.Val,
"stat_user-tx_dropped": u.Stat.Ap.UserTxDropped.Val,
"stat_guest-tx_dropped": u.Stat.Ap.GuestTxDropped.Val,
"stat_tx_dropped": u.Stat.Ap.TxDropped.Val,
"stat_user-tx_retries": u.Stat.Ap.UserTxRetries.Val,
"stat_guest-tx_retries": u.Stat.Ap.GuestTxRetries.Val,
}
pt, err := influx.NewPoint("uap", tags, fields, now)
if err != nil {
return nil, err
}
morePoints, err := processVAPs(u.VapTable, u.RadioTable, u.RadioTableStats, u.Name, u.ID, u.Mac, u.SiteName, now)
if err != nil {
return nil, err
}
return append(morePoints, pt), nil
}
// processVAPs creates points for Wifi Radios. This works with several types of UAP-capable devices.
func processVAPs(vt unifi.VapTable, rt unifi.RadioTable, rts unifi.RadioTableStats, name, id, mac, sitename string, ts time.Time) ([]*influx.Point, error) {
tags := make(map[string]string)
fields := make(map[string]interface{})
points := []*influx.Point{}
// Loop each virtual AP (ESSID) and extract data for it
// from radio_tables and radio_table_stats.
for _, s := range vt {
tags["device_name"] = name
tags["device_id"] = id
tags["device_mac"] = mac
tags["site_name"] = sitename
tags["ap_mac"] = s.ApMac
tags["bssid"] = s.Bssid
tags["id"] = s.ID
tags["name"] = s.Name
tags["radio_name"] = s.RadioName
tags["wlanconf_id"] = s.WlanconfID
tags["essid"] = s.Essid
tags["site_id"] = s.SiteID
tags["usage"] = s.Usage
tags["state"] = s.State
tags["is_guest"] = s.IsGuest.Txt
tags["is_wep"] = s.IsWep.Txt
fields["ccq"] = s.Ccq
fields["extchannel"] = s.Extchannel
fields["mac_filter_rejections"] = s.MacFilterRejections
fields["num_satisfaction_sta"] = s.NumSatisfactionSta.Val
fields["avg_client_signal"] = s.AvgClientSignal.Val
fields["satisfaction"] = s.Satisfaction.Val
fields["satisfaction_now"] = s.SatisfactionNow.Val
fields["rx_bytes"] = s.RxBytes.Val
fields["rx_crypts"] = s.RxCrypts.Val
fields["rx_dropped"] = s.RxDropped.Val
fields["rx_errors"] = s.RxErrors.Val
fields["rx_frags"] = s.RxFrags.Val
fields["rx_nwids"] = s.RxNwids.Val
fields["rx_packets"] = s.RxPackets.Val
fields["tx_bytes"] = s.TxBytes.Val
fields["tx_dropped"] = s.TxDropped.Val
fields["tx_errors"] = s.TxErrors.Val
fields["tx_packets"] = s.TxPackets.Val
fields["tx_power"] = s.TxPower.Val
fields["tx_retries"] = s.TxRetries.Val
fields["tx_combined_retries"] = s.TxCombinedRetries.Val
fields["tx_data_mpdu_bytes"] = s.TxDataMpduBytes.Val
fields["tx_rts_retries"] = s.TxRtsRetries.Val
fields["tx_success"] = s.TxSuccess.Val
fields["tx_total"] = s.TxTotal.Val
fields["tx_tcp_goodbytes"] = s.TxTCPStats.Goodbytes.Val
fields["tx_tcp_lat_avg"] = s.TxTCPStats.LatAvg.Val
fields["tx_tcp_lat_max"] = s.TxTCPStats.LatMax.Val
fields["tx_tcp_lat_min"] = s.TxTCPStats.LatMin.Val
fields["rx_tcp_goodbytes"] = s.RxTCPStats.Goodbytes.Val
fields["rx_tcp_lat_avg"] = s.RxTCPStats.LatAvg.Val
fields["rx_tcp_lat_max"] = s.RxTCPStats.LatMax.Val
fields["rx_tcp_lat_min"] = s.RxTCPStats.LatMin.Val
fields["wifi_tx_latency_mov_avg"] = s.WifiTxLatencyMov.Avg.Val
fields["wifi_tx_latency_mov_max"] = s.WifiTxLatencyMov.Max.Val
fields["wifi_tx_latency_mov_min"] = s.WifiTxLatencyMov.Min.Val
fields["wifi_tx_latency_mov_total"] = s.WifiTxLatencyMov.Total.Val
fields["wifi_tx_latency_mov_cuont"] = s.WifiTxLatencyMov.TotalCount.Val
for _, p := range rt {
if p.Name != s.RadioName {
continue
}
tags["wlangroup_id"] = p.WlangroupID
tags["channel"] = p.Channel.Txt
tags["radio"] = p.Radio
fields["current_antenna_gain"] = p.CurrentAntennaGain.Val
fields["ht"] = p.Ht.Txt
fields["max_txpower"] = p.MaxTxpower.Val
fields["min_rssi_enabled"] = p.MinRssiEnabled.Val
fields["min_txpower"] = p.MinTxpower.Val
fields["nss"] = p.Nss.Val
fields["radio_caps"] = p.RadioCaps.Val
fields["tx_power"] = p.TxPower.Val
}
for _, p := range rts {
if p.Name != s.RadioName {
continue
}
fields["ast_be_xmit"] = p.AstBeXmit.Val
fields["channel"] = p.Channel.Val
fields["cu_self_rx"] = p.CuSelfRx.Val
fields["cu_self_tx"] = p.CuSelfTx.Val
fields["cu_total"] = p.CuTotal.Val
fields["extchannel"] = p.Extchannel.Val
fields["gain"] = p.Gain.Val
fields["guest-num_sta"] = p.GuestNumSta.Val
fields["num_sta"] = p.NumSta.Val
fields["radio"] = p.Radio
fields["tx_packets"] = p.TxPackets.Val
fields["tx_power"] = p.TxPower.Val
fields["tx_retries"] = p.TxRetries.Val
fields["user-num_sta"] = p.UserNumSta.Val
}
pt, err := influx.NewPoint("uap_vaps", tags, fields, ts)
if err != nil {
return points, err
}
points = append(points, pt)
}
return points, nil
}
+416
View File
@@ -0,0 +1,416 @@
package influxunifi
import (
"time"
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// UDMPoints generates Unifi Gateway datapoints for InfluxDB.
// These points can be passed directly to influx.
func UDMPoints(u *unifi.UDM, now time.Time) ([]*influx.Point, error) {
if u.Stat.Sw == nil {
u.Stat.Sw = &unifi.Sw{}
}
if u.Stat.Gw == nil {
u.Stat.Gw = &unifi.Gw{}
}
tags := map[string]string{
"id": u.ID,
"mac": u.Mac,
"device_oid": u.Stat.Gw.Oid,
"site_id": u.SiteID,
"site_name": u.SiteName,
"adopted": u.Adopted.Txt,
"name": u.Name,
"cfgversion": u.Cfgversion,
"config_network_ip": u.ConfigNetwork.IP,
"config_network_type": u.ConfigNetwork.Type,
"connect_request_ip": u.ConnectRequestIP,
"connect_request_port": u.ConnectRequestPort,
"device_id": u.DeviceID,
"guest_token": u.GuestToken,
"inform_ip": u.InformIP,
"known_cfgversion": u.KnownCfgversion,
"model": u.Model,
"serial": u.Serial,
"type": u.Type,
"usg_caps": u.UsgCaps.Txt,
"speedtest-status-saved": u.SpeedtestStatusSaved.Txt,
"wan1_up": u.Wan1.Up.Txt,
"wan2_up": u.Wan2.Up.Txt,
}
fields := map[string]interface{}{
"ip": u.IP,
"bytes": u.Bytes.Val,
"last_seen": u.LastSeen.Val,
"license_state": u.LicenseState,
"fw_caps": u.FwCaps.Val,
"guest-num_sta": u.GuestNumSta.Val,
"rx_bytes": u.RxBytes.Val,
"tx_bytes": u.TxBytes.Val,
"uptime": u.Uptime.Val,
"state": u.State.Val,
"user-num_sta": u.UserNumSta.Val,
"num_sta": u.NumSta.Val,
"version": u.Version,
"num_desktop": u.NumDesktop.Val,
"num_handheld": u.NumHandheld.Val,
"num_mobile": u.NumMobile.Val,
"speedtest-status_latency": u.SpeedtestStatus.Latency.Val,
"speedtest-status_rundate": u.SpeedtestStatus.Rundate.Val,
"speedtest-status_runtime": u.SpeedtestStatus.Runtime.Val,
"speedtest-status_download": u.SpeedtestStatus.StatusDownload.Val,
"speedtest-status_ping": u.SpeedtestStatus.StatusPing.Val,
"speedtest-status_summary": u.SpeedtestStatus.StatusSummary.Val,
"speedtest-status_upload": u.SpeedtestStatus.StatusUpload.Val,
"speedtest-status_xput_download": u.SpeedtestStatus.XputDownload.Val,
"speedtest-status_xput_upload": u.SpeedtestStatus.XputUpload.Val,
"config_network_wan_type": u.ConfigNetwork.Type,
"wan1_bytes-r": u.Wan1.BytesR.Val,
"wan1_enable": u.Wan1.Enable.Val,
"wan1_full_duplex": u.Wan1.FullDuplex.Val,
"wan1_gateway": u.Wan1.Gateway,
"wan1_ifname": u.Wan1.Ifname,
"wan1_ip": u.Wan1.IP,
"wan1_mac": u.Wan1.Mac,
"wan1_max_speed": u.Wan1.MaxSpeed.Val,
"wan1_name": u.Wan1.Name,
"wan1_netmask": u.Wan1.Netmask,
"wan1_rx_bytes": u.Wan1.RxBytes.Val,
"wan1_rx_bytes-r": u.Wan1.RxBytesR.Val,
"wan1_rx_dropped": u.Wan1.RxDropped.Val,
"wan1_rx_errors": u.Wan1.RxErrors.Val,
"wan1_rx_multicast": u.Wan1.RxMulticast.Val,
"wan1_rx_packets": u.Wan1.RxPackets.Val,
"wan1_type": u.Wan1.Type,
"wan1_speed": u.Wan1.Speed.Val,
"wan1_up": u.Wan1.Up.Val,
"wan1_tx_bytes": u.Wan1.TxBytes.Val,
"wan1_tx_bytes-r": u.Wan1.TxBytesR.Val,
"wan1_tx_dropped": u.Wan1.TxDropped.Val,
"wan1_tx_errors": u.Wan1.TxErrors.Val,
"wan1_tx_packets": u.Wan1.TxPackets.Val,
"wan2_bytes-r": u.Wan2.BytesR.Val,
"wan2_enable": u.Wan2.Enable.Val,
"wan2_full_duplex": u.Wan2.FullDuplex.Val,
"wan2_gateway": u.Wan2.Gateway,
"wan2_ifname": u.Wan2.Ifname,
"wan2_ip": u.Wan2.IP,
"wan2_mac": u.Wan2.Mac,
"wan2_max_speed": u.Wan2.MaxSpeed.Val,
"wan2_name": u.Wan2.Name,
"wan2_netmask": u.Wan2.Netmask,
"wan2_rx_bytes": u.Wan2.RxBytes.Val,
"wan2_rx_bytes-r": u.Wan2.RxBytesR.Val,
"wan2_rx_dropped": u.Wan2.RxDropped.Val,
"wan2_rx_errors": u.Wan2.RxErrors.Val,
"wan2_rx_multicast": u.Wan2.RxMulticast.Val,
"wan2_rx_packets": u.Wan2.RxPackets.Val,
"wan2_type": u.Wan2.Type,
"wan2_speed": u.Wan2.Speed.Val,
"wan2_up": u.Wan2.Up.Val,
"wan2_tx_bytes": u.Wan2.TxBytes.Val,
"wan2_tx_bytes-r": u.Wan2.TxBytesR.Val,
"wan2_tx_dropped": u.Wan2.TxDropped.Val,
"wan2_tx_errors": u.Wan2.TxErrors.Val,
"wan2_tx_packets": u.Wan2.TxPackets.Val,
"loadavg_1": u.SysStats.Loadavg1.Val,
"loadavg_5": u.SysStats.Loadavg5.Val,
"loadavg_15": u.SysStats.Loadavg15.Val,
"mem_used": u.SysStats.MemUsed.Val,
"mem_buffer": u.SysStats.MemBuffer.Val,
"mem_total": u.SysStats.MemTotal.Val,
"cpu": u.SystemStats.CPU.Val,
"mem": u.SystemStats.Mem.Val,
"system_uptime": u.SystemStats.Uptime.Val,
"gw": u.Stat.Gw,
"lan-rx_bytes": u.Stat.LanRxBytes.Val,
"lan-rx_packets": u.Stat.LanRxPackets.Val,
"lan-tx_bytes": u.Stat.LanTxBytes.Val,
"lan-tx_packets": u.Stat.LanTxPackets.Val,
"wan-rx_bytes": u.Stat.WanRxBytes.Val,
"wan-rx_dropped": u.Stat.WanRxDropped.Val,
"wan-rx_packets": u.Stat.WanRxPackets.Val,
"wan-tx_bytes": u.Stat.WanTxBytes.Val,
"wan-tx_packets": u.Stat.WanTxPackets.Val,
"uplink_name": u.Uplink.Name,
"uplink_latency": u.Uplink.Latency.Val,
"uplink_speed": u.Uplink.Speed.Val,
"uplink_num_ports": u.Uplink.NumPort.Val,
"uplink_max_speed": u.Uplink.MaxSpeed.Val,
}
pt, err := influx.NewPoint("usg", tags, fields, now)
if err != nil {
return nil, err
}
points := []*influx.Point{pt}
tags = map[string]string{
"id": u.ID,
"mac": u.Mac,
"device_oid": u.Stat.Sw.Oid,
"site_id": u.SiteID,
"site_name": u.SiteName,
"name": u.Name,
"adopted": u.Adopted.Txt,
"cfgversion": u.Cfgversion,
"config_network_ip": u.ConfigNetwork.IP,
"config_network_type": u.ConfigNetwork.Type,
"device_id": u.DeviceID,
"inform_ip": u.InformIP,
"known_cfgversion": u.KnownCfgversion,
"locating": u.Locating.Txt,
"model": u.Model,
"serial": u.Serial,
"type": u.Type,
"dot1x_portctrl_enabled": u.Dot1XPortctrlEnabled.Txt,
"flowctrl_enabled": u.FlowctrlEnabled.Txt,
"has_fan": u.HasFan.Txt,
"has_temperature": u.HasTemperature.Txt,
"jumboframe_enabled": u.JumboframeEnabled.Txt,
"stp_version": u.StpVersion,
}
fields = map[string]interface{}{
"fw_caps": u.FwCaps.Val,
"guest-num_sta": u.GuestLanNumSta.Val,
"ip": u.IP,
"bytes": u.Bytes.Val,
"fan_level": float64(0),
"general_temperature": float64(0),
"last_seen": u.LastSeen.Val,
"license_state": u.LicenseState,
"overheating": u.Overheating.Val,
"rx_bytes": u.RxBytes.Val,
"tx_bytes": u.TxBytes.Val,
"uptime": u.Uptime.Val,
"state": u.State.Val,
"user-num_sta": u.UserLanNumSta.Val,
"num_sta": u.LanNumSta.Val,
"version": u.Version,
"loadavg_1": u.SysStats.Loadavg1.Val,
"loadavg_5": u.SysStats.Loadavg5.Val,
"loadavg_15": u.SysStats.Loadavg15.Val,
"mem_buffer": u.SysStats.MemBuffer.Val,
"mem_used": u.SysStats.MemUsed.Val,
"mem_total": u.SysStats.MemTotal.Val,
"cpu": u.SystemStats.CPU.Val,
"mem": u.SystemStats.Mem.Val,
"system_uptime": u.SystemStats.Uptime.Val,
"stp_priority": u.StpPriority.Val,
"stat_bytes": u.Stat.Sw.Bytes.Val,
"stat_rx_bytes": u.Stat.Sw.RxBytes.Val,
"stat_rx_crypts": u.Stat.Sw.RxCrypts.Val,
"stat_rx_dropped": u.Stat.Sw.RxDropped.Val,
"stat_rx_errors": u.Stat.Sw.RxErrors.Val,
"stat_rx_frags": u.Stat.Sw.RxFrags.Val,
"stat_rx_packets": u.Stat.Sw.TxPackets.Val,
"stat_tx_bytes": u.Stat.Sw.TxBytes.Val,
"stat_tx_dropped": u.Stat.Sw.TxDropped.Val,
"stat_tx_errors": u.Stat.Sw.TxErrors.Val,
"stat_tx_packets": u.Stat.Sw.TxPackets.Val,
"stat_tx_retries": u.Stat.Sw.TxRetries.Val,
}
pt, err = influx.NewPoint("usw", tags, fields, now)
if err != nil {
return nil, err
}
points = append(points, pt)
for _, p := range u.NetworkTable {
tags := map[string]string{
"device_name": u.Name,
"device_id": u.ID,
"device_mac": u.Mac,
"site_name": u.SiteName,
"up": p.Up.Txt,
"dhcpd_dns_enabled": p.DhcpdDNSEnabled.Txt,
"dhcpd_enabled": p.DhcpdEnabled.Txt,
"dhcpd_time_offset_enabled": p.DhcpdTimeOffsetEnabled.Txt,
"dhcp_relay_enabledy": p.DhcpRelayEnabled.Txt,
"dhcpd_gateway_enabled": p.DhcpdGatewayEnabled.Txt,
"enabled": p.Enabled.Txt,
"vlan_enabled": p.VlanEnabled.Txt,
"attr_no_delete": p.AttrNoDelete.Txt,
"is_guest": p.IsGuest.Txt,
"is_nat": p.IsNat.Txt,
"networkgroup": p.Networkgroup,
"site_id": p.SiteID,
}
fields := map[string]interface{}{
"domain_name": p.DomainName,
"dhcpd_start": p.DhcpdStart,
"dhcpd_stop": p.DhcpdStop,
"ip": p.IP,
"ip_subnet": p.IPSubnet,
"mac": p.Mac,
"name": p.Name,
"num_sta": p.NumSta.Val,
"purpose": p.Purpose,
"rx_bytes": p.RxBytes.Val,
"rx_packets": p.RxPackets.Val,
"tx_bytes": p.TxBytes.Val,
"tx_packets": p.TxPackets.Val,
"ipv6_interface_type": p.Ipv6InterfaceType,
"attr_hidden_id": p.AttrHiddenID,
}
pt, err = influx.NewPoint("usg_networks", tags, fields, now)
if err != nil {
return points, err
}
points = append(points, pt)
}
for _, p := range u.PortTable {
tags := map[string]string{
"site_id": u.SiteID,
"site_name": u.SiteName,
"device_name": u.Name,
"name": p.Name,
"enable": p.Enable.Txt,
"is_uplink": p.IsUplink.Txt,
"up": p.Up.Txt,
"portconf_id": p.PortconfID,
"dot1x_mode": p.Dot1XMode,
"dot1x_status": p.Dot1XStatus,
"stp_state": p.StpState,
"sfp_found": p.SfpFound.Txt,
"op_mode": p.OpMode,
"poe_mode": p.PoeMode,
"port_poe": p.PortPoe.Txt,
"port_idx": p.PortIdx.Txt,
"port_id": u.Name + " Port " + p.PortIdx.Txt,
"poe_enable": p.PoeEnable.Txt,
"flowctrl_rx": p.FlowctrlRx.Txt,
"flowctrl_tx": p.FlowctrlTx.Txt,
"autoneg": p.Autoneg.Txt,
"full_duplex": p.FullDuplex.Txt,
"jumbo": p.Jumbo.Txt,
"masked": p.Masked.Txt,
"poe_good": p.PoeGood.Txt,
"media": p.Media,
"poe_class": p.PoeClass,
"poe_caps": p.PoeCaps.Txt,
"aggregated_by": p.AggregatedBy.Txt,
}
fields := map[string]interface{}{
"dbytes_r": p.BytesR.Val,
"rx_broadcast": p.RxBroadcast.Val,
"rx_bytes": p.RxBytes.Val,
"rx_bytes-r": p.RxBytesR.Val,
"rx_dropped": p.RxDropped.Val,
"rx_errors": p.RxErrors.Val,
"rx_multicast": p.RxMulticast.Val,
"rx_packets": p.RxPackets.Val,
"speed": p.Speed.Val,
"stp_pathcost": p.StpPathcost.Val,
"tx_broadcast": p.TxBroadcast.Val,
"tx_bytes": p.TxBytes.Val,
"tx_bytes-r": p.TxBytesR.Val,
"tx_dropped": p.TxDropped.Val,
"tx_errors": p.TxErrors.Val,
"tx_multicast": p.TxMulticast.Val,
"tx_packets": p.TxPackets.Val,
"poe_current": p.PoeCurrent.Val,
"poe_power": p.PoePower.Val,
"poe_voltage": p.PoeVoltage.Val,
"full_duplex": p.FullDuplex.Val,
}
pt, err = influx.NewPoint("usw_ports", tags, fields, now)
if err != nil {
return points, err
}
points = append(points, pt)
}
if u.Stat.Ap == nil {
return points, nil
// we're done now. the following code process UDM (non-pro) UAP data.
}
tags = map[string]string{
"id": u.ID,
"ip": u.IP,
"mac": u.Mac,
"device_type": u.Stat.Ap.O,
"device_oid": u.Stat.Ap.Oid,
"device_ap": u.Stat.Ap.Ap,
"site_id": u.SiteID,
"site_name": u.SiteName,
"name": u.Name,
"adopted": u.Adopted.Txt,
"cfgversion": u.Cfgversion,
"config_network_ip": u.ConfigNetwork.IP,
"config_network_type": u.ConfigNetwork.Type,
"connect_request_ip": u.ConnectRequestIP,
"device_id": u.DeviceID,
"has_eth1": u.HasEth1.Txt,
"inform_ip": u.InformIP,
"known_cfgversion": u.KnownCfgversion,
"model": u.Model,
"serial": u.Serial,
"type": u.Type,
}
fields = map[string]interface{}{
"ip": u.IP,
"bytes": u.Bytes.Val,
"last_seen": u.LastSeen.Val,
"rx_bytes": u.RxBytes.Val,
"tx_bytes": u.TxBytes.Val,
"uptime": u.Uptime.Val,
"state": int(u.State.Val),
"user-num_sta": int(u.UserWlanNumSta.Val),
"guest-num_sta": int(u.GuestWlanNumSta.Val),
"num_sta": u.WlanNumSta.Val,
"version": u.Version,
"loadavg_1": u.SysStats.Loadavg1.Val,
"loadavg_5": u.SysStats.Loadavg5.Val,
"loadavg_15": u.SysStats.Loadavg15.Val,
"mem_buffer": u.SysStats.MemBuffer.Val,
"mem_total": u.SysStats.MemTotal.Val,
"mem_used": u.SysStats.MemUsed.Val,
"cpu": u.SystemStats.CPU.Val,
"mem": u.SystemStats.Mem.Val,
"system_uptime": u.SystemStats.Uptime.Val,
// Accumulative Statistics.
"stat_user-rx_packets": u.Stat.Ap.UserRxPackets.Val,
"stat_guest-rx_packets": u.Stat.Ap.GuestRxPackets.Val,
"stat_rx_packets": u.Stat.Ap.RxPackets.Val,
"stat_user-rx_bytes": u.Stat.Ap.UserRxBytes.Val,
"stat_guest-rx_bytes": u.Stat.Ap.GuestRxBytes.Val,
"stat_rx_bytes": u.Stat.Ap.RxBytes.Val,
"stat_user-rx_errors": u.Stat.Ap.UserRxErrors.Val,
"stat_guest-rx_errors": u.Stat.Ap.GuestRxErrors.Val,
"stat_rx_errors": u.Stat.Ap.RxErrors.Val,
"stat_user-rx_dropped": u.Stat.Ap.UserRxDropped.Val,
"stat_guest-rx_dropped": u.Stat.Ap.GuestRxDropped.Val,
"stat_rx_dropped": u.Stat.Ap.RxDropped.Val,
"stat_user-rx_crypts": u.Stat.Ap.UserRxCrypts.Val,
"stat_guest-rx_crypts": u.Stat.Ap.GuestRxCrypts.Val,
"stat_rx_crypts": u.Stat.Ap.RxCrypts.Val,
"stat_user-rx_frags": u.Stat.Ap.UserRxFrags.Val,
"stat_guest-rx_frags": u.Stat.Ap.GuestRxFrags.Val,
"stat_rx_frags": u.Stat.Ap.RxFrags.Val,
"stat_user-tx_packets": u.Stat.Ap.UserTxPackets.Val,
"stat_guest-tx_packets": u.Stat.Ap.GuestTxPackets.Val,
"stat_tx_packets": u.Stat.Ap.TxPackets.Val,
"stat_user-tx_bytes": u.Stat.Ap.UserTxBytes.Val,
"stat_guest-tx_bytes": u.Stat.Ap.GuestTxBytes.Val,
"stat_tx_bytes": u.Stat.Ap.TxBytes.Val,
"stat_user-tx_errors": u.Stat.Ap.UserTxErrors.Val,
"stat_guest-tx_errors": u.Stat.Ap.GuestTxErrors.Val,
"stat_tx_errors": u.Stat.Ap.TxErrors.Val,
"stat_user-tx_dropped": u.Stat.Ap.UserTxDropped.Val,
"stat_guest-tx_dropped": u.Stat.Ap.GuestTxDropped.Val,
"stat_tx_dropped": u.Stat.Ap.TxDropped.Val,
"stat_user-tx_retries": u.Stat.Ap.UserTxRetries.Val,
"stat_guest-tx_retries": u.Stat.Ap.GuestTxRetries.Val,
}
pt, err = influx.NewPoint("uap", tags, fields, now)
if err != nil {
return nil, err
}
uapPoints, err := processVAPs(*u.VapTable, *u.RadioTable, *u.RadioTableStats, u.Name, u.ID, u.Mac, u.SiteName, now)
if err != nil {
return nil, err
}
return append(append(points, pt), uapPoints...), nil
}
+231
View File
@@ -0,0 +1,231 @@
package influxunifi
import (
"strings"
"time"
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// USGPoints generates Unifi Gateway datapoints for InfluxDB.
// These points can be passed directly to influx.
func USGPoints(u *unifi.USG, now time.Time) ([]*influx.Point, error) {
if u.Stat.Gw == nil {
u.Stat.Gw = &unifi.Gw{}
}
tags := map[string]string{
"id": u.ID,
"mac": u.Mac,
"device_type": u.Stat.O,
"device_oid": u.Stat.Oid,
"site_id": u.SiteID,
"site_name": u.SiteName,
"adopted": u.Adopted.Txt,
"name": u.Name,
"cfgversion": u.Cfgversion,
"config_network_ip": u.ConfigNetwork.IP,
"config_network_type": u.ConfigNetwork.Type,
"connect_request_ip": u.ConnectRequestIP,
"connect_request_port": u.ConnectRequestPort,
"device_id": u.DeviceID,
"guest_token": u.GuestToken,
"inform_ip": u.InformIP,
"known_cfgversion": u.KnownCfgversion,
"led_override": u.LedOverride,
"locating": u.Locating.Txt,
"model": u.Model,
"outdoor_mode_override": u.OutdoorModeOverride,
"serial": u.Serial,
"type": u.Type,
"usg_caps": u.UsgCaps.Txt,
"speedtest-status-saved": u.SpeedtestStatusSaved.Txt,
"wan1_up": u.Wan1.Up.Txt,
"wan2_up": u.Wan2.Up.Txt,
}
fields := map[string]interface{}{
"ip": u.IP,
"bytes": u.Bytes.Val,
"last_seen": u.LastSeen.Val,
"license_state": u.LicenseState,
"fw_caps": u.FwCaps.Val,
"guest-num_sta": u.GuestNumSta.Val,
"rx_bytes": u.RxBytes.Val,
"tx_bytes": u.TxBytes.Val,
"uptime": u.Uptime.Val,
"roll_upgrade": u.Rollupgrade.Val,
"state": u.State.Val,
"upgradable": u.Upgradable.Val,
"user-num_sta": u.UserNumSta.Val,
"version": u.Version,
"num_desktop": u.NumDesktop.Val,
"num_handheld": u.NumHandheld.Val,
"num_mobile": u.NumMobile.Val,
"speedtest-status_latency": u.SpeedtestStatus.Latency.Val,
"speedtest-status_rundate": u.SpeedtestStatus.Rundate.Val,
"speedtest-status_runtime": u.SpeedtestStatus.Runtime.Val,
"speedtest-status_download": u.SpeedtestStatus.StatusDownload.Val,
"speedtest-status_ping": u.SpeedtestStatus.StatusPing.Val,
"speedtest-status_summary": u.SpeedtestStatus.StatusSummary.Val,
"speedtest-status_upload": u.SpeedtestStatus.StatusUpload.Val,
"speedtest-status_xput_download": u.SpeedtestStatus.XputDownload.Val,
"speedtest-status_xput_upload": u.SpeedtestStatus.XputUpload.Val,
"config_network_wan_type": u.ConfigNetwork.Type,
"wan1_bytes-r": u.Wan1.BytesR.Val,
"wan1_enable": u.Wan1.Enable.Val,
"wan1_full_duplex": u.Wan1.FullDuplex.Val,
"wan1_gateway": u.Wan1.Gateway,
"wan1_ifname": u.Wan1.Ifname,
"wan1_ip": u.Wan1.IP,
"wan1_mac": u.Wan1.Mac,
"wan1_max_speed": u.Wan1.MaxSpeed.Val,
"wan1_name": u.Wan1.Name,
"wan1_netmask": u.Wan1.Netmask,
"wan1_rx_bytes": u.Wan1.RxBytes.Val,
"wan1_rx_bytes-r": u.Wan1.RxBytesR.Val,
"wan1_rx_dropped": u.Wan1.RxDropped.Val,
"wan1_rx_errors": u.Wan1.RxErrors.Val,
"wan1_rx_multicast": u.Wan1.RxMulticast.Val,
"wan1_rx_packets": u.Wan1.RxPackets.Val,
"wan1_type": u.Wan1.Type,
"wan1_speed": u.Wan1.Speed.Val,
"wan1_up": u.Wan1.Up.Val,
"wan1_tx_bytes": u.Wan1.TxBytes.Val,
"wan1_tx_bytes-r": u.Wan1.TxBytesR.Val,
"wan1_tx_dropped": u.Wan1.TxDropped.Val,
"wan1_tx_errors": u.Wan1.TxErrors.Val,
"wan1_tx_packets": u.Wan1.TxPackets.Val,
"wan2_bytes-r": u.Wan2.BytesR.Val,
"wan2_enable": u.Wan2.Enable.Val,
"wan2_full_duplex": u.Wan2.FullDuplex.Val,
"wan2_gateway": u.Wan2.Gateway,
"wan2_ifname": u.Wan2.Ifname,
"wan2_ip": u.Wan2.IP,
"wan2_mac": u.Wan2.Mac,
"wan2_max_speed": u.Wan2.MaxSpeed.Val,
"wan2_name": u.Wan2.Name,
"wan2_netmask": u.Wan2.Netmask,
"wan2_rx_bytes": u.Wan2.RxBytes.Val,
"wan2_rx_bytes-r": u.Wan2.RxBytesR.Val,
"wan2_rx_dropped": u.Wan2.RxDropped.Val,
"wan2_rx_errors": u.Wan2.RxErrors.Val,
"wan2_rx_multicast": u.Wan2.RxMulticast.Val,
"wan2_rx_packets": u.Wan2.RxPackets.Val,
"wan2_type": u.Wan2.Type,
"wan2_speed": u.Wan2.Speed.Val,
"wan2_up": u.Wan2.Up.Val,
"wan2_tx_bytes": u.Wan2.TxBytes.Val,
"wan2_tx_bytes-r": u.Wan2.TxBytesR.Val,
"wan2_tx_dropped": u.Wan2.TxDropped.Val,
"wan2_tx_errors": u.Wan2.TxErrors.Val,
"wan2_tx_packets": u.Wan2.TxPackets.Val,
"loadavg_1": u.SysStats.Loadavg1.Val,
"loadavg_5": u.SysStats.Loadavg5.Val,
"loadavg_15": u.SysStats.Loadavg15.Val,
"mem_used": u.SysStats.MemUsed.Val,
"mem_buffer": u.SysStats.MemBuffer.Val,
"mem_total": u.SysStats.MemTotal.Val,
"cpu": u.SystemStats.CPU.Val,
"mem": u.SystemStats.Mem.Val,
"system_uptime": u.SystemStats.Uptime.Val,
"stat_duration": u.Stat.Duration.Val,
"stat_datetime": u.Stat.Datetime,
"gw": u.Stat.Gw,
"lan-rx_bytes": u.Stat.LanRxBytes.Val,
"lan-rx_packets": u.Stat.LanRxPackets.Val,
"lan-tx_bytes": u.Stat.LanTxBytes.Val,
"lan-tx_packets": u.Stat.LanTxPackets.Val,
"wan-rx_bytes": u.Stat.WanRxBytes.Val,
"wan-rx_dropped": u.Stat.WanRxDropped.Val,
"wan-rx_packets": u.Stat.WanRxPackets.Val,
"wan-tx_bytes": u.Stat.WanTxBytes.Val,
"wan-tx_packets": u.Stat.WanTxPackets.Val,
"uplink_name": u.Uplink.Name,
"uplink_latency": u.Uplink.Latency.Val,
"uplink_speed": u.Uplink.Speed.Val,
"uplink_num_ports": u.Uplink.NumPort.Val,
"uplink_max_speed": u.Uplink.MaxSpeed.Val,
}
pt, err := influx.NewPoint("usg", tags, fields, now)
if err != nil {
return nil, err
}
points := []*influx.Point{pt}
for _, p := range u.NetworkTable {
tags := map[string]string{
"device_name": u.Name,
"device_id": u.ID,
"device_mac": u.Mac,
"site_name": u.SiteName,
"up": p.Up.Txt,
"dhcpd_dns_enabled": p.DhcpdDNSEnabled.Txt,
"dhcpd_enabled": p.DhcpdEnabled.Txt,
"dhcpd_time_offset_enabled": p.DhcpdTimeOffsetEnabled.Txt,
"dhcp_relay_enabledy": p.DhcpRelayEnabled.Txt,
"dhcpd_gateway_enabled": p.DhcpdGatewayEnabled.Txt,
"enabled": p.Enabled.Txt,
"vlan_enabled": p.VlanEnabled.Txt,
"attr_no_delete": p.AttrNoDelete.Txt,
"is_guest": p.IsGuest.Txt,
"is_nat": p.IsNat.Txt,
"networkgroup": p.Networkgroup,
"site_id": p.SiteID,
"ip": p.IP,
"ip_subnet": p.IPSubnet,
"mac": p.Mac,
"name": p.Name,
"domain_name": p.DomainName,
"dhcpd_start": p.DhcpdStart,
"dhcpd_stop": p.DhcpdStop,
"ipv6_interface_type": p.Ipv6InterfaceType,
"attr_hidden_id": p.AttrHiddenID,
"purpose": p.Purpose,
}
fields := map[string]interface{}{
"num_sta": p.NumSta.Val,
"rx_bytes": p.RxBytes.Val,
"rx_packets": p.RxPackets.Val,
"tx_bytes": p.TxBytes.Val,
"tx_packets": p.TxPackets.Val,
}
pt, err = influx.NewPoint("usg_networks", tags, fields, now)
if err != nil {
return points, err
}
points = append(points, pt)
}
for _, p := range u.PortTable {
tags := map[string]string{
"device_name": u.Name,
"device_id": u.ID,
"device_mac": u.Mac,
"site_name": u.SiteName,
"name": p.Name,
"ifname": p.Ifname,
"ip": p.IP,
"mac": p.Mac,
"up": p.Up.Txt,
"speed": p.Speed.Txt,
"full_duplex": p.FullDuplex.Txt,
"enable": p.Enable.Txt,
}
fields := map[string]interface{}{
"rx_bytes": p.RxBytes.Val,
"rx_dropped": p.RxDropped.Val,
"rx_errors": p.RxErrors.Val,
"rx_packets": p.RxBytes.Val,
"tx_bytes": p.TxBytes.Val,
"tx_dropped": p.TxDropped.Val,
"tx_errors": p.TxErrors.Val,
"tx_packets": p.TxPackets.Val,
"rx_multicast": p.RxMulticast.Val,
"dns_servers": strings.Join(p.DNS, ","),
}
pt, err = influx.NewPoint("usg_ports", tags, fields, now)
if err != nil {
return points, err
}
points = append(points, pt)
}
return points, nil
}
+148
View File
@@ -0,0 +1,148 @@
package influxunifi
import (
"time"
influx "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
// USWPoints generates Unifi Switch datapoints for InfluxDB.
// These points can be passed directly to influx.
func USWPoints(u *unifi.USW, now time.Time) ([]*influx.Point, error) {
if u.Stat.Sw == nil {
u.Stat.Sw = &unifi.Sw{}
}
tags := map[string]string{
"id": u.ID,
"mac": u.Mac,
"device_oid": u.Stat.Oid,
"site_id": u.SiteID,
"site_name": u.SiteName,
"name": u.Name,
"adopted": u.Adopted.Txt,
"cfgversion": u.Cfgversion,
"config_network_ip": u.ConfigNetwork.IP,
"config_network_type": u.ConfigNetwork.Type,
"device_id": u.DeviceID,
"inform_ip": u.InformIP,
"known_cfgversion": u.KnownCfgversion,
"locating": u.Locating.Txt,
"model": u.Model,
"serial": u.Serial,
"type": u.Type,
"dot1x_portctrl_enabled": u.Dot1XPortctrlEnabled.Txt,
"flowctrl_enabled": u.FlowctrlEnabled.Txt,
"has_fan": u.HasFan.Txt,
"has_temperature": u.HasTemperature.Txt,
"jumboframe_enabled": u.JumboframeEnabled.Txt,
"stp_version": u.StpVersion,
}
fields := map[string]interface{}{
"fw_caps": u.FwCaps.Val,
"guest-num_sta": u.GuestNumSta.Val,
"ip": u.IP,
"bytes": u.Bytes.Val,
"fan_level": u.FanLevel.Val,
"general_temperature": u.GeneralTemperature.Val,
"last_seen": u.LastSeen.Val,
"license_state": u.LicenseState,
"overheating": u.Overheating.Val,
"rx_bytes": u.RxBytes.Val,
"tx_bytes": u.TxBytes.Val,
"uptime": u.Uptime.Val,
"state": u.State.Val,
"user-num_sta": u.UserNumSta.Val,
"version": u.Version,
"loadavg_1": u.SysStats.Loadavg1.Val,
"loadavg_5": u.SysStats.Loadavg5.Val,
"loadavg_15": u.SysStats.Loadavg15.Val,
"mem_buffer": u.SysStats.MemBuffer.Val,
"mem_used": u.SysStats.MemUsed.Val,
"mem_total": u.SysStats.MemTotal.Val,
"cpu": u.SystemStats.CPU.Val,
"mem": u.SystemStats.Mem.Val,
"stp_priority": u.StpPriority.Val,
"system_uptime": u.SystemStats.Uptime.Val,
"stat_bytes": u.Stat.Bytes.Val,
"stat_rx_bytes": u.Stat.RxBytes.Val,
"stat_rx_crypts": u.Stat.RxCrypts.Val,
"stat_rx_dropped": u.Stat.RxDropped.Val,
"stat_rx_errors": u.Stat.RxErrors.Val,
"stat_rx_frags": u.Stat.RxFrags.Val,
"stat_rx_packets": u.Stat.TxPackets.Val,
"stat_tx_bytes": u.Stat.TxBytes.Val,
"stat_tx_dropped": u.Stat.TxDropped.Val,
"stat_tx_errors": u.Stat.TxErrors.Val,
"stat_tx_packets": u.Stat.TxPackets.Val,
"stat_tx_retries": u.Stat.TxRetries.Val,
"uplink_depth": u.UplinkDepth.Txt,
}
pt, err := influx.NewPoint("usw", tags, fields, now)
if err != nil {
return nil, err
}
points := []*influx.Point{pt}
for _, p := range u.PortTable {
tags := map[string]string{
"site_id": u.SiteID,
"site_name": u.SiteName,
"device_name": u.Name,
"name": p.Name,
"enable": p.Enable.Txt,
"is_uplink": p.IsUplink.Txt,
"up": p.Up.Txt,
"portconf_id": p.PortconfID,
"dot1x_mode": p.Dot1XMode,
"dot1x_status": p.Dot1XStatus,
"stp_state": p.StpState,
"sfp_found": p.SfpFound.Txt,
"op_mode": p.OpMode,
"poe_mode": p.PoeMode,
"port_poe": p.PortPoe.Txt,
"port_idx": p.PortIdx.Txt,
"port_id": u.Name + " Port " + p.PortIdx.Txt,
"poe_enable": p.PoeEnable.Txt,
"flowctrl_rx": p.FlowctrlRx.Txt,
"flowctrl_tx": p.FlowctrlTx.Txt,
"autoneg": p.Autoneg.Txt,
"full_duplex": p.FullDuplex.Txt,
"jumbo": p.Jumbo.Txt,
"masked": p.Masked.Txt,
"poe_good": p.PoeGood.Txt,
"media": p.Media,
"poe_class": p.PoeClass,
"poe_caps": p.PoeCaps.Txt,
"aggregated_by": p.AggregatedBy.Txt,
}
fields := map[string]interface{}{
"dbytes_r": p.BytesR.Val,
"rx_broadcast": p.RxBroadcast.Val,
"rx_bytes": p.RxBytes.Val,
"rx_bytes-r": p.RxBytesR.Val,
"rx_dropped": p.RxDropped.Val,
"rx_errors": p.RxErrors.Val,
"rx_multicast": p.RxMulticast.Val,
"rx_packets": p.RxPackets.Val,
"speed": p.Speed.Val,
"stp_pathcost": p.StpPathcost.Val,
"tx_broadcast": p.TxBroadcast.Val,
"tx_bytes": p.TxBytes.Val,
"tx_bytes-r": p.TxBytesR.Val,
"tx_dropped": p.TxDropped.Val,
"tx_errors": p.TxErrors.Val,
"tx_multicast": p.TxMulticast.Val,
"tx_packets": p.TxPackets.Val,
"poe_current": p.PoeCurrent.Val,
"poe_power": p.PoePower.Val,
"poe_voltage": p.PoeVoltage.Val,
"full_duplex": p.FullDuplex.Val,
}
pt, err = influx.NewPoint("usw_ports", tags, fields, now)
if err != nil {
return points, err
}
points = append(points, pt)
}
return points, nil
}
+16
View File
@@ -0,0 +1,16 @@
package metrics
import (
"time"
"golift.io/unifi"
)
// Metrics is a type shared by the exporting and reporting packages.
type Metrics struct {
TS time.Time
unifi.Sites
unifi.IDSList
unifi.Clients
*unifi.Devices
}
+6
View File
@@ -0,0 +1,6 @@
// +build darwin
package poller
// DefaultConfFile is where to find config is --config is not prvided.
const DefaultConfFile = "/usr/local/etc/unifi-poller/up.conf"
+6
View File
@@ -0,0 +1,6 @@
// +build !windows,!darwin
package poller
// DefaultConfFile is where to find config is --config is not prvided.
const DefaultConfFile = "/etc/unifi-poller/up.conf"
+6
View File
@@ -0,0 +1,6 @@
// +build windows
package poller
// DefaultConfFile is where to find config is --config is not prvided.
const DefaultConfFile = `C:\ProgramData\unifi-poller\up.conf`
+152
View File
@@ -0,0 +1,152 @@
package poller
import (
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"path"
"reflect"
"strconv"
"strings"
"time"
"github.com/BurntSushi/toml"
influx "github.com/influxdata/influxdb1-client/v2"
"github.com/spf13/pflag"
"golift.io/unifi"
yaml "gopkg.in/yaml.v2"
)
// Version is injected by the Makefile
var Version = "development"
const (
// App defaults in case they're missing from the config.
appName = "unifi-poller"
defaultInterval = 30 * time.Second
defaultInfluxDB = "unifi"
defaultInfluxUser = "unifi"
defaultInfluxPass = "unifi"
defaultInfluxURL = "http://127.0.0.1:8086"
defaultUnifiUser = "influx"
defaultUnifiURL = "https://127.0.0.1:8443"
defaultHTTPListen = "0.0.0.0:9130"
)
// ENVConfigPrefix is the prefix appended to an env variable tag
// name before retrieving the value from the OS.
const ENVConfigPrefix = "UP_"
// UnifiPoller contains the application startup data, and auth info for UniFi & Influx.
type UnifiPoller struct {
Influx influx.Client
Unifi *unifi.Unifi
Flag *Flag
Config *Config
errorCount int
LastCheck time.Time
}
// Flag represents the CLI args available and their settings.
type Flag struct {
ConfigFile string
DumpJSON string
ShowVer bool
*pflag.FlagSet
}
// Config represents the data needed to poll a controller and report to influxdb.
// This is all of the data stored in the config file.
// Any with explicit defaults have omitempty on json and toml tags.
type Config struct {
Interval Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug"`
Quiet bool `json:"quiet,omitempty" toml:"quiet,omitempty" xml:"quiet" yaml:"quiet"`
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids"`
ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate"`
InfxBadSSL bool `json:"influx_insecure_ssl" toml:"influx_insecure_ssl" xml:"influx_insecure_ssl" yaml:"influx_insecure_ssl"`
Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode"`
HTTPListen string `json:"http_listen" toml:"http_listen" xml:"http_listen" yaml:"http_listen"`
Namespace string `json:"namespace" toml:"namespace" xml:"namespace" yaml:"namespace"`
InfluxURL string `json:"influx_url,omitempty" toml:"influx_url,omitempty" xml:"influx_url" yaml:"influx_url"`
InfluxUser string `json:"influx_user,omitempty" toml:"influx_user,omitempty" xml:"influx_user" yaml:"influx_user"`
InfluxPass string `json:"influx_pass,omitempty" toml:"influx_pass,omitempty" xml:"influx_pass" yaml:"influx_pass"`
InfluxDB string `json:"influx_db,omitempty" toml:"influx_db,omitempty" xml:"influx_db" yaml:"influx_db"`
UnifiUser string `json:"unifi_user,omitempty" toml:"unifi_user,omitempty" xml:"unifi_user" yaml:"unifi_user"`
UnifiPass string `json:"unifi_pass,omitempty" toml:"unifi_pass,omitempty" xml:"unifi_pass" yaml:"unifi_pass"`
UnifiBase string `json:"unifi_url,omitempty" toml:"unifi_url,omitempty" xml:"unifi_url" yaml:"unifi_url"`
Sites []string `json:"sites,omitempty" toml:"sites,omitempty" xml:"sites" yaml:"sites"`
}
// Duration is used to UnmarshalTOML into a time.Duration value.
type Duration struct{ time.Duration }
// UnmarshalText parses a duration type from a config file.
func (d *Duration) UnmarshalText(data []byte) (err error) {
d.Duration, err = time.ParseDuration(string(data))
return
}
// ParseFile parses and returns our configuration data.
func (c *Config) ParseFile(configFile string) error {
switch buf, err := ioutil.ReadFile(configFile); {
case err != nil:
return err
case strings.Contains(configFile, ".json"):
return json.Unmarshal(buf, c)
case strings.Contains(configFile, ".xml"):
return xml.Unmarshal(buf, c)
case strings.Contains(configFile, ".yaml"):
return yaml.Unmarshal(buf, c)
default:
return toml.Unmarshal(buf, c)
}
}
// ParseENV copies environment variables into configuration values.
// This is useful for Docker users that find it easier to pass ENV variables
// than a specific configuration file. Uses reflection to find struct tags.
func (c *Config) ParseENV() error {
t := reflect.TypeOf(Config{}) // Get tag names from the Config struct.
// Loop each Config struct member; get reflect tag & env var value; update config.
for i := 0; i < t.NumField(); i++ {
tag := strings.ToUpper(t.Field(i).Tag.Get("json")) // Get the ENV variable name from capitalized "json" struct tag
env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS.
if tag == "" || env == "" {
continue // Skip if either are empty.
}
// Reflect and update the u.Config struct member at position i.
switch c := reflect.ValueOf(c).Elem().Field(i); c.Type().String() {
// Handle each member type appropriately (differently).
case "string":
// This is a reflect package method to update a struct member by index.
c.SetString(env)
case "int":
val, err := strconv.Atoi(env)
if err != nil {
return fmt.Errorf("%s: %v", tag, err)
}
c.Set(reflect.ValueOf(val))
case "[]string":
c.Set(reflect.ValueOf(strings.Split(env, ",")))
case path.Base(t.PkgPath()) + ".Duration":
val, err := time.ParseDuration(env)
if err != nil {
return fmt.Errorf("%s: %v", tag, err)
}
c.Set(reflect.ValueOf(Duration{val}))
case "bool":
val, err := strconv.ParseBool(env)
if err != nil {
return fmt.Errorf("%s: %v", tag, err)
}
c.SetBool(val)
}
}
return nil
}
+67
View File
@@ -0,0 +1,67 @@
package poller
import (
"fmt"
"os"
"strings"
"golift.io/unifi"
)
// DumpJSONPayload prints raw json from the UniFi Controller.
func (u *UnifiPoller) DumpJSONPayload() (err error) {
u.Config.Quiet = true
u.Unifi, err = unifi.NewUnifi(&unifi.Config{
User: u.Config.UnifiUser,
Pass: u.Config.UnifiPass,
URL: u.Config.UnifiBase,
VerifySSL: u.Config.VerifySSL,
})
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "[INFO] Authenticated to UniFi Controller @ %v as user %v",
u.Config.UnifiBase, u.Config.UnifiUser)
if err := u.CheckSites(); err != nil {
return err
}
u.Unifi.ErrorLog = func(m string, v ...interface{}) {
fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...)
} // Log all errors to stderr.
switch sites, err := u.GetFilteredSites(); {
case err != nil:
return err
case StringInSlice(u.Flag.DumpJSON, []string{"d", "device", "devices"}):
return u.dumpSitesJSON(unifi.DevicePath, "Devices", sites)
case StringInSlice(u.Flag.DumpJSON, []string{"client", "clients", "c"}):
return u.dumpSitesJSON(unifi.ClientPath, "Clients", sites)
case strings.HasPrefix(u.Flag.DumpJSON, "other "):
apiPath := strings.SplitN(u.Flag.DumpJSON, " ", 2)[1]
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", apiPath)
return u.PrintRawAPIJSON(apiPath)
default:
return fmt.Errorf("must provide filter: devices, clients, other")
}
}
func (u *UnifiPoller) dumpSitesJSON(path, name string, sites unifi.Sites) error {
for _, s := range sites {
apiPath := fmt.Sprintf(path, s.Name)
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping %s: '%s' JSON for site: %s (%s):\n",
name, apiPath, s.Desc, s.Name)
if err := u.PrintRawAPIJSON(apiPath); err != nil {
return err
}
}
return nil
}
// PrintRawAPIJSON prints the raw json for a user-provided path on a UniFi Controller.
func (u *UnifiPoller) PrintRawAPIJSON(apiPath string) error {
body, err := u.Unifi.GetJSON(apiPath)
fmt.Println(string(body))
return err
}
+47
View File
@@ -0,0 +1,47 @@
package poller
import (
"fmt"
"log"
"strings"
)
const callDepth = 2
// LogError logs an error and increments the error counter.
// Should be used in the poller loop.
func (u *UnifiPoller) LogError(err error, prefix string) {
if err != nil {
u.errorCount++
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] %v: %v", prefix, err))
}
}
// StringInSlice returns true if a string is in a slice.
func StringInSlice(str string, slice []string) bool {
for _, s := range slice {
if strings.EqualFold(s, str) {
return true
}
}
return false
}
// Logf prints a log entry if quiet is false.
func (u *UnifiPoller) Logf(m string, v ...interface{}) {
if !u.Config.Quiet {
_ = log.Output(callDepth, fmt.Sprintf("[INFO] "+m, v...))
}
}
// LogDebugf prints a debug log entry if debug is true and quite is false
func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) {
if u.Config.Debug && !u.Config.Quiet {
_ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...))
}
}
// LogErrorf prints an error log entry. This is used for external library logging.
func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) {
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
}
+80
View File
@@ -0,0 +1,80 @@
package poller
import (
"crypto/tls"
"fmt"
"github.com/davidnewhall/unifi-poller/pkg/influxunifi"
"github.com/davidnewhall/unifi-poller/pkg/metrics"
influx "github.com/influxdata/influxdb1-client/v2"
)
// GetInfluxDB returns an InfluxDB interface.
func (u *UnifiPoller) GetInfluxDB() (err error) {
u.Influx, err = influx.NewHTTPClient(influx.HTTPConfig{
Addr: u.Config.InfluxURL,
Username: u.Config.InfluxUser,
Password: u.Config.InfluxPass,
TLSConfig: &tls.Config{InsecureSkipVerify: u.Config.InfxBadSSL},
})
if err != nil {
return fmt.Errorf("influxdb: %v", err)
}
u.Logf("Logging Measurements to InfluxDB at %s as user %s", u.Config.InfluxURL, u.Config.InfluxUser)
return nil
}
// CollectAndProcess collects measurements and then reports them to InfluxDB
// Can be called once or in a ticker loop. This function and all the ones below
// handle their own logging. An error is returned so the calling function may
// determine if there was a read or write error and act on it. This is currently
// called in two places in this library. One returns an error, one does not.
func (u *UnifiPoller) CollectAndProcess() error {
metrics, err := u.CollectMetrics()
if err != nil {
return err
}
u.AugmentMetrics(metrics)
err = u.ReportMetrics(metrics)
u.LogError(err, "processing metrics")
return err
}
// ReportMetrics batches all the metrics and writes them to InfluxDB.
// This creates an InfluxDB writer, and returns an error if the write fails.
func (u *UnifiPoller) ReportMetrics(metrics *metrics.Metrics) error {
// Batch (and send) all the points.
m := &influxunifi.Metrics{Metrics: metrics}
// Make a new Influx Points Batcher.
var err error
m.BatchPoints, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.Config.InfluxDB})
if err != nil {
return fmt.Errorf("influx.NewBatchPoints: %v", err)
}
for _, err := range m.ProcessPoints() {
u.LogError(err, "influx.ProcessPoints")
}
if err = u.Influx.Write(m.BatchPoints); err != nil {
return fmt.Errorf("influxdb.Write(points): %v", err)
}
u.LogInfluxReport(m)
return nil
}
// LogInfluxReport writes a log message after exporting to influxdb.
func (u *UnifiPoller) LogInfluxReport(m *influxunifi.Metrics) {
var fields, points int
for _, p := range m.Points() {
points++
i, _ := p.Fields()
fields += len(i)
}
idsMsg := ""
if u.Config.CollectIDS {
idsMsg = fmt.Sprintf("IDS Events: %d, ", len(m.IDSList))
}
u.Logf("UniFi Measurements Recorded. Sites: %d, Clients: %d, "+
"Wireless APs: %d, Gateways: %d, Switches: %d, %sPoints: %d, Fields: %d",
len(m.Sites), len(m.Clients), len(m.UAPs),
len(m.UDMs)+len(m.USGs), len(m.USWs), idsMsg, points, fields)
}
+69
View File
@@ -0,0 +1,69 @@
package poller
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/davidnewhall/unifi-poller/pkg/metrics"
"github.com/davidnewhall/unifi-poller/pkg/promunifi"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const oneDecimalPoint = 10
// RunPrometheus starts the web server and registers the collector.
func (u *UnifiPoller) RunPrometheus() error {
u.Logf("Exporting Measurements at https://%s/metrics for Prometheus", u.Config.HTTPListen)
http.Handle("/metrics", promhttp.Handler())
prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorCnfg{
Namespace: strings.Replace(u.Config.Namespace, "-", "", -1),
CollectFn: u.ExportMetrics,
LoggingFn: u.LogExportReport,
ReportErrors: true, // XXX: Does this need to be configurable?
}))
return http.ListenAndServe(u.Config.HTTPListen, nil)
}
// ExportMetrics updates the internal metrics provided via
// HTTP at /metrics for prometheus collection.
// This is run by Prometheus as CollectFn.
func (u *UnifiPoller) ExportMetrics() (*metrics.Metrics, error) {
m, err := u.CollectMetrics()
if err != nil {
u.LogErrorf("collecting metrics: %v", err)
u.Logf("Re-authenticating to UniFi Controller")
if err := u.Unifi.Login(); err != nil {
u.LogError(err, "re-authenticating")
return nil, err
}
if m, err = u.CollectMetrics(); err != nil {
u.LogErrorf("collecting metrics: %v", err)
return nil, err
}
}
u.AugmentMetrics(m)
return m, nil
}
// LogExportReport is called after prometheus exports metrics.
// This is run by Prometheus as LoggingFn
func (u *UnifiPoller) LogExportReport(report *promunifi.Report) {
m := report.Metrics
idsMsg := ""
if u.Config.CollectIDS {
idsMsg = fmt.Sprintf(", IDS Events: %d, ", len(m.IDSList))
}
u.Logf("UniFi Measurements Exported. Site: %d, Client: %d, "+
"UAP: %d, USG/UDM: %d, USW: %d%s, Descs: %d, "+
"Metrics: %d, Errs: %d, 0s: %d, Reqs/Total: %v / %v",
len(m.Sites), len(m.Clients), len(m.UAPs), len(m.UDMs)+len(m.USGs), len(m.USWs),
idsMsg, report.Descs, report.Total, report.Errors, report.Zeros,
report.Fetch.Round(time.Millisecond/oneDecimalPoint),
report.Elapsed.Round(time.Millisecond/oneDecimalPoint))
}
+144
View File
@@ -0,0 +1,144 @@
// Package poller provides the CLI interface to setup unifi-poller.
package poller
import (
"fmt"
"log"
"os"
"strings"
"time"
"github.com/spf13/pflag"
)
// New returns a new poller struct preloaded with default values.
// No need to call this if you call Start.c
func New() *UnifiPoller {
return &UnifiPoller{
Config: &Config{
InfluxURL: defaultInfluxURL,
InfluxUser: defaultInfluxUser,
InfluxPass: defaultInfluxPass,
InfluxDB: defaultInfluxDB,
UnifiUser: defaultUnifiUser,
UnifiPass: defaultUnifiUser,
UnifiBase: defaultUnifiURL,
Interval: Duration{defaultInterval},
Sites: []string{"all"},
HTTPListen: defaultHTTPListen,
Namespace: appName,
}, Flag: &Flag{},
}
}
// Start begins the application from a CLI.
// Parses cli flags, parses config file, parses env vars, sets up logging, then:
// - dumps a json payload OR - executes Run().
func (u *UnifiPoller) Start() error {
log.SetFlags(log.LstdFlags)
u.Flag.Parse(os.Args[1:])
if u.Flag.ShowVer {
fmt.Printf("%s v%s\n", appName, Version)
return nil // don't run anything else w/ version request.
}
if u.Flag.DumpJSON == "" { // do not print this when dumping JSON.
u.Logf("Loading Configuration File: %s", u.Flag.ConfigFile)
}
// Parse config file.
if err := u.Config.ParseFile(u.Flag.ConfigFile); err != nil {
u.Flag.Usage()
return err
}
// Update Config with ENV variable overrides.
if err := u.Config.ParseENV(); err != nil {
return err
}
if u.Flag.DumpJSON != "" {
return u.DumpJSONPayload()
}
if u.Config.Debug {
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
u.LogDebugf("Debug Logging Enabled")
}
log.Printf("[INFO] UniFi Poller v%v Starting Up! PID: %d", Version, os.Getpid())
return u.Run()
}
// Parse turns CLI arguments into data structures. Called by Start() on startup.
func (f *Flag) Parse(args []string) {
f.FlagSet = pflag.NewFlagSet(appName, pflag.ExitOnError)
f.Usage = func() {
fmt.Printf("Usage: %s [--config=/path/to/up.conf] [--version]", appName)
f.PrintDefaults()
}
f.StringVarP(&f.DumpJSON, "dumpjson", "j", "",
"This debug option prints a json payload and exits. See man page for more info.")
f.StringVarP(&f.ConfigFile, "config", "c", DefaultConfFile, "Poller config file path.")
f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit.")
_ = f.FlagSet.Parse(args) // pflag.ExitOnError means this will never return error.
}
// Run picks a mode and executes the associated functions. This will do one of three things:
// 1. Start the collector routine that polls unifi and reports to influx on an interval. (default)
// 2. Run the collector one time and report the metrics to influxdb. (lambda)
// 3. Start a web server and wait for Prometheus to poll the application for metrics.
func (u *UnifiPoller) Run() error {
if err := u.GetUnifi(); err != nil {
return err
}
u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
u.Config.UnifiBase, u.Unifi.ServerVersion, u.Config.UnifiUser, u.Config.Sites)
switch strings.ToLower(u.Config.Mode) {
default:
if err := u.GetInfluxDB(); err != nil {
return err
}
return u.PollController()
case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda":
if err := u.GetInfluxDB(); err != nil {
return err
}
u.LastCheck = time.Now()
return u.CollectAndProcess()
case "prometheus", "exporter":
return u.RunPrometheus()
}
}
// PollController runs forever, polling UniFi and pushing to InfluxDB
// This is started by Run() after everything checks out.
func (u *UnifiPoller) PollController() error {
interval := u.Config.Interval.Round(time.Second)
log.Printf("[INFO] Everything checks out! Poller started, interval: %v", interval)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for u.LastCheck = range ticker.C {
var err error
if u.Config.ReAuth {
u.LogDebugf("Re-authenticating to UniFi Controller")
// Some users need to re-auth every interval because the cookie times out.
if err = u.Unifi.Login(); err != nil {
u.LogError(err, "re-authenticating")
}
}
if err == nil {
// Only run this if the authentication procedure didn't return error.
_ = u.CollectAndProcess()
}
if u.errorCount > 0 {
return fmt.Errorf("too many errors, stopping poller")
}
}
return nil
}
+135
View File
@@ -0,0 +1,135 @@
package poller
import (
"fmt"
"strings"
"time"
"github.com/davidnewhall/unifi-poller/pkg/metrics"
"golift.io/unifi"
)
// GetUnifi returns a UniFi controller interface.
func (u *UnifiPoller) GetUnifi() (err error) {
// Create an authenticated session to the Unifi Controller.
u.Unifi, err = unifi.NewUnifi(&unifi.Config{
User: u.Config.UnifiUser,
Pass: u.Config.UnifiPass,
URL: u.Config.UnifiBase,
VerifySSL: u.Config.VerifySSL,
ErrorLog: u.LogErrorf, // Log all errors.
DebugLog: u.LogDebugf, // Log debug messages.
})
if err != nil {
return fmt.Errorf("unifi controller: %v", err)
}
u.LogDebugf("Authenticated with controller successfully")
return u.CheckSites()
}
// CheckSites makes sure the list of provided sites exists on the controller.
// This does not run in Lambda (run-once) mode.
func (u *UnifiPoller) CheckSites() error {
if strings.Contains(strings.ToLower(u.Config.Mode), "lambda") {
return nil // Skip this in lambda mode.
}
u.LogDebugf("Checking Controller Sites List")
sites, err := u.Unifi.GetSites()
if err != nil {
return err
}
msg := []string{}
for _, site := range sites {
msg = append(msg, site.Name+" ("+site.Desc+")")
}
u.Logf("Found %d site(s) on controller: %v", len(msg), strings.Join(msg, ", "))
if StringInSlice("all", u.Config.Sites) {
u.Config.Sites = []string{"all"}
return nil
}
FIRST:
for _, s := range u.Config.Sites {
for _, site := range sites {
if s == site.Name {
continue FIRST
}
}
// This is fine, it may get added later.
u.LogErrorf("configured site not found on controller: %v", s)
}
return nil
}
// CollectMetrics grabs all the measurements from a UniFi controller and returns them.
func (u *UnifiPoller) CollectMetrics() (*metrics.Metrics, error) {
m := &metrics.Metrics{TS: u.LastCheck} // At this point, it's the Current Check.
var err error
// Get the sites we care about.
m.Sites, err = u.GetFilteredSites()
u.LogError(err, "unifi.GetSites()")
if u.Config.CollectIDS {
m.IDSList, err = u.Unifi.GetIDS(m.Sites, time.Now().Add(u.Config.Interval.Duration), time.Now())
u.LogError(err, "unifi.GetIDS()")
}
// Get all the points.
m.Clients, err = u.Unifi.GetClients(m.Sites)
u.LogError(err, "unifi.GetClients()")
m.Devices, err = u.Unifi.GetDevices(m.Sites)
u.LogError(err, "unifi.GetDevices()")
return m, err
}
// AugmentMetrics is our middleware layer between collecting metrics and writing them.
// This is where we can manipuate the returned data or make arbitrary decisions.
// This function currently adds parent device names to client metrics.
func (u *UnifiPoller) AugmentMetrics(metrics *metrics.Metrics) {
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
return
}
devices := make(map[string]string)
bssdIDs := make(map[string]string)
for _, r := range metrics.UAPs {
devices[r.Mac] = r.Name
for _, v := range r.VapTable {
bssdIDs[v.Bssid] = fmt.Sprintf("%s %s %s:", r.Name, v.Radio, v.RadioName)
}
}
for _, r := range metrics.USGs {
devices[r.Mac] = r.Name
}
for _, r := range metrics.USWs {
devices[r.Mac] = r.Name
}
for _, r := range metrics.UDMs {
devices[r.Mac] = r.Name
}
// These come blank, so set them here.
for i, c := range metrics.Clients {
metrics.Clients[i].SwName = devices[c.SwMac]
metrics.Clients[i].ApName = devices[c.ApMac]
metrics.Clients[i].GwName = devices[c.GwMac]
metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
}
}
// GetFilteredSites returns a list of sites to fetch data for.
// Omits requested but unconfigured sites. Grabs the full list from the
// controller and returns the sites provided in the config file.
func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) {
sites, err := u.Unifi.GetSites()
if err != nil {
return nil, err
} else if len(u.Config.Sites) < 1 || StringInSlice("all", u.Config.Sites) {
return sites, nil
}
var i int
for _, s := range sites {
// Only include valid sites in the request filter.
if StringInSlice(s.Name, u.Config.Sites) {
sites[i] = s
i++
}
}
return sites[:i], nil
}
+4
View File
@@ -0,0 +1,4 @@
# prometheus
This package provides the interface to turn UniFi measurements into prometheus
exported metrics. Requires the poller package for actual UniFi data collection.
+129
View File
@@ -0,0 +1,129 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
type uclient struct {
Anomalies *prometheus.Desc
BytesR *prometheus.Desc
CCQ *prometheus.Desc
Satisfaction *prometheus.Desc
Noise *prometheus.Desc
RoamCount *prometheus.Desc
RSSI *prometheus.Desc
RxBytes *prometheus.Desc
RxBytesR *prometheus.Desc
RxPackets *prometheus.Desc
RxRate *prometheus.Desc
Signal *prometheus.Desc
TxBytes *prometheus.Desc
TxBytesR *prometheus.Desc
TxPackets *prometheus.Desc
TxRetries *prometheus.Desc
TxPower *prometheus.Desc
TxRate *prometheus.Desc
Uptime *prometheus.Desc
WifiTxAttempts *prometheus.Desc
WiredRxBytes *prometheus.Desc
WiredRxBytesR *prometheus.Desc
WiredRxPackets *prometheus.Desc
WiredTxBytes *prometheus.Desc
WiredTxBytesR *prometheus.Desc
WiredTxPackets *prometheus.Desc
DpiStatsApp *prometheus.Desc
DpiStatsCat *prometheus.Desc
DpiStatsRxBytes *prometheus.Desc
DpiStatsRxPackets *prometheus.Desc
DpiStatsTxBytes *prometheus.Desc
DpiStatsTxPackets *prometheus.Desc
}
func descClient(ns string) *uclient {
labels := []string{"name", "mac", "site_name", "gw_name", "sw_name", "vlan", "ip", "oui", "network", "sw_port", "ap_name", "wired"}
labelW := append([]string{"radio_name", "radio", "radio_proto", "channel", "essid", "bssid", "radio_desc"}, labels...)
return &uclient{
Anomalies: prometheus.NewDesc(ns+"anomalies", "Client Anomalies", labelW, nil),
BytesR: prometheus.NewDesc(ns+"transfer_rate_bytes", "Client Data Rate", labelW, nil),
CCQ: prometheus.NewDesc(ns+"ccq_percent", "Client Connection Quality", labelW, nil),
Satisfaction: prometheus.NewDesc(ns+"satisfaction_percent", "Client Satisfaction", labelW, nil),
Noise: prometheus.NewDesc(ns+"noise_db", "Client AP Noise", labelW, nil),
RoamCount: prometheus.NewDesc(ns+"roam_count_total", "Client Roam Counter", labelW, nil),
RSSI: prometheus.NewDesc(ns+"rssi_db", "Client RSSI", labelW, nil),
RxBytes: prometheus.NewDesc(ns+"receive_bytes_total", "Client Receive Bytes", labels, nil),
RxBytesR: prometheus.NewDesc(ns+"receive_rate_bytes", "Client Receive Data Rate", labels, nil),
RxPackets: prometheus.NewDesc(ns+"receive_packets_total", "Client Receive Packets", labels, nil),
RxRate: prometheus.NewDesc(ns+"radio_receive_rate_bps", "Client Receive Rate", labelW, nil),
Signal: prometheus.NewDesc(ns+"radio_signal_db", "Client Signal Strength", labelW, nil),
TxBytes: prometheus.NewDesc(ns+"transmit_bytes_total", "Client Transmit Bytes", labels, nil),
TxBytesR: prometheus.NewDesc(ns+"transmit_rate_bytes", "Client Transmit Data Rate", labels, nil),
TxPackets: prometheus.NewDesc(ns+"transmit_packets_total", "Client Transmit Packets", labels, nil),
TxRetries: prometheus.NewDesc(ns+"transmit_retries_total", "Client Transmit Retries", labels, nil),
TxPower: prometheus.NewDesc(ns+"radio_transmit_power_dbm", "Client Transmit Power", labelW, nil),
TxRate: prometheus.NewDesc(ns+"radio_transmit_rate_bps", "Client Transmit Rate", labelW, nil),
WifiTxAttempts: prometheus.NewDesc(ns+"wifi_attempts_transmit_total", "Client Wifi Transmit Attempts", labelW, nil),
Uptime: prometheus.NewDesc(ns+"uptime_seconds", "Client Uptime", labelW, nil),
/* needs more "looking into"
DpiStatsApp: prometheus.NewDesc(ns+"dpi_stats_app", "Client DPI Stats App", labels, nil),
DpiStatsCat: prometheus.NewDesc(ns+"dpi_stats_cat", "Client DPI Stats Cat", labels, nil),
DpiStatsRxBytes: prometheus.NewDesc(ns+"dpi_stats_receive_bytes_total", "Client DPI Stats Receive Bytes", labels, nil),
DpiStatsRxPackets: prometheus.NewDesc(ns+"dpi_stats_receive_packets_total", "Client DPI Stats Receive Packets", labels, nil),
DpiStatsTxBytes: prometheus.NewDesc(ns+"dpi_stats_transmit_bytes_total", "Client DPI Stats Transmit Bytes", labels, nil),
DpiStatsTxPackets: prometheus.NewDesc(ns+"dpi_stats_transmit_packets_total", "Client DPI Stats Transmit Packets", labels, nil),
*/
}
}
func (u *promUnifi) exportClient(r report, c *unifi.Client) {
labels := []string{c.Name, c.Mac, c.SiteName, c.GwName, c.SwName, c.Vlan.Txt, c.IP, c.Oui, c.Network, c.SwPort.Txt, c.ApName, ""}
labelW := append([]string{c.RadioName, c.Radio, c.RadioProto, c.Channel.Txt, c.Essid, c.Bssid, c.RadioDescription}, labels...)
if c.IsWired.Val {
labels[len(labels)-1] = "true"
labelW[len(labelW)-1] = "true"
r.send([]*metric{
{u.Client.RxBytes, prometheus.CounterValue, c.WiredRxBytes, labels},
{u.Client.RxBytesR, prometheus.GaugeValue, c.WiredRxBytesR, labels},
{u.Client.RxPackets, prometheus.CounterValue, c.WiredRxPackets, labels},
{u.Client.TxBytes, prometheus.CounterValue, c.WiredTxBytes, labels},
{u.Client.TxBytesR, prometheus.GaugeValue, c.WiredTxBytesR, labels},
{u.Client.TxPackets, prometheus.CounterValue, c.WiredTxPackets, labels},
})
} else {
labels[len(labels)-1] = "false"
labelW[len(labelW)-1] = "false"
r.send([]*metric{
{u.Client.Anomalies, prometheus.CounterValue, c.Anomalies, labelW},
{u.Client.CCQ, prometheus.GaugeValue, c.Ccq / 10, labelW},
{u.Client.Satisfaction, prometheus.GaugeValue, c.Satisfaction, labelW},
{u.Client.Noise, prometheus.GaugeValue, c.Noise, labelW},
{u.Client.RoamCount, prometheus.CounterValue, c.RoamCount, labelW},
{u.Client.RSSI, prometheus.GaugeValue, c.Rssi, labelW},
{u.Client.Signal, prometheus.GaugeValue, c.Signal, labelW},
{u.Client.TxPower, prometheus.GaugeValue, c.TxPower, labelW},
{u.Client.TxRate, prometheus.GaugeValue, c.TxRate * 1000, labelW},
{u.Client.WifiTxAttempts, prometheus.CounterValue, c.WifiTxAttempts, labelW},
{u.Client.RxRate, prometheus.GaugeValue, c.RxRate * 1000, labelW},
{u.Client.TxRetries, prometheus.CounterValue, c.TxRetries, labels},
{u.Client.TxBytes, prometheus.CounterValue, c.TxBytes, labels},
{u.Client.TxBytesR, prometheus.GaugeValue, c.TxBytesR, labels},
{u.Client.TxPackets, prometheus.CounterValue, c.TxPackets, labels},
{u.Client.RxBytes, prometheus.CounterValue, c.RxBytes, labels},
{u.Client.RxBytesR, prometheus.GaugeValue, c.RxBytesR, labels},
{u.Client.RxPackets, prometheus.CounterValue, c.RxPackets, labels},
{u.Client.BytesR, prometheus.GaugeValue, c.BytesR, labelW},
})
}
r.send([]*metric{
{u.Client.Uptime, prometheus.GaugeValue, c.Uptime, labelW},
/* needs more "looking into"
{u.Client.DpiStatsApp, prometheus.GaugeValue, c.DpiStats.App, labels},
{u.Client.DpiStatsCat, prometheus.GaugeValue, c.DpiStats.Cat, labels},
{u.Client.DpiStatsRxBytes, prometheus.CounterValue, c.DpiStats.RxBytes, labels},
{u.Client.DpiStatsRxPackets, prometheus.CounterValue, c.DpiStats.RxPackets, labels},
{u.Client.DpiStatsTxBytes, prometheus.CounterValue, c.DpiStats.TxBytes, labels},
{u.Client.DpiStatsTxPackets, prometheus.CounterValue, c.DpiStats.TxPackets, labels},
*/
})
}
+150
View File
@@ -0,0 +1,150 @@
// Package promunifi provides the bridge between unifi metrics and prometheus.
package promunifi
import (
"fmt"
"reflect"
"strings"
"sync"
"time"
"github.com/davidnewhall/unifi-poller/pkg/metrics"
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
// channel buffer, fits at least one batch.
const buffer = 50
// UnifiCollectorCnfg defines the data needed to collect and report UniFi Metrics.
type UnifiCollectorCnfg struct {
// If non-empty, each of the collected metrics is prefixed by the
// provided string and an underscore ("_").
Namespace string
// If true, any error encountered during collection is reported as an
// invalid metric (see NewInvalidMetric). Otherwise, errors are ignored
// and the collected metrics will be incomplete. Possibly, no metrics
// will be collected at all.
ReportErrors bool
// This function is passed to the Collect() method. The Collect method runs
// this function to retrieve the latest UniFi measurements and export them.
CollectFn func() (*metrics.Metrics, error)
// Provide a logger function if you want to run a routine *after* prometheus checks in.
LoggingFn func(*Report)
}
type promUnifi struct {
Config UnifiCollectorCnfg
Client *uclient
Device *unifiDevice
UAP *uap
USG *usg
USW *usw
Site *site
}
type metric struct {
Desc *prometheus.Desc
ValueType prometheus.ValueType
Value interface{}
Labels []string
}
// Report is passed into LoggingFn to log the export metrics to stdout (outside this package).
type Report struct {
Total int // Total count of metrics recorded.
Errors int // Total count of errors recording metrics.
Zeros int // Total count of metrics equal to zero.
Descs int // Total count of unique metrics descriptions.
Metrics *metrics.Metrics // Metrics collected and recorded.
Elapsed time.Duration // Duration elapsed collecting and exporting.
Fetch time.Duration // Duration elapsed making controller requests.
Start time.Time // Time collection began.
ch chan []*metric
wg sync.WaitGroup
cf UnifiCollectorCnfg
}
// NewUnifiCollector returns a prometheus collector that will export any available
// UniFi metrics. You must provide a collection function in the opts.
func NewUnifiCollector(opts UnifiCollectorCnfg) prometheus.Collector {
if opts.CollectFn == nil {
panic("nil collector function")
}
if opts.Namespace = strings.Trim(opts.Namespace, "_") + "_"; opts.Namespace == "_" {
opts.Namespace = ""
}
return &promUnifi{
Config: opts,
Client: descClient(opts.Namespace + "client_"),
Device: descDevice(opts.Namespace + "device_"), // stats for all device types.
UAP: descUAP(opts.Namespace + "device_"),
USG: descUSG(opts.Namespace + "device_"),
USW: descUSW(opts.Namespace + "device_"),
Site: descSite(opts.Namespace + "site_"),
}
}
// 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 []interface{}{u.Client, u.Device, u.UAP, u.USG, u.USW, u.Site} {
v := reflect.Indirect(reflect.ValueOf(f))
// Loop each struct member and send it to the provided channel.
for i := 0; i < v.NumField(); i++ {
desc, ok := v.Field(i).Interface().(*prometheus.Desc)
if ok && desc != nil {
ch <- desc
}
}
}
}
// Collect satisfies the prometheus Collector. This runs the input method to get
// the current metrics (from another package) then exports them for prometheus.
func (u *promUnifi) Collect(ch chan<- prometheus.Metric) {
var err error
r := &Report{cf: u.Config, ch: make(chan []*metric, buffer), Start: time.Now()}
defer r.close()
if r.Metrics, err = r.cf.CollectFn(); err != nil {
r.error(ch, prometheus.NewInvalidDesc(fmt.Errorf("metric fetch failed")), err)
return
}
r.Fetch = time.Since(r.Start)
if r.Metrics.Devices == nil {
r.Metrics.Devices = &unifi.Devices{}
}
// Pass Report interface into our collecting and reporting methods.
go u.exportMetrics(r, ch)
for _, f := range []func(report){u.loopClients, u.loopSites, u.loopUAPs, u.loopUSWs, u.loopUSGs, u.loopUDMs} {
r.add()
go f(r) // in loops.go.
}
}
// This is closely tied to the method above with a sync.WaitGroup.
// This method runs in a go routine and exits when the channel closes.
func (u *promUnifi) exportMetrics(r report, ch chan<- prometheus.Metric) {
descs := make(map[*prometheus.Desc]bool) // used as a counter
defer r.report(descs)
for newMetrics := range r.channel() {
for _, m := range newMetrics {
descs[m.Desc] = true
switch v := m.Value.(type) {
case unifi.FlexInt:
ch <- r.export(m, v.Val)
case float64:
ch <- r.export(m, v)
case int64:
ch <- r.export(m, float64(v))
case int:
ch <- r.export(m, float64(v))
default:
r.error(ch, m.Desc, fmt.Sprintf("not a number: %v", m.Value))
}
}
r.done()
}
}
+47
View File
@@ -0,0 +1,47 @@
package promunifi
// This file contains all the loop methods for each device type, clients and sites.
// Moved them here to consolate clutter from the other files. Also, if these change,
// they usually all change at once since they're pretty much the same code.
func (u *promUnifi) loopSites(r report) {
defer r.done()
for _, s := range r.metrics().Sites {
u.exportSite(r, s)
}
}
func (u *promUnifi) loopUAPs(r report) {
defer r.done()
for _, d := range r.metrics().UAPs {
u.exportUAP(r, d)
}
}
func (u *promUnifi) loopUDMs(r report) {
defer r.done()
for _, d := range r.metrics().UDMs {
u.exportUDM(r, d)
}
}
func (u *promUnifi) loopUSGs(r report) {
defer r.done()
for _, d := range r.metrics().USGs {
u.exportUSG(r, d)
}
}
func (u *promUnifi) loopUSWs(r report) {
defer r.done()
for _, d := range r.metrics().USWs {
u.exportUSW(r, d)
}
}
func (u *promUnifi) loopClients(r report) {
defer r.done()
for _, c := range r.metrics().Clients {
u.exportClient(r, c)
}
}
+78
View File
@@ -0,0 +1,78 @@
package promunifi
import (
"fmt"
"time"
"github.com/davidnewhall/unifi-poller/pkg/metrics"
"github.com/prometheus/client_golang/prometheus"
)
// This file contains the report interface.
// This interface can be mocked and overridden for tests.
// report is an internal interface used to "process metrics"
type report interface {
add()
done()
send([]*metric)
metrics() *metrics.Metrics
channel() chan []*metric
report(descs map[*prometheus.Desc]bool)
export(m *metric, v float64) prometheus.Metric
error(ch chan<- prometheus.Metric, d *prometheus.Desc, v interface{})
}
// satisfy gomnd
const one = 1
func (r *Report) add() {
r.wg.Add(one)
}
func (r *Report) done() {
r.wg.Add(-one)
}
func (r *Report) send(m []*metric) {
r.wg.Add(one)
r.ch <- m
}
func (r *Report) metrics() *metrics.Metrics {
return r.Metrics
}
func (r *Report) channel() chan []*metric {
return r.ch
}
func (r *Report) report(descs map[*prometheus.Desc]bool) {
if r.cf.LoggingFn == nil {
return
}
r.Descs = len(descs)
r.cf.LoggingFn(r)
}
func (r *Report) export(m *metric, v float64) prometheus.Metric {
r.Total++
if v == 0 {
r.Zeros++
}
return prometheus.MustNewConstMetric(m.Desc, m.ValueType, v, m.Labels...)
}
func (r *Report) error(ch chan<- prometheus.Metric, d *prometheus.Desc, v interface{}) {
r.Errors++
if r.cf.ReportErrors {
ch <- prometheus.NewInvalidMetric(d, fmt.Errorf("error: %v", v))
}
}
// close is not part of the interface.
func (r *Report) close() {
r.wg.Wait()
r.Elapsed = time.Since(r.Start)
close(r.ch)
}
+132
View File
@@ -0,0 +1,132 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
type site struct {
NumUser *prometheus.Desc
NumGuest *prometheus.Desc
NumIot *prometheus.Desc
TxBytesR *prometheus.Desc
RxBytesR *prometheus.Desc
NumAp *prometheus.Desc
NumAdopted *prometheus.Desc
NumDisabled *prometheus.Desc
NumDisconnected *prometheus.Desc
NumPending *prometheus.Desc
NumGw *prometheus.Desc
NumSw *prometheus.Desc
NumSta *prometheus.Desc
Latency *prometheus.Desc
Drops *prometheus.Desc
Uptime *prometheus.Desc
XputUp *prometheus.Desc
XputDown *prometheus.Desc
SpeedtestPing *prometheus.Desc
RemoteUserNumActive *prometheus.Desc
RemoteUserNumInactive *prometheus.Desc
RemoteUserRxBytes *prometheus.Desc
RemoteUserTxBytes *prometheus.Desc
RemoteUserRxPackets *prometheus.Desc
RemoteUserTxPackets *prometheus.Desc
}
func descSite(ns string) *site {
labels := []string{"subsystem", "status", "site_name"}
return &site{
NumUser: prometheus.NewDesc(ns+"users", "Number of Users", labels, nil),
NumGuest: prometheus.NewDesc(ns+"guests", "Number of Guests", labels, nil),
NumIot: prometheus.NewDesc(ns+"iots", "Number of IoT Devices", labels, nil),
TxBytesR: prometheus.NewDesc(ns+"transmit_rate_bytes", "Bytes Transmit Rate", labels, nil),
RxBytesR: prometheus.NewDesc(ns+"receive_rate_bytes", "Bytes Receive Rate", labels, nil),
NumAp: prometheus.NewDesc(ns+"aps", "Access Point Count", labels, nil),
NumAdopted: prometheus.NewDesc(ns+"adopted", "Adoption Count", labels, nil),
NumDisabled: prometheus.NewDesc(ns+"disabled", "Disabled Count", labels, nil),
NumDisconnected: prometheus.NewDesc(ns+"disconnected", "Disconnected Count", labels, nil),
NumPending: prometheus.NewDesc(ns+"pending", "Pending Count", labels, nil),
NumGw: prometheus.NewDesc(ns+"gateways", "Gateway Count", labels, nil),
NumSw: prometheus.NewDesc(ns+"switches", "Switch Count", labels, nil),
NumSta: prometheus.NewDesc(ns+"stations", "Station Count", labels, nil),
Latency: prometheus.NewDesc(ns+"latency_seconds", "Latency", labels, nil),
Uptime: prometheus.NewDesc(ns+"uptime_seconds", "Uptime", labels, nil),
Drops: prometheus.NewDesc(ns+"intenet_drops_total", "Internet (WAN) Disconnections", labels, nil),
XputUp: prometheus.NewDesc(ns+"xput_up_rate", "Speedtest Upload", labels, nil),
XputDown: prometheus.NewDesc(ns+"xput_down_rate", "Speedtest Download", labels, nil),
SpeedtestPing: prometheus.NewDesc(ns+"speedtest_ping", "Speedtest Ping", labels, nil),
RemoteUserNumActive: prometheus.NewDesc(ns+"remote_user_active", "Remote Users Active", labels, nil),
RemoteUserNumInactive: prometheus.NewDesc(ns+"remote_user_inactive", "Remote Users Inactive", labels, nil),
RemoteUserRxBytes: prometheus.NewDesc(ns+"remote_user_receive_bytes_total", "Remote Users Receive Bytes", labels, nil),
RemoteUserTxBytes: prometheus.NewDesc(ns+"remote_user_transmit_bytes_total", "Remote Users Transmit Bytes", labels, nil),
RemoteUserRxPackets: prometheus.NewDesc(ns+"remote_user_receive_packets_total", "Remote Users Receive Packets", labels, nil),
RemoteUserTxPackets: prometheus.NewDesc(ns+"remote_user_transmit_packets_total", "Remote Users Transmit Packets", labels, nil),
}
}
func (u *promUnifi) exportSite(r report, s *unifi.Site) {
for _, h := range s.Health {
labels := []string{h.Subsystem, h.Status, s.SiteName}
switch h.Subsystem {
case "www":
r.send([]*metric{
{u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
{u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
{u.Site.Uptime, prometheus.GaugeValue, h.Latency, labels},
{u.Site.Latency, prometheus.GaugeValue, h.Latency.Val / 1000, labels},
{u.Site.XputUp, prometheus.GaugeValue, h.XputUp, labels},
{u.Site.XputDown, prometheus.GaugeValue, h.XputDown, labels},
{u.Site.SpeedtestPing, prometheus.GaugeValue, h.SpeedtestPing, labels},
{u.Site.Drops, prometheus.CounterValue, h.Drops, labels},
})
case "wlan":
r.send([]*metric{
{u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
{u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
{u.Site.NumAdopted, prometheus.GaugeValue, h.NumAdopted, labels},
{u.Site.NumDisconnected, prometheus.GaugeValue, h.NumDisconnected, labels},
{u.Site.NumPending, prometheus.GaugeValue, h.NumPending, labels},
{u.Site.NumUser, prometheus.GaugeValue, h.NumUser, labels},
{u.Site.NumGuest, prometheus.GaugeValue, h.NumGuest, labels},
{u.Site.NumIot, prometheus.GaugeValue, h.NumIot, labels},
{u.Site.NumAp, prometheus.GaugeValue, h.NumAp, labels},
{u.Site.NumDisabled, prometheus.GaugeValue, h.NumDisabled, labels},
})
case "wan":
r.send([]*metric{
{u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
{u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
{u.Site.NumAdopted, prometheus.GaugeValue, h.NumAdopted, labels},
{u.Site.NumDisconnected, prometheus.GaugeValue, h.NumDisconnected, labels},
{u.Site.NumPending, prometheus.GaugeValue, h.NumPending, labels},
{u.Site.NumGw, prometheus.GaugeValue, h.NumGw, labels},
{u.Site.NumSta, prometheus.GaugeValue, h.NumSta, labels},
})
case "lan":
r.send([]*metric{
{u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
{u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
{u.Site.NumAdopted, prometheus.GaugeValue, h.NumAdopted, labels},
{u.Site.NumDisconnected, prometheus.GaugeValue, h.NumDisconnected, labels},
{u.Site.NumPending, prometheus.GaugeValue, h.NumPending, labels},
{u.Site.NumUser, prometheus.GaugeValue, h.NumUser, labels},
{u.Site.NumGuest, prometheus.GaugeValue, h.NumGuest, labels},
{u.Site.NumIot, prometheus.GaugeValue, h.NumIot, labels},
{u.Site.NumSw, prometheus.GaugeValue, h.NumSw, labels},
})
case "vpn":
r.send([]*metric{
{u.Site.RemoteUserNumActive, prometheus.GaugeValue, h.RemoteUserNumActive, labels},
{u.Site.RemoteUserNumInactive, prometheus.GaugeValue, h.RemoteUserNumInactive, labels},
{u.Site.RemoteUserRxBytes, prometheus.CounterValue, h.RemoteUserRxBytes, labels},
{u.Site.RemoteUserTxBytes, prometheus.CounterValue, h.RemoteUserTxBytes, labels},
{u.Site.RemoteUserRxPackets, prometheus.CounterValue, h.RemoteUserRxPackets, labels},
{u.Site.RemoteUserTxPackets, prometheus.CounterValue, h.RemoteUserTxPackets, labels},
})
}
}
}
+337
View File
@@ -0,0 +1,337 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
type uap struct {
// Ap Traffic Stats
ApWifiTxDropped *prometheus.Desc
ApRxErrors *prometheus.Desc
ApRxDropped *prometheus.Desc
ApRxFrags *prometheus.Desc
ApRxCrypts *prometheus.Desc
ApTxPackets *prometheus.Desc
ApTxBytes *prometheus.Desc
ApTxErrors *prometheus.Desc
ApTxDropped *prometheus.Desc
ApTxRetries *prometheus.Desc
ApRxPackets *prometheus.Desc
ApRxBytes *prometheus.Desc
WifiTxAttempts *prometheus.Desc
MacFilterRejections *prometheus.Desc
// VAP Stats
VAPCcq *prometheus.Desc
VAPMacFilterRejections *prometheus.Desc
VAPNumSatisfactionSta *prometheus.Desc
VAPAvgClientSignal *prometheus.Desc
VAPSatisfaction *prometheus.Desc
VAPSatisfactionNow *prometheus.Desc
VAPDNSAvgLatency *prometheus.Desc
VAPRxBytes *prometheus.Desc
VAPRxCrypts *prometheus.Desc
VAPRxDropped *prometheus.Desc
VAPRxErrors *prometheus.Desc
VAPRxFrags *prometheus.Desc
VAPRxNwids *prometheus.Desc
VAPRxPackets *prometheus.Desc
VAPTxBytes *prometheus.Desc
VAPTxDropped *prometheus.Desc
VAPTxErrors *prometheus.Desc
VAPTxPackets *prometheus.Desc
VAPTxPower *prometheus.Desc
VAPTxRetries *prometheus.Desc
VAPTxCombinedRetries *prometheus.Desc
VAPTxDataMpduBytes *prometheus.Desc
VAPTxRtsRetries *prometheus.Desc
VAPTxSuccess *prometheus.Desc
VAPTxTotal *prometheus.Desc
VAPTxGoodbytes *prometheus.Desc
VAPTxLatAvg *prometheus.Desc
VAPTxLatMax *prometheus.Desc
VAPTxLatMin *prometheus.Desc
VAPRxGoodbytes *prometheus.Desc
VAPRxLatAvg *prometheus.Desc
VAPRxLatMax *prometheus.Desc
VAPRxLatMin *prometheus.Desc
VAPWifiTxLatencyMovAvg *prometheus.Desc
VAPWifiTxLatencyMovMax *prometheus.Desc
VAPWifiTxLatencyMovMin *prometheus.Desc
VAPWifiTxLatencyMovTotal *prometheus.Desc
VAPWifiTxLatencyMovCount *prometheus.Desc
// Radio Stats
RadioCurrentAntennaGain *prometheus.Desc
RadioHt *prometheus.Desc
RadioMaxTxpower *prometheus.Desc
RadioMinTxpower *prometheus.Desc
RadioNss *prometheus.Desc
RadioRadioCaps *prometheus.Desc
RadioTxPower *prometheus.Desc
RadioAstBeXmit *prometheus.Desc
RadioChannel *prometheus.Desc
RadioCuSelfRx *prometheus.Desc
RadioCuSelfTx *prometheus.Desc
RadioCuTotal *prometheus.Desc
RadioExtchannel *prometheus.Desc
RadioGain *prometheus.Desc
RadioGuestNumSta *prometheus.Desc
RadioNumSta *prometheus.Desc
RadioUserNumSta *prometheus.Desc
RadioTxPackets *prometheus.Desc
RadioTxRetries *prometheus.Desc
}
func descUAP(ns string) *uap {
// labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
labelA := []string{"stat", "site_name", "name"} // stat + labels[6:]
labelV := []string{"vap_name", "bssid", "radio", "radio_name", "essid", "usage", "site_name", "name"}
labelR := []string{"radio_name", "radio", "site_name", "name"}
return &uap{
// 3x each - stat table: total, guest, user
ApWifiTxDropped: prometheus.NewDesc(ns+"stat_wifi_transmt_dropped_total", "Wifi Transmissions Dropped", labelA, nil),
ApRxErrors: prometheus.NewDesc(ns+"stat_receive_errors_total", "Receive Errors", labelA, nil),
ApRxDropped: prometheus.NewDesc(ns+"stat_receive_dropped_total", "Receive Dropped", labelA, nil),
ApRxFrags: prometheus.NewDesc(ns+"stat_receive_frags_total", "Received Frags", labelA, nil),
ApRxCrypts: prometheus.NewDesc(ns+"stat_receive_crypts_total", "Receive Crypts", labelA, nil),
ApTxPackets: prometheus.NewDesc(ns+"stat_transmit_packets_total", "Transmit Packets", labelA, nil),
ApTxBytes: prometheus.NewDesc(ns+"stat_transmit_bytes_total", "Transmit Bytes", labelA, nil),
ApTxErrors: prometheus.NewDesc(ns+"stat_transmit_errors_total", "Transmit Errors", labelA, nil),
ApTxDropped: prometheus.NewDesc(ns+"stat_transmit_dropped_total", "Transmit Dropped", labelA, nil),
ApTxRetries: prometheus.NewDesc(ns+"stat_retries_tx_total", "Transmit Retries", labelA, nil),
ApRxPackets: prometheus.NewDesc(ns+"stat_receive_packets_total", "Receive Packets", labelA, nil),
ApRxBytes: prometheus.NewDesc(ns+"stat_receive_bytes_total", "Receive Bytes", labelA, nil),
WifiTxAttempts: prometheus.NewDesc(ns+"stat_wifi_transmit_attempts_total", "Wifi Transmission Attempts", labelA, nil),
MacFilterRejections: prometheus.NewDesc(ns+"stat_mac_filter_rejects_total", "MAC Filter Rejections", labelA, nil),
// N each - 1 per Virtual AP (VAP)
VAPCcq: prometheus.NewDesc(ns+"vap_ccq", "VAP Client Connection Quality", labelV, nil),
VAPMacFilterRejections: prometheus.NewDesc(ns+"vap_mac_filter_rejects_total", "VAP MAC Filter Rejections", labelV, nil),
VAPNumSatisfactionSta: prometheus.NewDesc(ns+"vap_satisfaction_stations", "VAP Number Satisifaction Stations", labelV, nil),
VAPAvgClientSignal: prometheus.NewDesc(ns+"vap_average_client_signal", "VAP Average Client Signal", labelV, nil),
VAPSatisfaction: prometheus.NewDesc(ns+"vap_satisfaction_percent", "VAP Satisfaction", labelV, nil),
VAPSatisfactionNow: prometheus.NewDesc(ns+"vap_satisfaction_now_percent", "VAP Satisfaction Now", labelV, nil),
VAPDNSAvgLatency: prometheus.NewDesc(ns+"vap_dns_latency_average_seconds", "VAP DNS Latency Average", labelV, nil),
VAPRxBytes: prometheus.NewDesc(ns+"vap_receive_bytes_total", "VAP Bytes Received", labelV, nil),
VAPRxCrypts: prometheus.NewDesc(ns+"vap_receive_crypts_total", "VAP Crypts Received", labelV, nil),
VAPRxDropped: prometheus.NewDesc(ns+"vap_receive_dropped_total", "VAP Dropped Received", labelV, nil),
VAPRxErrors: prometheus.NewDesc(ns+"vap_receive_errors_total", "VAP Errors Received", labelV, nil),
VAPRxFrags: prometheus.NewDesc(ns+"vap_receive_frags_total", "VAP Frags Received", labelV, nil),
VAPRxNwids: prometheus.NewDesc(ns+"vap_receive_nwids_total", "VAP Nwids Received", labelV, nil),
VAPRxPackets: prometheus.NewDesc(ns+"vap_receive_packets_total", "VAP Packets Received", labelV, nil),
VAPTxBytes: prometheus.NewDesc(ns+"vap_transmit_bytes_total", "VAP Bytes Transmitted", labelV, nil),
VAPTxDropped: prometheus.NewDesc(ns+"vap_transmit_dropped_total", "VAP Dropped Transmitted", labelV, nil),
VAPTxErrors: prometheus.NewDesc(ns+"vap_transmit_errors_total", "VAP Errors Transmitted", labelV, nil),
VAPTxPackets: prometheus.NewDesc(ns+"vap_transmit_packets_total", "VAP Packets Transmitted", labelV, nil),
VAPTxPower: prometheus.NewDesc(ns+"vap_transmit_power", "VAP Transmit Power", labelV, nil),
VAPTxRetries: prometheus.NewDesc(ns+"vap_transmit_retries_total", "VAP Retries Transmitted", labelV, nil),
VAPTxCombinedRetries: prometheus.NewDesc(ns+"vap_transmit_retries_combined_total", "VAP Retries Combined Transmitted", labelV, nil),
VAPTxDataMpduBytes: prometheus.NewDesc(ns+"vap_data_mpdu_transmit_bytes_total", "VAP Data MPDU Bytes Transmitted", labelV, nil),
VAPTxRtsRetries: prometheus.NewDesc(ns+"vap_transmit_rts_retries_total", "VAP RTS Retries Transmitted", labelV, nil),
VAPTxSuccess: prometheus.NewDesc(ns+"vap_transmit_success_total", "VAP Success Transmits", labelV, nil),
VAPTxTotal: prometheus.NewDesc(ns+"vap_transmit_total", "VAP Transmit Total", labelV, nil),
VAPTxGoodbytes: prometheus.NewDesc(ns+"vap_transmit_goodbyes", "VAP Goodbyes Transmitted", labelV, nil),
VAPTxLatAvg: prometheus.NewDesc(ns+"vap_transmit_latency_average_seconds", "VAP Latency Average Transmit", labelV, nil),
VAPTxLatMax: prometheus.NewDesc(ns+"vap_transmit_latency_maximum_seconds", "VAP Latency Maximum Transmit", labelV, nil),
VAPTxLatMin: prometheus.NewDesc(ns+"vap_transmit_latency_minimum_seconds", "VAP Latency Minimum Transmit", labelV, nil),
VAPRxGoodbytes: prometheus.NewDesc(ns+"vap_receive_goodbyes", "VAP Goodbyes Received", labelV, nil),
VAPRxLatAvg: prometheus.NewDesc(ns+"vap_receive_latency_average_seconds", "VAP Latency Average Receive", labelV, nil),
VAPRxLatMax: prometheus.NewDesc(ns+"vap_receive_latency_maximum_seconds", "VAP Latency Maximum Receive", labelV, nil),
VAPRxLatMin: prometheus.NewDesc(ns+"vap_receive_latency_minimum_seconds", "VAP Latency Minimum Receive", labelV, nil),
VAPWifiTxLatencyMovAvg: prometheus.NewDesc(ns+"vap_transmit_latency_moving_avg_seconds", "VAP Latency Moving Average Tramsit", labelV, nil),
VAPWifiTxLatencyMovMax: prometheus.NewDesc(ns+"vap_transmit_latency_moving_max_seconds", "VAP Latency Moving Maximum Tramsit", labelV, nil),
VAPWifiTxLatencyMovMin: prometheus.NewDesc(ns+"vap_transmit_latency_moving_min_seconds", "VAP Latency Moving Minimum Tramsit", labelV, nil),
VAPWifiTxLatencyMovTotal: prometheus.NewDesc(ns+"vap_transmit_latency_moving_total", "VAP Latency Moving Total Tramsit", labelV, nil),
VAPWifiTxLatencyMovCount: prometheus.NewDesc(ns+"vap_transmit_latency_moving_count", "VAP Latency Moving Count Tramsit", labelV, nil),
// N each - 1 per Radio. 1-4 radios per AP usually
RadioCurrentAntennaGain: prometheus.NewDesc(ns+"radio_current_antenna_gain", "Radio Current Antenna Gain", labelR, nil),
RadioHt: prometheus.NewDesc(ns+"radio_ht", "Radio HT", labelR, nil),
RadioMaxTxpower: prometheus.NewDesc(ns+"radio_max_transmit_power", "Radio Maximum Transmit Power", labelR, nil),
RadioMinTxpower: prometheus.NewDesc(ns+"radio_min_transmit_power", "Radio Minimum Transmit Power", labelR, nil),
RadioNss: prometheus.NewDesc(ns+"radio_nss", "Radio Nss", labelR, nil),
RadioRadioCaps: prometheus.NewDesc(ns+"radio_caps", "Radio Capabilities", labelR, nil),
RadioTxPower: prometheus.NewDesc(ns+"radio_transmit_power", "Radio Transmit Power", labelR, nil),
RadioAstBeXmit: prometheus.NewDesc(ns+"radio_ast_be_xmit", "Radio AstBe Transmit", labelR, nil),
RadioChannel: prometheus.NewDesc(ns+"radio_channel", "Radio Channel", labelR, nil),
RadioCuSelfRx: prometheus.NewDesc(ns+"radio_channel_utilization_receive", "Radio Channel Utilization Receive", labelR, nil),
RadioCuSelfTx: prometheus.NewDesc(ns+"radio_channel_utilization_transmit", "Radio Channel Utilization Transmit", labelR, nil),
RadioCuTotal: prometheus.NewDesc(ns+"radio_channel_utilization_total", "Radio Channel Utilization", labelR, nil),
RadioExtchannel: prometheus.NewDesc(ns+"radio_ext_channel", "Radio Ext Channel", labelR, nil),
RadioGain: prometheus.NewDesc(ns+"radio_gain", "Radio Gain", labelR, nil),
RadioGuestNumSta: prometheus.NewDesc(ns+"radio_guest_stations", "Radio Guest Station Count", labelR, nil),
RadioNumSta: prometheus.NewDesc(ns+"radio_stations", "Radio Total Station Count", labelR, nil),
RadioUserNumSta: prometheus.NewDesc(ns+"radio_user_stations", "Radio User Station Count", labelR, nil),
RadioTxPackets: prometheus.NewDesc(ns+"radio_transmit_packets_total", "Radio Transmitted Packets", labelR, nil),
RadioTxRetries: prometheus.NewDesc(ns+"radio_transmit_retries_total", "Radio Transmit Retries", labelR, nil),
}
}
func (u *promUnifi) exportUAP(r report, d *unifi.UAP) {
labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
// Wireless System Data.
r.send([]*metric{
{u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
{u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
{u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
{u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
{u.Device.BytesD, prometheus.CounterValue, d.BytesD, labels}, // not sure if these 3 Ds are counters or gauges.
{u.Device.TxBytesD, prometheus.CounterValue, d.TxBytesD, labels}, // not sure if these 3 Ds are counters or gauges.
{u.Device.RxBytesD, prometheus.CounterValue, d.RxBytesD, labels}, // not sure if these 3 Ds are counters or gauges.
{u.Device.BytesR, prometheus.GaugeValue, d.BytesR, labels},
{u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
{u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
{u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
{u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
{u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
{u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
{u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
{u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
{u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
{u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
{u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
})
u.exportUAPstats(r, labels, d.Stat.Ap)
u.exportVAPtable(r, labels, d.VapTable)
u.exportRadtable(r, labels, d.RadioTable, d.RadioTableStats)
}
func (u *promUnifi) exportUAPstats(r report, labels []string, ap *unifi.Ap) {
// labelA := append([]string{"all"}, labels[2:]...)
labelU := append([]string{"user"}, labels[6:]...)
labelG := append([]string{"guest"}, labels[6:]...)
r.send([]*metric{
/* // all
{u.UAP.ApWifiTxDropped, prometheus.CounterValue, ap.WifiTxDropped, labelA},
{u.UAP.ApRxErrors, prometheus.CounterValue, ap.RxErrors, labelA},
{u.UAP.ApRxDropped, prometheus.CounterValue, ap.RxDropped, labelA},
{u.UAP.ApRxFrags, prometheus.CounterValue, ap.RxFrags, labelA},
{u.UAP.ApRxCrypts, prometheus.CounterValue, ap.RxCrypts, labelA},
{u.UAP.ApTxPackets, prometheus.CounterValue, ap.TxPackets, labelA},
{u.UAP.ApTxBytes, prometheus.CounterValue, ap.TxBytes, labelA},
{u.UAP.ApTxErrors, prometheus.CounterValue, ap.TxErrors, labelA},
{u.UAP.ApTxDropped, prometheus.CounterValue, ap.TxDropped, labelA},
{u.UAP.ApTxRetries, prometheus.CounterValue, ap.TxRetries, labelA},
{u.UAP.ApRxPackets, prometheus.CounterValue, ap.RxPackets, labelA},
{u.UAP.ApRxBytes, prometheus.CounterValue, ap.RxBytes, labelA},
{u.UAP.WifiTxAttempts, prometheus.CounterValue, ap.WifiTxAttempts, labelA},
{u.UAP.MacFilterRejections, prometheus.CounterValue, ap.MacFilterRejections, labelA},
*/
// user
{u.UAP.ApWifiTxDropped, prometheus.CounterValue, ap.UserWifiTxDropped, labelU},
{u.UAP.ApRxErrors, prometheus.CounterValue, ap.UserRxErrors, labelU},
{u.UAP.ApRxDropped, prometheus.CounterValue, ap.UserRxDropped, labelU},
{u.UAP.ApRxFrags, prometheus.CounterValue, ap.UserRxFrags, labelU},
{u.UAP.ApRxCrypts, prometheus.CounterValue, ap.UserRxCrypts, labelU},
{u.UAP.ApTxPackets, prometheus.CounterValue, ap.UserTxPackets, labelU},
{u.UAP.ApTxBytes, prometheus.CounterValue, ap.UserTxBytes, labelU},
{u.UAP.ApTxErrors, prometheus.CounterValue, ap.UserTxErrors, labelU},
{u.UAP.ApTxDropped, prometheus.CounterValue, ap.UserTxDropped, labelU},
{u.UAP.ApTxRetries, prometheus.CounterValue, ap.UserTxRetries, labelU},
{u.UAP.ApRxPackets, prometheus.CounterValue, ap.UserRxPackets, labelU},
{u.UAP.ApRxBytes, prometheus.CounterValue, ap.UserRxBytes, labelU},
{u.UAP.WifiTxAttempts, prometheus.CounterValue, ap.UserWifiTxAttempts, labelU},
{u.UAP.MacFilterRejections, prometheus.CounterValue, ap.UserMacFilterRejections, labelU},
// guest
{u.UAP.ApWifiTxDropped, prometheus.CounterValue, ap.GuestWifiTxDropped, labelG},
{u.UAP.ApRxErrors, prometheus.CounterValue, ap.GuestRxErrors, labelG},
{u.UAP.ApRxDropped, prometheus.CounterValue, ap.GuestRxDropped, labelG},
{u.UAP.ApRxFrags, prometheus.CounterValue, ap.GuestRxFrags, labelG},
{u.UAP.ApRxCrypts, prometheus.CounterValue, ap.GuestRxCrypts, labelG},
{u.UAP.ApTxPackets, prometheus.CounterValue, ap.GuestTxPackets, labelG},
{u.UAP.ApTxBytes, prometheus.CounterValue, ap.GuestTxBytes, labelG},
{u.UAP.ApTxErrors, prometheus.CounterValue, ap.GuestTxErrors, labelG},
{u.UAP.ApTxDropped, prometheus.CounterValue, ap.GuestTxDropped, labelG},
{u.UAP.ApTxRetries, prometheus.CounterValue, ap.GuestTxRetries, labelG},
{u.UAP.ApRxPackets, prometheus.CounterValue, ap.GuestRxPackets, labelG},
{u.UAP.ApRxBytes, prometheus.CounterValue, ap.GuestRxBytes, labelG},
{u.UAP.WifiTxAttempts, prometheus.CounterValue, ap.GuestWifiTxAttempts, labelG},
{u.UAP.MacFilterRejections, prometheus.CounterValue, ap.GuestMacFilterRejections, labelG},
})
}
func (u *promUnifi) exportVAPtable(r report, labels []string, vt unifi.VapTable) {
// vap table stats
for _, v := range vt {
if !v.Up.Val {
continue
}
labelV := append([]string{v.Name, v.Bssid, v.Radio, v.RadioName, v.Essid, v.Usage}, labels[6:]...)
r.send([]*metric{
{u.UAP.VAPCcq, prometheus.GaugeValue, v.Ccq / 10, labelV},
{u.UAP.VAPMacFilterRejections, prometheus.CounterValue, v.MacFilterRejections, labelV},
{u.UAP.VAPNumSatisfactionSta, prometheus.GaugeValue, v.NumSatisfactionSta, labelV},
{u.UAP.VAPAvgClientSignal, prometheus.GaugeValue, v.AvgClientSignal, labelV},
{u.UAP.VAPSatisfaction, prometheus.GaugeValue, v.Satisfaction, labelV},
{u.UAP.VAPSatisfactionNow, prometheus.GaugeValue, v.SatisfactionNow, labelV},
{u.UAP.VAPDNSAvgLatency, prometheus.GaugeValue, v.DNSAvgLatency.Val / 1000, labelV},
{u.UAP.VAPRxBytes, prometheus.CounterValue, v.RxBytes, labelV},
{u.UAP.VAPRxCrypts, prometheus.CounterValue, v.RxCrypts, labelV},
{u.UAP.VAPRxDropped, prometheus.CounterValue, v.RxDropped, labelV},
{u.UAP.VAPRxErrors, prometheus.CounterValue, v.RxErrors, labelV},
{u.UAP.VAPRxFrags, prometheus.CounterValue, v.RxFrags, labelV},
{u.UAP.VAPRxNwids, prometheus.CounterValue, v.RxNwids, labelV},
{u.UAP.VAPRxPackets, prometheus.CounterValue, v.RxPackets, labelV},
{u.UAP.VAPTxBytes, prometheus.CounterValue, v.TxBytes, labelV},
{u.UAP.VAPTxDropped, prometheus.CounterValue, v.TxDropped, labelV},
{u.UAP.VAPTxErrors, prometheus.CounterValue, v.TxErrors, labelV},
{u.UAP.VAPTxPackets, prometheus.CounterValue, v.TxPackets, labelV},
{u.UAP.VAPTxPower, prometheus.GaugeValue, v.TxPower, labelV},
{u.UAP.VAPTxRetries, prometheus.CounterValue, v.TxRetries, labelV},
{u.UAP.VAPTxCombinedRetries, prometheus.CounterValue, v.TxCombinedRetries, labelV},
{u.UAP.VAPTxDataMpduBytes, prometheus.CounterValue, v.TxDataMpduBytes, labelV},
{u.UAP.VAPTxRtsRetries, prometheus.CounterValue, v.TxRtsRetries, labelV},
{u.UAP.VAPTxTotal, prometheus.CounterValue, v.TxTotal, labelV},
{u.UAP.VAPTxGoodbytes, prometheus.CounterValue, v.TxTCPStats.Goodbytes, labelV},
{u.UAP.VAPTxLatAvg, prometheus.GaugeValue, v.TxTCPStats.LatAvg.Val / 1000, labelV},
{u.UAP.VAPTxLatMax, prometheus.GaugeValue, v.TxTCPStats.LatMax.Val / 1000, labelV},
{u.UAP.VAPTxLatMin, prometheus.GaugeValue, v.TxTCPStats.LatMin.Val / 1000, labelV},
{u.UAP.VAPRxGoodbytes, prometheus.CounterValue, v.RxTCPStats.Goodbytes, labelV},
{u.UAP.VAPRxLatAvg, prometheus.GaugeValue, v.RxTCPStats.LatAvg.Val / 1000, labelV},
{u.UAP.VAPRxLatMax, prometheus.GaugeValue, v.RxTCPStats.LatMax.Val / 1000, labelV},
{u.UAP.VAPRxLatMin, prometheus.GaugeValue, v.RxTCPStats.LatMin.Val / 1000, labelV},
{u.UAP.VAPWifiTxLatencyMovAvg, prometheus.GaugeValue, v.WifiTxLatencyMov.Avg.Val / 1000, labelV},
{u.UAP.VAPWifiTxLatencyMovMax, prometheus.GaugeValue, v.WifiTxLatencyMov.Max.Val / 1000, labelV},
{u.UAP.VAPWifiTxLatencyMovMin, prometheus.GaugeValue, v.WifiTxLatencyMov.Min.Val / 1000, labelV},
{u.UAP.VAPWifiTxLatencyMovTotal, prometheus.CounterValue, v.WifiTxLatencyMov.Total, labelV}, // not sure if gauge or counter.
{u.UAP.VAPWifiTxLatencyMovCount, prometheus.CounterValue, v.WifiTxLatencyMov.TotalCount, labelV}, // not sure if gauge or counter.
})
}
}
func (u *promUnifi) exportRadtable(r report, labels []string, rt unifi.RadioTable, rts unifi.RadioTableStats) {
// radio table
for _, p := range rt {
labelR := append([]string{p.Name, p.Radio}, labels[6:]...)
r.send([]*metric{
{u.UAP.RadioCurrentAntennaGain, prometheus.GaugeValue, p.CurrentAntennaGain, labelR},
{u.UAP.RadioHt, prometheus.GaugeValue, p.Ht, labelR},
{u.UAP.RadioMaxTxpower, prometheus.GaugeValue, p.MaxTxpower, labelR},
{u.UAP.RadioMinTxpower, prometheus.GaugeValue, p.MinTxpower, labelR},
{u.UAP.RadioNss, prometheus.GaugeValue, p.Nss, labelR},
{u.UAP.RadioRadioCaps, prometheus.GaugeValue, p.RadioCaps, labelR},
})
// combine radio table with radio stats table.
for _, t := range rts {
if t.Name != p.Name {
continue
}
r.send([]*metric{
{u.UAP.RadioTxPower, prometheus.GaugeValue, t.TxPower, labelR},
{u.UAP.RadioAstBeXmit, prometheus.GaugeValue, t.AstBeXmit, labelR},
{u.UAP.RadioChannel, prometheus.GaugeValue, t.Channel, labelR},
{u.UAP.RadioCuSelfRx, prometheus.GaugeValue, t.CuSelfRx, labelR},
{u.UAP.RadioCuSelfTx, prometheus.GaugeValue, t.CuSelfTx, labelR},
{u.UAP.RadioCuTotal, prometheus.GaugeValue, t.CuTotal, labelR},
{u.UAP.RadioExtchannel, prometheus.GaugeValue, t.Extchannel, labelR},
{u.UAP.RadioGain, prometheus.GaugeValue, t.Gain, labelR},
{u.UAP.RadioGuestNumSta, prometheus.GaugeValue, t.GuestNumSta, labelR},
{u.UAP.RadioNumSta, prometheus.GaugeValue, t.NumSta, labelR},
{u.UAP.RadioUserNumSta, prometheus.GaugeValue, t.UserNumSta, labelR},
{u.UAP.RadioTxPackets, prometheus.CounterValue, t.TxPackets, labelR},
{u.UAP.RadioTxRetries, prometheus.CounterValue, t.TxRetries, labelR},
})
}
}
}
+107
View File
@@ -0,0 +1,107 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
// These are shared by all four device types: UDM, UAP, USG, USW
type unifiDevice struct {
Uptime *prometheus.Desc
Temperature *prometheus.Desc // sw only
TotalMaxPower *prometheus.Desc // sw only
FanLevel *prometheus.Desc // sw only
TotalTxBytes *prometheus.Desc
TotalRxBytes *prometheus.Desc
TotalBytes *prometheus.Desc
BytesR *prometheus.Desc // ap only
BytesD *prometheus.Desc // ap only
Bytes *prometheus.Desc // ap only
TxBytesD *prometheus.Desc // ap only
RxBytesD *prometheus.Desc // ap only
NumSta *prometheus.Desc
UserNumSta *prometheus.Desc
GuestNumSta *prometheus.Desc
NumDesktop *prometheus.Desc // gw only
NumMobile *prometheus.Desc // gw only
NumHandheld *prometheus.Desc // gw only
Loadavg1 *prometheus.Desc
Loadavg5 *prometheus.Desc
Loadavg15 *prometheus.Desc
MemBuffer *prometheus.Desc
MemTotal *prometheus.Desc
MemUsed *prometheus.Desc
CPU *prometheus.Desc
Mem *prometheus.Desc
}
func descDevice(ns string) *unifiDevice {
labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
return &unifiDevice{
Uptime: prometheus.NewDesc(ns+"uptime", "Uptime", labels, nil),
Temperature: prometheus.NewDesc(ns+"temperature_celsius", "Temperature", labels, nil),
TotalMaxPower: prometheus.NewDesc(ns+"max_power_total", "Total Max Power", labels, nil),
FanLevel: prometheus.NewDesc(ns+"fan_level", "Fan Level", labels, nil),
TotalTxBytes: prometheus.NewDesc(ns+"transmit_bytes_total", "Total Transmitted Bytes", labels, nil),
TotalRxBytes: prometheus.NewDesc(ns+"receive_bytes_total", "Total Received Bytes", labels, nil),
TotalBytes: prometheus.NewDesc(ns+"bytes_total", "Total Bytes Transferred", labels, nil),
BytesR: prometheus.NewDesc(ns+"rate_bytes", "Transfer Rate", labels, nil),
BytesD: prometheus.NewDesc(ns+"d_bytes", "Total Bytes D???", labels, nil),
Bytes: prometheus.NewDesc(ns+"transferred_bytes_total", "Bytes Transferred", labels, nil),
TxBytesD: prometheus.NewDesc(ns+"d_tranmsit_bytes", "Transmit Bytes D???", labels, nil),
RxBytesD: prometheus.NewDesc(ns+"d_receive_bytes", "Receive Bytes D???", labels, nil),
NumSta: prometheus.NewDesc(ns+"stations", "Number of Stations", labels, nil),
UserNumSta: prometheus.NewDesc(ns+"user_stations", "Number of User Stations", labels, nil),
GuestNumSta: prometheus.NewDesc(ns+"guest_stations", "Number of Guest Stations", labels, nil),
NumDesktop: prometheus.NewDesc(ns+"desktops", "Number of Desktops", labels, nil),
NumMobile: prometheus.NewDesc(ns+"mobile", "Number of Mobiles", labels, nil),
NumHandheld: prometheus.NewDesc(ns+"handheld", "Number of Handhelds", labels, nil),
Loadavg1: prometheus.NewDesc(ns+"load_average_1", "System Load Average 1 Minute", labels, nil),
Loadavg5: prometheus.NewDesc(ns+"load_average_5", "System Load Average 5 Minutes", labels, nil),
Loadavg15: prometheus.NewDesc(ns+"load_average_15", "System Load Average 15 Minutes", labels, nil),
MemUsed: prometheus.NewDesc(ns+"memory_used_bytes", "System Memory Used", labels, nil),
MemTotal: prometheus.NewDesc(ns+"memory_installed_bytes", "System Installed Memory", labels, nil),
MemBuffer: prometheus.NewDesc(ns+"memory_buffer_bytes", "System Memory Buffer", labels, nil),
CPU: prometheus.NewDesc(ns+"cpu_utilization_percent", "System CPU % Utilized", labels, nil),
Mem: prometheus.NewDesc(ns+"memory_utilization_percent", "System Memory % Utilized", labels, nil),
}
}
// UDM is a collection of stats from USG, USW and UAP. It has no unique stats.
func (u *promUnifi) exportUDM(r report, d *unifi.UDM) {
labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
// Dream Machine System Data.
r.send([]*metric{
{u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
{u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
{u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
{u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
{u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
{u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
{u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
{u.Device.NumDesktop, prometheus.GaugeValue, d.NumDesktop, labels},
{u.Device.NumMobile, prometheus.GaugeValue, d.NumMobile, labels},
{u.Device.NumHandheld, prometheus.GaugeValue, d.NumHandheld, labels},
{u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
{u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
{u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
{u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
{u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
{u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
{u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
{u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
})
// Switch Data
u.exportUSWstats(r, labels, d.Stat.Sw)
u.exportPortTable(r, labels, d.PortTable)
// Gateway Data
u.exportUSGstats(r, labels, d.Stat.Gw, d.SpeedtestStatus)
u.exportWANPorts(r, labels, d.Wan1, d.Wan2)
// Wireless Data - UDM (non-pro) only
if d.Stat.Ap != nil && d.VapTable != nil {
u.exportUAPstats(r, labels, d.Stat.Ap)
u.exportVAPtable(r, labels, *d.VapTable)
u.exportRadtable(r, labels, *d.RadioTable, *d.RadioTableStats)
}
}
+145
View File
@@ -0,0 +1,145 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
type usg struct {
WanRxPackets *prometheus.Desc
WanRxBytes *prometheus.Desc
WanRxDropped *prometheus.Desc
WanRxErrors *prometheus.Desc
WanTxPackets *prometheus.Desc
WanTxBytes *prometheus.Desc
LanRxPackets *prometheus.Desc
LanRxBytes *prometheus.Desc
LanRxDropped *prometheus.Desc
LanTxPackets *prometheus.Desc
LanTxBytes *prometheus.Desc
WanRxBroadcast *prometheus.Desc
WanRxBytesR *prometheus.Desc
WanRxMulticast *prometheus.Desc
WanSpeed *prometheus.Desc
WanTxBroadcast *prometheus.Desc
WanTxBytesR *prometheus.Desc
WanTxDropped *prometheus.Desc
WanTxErrors *prometheus.Desc
WanTxMulticast *prometheus.Desc
WanBytesR *prometheus.Desc
Latency *prometheus.Desc
Runtime *prometheus.Desc
XputDownload *prometheus.Desc
XputUpload *prometheus.Desc
}
func descUSG(ns string) *usg {
// labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
// labelWan := append([]string{"port"}, labels[6:]...)
labels := []string{"port", "site_name", "name"}
return &usg{
WanRxPackets: prometheus.NewDesc(ns+"wan_receive_packets_total", "WAN Receive Packets Total", labels, nil),
WanRxBytes: prometheus.NewDesc(ns+"wan_receive_bytes_total", "WAN Receive Bytes Total", labels, nil),
WanRxDropped: prometheus.NewDesc(ns+"wan_receive_dropped_total", "WAN Receive Dropped Total", labels, nil),
WanRxErrors: prometheus.NewDesc(ns+"wan_receive_errors_total", "WAN Receive Errors Total", labels, nil),
WanTxPackets: prometheus.NewDesc(ns+"wan_transmit_packets_total", "WAN Transmit Packets Total", labels, nil),
WanTxBytes: prometheus.NewDesc(ns+"wan_transmit_bytes_total", "WAN Transmit Bytes Total", labels, nil),
WanRxBroadcast: prometheus.NewDesc(ns+"wan_receive_broadcast_total", "WAN Receive Broadcast Total", labels, nil),
WanRxBytesR: prometheus.NewDesc(ns+"wan_receive_rate_bytes", "WAN Receive Bytes Rate", labels, nil),
WanRxMulticast: prometheus.NewDesc(ns+"wan_receive_multicast_total", "WAN Receive Multicast Total", labels, nil),
WanSpeed: prometheus.NewDesc(ns+"wan_speed_bps", "WAN Speed", labels, nil),
WanTxBroadcast: prometheus.NewDesc(ns+"wan_transmit_broadcast_total", "WAN Transmit Broadcast Total", labels, nil),
WanTxBytesR: prometheus.NewDesc(ns+"wan_transmit_rate_bytes", "WAN Transmit Bytes Rate", labels, nil),
WanTxDropped: prometheus.NewDesc(ns+"wan_transmit_dropped_total", "WAN Transmit Dropped Total", labels, nil),
WanTxErrors: prometheus.NewDesc(ns+"wan_transmit_errors_total", "WAN Transmit Errors Total", labels, nil),
WanTxMulticast: prometheus.NewDesc(ns+"wan_transmit_multicast_total", "WAN Transmit Multicast Total", labels, nil),
WanBytesR: prometheus.NewDesc(ns+"wan_rate_bytes", "WAN Transfer Rate", labels, nil),
LanRxPackets: prometheus.NewDesc(ns+"lan_receive_packets_total", "LAN Receive Packets Total", labels, nil),
LanRxBytes: prometheus.NewDesc(ns+"lan_receive_bytes_total", "LAN Receive Bytes Total", labels, nil),
LanRxDropped: prometheus.NewDesc(ns+"lan_receive_dropped_total", "LAN Receive Dropped Total", labels, nil),
LanTxPackets: prometheus.NewDesc(ns+"lan_transmit_packets_total", "LAN Transmit Packets Total", labels, nil),
LanTxBytes: prometheus.NewDesc(ns+"lan_transmit_bytes_total", "LAN Transmit Bytes Total", labels, nil),
Latency: prometheus.NewDesc(ns+"speedtest_latency_seconds", "Speedtest Latency", labels, nil),
Runtime: prometheus.NewDesc(ns+"speedtest_runtime", "Speedtest Run Time", labels, nil),
XputDownload: prometheus.NewDesc(ns+"speedtest_download", "Speedtest Download Rate", labels, nil),
XputUpload: prometheus.NewDesc(ns+"speedtest_upload", "Speedtest Upload Rate", labels, nil),
}
}
func (u *promUnifi) exportUSG(r report, d *unifi.USG) {
labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
// Gateway System Data.
r.send([]*metric{
{u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
{u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
{u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
{u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
{u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
{u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
{u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
{u.Device.NumDesktop, prometheus.GaugeValue, d.NumDesktop, labels},
{u.Device.NumMobile, prometheus.GaugeValue, d.NumMobile, labels},
{u.Device.NumHandheld, prometheus.GaugeValue, d.NumHandheld, labels},
{u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
{u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
{u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
{u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
{u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
{u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
{u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
{u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
})
u.exportWANPorts(r, labels, d.Wan1, d.Wan2)
u.exportUSGstats(r, labels, d.Stat.Gw, d.SpeedtestStatus)
}
func (u *promUnifi) exportUSGstats(r report, labels []string, gw *unifi.Gw, st unifi.SpeedtestStatus) {
labelLan := []string{"lan", labels[6], labels[7]}
labelWan := []string{"all", labels[6], labels[7]}
r.send([]*metric{
/* // Combined Port Stats - not really needed. sum() the others instead.
{u.USG.WanRxPackets, prometheus.CounterValue, gw.WanRxPackets, labelWan},
{u.USG.WanRxBytes, prometheus.CounterValue, gw.WanRxBytes, labelWan},
{u.USG.WanRxDropped, prometheus.CounterValue, gw.WanRxDropped, labelWan},
{u.USG.WanTxPackets, prometheus.CounterValue, gw.WanTxPackets, labelWan},
{u.USG.WanTxBytes, prometheus.CounterValue, gw.WanTxBytes, labelWan},
{u.USG.WanRxErrors, prometheus.CounterValue, gw.WanRxErrors, labelWan},
*/
{u.USG.LanRxPackets, prometheus.CounterValue, gw.LanRxPackets, labelLan},
{u.USG.LanRxBytes, prometheus.CounterValue, gw.LanRxBytes, labelLan},
{u.USG.LanTxPackets, prometheus.CounterValue, gw.LanTxPackets, labelLan},
{u.USG.LanTxBytes, prometheus.CounterValue, gw.LanTxBytes, labelLan},
{u.USG.LanRxDropped, prometheus.CounterValue, gw.LanRxDropped, labelLan},
// Speed Test Stats
{u.USG.Latency, prometheus.GaugeValue, st.Latency.Val / 1000, labelWan},
{u.USG.Runtime, prometheus.GaugeValue, st.Runtime, labelWan},
{u.USG.XputDownload, prometheus.GaugeValue, st.XputDownload, labelWan},
{u.USG.XputUpload, prometheus.GaugeValue, st.XputUpload, labelWan},
})
}
func (u *promUnifi) exportWANPorts(r report, labels []string, wans ...unifi.Wan) {
for _, wan := range wans {
if !wan.Up.Val {
continue // only record UP interfaces.
}
labelWan := []string{wan.Name, labels[6], labels[7]}
r.send([]*metric{
{u.USG.WanRxPackets, prometheus.CounterValue, wan.RxPackets, labelWan},
{u.USG.WanRxBytes, prometheus.CounterValue, wan.RxBytes, labelWan},
{u.USG.WanRxDropped, prometheus.CounterValue, wan.RxDropped, labelWan},
{u.USG.WanRxErrors, prometheus.CounterValue, wan.RxErrors, labelWan},
{u.USG.WanTxPackets, prometheus.CounterValue, wan.TxPackets, labelWan},
{u.USG.WanTxBytes, prometheus.CounterValue, wan.TxBytes, labelWan},
{u.USG.WanRxBroadcast, prometheus.CounterValue, wan.RxBroadcast, labelWan},
{u.USG.WanRxMulticast, prometheus.CounterValue, wan.RxMulticast, labelWan},
{u.USG.WanSpeed, prometheus.CounterValue, wan.Speed.Val * 1000000, labelWan},
{u.USG.WanTxBroadcast, prometheus.CounterValue, wan.TxBroadcast, labelWan},
{u.USG.WanTxBytesR, prometheus.CounterValue, wan.TxBytesR, labelWan},
{u.USG.WanTxDropped, prometheus.CounterValue, wan.TxDropped, labelWan},
{u.USG.WanTxErrors, prometheus.CounterValue, wan.TxErrors, labelWan},
{u.USG.WanTxMulticast, prometheus.CounterValue, wan.TxMulticast, labelWan},
{u.USG.WanBytesR, prometheus.GaugeValue, wan.BytesR, labelWan},
})
}
}
+181
View File
@@ -0,0 +1,181 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
type usw struct {
// Switch "total" traffic stats
SwRxPackets *prometheus.Desc
SwRxBytes *prometheus.Desc
SwRxErrors *prometheus.Desc
SwRxDropped *prometheus.Desc
SwRxCrypts *prometheus.Desc
SwRxFrags *prometheus.Desc
SwTxPackets *prometheus.Desc
SwTxBytes *prometheus.Desc
SwTxErrors *prometheus.Desc
SwTxDropped *prometheus.Desc
SwTxRetries *prometheus.Desc
SwRxMulticast *prometheus.Desc
SwRxBroadcast *prometheus.Desc
SwTxMulticast *prometheus.Desc
SwTxBroadcast *prometheus.Desc
SwBytes *prometheus.Desc
// Port data.
PoeCurrent *prometheus.Desc
PoePower *prometheus.Desc
PoeVoltage *prometheus.Desc
RxBroadcast *prometheus.Desc
RxBytes *prometheus.Desc
RxBytesR *prometheus.Desc
RxDropped *prometheus.Desc
RxErrors *prometheus.Desc
RxMulticast *prometheus.Desc
RxPackets *prometheus.Desc
Satisfaction *prometheus.Desc
Speed *prometheus.Desc
TxBroadcast *prometheus.Desc
TxBytes *prometheus.Desc
TxBytesR *prometheus.Desc
TxDropped *prometheus.Desc
TxErrors *prometheus.Desc
TxMulticast *prometheus.Desc
TxPackets *prometheus.Desc
}
func descUSW(ns string) *usw {
pns := ns + "port_"
// labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
labelS := []string{"site_name", "name"} // labels[6:]
labelP := []string{"port_num", "port_name", "port_mac", "port_ip", "site_name", "name"}
return &usw{
SwRxPackets: prometheus.NewDesc(ns+"switch_receive_packets_total", "Switch Packets Received Total", labelS, nil),
SwRxBytes: prometheus.NewDesc(ns+"switch_receive_bytes_total", "Switch Bytes Received Total", labelS, nil),
SwRxErrors: prometheus.NewDesc(ns+"switch_receive_errors_total", "Switch Errors Received Total", labelS, nil),
SwRxDropped: prometheus.NewDesc(ns+"switch_receive_dropped_total", "Switch Dropped Received Total", labelS, nil),
SwRxCrypts: prometheus.NewDesc(ns+"switch_receive_crypts_total", "Switch Crypts Received Total", labelS, nil),
SwRxFrags: prometheus.NewDesc(ns+"switch_receive_frags_total", "Switch Frags Received Total", labelS, nil),
SwTxPackets: prometheus.NewDesc(ns+"switch_transmit_packets_total", "Switch Packets Transmit Total", labelS, nil),
SwTxBytes: prometheus.NewDesc(ns+"switch_transmit_bytes_total", "Switch Bytes Transmit Total", labelS, nil),
SwTxErrors: prometheus.NewDesc(ns+"switch_transmit_errors_total", "Switch Errors Transmit Total", labelS, nil),
SwTxDropped: prometheus.NewDesc(ns+"switch_transmit_dropped_total", "Switch Dropped Transmit Total", labelS, nil),
SwTxRetries: prometheus.NewDesc(ns+"switch_transmit_retries_total", "Switch Retries Transmit Total", labelS, nil),
SwRxMulticast: prometheus.NewDesc(ns+"switch_receive_multicast_total", "Switch Multicast Receive Total", labelS, nil),
SwRxBroadcast: prometheus.NewDesc(ns+"switch_receive_broadcast_total", "Switch Broadcast Receive Total", labelS, nil),
SwTxMulticast: prometheus.NewDesc(ns+"switch_transmit_multicast_total", "Switch Multicast Transmit Total", labelS, nil),
SwTxBroadcast: prometheus.NewDesc(ns+"switch_transmit_broadcast_total", "Switch Broadcast Transmit Total", labelS, nil),
SwBytes: prometheus.NewDesc(ns+"switch_bytes_total", "Switch Bytes Transferred Total", labelS, nil),
// per-port data
PoeCurrent: prometheus.NewDesc(pns+"poe_amperes", "POE Current", labelP, nil),
PoePower: prometheus.NewDesc(pns+"poe_watts", "POE Power", labelP, nil),
PoeVoltage: prometheus.NewDesc(pns+"poe_volts", "POE Voltage", labelP, nil),
RxBroadcast: prometheus.NewDesc(pns+"receive_broadcast_total", "Receive Broadcast", labelP, nil),
RxBytes: prometheus.NewDesc(pns+"receive_bytes_total", "Total Receive Bytes", labelP, nil),
RxBytesR: prometheus.NewDesc(pns+"receive_rate_bytes", "Receive Bytes Rate", labelP, nil),
RxDropped: prometheus.NewDesc(pns+"receive_dropped_total", "Total Receive Dropped", labelP, nil),
RxErrors: prometheus.NewDesc(pns+"receive_errors_total", "Total Receive Errors", labelP, nil),
RxMulticast: prometheus.NewDesc(pns+"receive_multicast_total", "Total Receive Multicast", labelP, nil),
RxPackets: prometheus.NewDesc(pns+"receive_packets_total", "Total Receive Packets", labelP, nil),
Satisfaction: prometheus.NewDesc(pns+"satisfaction_percent", "Satisfaction", labelP, nil),
Speed: prometheus.NewDesc(pns+"port_speed_bps", "Speed", labelP, nil),
TxBroadcast: prometheus.NewDesc(pns+"transmit_broadcast_total", "Total Transmit Broadcast", labelP, nil),
TxBytes: prometheus.NewDesc(pns+"transmit_bytes_total", "Total Transmit Bytes", labelP, nil),
TxBytesR: prometheus.NewDesc(pns+"transmit_rate_bytes", "Transmit Bytes Rate", labelP, nil),
TxDropped: prometheus.NewDesc(pns+"transmit_dropped_total", "Total Transmit Dropped", labelP, nil),
TxErrors: prometheus.NewDesc(pns+"transmit_errors_total", "Total Transmit Errors", labelP, nil),
TxMulticast: prometheus.NewDesc(pns+"transmit_multicast_total", "Total Tranmist Multicast", labelP, nil),
TxPackets: prometheus.NewDesc(pns+"transmit_packets_total", "Total Transmit Packets", labelP, nil),
}
}
func (u *promUnifi) exportUSW(r report, d *unifi.USW) {
labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
if d.HasTemperature.Val {
r.send([]*metric{{u.Device.Temperature, prometheus.GaugeValue, d.GeneralTemperature, labels}})
}
if d.HasFan.Val {
r.send([]*metric{{u.Device.FanLevel, prometheus.GaugeValue, d.FanLevel, labels}})
}
// Switch System Data.
r.send([]*metric{
{u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
{u.Device.TotalMaxPower, prometheus.GaugeValue, d.TotalMaxPower, labels},
{u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
{u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
{u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
{u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
{u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
{u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
{u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
{u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
{u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
{u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
{u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
{u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
{u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
{u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
})
u.exportPortTable(r, labels, d.PortTable)
u.exportUSWstats(r, labels, d.Stat.Sw)
}
func (u *promUnifi) exportUSWstats(r report, labels []string, sw *unifi.Sw) {
labelS := labels[6:]
r.send([]*metric{
{u.USW.SwRxPackets, prometheus.CounterValue, sw.RxPackets, labelS},
{u.USW.SwRxBytes, prometheus.CounterValue, sw.RxBytes, labelS},
{u.USW.SwRxErrors, prometheus.CounterValue, sw.RxErrors, labelS},
{u.USW.SwRxDropped, prometheus.CounterValue, sw.RxDropped, labelS},
{u.USW.SwRxCrypts, prometheus.CounterValue, sw.RxCrypts, labelS},
{u.USW.SwRxFrags, prometheus.CounterValue, sw.RxFrags, labelS},
{u.USW.SwTxPackets, prometheus.CounterValue, sw.TxPackets, labelS},
{u.USW.SwTxBytes, prometheus.CounterValue, sw.TxBytes, labelS},
{u.USW.SwTxErrors, prometheus.CounterValue, sw.TxErrors, labelS},
{u.USW.SwTxDropped, prometheus.CounterValue, sw.TxDropped, labelS},
{u.USW.SwTxRetries, prometheus.CounterValue, sw.TxRetries, labelS},
{u.USW.SwRxMulticast, prometheus.CounterValue, sw.RxMulticast, labelS},
{u.USW.SwRxBroadcast, prometheus.CounterValue, sw.RxBroadcast, labelS},
{u.USW.SwTxMulticast, prometheus.CounterValue, sw.TxMulticast, labelS},
{u.USW.SwTxBroadcast, prometheus.CounterValue, sw.TxBroadcast, labelS},
{u.USW.SwBytes, prometheus.CounterValue, sw.Bytes, labelS},
})
}
func (u *promUnifi) exportPortTable(r report, labels []string, pt []unifi.Port) {
// Per-port data on a switch
for _, p := range pt {
if !p.Up.Val {
continue
}
// Copy labels, and add four new ones.
labelP := []string{p.PortIdx.Txt, p.Name, p.Mac, p.IP, labels[6], labels[7]}
if p.PoeEnable.Val && p.PortPoe.Val {
r.send([]*metric{
{u.USW.PoeCurrent, prometheus.GaugeValue, p.PoeCurrent, labelP},
{u.USW.PoePower, prometheus.GaugeValue, p.PoePower, labelP},
{u.USW.PoeVoltage, prometheus.GaugeValue, p.PoeVoltage, labelP},
})
}
r.send([]*metric{
{u.USW.RxBroadcast, prometheus.CounterValue, p.RxBroadcast, labelP},
{u.USW.RxBytes, prometheus.CounterValue, p.RxBytes, labelP},
{u.USW.RxBytesR, prometheus.GaugeValue, p.RxBytesR, labelP},
{u.USW.RxDropped, prometheus.CounterValue, p.RxDropped, labelP},
{u.USW.RxErrors, prometheus.CounterValue, p.RxErrors, labelP},
{u.USW.RxMulticast, prometheus.CounterValue, p.RxMulticast, labelP},
{u.USW.RxPackets, prometheus.CounterValue, p.RxPackets, labelP},
{u.USW.Satisfaction, prometheus.GaugeValue, p.Satisfaction, labelP},
{u.USW.Speed, prometheus.GaugeValue, p.Speed.Val * 1000000, labelP},
{u.USW.TxBroadcast, prometheus.CounterValue, p.TxBroadcast, labelP},
{u.USW.TxBytes, prometheus.CounterValue, p.TxBytes, labelP},
{u.USW.TxBytesR, prometheus.GaugeValue, p.TxBytesR, labelP},
{u.USW.TxDropped, prometheus.CounterValue, p.TxDropped, labelP},
{u.USW.TxErrors, prometheus.CounterValue, p.TxErrors, labelP},
{u.USW.TxMulticast, prometheus.CounterValue, p.TxMulticast, labelP},
})
}
}