diff --git a/integrations/inputunifi/poller/config.go b/integrations/inputunifi/poller/config.go index a80ca239..c5df02ac 100644 --- a/integrations/inputunifi/poller/config.go +++ b/integrations/inputunifi/poller/config.go @@ -24,6 +24,7 @@ var Version = "development" const ( // App defaults in case they're missing from the config. + defaultNamespace = "unifi" defaultInterval = 30 * time.Second defaultInfluxDB = "unifi" defaultInfluxUser = "unifi" diff --git a/integrations/inputunifi/poller/prometheus.go b/integrations/inputunifi/poller/prometheus.go index 07eeb7a7..f0dea3ea 100644 --- a/integrations/inputunifi/poller/prometheus.go +++ b/integrations/inputunifi/poller/prometheus.go @@ -9,31 +9,33 @@ import ( // ExportMetrics updates the internal metrics provided via // HTTP at /metrics for prometheus collection. This is run by Prometheus. -func (u *UnifiPoller) ExportMetrics() *metrics.Metrics { +func (u *UnifiPoller) ExportMetrics() (*metrics.Metrics, 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") - return nil + return nil, err } } u.LastCheck = time.Now() m, err := u.CollectMetrics() if err != nil { u.LogErrorf("collecting metrics: %v", err) - return nil + return nil, err } u.AugmentMetrics(m) + return m, nil +} +// LogExportReport is called after prometheus exports metrics. This is run by Prometheus. +func (u *UnifiPoller) LogExportReport(m *metrics.Metrics, count int64) { idsMsg := "" if u.Config.CollectIDS { idsMsg = fmt.Sprintf(", IDS Events: %d, ", len(m.IDSList)) } u.Logf("UniFi Measurements Exported. Sites: %d, Clients: %d, "+ - "Wireless APs: %d, Gateways: %d, Switches: %d%s", + "Wireless APs: %d, Gateways: %d, Switches: %d%s, Metrics: %d", len(m.Sites), len(m.Clients), len(m.UAPs), - len(m.UDMs)+len(m.USGs), len(m.USWs), idsMsg) - - return m + len(m.UDMs)+len(m.USGs), len(m.USWs), idsMsg, count) } diff --git a/integrations/inputunifi/poller/start.go b/integrations/inputunifi/poller/start.go index 13f02621..7c0f89a9 100644 --- a/integrations/inputunifi/poller/start.go +++ b/integrations/inputunifi/poller/start.go @@ -101,10 +101,11 @@ func (u *UnifiPoller) Run() (err 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: "unifi", + Namespace: defaultNamespace, // XXX: pass this in from config. CollectFn: u.ExportMetrics, - ReportErrors: true, - CollectIDS: true, + LoggerFn: u.LogExportReport, + CollectIDS: u.Config.CollectIDS, + ReportErrors: true, // XXX: Does this need to be configurable? })) return http.ListenAndServe(u.Config.HTTPListen, nil) diff --git a/integrations/inputunifi/promunifi/collector.go b/integrations/inputunifi/promunifi/collector.go index 251d8bc3..791e65c7 100644 --- a/integrations/inputunifi/promunifi/collector.go +++ b/integrations/inputunifi/promunifi/collector.go @@ -22,7 +22,9 @@ type UnifiCollectorCnfg struct { ReportErrors bool // This function is passed to the Collect() method. The Collect method runs This // function to retreive the latest UniFi - CollectFn func() *metrics.Metrics + CollectFn func() (*metrics.Metrics, error) + // provide a logger function if you want to run a routine *after* prometheus checks in. + LoggerFn func(*metrics.Metrics, int64) // Setting this to true will enable IDS exports. CollectIDS bool } @@ -93,61 +95,46 @@ func (u *unifiCollector) Describe(ch chan<- *prometheus.Desc) { // Collect satisifes the prometheus Collector. This runs the input method to get // the current metrics (from another package) then exports them for prometheus. func (u *unifiCollector) Collect(ch chan<- prometheus.Metric) { - m := u.Config.CollectFn() - if m == nil { + var count int64 + m, err := u.Config.CollectFn() + if err != nil { + ch <- prometheus.NewInvalidMetric(prometheus.NewInvalidDesc(fmt.Errorf("metric fetch failed")), err) return } for _, asset := range m.Clients { - u.export(ch, u.exportClient(asset), m.TS) + count += u.export(ch, u.exportClient(asset), m.TS) } for _, asset := range m.Sites { - u.export(ch, u.exportSite(asset), m.TS) + count += u.export(ch, u.exportSite(asset), m.TS) } if u.Config.CollectIDS { for _, asset := range m.IDSList { - u.export(ch, u.exportIDS(asset), m.TS) + count += u.export(ch, u.exportIDS(asset), m.TS) } } - if m.Devices == nil { - return + if m.Devices != nil { + for _, asset := range m.Devices.UAPs { + count += u.export(ch, u.exportUAP(asset), m.TS) + } + for _, asset := range m.Devices.USGs { + count += u.export(ch, u.exportUSG(asset), m.TS) + } + for _, asset := range m.Devices.USWs { + count += u.export(ch, u.exportUSW(asset), m.TS) + } + for _, asset := range m.Devices.UDMs { + count += u.export(ch, u.exportUDM(asset), m.TS) + } } - for _, asset := range m.Devices.UAPs { - u.export(ch, u.exportUAP(asset), m.TS) - } - for _, asset := range m.Devices.USGs { - u.export(ch, u.exportUSG(asset), m.TS) - } - for _, asset := range m.Devices.USWs { - u.export(ch, u.exportUSW(asset), m.TS) - } - for _, asset := range m.Devices.UDMs { - u.export(ch, u.exportUDM(asset), m.TS) + if u.Config.LoggerFn != nil { + u.Config.LoggerFn(m, count) } } -/* -func (u *unifiCollector) export(ch chan<- prometheus.Metric, exports []*metricExports, ts time.Time) { - for _, e := range exports { - v, ok := e.Value.(float64) - if !ok { - j, ok := e.Value.(int64) - if !ok { - // log.Printf("not a number: %v %v", e.Value, e.Desc.String()) - if u.Config.ReportErrors { - ch <- prometheus.NewInvalidMetric(e.Desc, fmt.Errorf("not a number: %v", e.Value)) - } - continue - } - v = float64(j) - } - ch <- prometheus.NewMetricWithTimestamp(ts, prometheus.MustNewConstMetric(e.Desc, e.ValueType, v, e.Labels...)) - } -}*/ - -func (u *unifiCollector) export(ch chan<- prometheus.Metric, exports []*metricExports, ts time.Time) { +func (u *unifiCollector) export(ch chan<- prometheus.Metric, exports []*metricExports, ts time.Time) (count int64) { for _, e := range exports { var val float64 switch v := e.Value.(type) { @@ -163,7 +150,8 @@ func (u *unifiCollector) export(ch chan<- prometheus.Metric, exports []*metricEx } continue } + count++ ch <- prometheus.NewMetricWithTimestamp(ts, prometheus.MustNewConstMetric(e.Desc, e.ValueType, val, e.Labels...)) } - + return } diff --git a/integrations/inputunifi/promunifi/usw.go b/integrations/inputunifi/promunifi/usw.go index 2938d37c..c91076e4 100644 --- a/integrations/inputunifi/promunifi/usw.go +++ b/integrations/inputunifi/promunifi/usw.go @@ -1,17 +1,132 @@ package promunifi import ( + "github.com/prometheus/client_golang/prometheus" "golift.io/unifi" ) type usw struct { + Uptime *prometheus.Desc `json:"uptime"` + Temperature *prometheus.Desc `json:"general_temperature"` + TotalMaxPower *prometheus.Desc `json:"total_max_power"` + FanLevel *prometheus.Desc `json:"fan_level"` + TotalTxBytes *prometheus.Desc `json:"total_tx_bytes"` + TotalRxBytes *prometheus.Desc `json:"total_rx_bytes"` + TotalBytes *prometheus.Desc `json:"bytes"` + NumSta *prometheus.Desc `json:"num_sta"` + UserNumSta *prometheus.Desc `json:"user-num_sta"` + GuestNumSta *prometheus.Desc `json:"guest-num_sta"` + // Port data. + PoeCurrent *prometheus.Desc `json:"poe_current,omitempty"` + PoePower *prometheus.Desc `json:"poe_power,omitempty"` + PoeVoltage *prometheus.Desc `json:"poe_voltage,omitempty"` + RxBroadcast *prometheus.Desc `json:"rx_broadcast"` + RxBytes *prometheus.Desc `json:"rx_bytes"` + RxBytesR *prometheus.Desc `json:"rx_bytes-r"` + RxDropped *prometheus.Desc `json:"rx_dropped"` + RxErrors *prometheus.Desc `json:"rx_errors"` + RxMulticast *prometheus.Desc `json:"rx_multicast"` + RxPackets *prometheus.Desc `json:"rx_packets"` + Satisfaction *prometheus.Desc `json:"satisfaction,omitempty"` + Speed *prometheus.Desc `json:"speed"` + TxBroadcast *prometheus.Desc `json:"tx_broadcast"` + TxBytes *prometheus.Desc `json:"tx_bytes"` + TxBytesR *prometheus.Desc `json:"tx_bytes-r"` + TxDropped *prometheus.Desc `json:"tx_dropped"` + TxErrors *prometheus.Desc `json:"tx_errors"` + TxMulticast *prometheus.Desc `json:"tx_multicast"` + TxPackets *prometheus.Desc `json:"tx_packets"` } func descUSW(ns string) *usw { - return &usw{} + if ns += "_usw_"; ns == "_usw_" { + ns = "usw_" + } + pns := ns + "port_" + // The first five labels for switch are shared with (the same as) switch ports. + labels := []string{"site_name", "mac", "model", "name", "serial", "site_id", + "type", "version", "device_id", "oid"} + // Copy labels, and replace last four with different names. + labelP := append(append([]string(nil), labels[:6]...), + "port_num", "port_name", "port_mac", "port_ip") + + return &usw{ + // switch data + Uptime: prometheus.NewDesc(ns+"Uptime", "Uptime", labels, nil), + Temperature: prometheus.NewDesc(ns+"Temperature", "Temperature", labels, nil), + TotalMaxPower: prometheus.NewDesc(ns+"TotalMaxPower", "TotalMaxPower", labels, nil), + FanLevel: prometheus.NewDesc(ns+"FanLevel", "FanLevel", labels, nil), + TotalTxBytes: prometheus.NewDesc(ns+"TxBytes", "TxBytes", labels, nil), + TotalRxBytes: prometheus.NewDesc(ns+"RxBytes", "RxBytes", labels, nil), + TotalBytes: prometheus.NewDesc(ns+"Bytes", "Bytes", labels, nil), + NumSta: prometheus.NewDesc(ns+"NumSta", "NumSta", labels, nil), + UserNumSta: prometheus.NewDesc(ns+"UserNumSta", "UserNumSta", labels, nil), + GuestNumSta: prometheus.NewDesc(ns+"GuestNumSta", "GuestNumSta", labels, nil), + // per-port data + PoeCurrent: prometheus.NewDesc(pns+"PoeCurrent", "PoeCurrent", labelP, nil), + PoePower: prometheus.NewDesc(pns+"PoePower", "PoePower", labelP, nil), + PoeVoltage: prometheus.NewDesc(pns+"PoeVoltage", "PoeVoltage", labelP, nil), + RxBroadcast: prometheus.NewDesc(pns+"RxBroadcast", "RxBroadcast", labelP, nil), + RxBytes: prometheus.NewDesc(pns+"RxBytes", "RxBytes", labelP, nil), + RxBytesR: prometheus.NewDesc(pns+"RxBytesR", "RxBytesR", labelP, nil), + RxDropped: prometheus.NewDesc(pns+"RxDropped", "RxDropped", labelP, nil), + RxErrors: prometheus.NewDesc(pns+"RxErrors", "RxErrors", labelP, nil), + RxMulticast: prometheus.NewDesc(pns+"RxMulticast", "RxMulticast", labelP, nil), + RxPackets: prometheus.NewDesc(pns+"RxPackets", "RxPackets", labelP, nil), + Satisfaction: prometheus.NewDesc(pns+"Satisfaction", "Satisfaction", labelP, nil), + Speed: prometheus.NewDesc(pns+"Speed", "Speed", labelP, nil), + TxBroadcast: prometheus.NewDesc(pns+"TxBroadcast", "TxBroadcast", labelP, nil), + TxBytes: prometheus.NewDesc(pns+"TxBytes", "TxBytes", labelP, nil), + TxBytesR: prometheus.NewDesc(pns+"TxBytesR", "TxBytesR", labelP, nil), + TxDropped: prometheus.NewDesc(pns+"TxDropped", "TxDropped", labelP, nil), + TxErrors: prometheus.NewDesc(pns+"TxErrors", "TxErrors", labelP, nil), + TxMulticast: prometheus.NewDesc(pns+"TxMulticast", "TxMulticast", labelP, nil), + TxPackets: prometheus.NewDesc(pns+"TxPackets", "TxPackets", labelP, nil), + } } // exportUSW exports Network Switch Data func (u *unifiCollector) exportUSW(s *unifi.USW) []*metricExports { - return nil + labels := []string{s.SiteName, s.Mac, s.Model, s.Name, s.Serial, s.SiteID, + s.Type, s.Version, s.DeviceID, s.Stat.Oid} + + // Switch data. + m := []*metricExports{ + {u.USW.Uptime, prometheus.GaugeValue, s.Uptime, labels}, + {u.USW.Temperature, prometheus.GaugeValue, s.GeneralTemperature, labels}, + {u.USW.TotalMaxPower, prometheus.GaugeValue, s.TotalMaxPower, labels}, + {u.USW.FanLevel, prometheus.GaugeValue, s.FanLevel, labels}, + {u.USW.TotalTxBytes, prometheus.CounterValue, s.TxBytes, labels}, + {u.USW.TotalRxBytes, prometheus.CounterValue, s.RxBytes, labels}, + {u.USW.TotalBytes, prometheus.CounterValue, s.Bytes, labels}, + {u.USW.NumSta, prometheus.GaugeValue, s.NumSta, labels}, + {u.USW.UserNumSta, prometheus.GaugeValue, s.UserNumSta, labels}, + {u.USW.GuestNumSta, prometheus.GaugeValue, s.GuestNumSta, labels}, + } + + // Per-port data on the switch + for _, p := range s.PortTable { + // Copy labels, and replace last four with different data. + l := append(append([]string(nil), labels[:6]...), p.PortIdx.Txt, p.Name, p.Mac, p.IP) + m = append(m, &metricExports{u.USW.PoeCurrent, prometheus.GaugeValue, p.PoeCurrent, l}) + m = append(m, &metricExports{u.USW.PoePower, prometheus.GaugeValue, p.PoePower, l}) + m = append(m, &metricExports{u.USW.PoeVoltage, prometheus.GaugeValue, p.PoeVoltage, l}) + m = append(m, &metricExports{u.USW.RxBroadcast, prometheus.CounterValue, p.RxBroadcast, l}) + m = append(m, &metricExports{u.USW.RxBytes, prometheus.CounterValue, p.RxBytes, l}) + m = append(m, &metricExports{u.USW.RxBytesR, prometheus.GaugeValue, p.RxBytesR, l}) + m = append(m, &metricExports{u.USW.RxDropped, prometheus.CounterValue, p.RxDropped, l}) + m = append(m, &metricExports{u.USW.RxErrors, prometheus.CounterValue, p.RxErrors, l}) + m = append(m, &metricExports{u.USW.RxMulticast, prometheus.CounterValue, p.RxMulticast, l}) + m = append(m, &metricExports{u.USW.RxPackets, prometheus.CounterValue, p.RxPackets, l}) + m = append(m, &metricExports{u.USW.Satisfaction, prometheus.GaugeValue, p.Satisfaction, l}) + m = append(m, &metricExports{u.USW.Speed, prometheus.GaugeValue, p.Speed, l}) + m = append(m, &metricExports{u.USW.TxBroadcast, prometheus.CounterValue, p.TxBroadcast, l}) + m = append(m, &metricExports{u.USW.TxBytes, prometheus.CounterValue, p.TxBytes, l}) + m = append(m, &metricExports{u.USW.TxBytesR, prometheus.GaugeValue, p.TxBytesR, l}) + m = append(m, &metricExports{u.USW.TxDropped, prometheus.CounterValue, p.TxDropped, l}) + m = append(m, &metricExports{u.USW.TxErrors, prometheus.CounterValue, p.TxErrors, l}) + m = append(m, &metricExports{u.USW.TxMulticast, prometheus.CounterValue, p.TxMulticast, l}) + } + + return m }