diff --git a/poller/prometheus.go b/poller/prometheus.go index 030a51b1..58e3d6fb 100644 --- a/poller/prometheus.go +++ b/poller/prometheus.go @@ -12,6 +12,8 @@ import ( "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) @@ -57,10 +59,11 @@ func (u *UnifiPoller) LogExportReport(report *promunifi.Report) { 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, Descs: %d, "+ - "Metrics: %d, Errors: %d, Zeros: %d, Elapsed: %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.Elapsed.Round(time.Millisecond)) + 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)) } diff --git a/promunifi/clients.go b/promunifi/clients.go index d1014a60..174ce35f 100644 --- a/promunifi/clients.go +++ b/promunifi/clients.go @@ -72,11 +72,12 @@ func descClient(ns string) *uclient { } func (u *unifiCollector) 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, "false"} + 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([]*metricExports{ {u.Client.RxBytes, prometheus.CounterValue, c.WiredRxBytes, labels}, {u.Client.RxBytesR, prometheus.GaugeValue, c.WiredRxBytesR, labels}, @@ -87,6 +88,7 @@ func (u *unifiCollector) exportClient(r report, c *unifi.Client) { }) } else { labels[len(labels)-1] = "false" + labelW[len(labelW)-1] = "false" r.send([]*metricExports{ {u.Client.Anomalies, prometheus.CounterValue, c.Anomalies, labelW}, {u.Client.CCQ, prometheus.GaugeValue, c.Ccq / 10, labelW}, diff --git a/promunifi/collector.go b/promunifi/collector.go index 285ac72a..85d8d2ba 100644 --- a/promunifi/collector.go +++ b/promunifi/collector.go @@ -58,6 +58,7 @@ type Report struct { Descs int Metrics *metrics.Metrics Elapsed time.Duration + Fetch time.Duration Start time.Time ch chan []*metricExports wg sync.WaitGroup @@ -121,12 +122,13 @@ func (u *unifiCollector) Collect(ch chan<- prometheus.Metric) { }() var err error - if r.Metrics, err = u.Config.CollectFn(); err != nil { - ch <- prometheus.NewInvalidMetric( - prometheus.NewInvalidDesc(fmt.Errorf("metric fetch failed")), err) + 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) + // Pass Report interface into our collecting and reporting methods. go u.exportMetrics(r, ch) // in loops.go. u.loopClients(r) @@ -155,7 +157,7 @@ func (u *unifiCollector) exportMetrics(r report, ch chan<- prometheus.Metric) { case int: ch <- r.export(m, float64(v)) default: - r.error(ch, m.Desc, m.Value) + r.error(ch, m.Desc, fmt.Sprintf("not a number: %v", m.Value)) } } r.done() diff --git a/promunifi/loops.go b/promunifi/loops.go index e1a4dcdb..1ddcebd5 100644 --- a/promunifi/loops.go +++ b/promunifi/loops.go @@ -5,78 +5,84 @@ package promunifi // they usually all change at once since they're pretty much the same code. func (u *unifiCollector) loopSites(r report) { - if r.metrics() == nil || len(r.metrics().Sites) < 1 { + m := r.metrics() + if m == nil || len(m.Sites) < 1 { return } r.add() go func() { defer r.done() - for _, s := range r.metrics().Sites { + for _, s := range m.Sites { u.exportSite(r, s) } }() } func (u *unifiCollector) loopUAPs(r report) { - if r.metrics() == nil || r.metrics().Devices == nil || len(r.metrics().Devices.UAPs) < 1 { + m := r.metrics() + if m == nil || m.Devices == nil || len(m.Devices.UAPs) < 1 { return } r.add() go func() { defer r.done() - for _, d := range r.metrics().Devices.UAPs { + for _, d := range m.Devices.UAPs { u.exportUAP(r, d) } }() } func (u *unifiCollector) loopUDMs(r report) { - if r.metrics() == nil || r.metrics().Devices == nil || len(r.metrics().Devices.UDMs) < 1 { + m := r.metrics() + if m == nil || m.Devices == nil || len(m.Devices.UDMs) < 1 { return } r.add() go func() { defer r.done() - for _, d := range r.metrics().Devices.UDMs { + for _, d := range m.Devices.UDMs { u.exportUDM(r, d) } }() } func (u *unifiCollector) loopUSGs(r report) { - if r.metrics() == nil || r.metrics().Devices == nil || len(r.metrics().Devices.USGs) < 1 { + m := r.metrics() + if m == nil || m.Devices == nil || len(m.Devices.USGs) < 1 { return } r.add() go func() { defer r.done() - for _, d := range r.metrics().Devices.USGs { + for _, d := range m.Devices.USGs { u.exportUSG(r, d) } }() } func (u *unifiCollector) loopUSWs(r report) { - if r.metrics() == nil || r.metrics().Devices == nil || len(r.metrics().Devices.USWs) < 1 { + m := r.metrics() + if m == nil || m.Devices == nil || len(m.Devices.USWs) < 1 { return } r.add() go func() { defer r.done() - for _, d := range r.metrics().Devices.USWs { + for _, d := range m.Devices.USWs { u.exportUSW(r, d) } }() } func (u *unifiCollector) loopClients(r report) { - if r.metrics() == nil || len(r.metrics().Clients) < 1 { + m := r.metrics() + if m == nil || len(m.Clients) < 1 { return } r.add() go func() { defer r.done() - for _, c := range r.metrics().Clients { + for _, c := range m.Clients { u.exportClient(r, c) } }() diff --git a/promunifi/report.go b/promunifi/report.go index cc3f70b5..d59c222c 100644 --- a/promunifi/report.go +++ b/promunifi/report.go @@ -9,23 +9,44 @@ import ( ) // This file contains the report interface. -// This interface can be mocked and overrridden for tests. +// This interface can be mocked and overridden for tests. // report is an internal interface used to "process metrics" type report interface { - send([]*metricExports) add() done() + send([]*metricExports) metrics() *metrics.Metrics + channel() chan []*metricExports report(descs map[*prometheus.Desc]bool) export(m *metricExports, v float64) prometheus.Metric - channel() chan []*metricExports 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 []*metricExports) { + r.wg.Add(one) + r.ch <- m +} + +func (r *Report) metrics() *metrics.Metrics { + return r.Metrics +} + +func (r *Report) channel() chan []*metricExports { + return r.ch +} + func (r *Report) report(descs map[*prometheus.Desc]bool) { if r.cf.LoggingFn == nil { return @@ -42,30 +63,9 @@ func (r *Report) export(m *metricExports, v float64) prometheus.Metric { return prometheus.MustNewConstMetric(m.Desc, m.ValueType, v, m.Labels...) } -func (r *Report) metrics() *metrics.Metrics { - return r.Metrics -} - -func (r *Report) channel() chan []*metricExports { - return r.ch -} - 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("not a number: %v", v)) + ch <- prometheus.NewInvalidMetric(d, fmt.Errorf("error: %v", v)) } } - -func (r *Report) add() { - r.wg.Add(one) -} - -func (r *Report) done() { - r.wg.Add(-one) -} - -func (r *Report) send(m []*metricExports) { - r.wg.Add(one) - r.ch <- m -} diff --git a/promunifi/uap.go b/promunifi/uap.go index 0ff7a3a2..0b14a840 100644 --- a/promunifi/uap.go +++ b/promunifi/uap.go @@ -167,7 +167,7 @@ func descUAP(ns string) *uap { func (u *unifiCollector) exportUAP(r report, d *unifi.UAP) { labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name} - // AP data. + // Wireless System Data. r.send([]*metricExports{ {u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels}, {u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels}, @@ -189,6 +189,7 @@ func (u *unifiCollector) exportUAP(r report, d *unifi.UAP) { {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) diff --git a/promunifi/udm.go b/promunifi/udm.go index e4e72091..9779cfad 100644 --- a/promunifi/udm.go +++ b/promunifi/udm.go @@ -70,7 +70,7 @@ func descDevice(ns string) *unifiDevice { // UDM is a collection of stats from USG, USW and UAP. It has no unique stats. func (u *unifiCollector) exportUDM(r report, d *unifi.UDM) { labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name} - // Gateway System Data. + // Dream Machine System Data. r.send([]*metricExports{ {u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels}, {u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels}, @@ -91,12 +91,15 @@ func (u *unifiCollector) exportUDM(r report, d *unifi.UDM) { {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) - u.exportPortTable(r, labels, d.PortTable) + // Wireless Data - UDM (non-pro) only if d.Stat.Ap != nil && d.VapTable != nil { - // UDM Pro does not have these. UDM non-Pro does. u.exportUAPstats(r, labels, d.Stat.Ap) u.exportVAPtable(r, labels, *d.VapTable) u.exportRadtable(r, labels, *d.RadioTable, *d.RadioTableStats) diff --git a/promunifi/usw.go b/promunifi/usw.go index b345323c..6ce9a463 100644 --- a/promunifi/usw.go +++ b/promunifi/usw.go @@ -47,11 +47,9 @@ type usw struct { func descUSW(ns string) *usw { pns := ns + "port_" - // The first five labels for switch are shared with (the same as) switch ports. // labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"} labelS := []string{"site_name", "name"} // labels[6:] - // Copy labels, and replace first four with different names. - labelP := append([]string{"port_num", "port_name", "port_mac", "port_ip"}, labelS...) + 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),