Base layout .. maybe.

This commit is contained in:
davidnewhall2 2019-11-17 00:52:13 -08:00
parent dd27f90a62
commit 9bd4737730
14 changed files with 471 additions and 54 deletions

5
Gopkg.lock generated
View File

@ -46,11 +46,12 @@
version = "v1.0.1"
[[projects]]
digest = "1:eb04f69c8991e52eff33c428bd729e04208bf03235be88e4df0d88497c6861b9"
digest = "1:eb8832cdc904ff89b70c524ab305bf3384c6411f9df6b9f2a41fddc2220e6613"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/internal",
"prometheus/promauto",
"prometheus/promhttp",
]
pruneopts = "UT"
@ -127,6 +128,8 @@
input-imports = [
"github.com/BurntSushi/toml",
"github.com/influxdata/influxdb1-client/v2",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promauto",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/spf13/pflag",
"golift.io/unifi",

View File

@ -34,7 +34,6 @@ quiet = false
# /metrics for polling collection by a prometheus server. This disables influxdb.
mode = "influx"
# This controls on which ip and port /metrics is exported when mode is "prometheus".
# This has no effect in other modes. Must contain a colon and port.
http_listen = "0.0.0.0:61317"

View File

@ -10,7 +10,9 @@ import (
"strings"
"time"
"github.com/davidnewhall/unifi-poller/promunifi"
client "github.com/influxdata/influxdb1-client/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/pflag"
"golift.io/unifi"
@ -98,19 +100,24 @@ func (u *UnifiPoller) Run() (err error) {
case "prometheus", "exporter":
u.Logf("Exporting Measurements at https://%s/metrics for Prometheus", u.Config.HTTPListen)
u.Config.Mode = "http exporter"
http.Handle("/metrics", promhttp.Handler())
go func() {
err = http.ListenAndServe(u.Config.HTTPListen, nil)
if err != http.ErrServerClosed {
log.Fatalf("[ERROR] http server: %v", err)
}
}()
return u.PollController(u.ExportMetrics)
http.Handle("/metrics", http.HandlerFunc(u.PromHandler))
prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorOpts{
CollectFn: u.ExportMetrics,
ReportErrors: true,
Namespace: "unifi",
CollectIDS: true,
}))
err = http.ListenAndServe(u.Config.HTTPListen, nil)
if err != http.ErrServerClosed {
return err
}
return nil
default:
if err = u.GetInfluxDB(); err != nil {
return err
}
u.Logf("Logging Measurements to InfluxDB at %s as user %s", u.Config.InfluxURL, u.Config.InfluxUser)
u.Config.Mode = "influx poller"
return u.PollController(u.ReportMetrics)
@ -128,9 +135,16 @@ func (u *UnifiPoller) GetInfluxDB() (err error) {
if err != nil {
return fmt.Errorf("influxdb: %v", err)
}
return nil
}
// PromHandler logs /metrics requests and serves them with the prometheus handler.
func (u *UnifiPoller) PromHandler(w http.ResponseWriter, r *http.Request) {
u.LogDebugf("/metrics endpoint polled by %v", r.RemoteAddr)
promhttp.Handler().ServeHTTP(w, r)
}
// GetUnifi returns a UniFi controller interface.
func (u *UnifiPoller) GetUnifi() (err error) {
// Create an authenticated session to the Unifi Controller.

View File

@ -8,7 +8,6 @@ import (
"github.com/davidnewhall/unifi-poller/influx"
"github.com/davidnewhall/unifi-poller/metrics"
"github.com/davidnewhall/unifi-poller/prometheus"
client "github.com/influxdata/influxdb1-client/v2"
"golift.io/unifi"
)
@ -84,14 +83,45 @@ func (u *UnifiPoller) CollectAndProcess(process func(*metrics.Metrics) error) er
if err != nil {
return err
}
if err := u.AugmentMetrics(metrics); err != nil {
return err
}
u.AugmentMetrics(metrics)
err = process(metrics)
u.LogError(err, "processing metrics")
return err
}
// ExportMetrics updates the internal metrics provided via
// HTTP at /metrics for prometheus collection.
func (u *UnifiPoller) ExportMetrics() *metrics.Metrics {
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
}
}
metrics, err := u.CollectMetrics()
if err != nil {
u.LogErrorf("collecting metrics: %v", err)
return nil
}
u.AugmentMetrics(metrics)
u.LogExportReport(metrics)
return metrics
}
// LogExportReport writes a log line after exporting metrics via HTTP.
func (u *UnifiPoller) LogExportReport(m *metrics.Metrics) {
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",
len(m.Sites), len(m.Clients), len(m.UAPs),
len(m.UDMs)+len(m.USGs), len(m.USWs), idsMsg)
}
// 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.
@ -115,9 +145,9 @@ func (u *UnifiPoller) CollectMetrics() (*metrics.Metrics, error) {
// 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) error {
func (u *UnifiPoller) AugmentMetrics(metrics *metrics.Metrics) {
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
return fmt.Errorf("nil metrics, augment impossible")
return
}
devices := make(map[string]string)
bssdIDs := make(map[string]string)
@ -143,30 +173,6 @@ func (u *UnifiPoller) AugmentMetrics(metrics *metrics.Metrics) error {
metrics.Clients[i].GwName = devices[c.GwMac]
metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
}
return nil
}
// ExportMetrics updates the internal metrics provided via
// HTTP at /metrics for prometheus collection.
func (u *UnifiPoller) ExportMetrics(metrics *metrics.Metrics) error {
m := &prometheus.Metrics{Metrics: metrics}
for _, err := range m.ProcessExports() {
u.LogError(err, "prometheus.ProcessExports")
}
u.LogExportReport(m)
return nil
}
// LogExportReport writes a log line after exporting metrics via HTTP.
func (u *UnifiPoller) LogExportReport(m *prometheus.Metrics) {
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",
len(m.Sites), len(m.Clients), len(m.UAPs),
len(m.UDMs)+len(m.USGs), len(m.USWs), idsMsg)
}
// ReportMetrics batches all the metrics and writes them to InfluxDB.

View File

@ -1,13 +0,0 @@
package prometheus
import "github.com/davidnewhall/unifi-poller/metrics"
// Metrics contains all the data from the controller.
type Metrics struct {
*metrics.Metrics
}
// ProcessExports turns the data into exported data.
func (m *Metrics) ProcessExports() []error {
return nil
}

View File

@ -1,4 +1,4 @@
# prometheus
This package provides the methods to turn UniFi measurements into prometheus
exported metrics with an HTTP listener.
exported metrics.

213
promunifi/clients.go Normal file
View File

@ -0,0 +1,213 @@
package promunifi
import (
"github.com/prometheus/client_golang/prometheus"
"golift.io/unifi"
)
type client struct {
Anomalies *prometheus.Desc
BytesR *prometheus.Desc
CCQ *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
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) *client {
labels := []string{"id", "mac", "user_id", "site_id", "site_name",
"network_id", "ap_mac", "gw_mac", "sw_mac", "ap_name", "gw_name",
"sw_name", "radio_name", "radio", "radio_proto", "name", "channel",
"vlan", "ip", "essid", "bssid", "radio_desc"}
ns2 := "client"
return &client{
Anomalies: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "anomalies"),
"Client Anomalies", labels, nil,
),
BytesR: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "bytesr"),
"Client Data Rate", labels, nil,
),
CCQ: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "ccq"),
"Client Connection Quality", labels, nil,
),
Noise: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "noise"),
"Client AP Noise", labels, nil,
),
RoamCount: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "roamcount"),
"Client Roam Counter", labels, nil,
),
RSSI: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "rssi"),
"Client RSSI", labels, nil,
),
RxBytes: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "rxbytes"),
"Client Receive Bytes", labels, nil,
),
RxBytesR: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "rxbytesr"),
"Client Receive Data Rate", labels, nil,
),
RxPackets: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "rxpackets"),
"Client Receive Packets", labels, nil,
),
RxRate: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "rxrate"),
"Client Receive Rate", labels, nil,
),
Signal: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "signal"),
"Client Signal Strength", labels, nil,
),
TxBytes: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "txbytes"),
"Client Transmit Bytes", labels, nil,
),
TxBytesR: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "txbytesr"),
"Client Transmit Data Rate", labels, nil,
),
TxPackets: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "txpackets"),
"Client Transmit Packets", labels, nil,
),
TxPower: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "txpower"),
"Client Transmit Power", labels, nil,
),
TxRate: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "txrate"),
"Client Transmit Rate", labels, nil,
),
Uptime: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "uptime"),
"Client Uptime", labels, nil,
),
WifiTxAttempts: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wifitxattempts"),
"Client Wifi Transmit Attempts", labels, nil,
),
WiredRxBytes: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wiredrxbytes"),
"Client Wired Receive Bytes", labels, nil,
),
WiredRxBytesR: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wiredrxbytesr"),
"Client Wired Receive Data Rate", labels, nil,
),
WiredRxPackets: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wiredrxpackets"),
"Client Wired Receive Packets", labels, nil,
),
WiredTxBytes: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wiredtxbytes"),
"Client Wired Transmit Bytes", labels, nil,
),
WiredTxBytesR: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wiredtxbytesr"),
"Client Wired Data Rate", labels, nil,
),
WiredTxPackets: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "wiredtxpackets"),
"Client Wired Transmit Packets", labels, nil,
),
DpiStatsApp: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "dpistatsapp"),
"Client DPI Stats App", labels, nil,
),
DpiStatsCat: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "dpistatscat"),
"Client DPI Stats Cat", labels, nil,
),
DpiStatsRxBytes: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "dpistatsrxbytes"),
"Client DPI Stats Receive Bytes", labels, nil,
),
DpiStatsRxPackets: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "dpistatsrxpackets"),
"Client DPI Stats Receive Packets", labels, nil,
),
DpiStatsTxBytes: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "dpistatstxbytes"),
"Client DPI Stats Transmit Bytes", labels, nil,
),
DpiStatsTxPackets: prometheus.NewDesc(
prometheus.BuildFQName(ns, ns2, "dpistatstxpackets"),
"Client DPI Stats Transmit Packets", labels, nil,
),
}
}
// CollectClient exports Clients' Data
func (u *unifiCollector) exportClient(c *unifi.Client) []*metricExports {
labels := []string{c.ID, c.Mac, c.UserID, c.SiteID, c.SiteName,
c.NetworkID, c.ApMac, c.GwMac, c.SwMac, c.ApName, c.GwName,
c.SwName, c.RadioName, c.Radio, c.RadioProto, c.Name, c.Channel.Txt,
c.Vlan.Txt, c.IP, c.Essid, c.Bssid, c.RadioDescription,
}
return []*metricExports{
{u.Client.Anomalies, prometheus.CounterValue, c.Anomalies, labels},
{u.Client.Anomalies, prometheus.CounterValue, c.Anomalies, labels},
{u.Client.BytesR, prometheus.GaugeValue, c.BytesR, labels},
{u.Client.CCQ, prometheus.GaugeValue, c.Ccq, labels},
{u.Client.Noise, prometheus.GaugeValue, c.Noise, labels},
{u.Client.RoamCount, prometheus.CounterValue, c.RoamCount, labels},
{u.Client.RSSI, prometheus.GaugeValue, c.Rssi, 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.RxRate, prometheus.GaugeValue, c.RxRate, labels},
{u.Client.Signal, prometheus.GaugeValue, c.Signal, 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.TxPower, prometheus.GaugeValue, c.TxPower, labels},
{u.Client.TxRate, prometheus.CounterValue, c.TxRate, labels},
{u.Client.Uptime, prometheus.GaugeValue, c.Uptime, labels},
{u.Client.WifiTxAttempts, prometheus.CounterValue, c.WifiTxAttempts, labels},
{u.Client.WiredRxBytes, prometheus.CounterValue, c.WiredRxBytes, labels},
{u.Client.WiredRxBytesR, prometheus.GaugeValue, c.WiredRxBytesR, labels},
{u.Client.WiredRxPackets, prometheus.CounterValue, c.WiredRxPackets, labels},
{u.Client.WiredTxBytes, prometheus.CounterValue, c.TxRate, labels},
{u.Client.WiredTxBytesR, prometheus.GaugeValue, c.WiredTxBytesR, labels},
{u.Client.WiredTxPackets, prometheus.CounterValue, c.WiredTxPackets, labels},
{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},
}
}

135
promunifi/collector.go Normal file
View File

@ -0,0 +1,135 @@
package promunifi
import (
"fmt"
"reflect"
"time"
"github.com/davidnewhall/unifi-poller/metrics"
"github.com/prometheus/client_golang/prometheus"
)
// UnifiCollectorOpts defines the data needed to collect and report UniFi Metrics.
type UnifiCollectorOpts 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 retreive the latest UniFi
CollectFn func() *metrics.Metrics
// Setting this to true will enable IDS exports.
CollectIDS bool
}
type unifiCollector struct {
opts UnifiCollectorOpts
Client *client
// UAP *UAP
// USG *USG
// USW *USW
// UDM *UDM
// IDS *IDS
// Site *Site
}
type metricExports struct {
Desc *prometheus.Desc
ValueType prometheus.ValueType
Value interface{}
Labels []string
}
// NewUnifiCollector returns a prometheus collector that will export any available
// UniFi metrics. You must provide a collection function in the opts.
func NewUnifiCollector(opts UnifiCollectorOpts) prometheus.Collector {
if opts.CollectFn == nil {
panic("nil collector function")
}
return &unifiCollector{
opts: opts,
Client: descClient(opts.Namespace),
// UAP: descUAP(opts.Namespace),
// USG: descUSG(opts.Namespace),
// USW: descUSW(opts.Namespace),
// UDM: descUDM(opts.Namespace),
// Site: descSite(opts.Namespace),
// IDS: descIDS(opts.Namespace),
}
}
// Describe satisfies the prometheus Collector. This returns all of the
// metric descriptions that this packages produces.
func (u *unifiCollector) Describe(ch chan<- *prometheus.Desc) {
describe := func(from interface{}) {
v := reflect.ValueOf(from)
// 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
}
}
}
describe(u.Client)
// describe(u.UAP)
// describe(u.USG)
// describe(u.USW)
// describe(u.UDM)
// describe(u.Site)
// if u.opts.CollectIDS {
// describe(u.IDS)
// }
}
// 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.opts.CollectFn()
for _, asset := range m.Clients {
u.export(ch, u.exportClient(asset), m.TS)
}
for _, asset := range m.Sites {
u.export(ch, u.exportSite(asset), m.TS)
}
if u.opts.CollectIDS {
for _, asset := range m.IDSList {
u.export(ch, u.exportIDS(asset), m.TS)
}
}
if m.Devices == nil {
return
}
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)
}
}
func (u *unifiCollector) export(ch chan<- prometheus.Metric, exports []*metricExports, ts time.Time) {
for _, e := range exports {
v, ok := e.Value.(float64)
if !ok {
if u.opts.ReportErrors {
ch <- prometheus.NewInvalidMetric(e.Desc, fmt.Errorf("not a number"))
}
return
}
ch <- prometheus.NewMetricWithTimestamp(ts, prometheus.MustNewConstMetric(e.Desc, e.ValueType, v, e.Labels...))
}
}

10
promunifi/ids.go Normal file
View File

@ -0,0 +1,10 @@
package promunifi
import (
"golift.io/unifi"
)
// exportIDS exports Intrusion Detection System Data
func (u *unifiCollector) exportIDS(i *unifi.IDS) []*metricExports {
return nil
}

10
promunifi/site.go Normal file
View File

@ -0,0 +1,10 @@
package promunifi
import (
"golift.io/unifi"
)
// exportSite exports Network Site Data
func (u *unifiCollector) exportSite(s *unifi.Site) []*metricExports {
return nil
}

10
promunifi/uap.go Normal file
View File

@ -0,0 +1,10 @@
package promunifi
import (
"golift.io/unifi"
)
// exportUAP exports Access Point Data
func (u *unifiCollector) exportUAP(a *unifi.UAP) []*metricExports {
return nil
}

10
promunifi/udm.go Normal file
View File

@ -0,0 +1,10 @@
package promunifi
import (
"golift.io/unifi"
)
// exportUDM exports UniFi Dream Machine (and Pro) Data
func (u *unifiCollector) exportUDM(d *unifi.UDM) []*metricExports {
return nil
}

10
promunifi/usg.go Normal file
View File

@ -0,0 +1,10 @@
package promunifi
import (
"golift.io/unifi"
)
// exportUSG Exports Security Gateway Data
func (u *unifiCollector) exportUSG(s *unifi.USG) []*metricExports {
return nil
}

10
promunifi/usw.go Normal file
View File

@ -0,0 +1,10 @@
package promunifi
import (
"golift.io/unifi"
)
// exportUSW exports Network Switch Data
func (u *unifiCollector) exportUSW(s *unifi.USW) []*metricExports {
return nil
}