From 5e203701b5d5b7b60ddb42eaba83f8db35378f6f Mon Sep 17 00:00:00 2001 From: davidnewhall2 Date: Sat, 28 Dec 2019 17:08:16 -0800 Subject: [PATCH] initial --- Makefile | 14 +- go.mod | 15 +- go.sum | 19 ++ main.go | 8 +- pkg/influxunifi/README.md | 4 - pkg/influxunifi/clients.go | 95 ---------- pkg/influxunifi/ids.go | 42 ----- pkg/influxunifi/influxdb.go | 295 ------------------------------ pkg/influxunifi/report.go | 64 ------- pkg/influxunifi/site.go | 78 -------- pkg/influxunifi/uap.go | 189 -------------------- pkg/influxunifi/udm.go | 133 -------------- pkg/influxunifi/usg.go | 140 --------------- pkg/influxunifi/usw.go | 114 ------------ pkg/inputunifi/collector.go | 195 -------------------- pkg/inputunifi/input.go | 198 --------------------- pkg/inputunifi/interface.go | 140 --------------- pkg/poller/build_macos.go | 9 - pkg/poller/build_unix.go | 9 - pkg/poller/build_windows.go | 9 - pkg/poller/config.go | 150 ---------------- pkg/poller/dumper.go | 33 ---- pkg/poller/inputs.go | 165 ----------------- pkg/poller/logger.go | 34 ---- pkg/poller/outputs.go | 72 -------- pkg/poller/start.go | 83 --------- pkg/promunifi/README.md | 4 - pkg/promunifi/clients.go | 136 -------------- pkg/promunifi/collector.go | 346 ------------------------------------ pkg/promunifi/report.go | 80 --------- pkg/promunifi/site.go | 152 ---------------- pkg/promunifi/uap.go | 320 --------------------------------- pkg/promunifi/udm.go | 131 -------------- pkg/promunifi/usg.go | 144 --------------- pkg/promunifi/usw.go | 194 -------------------- plugins/mysql/README.md | 26 --- plugins/mysql/main.go | 45 ----- 37 files changed, 37 insertions(+), 3848 deletions(-) delete mode 100644 pkg/influxunifi/README.md delete mode 100644 pkg/influxunifi/clients.go delete mode 100644 pkg/influxunifi/ids.go delete mode 100644 pkg/influxunifi/influxdb.go delete mode 100644 pkg/influxunifi/report.go delete mode 100644 pkg/influxunifi/site.go delete mode 100644 pkg/influxunifi/uap.go delete mode 100644 pkg/influxunifi/udm.go delete mode 100644 pkg/influxunifi/usg.go delete mode 100644 pkg/influxunifi/usw.go delete mode 100644 pkg/inputunifi/collector.go delete mode 100644 pkg/inputunifi/input.go delete mode 100644 pkg/inputunifi/interface.go delete mode 100644 pkg/poller/build_macos.go delete mode 100644 pkg/poller/build_unix.go delete mode 100644 pkg/poller/build_windows.go delete mode 100644 pkg/poller/config.go delete mode 100644 pkg/poller/dumper.go delete mode 100644 pkg/poller/inputs.go delete mode 100644 pkg/poller/logger.go delete mode 100644 pkg/poller/outputs.go delete mode 100644 pkg/poller/start.go delete mode 100644 pkg/promunifi/README.md delete mode 100644 pkg/promunifi/clients.go delete mode 100644 pkg/promunifi/collector.go delete mode 100644 pkg/promunifi/report.go delete mode 100644 pkg/promunifi/site.go delete mode 100644 pkg/promunifi/uap.go delete mode 100644 pkg/promunifi/udm.go delete mode 100644 pkg/promunifi/usg.go delete mode 100644 pkg/promunifi/usw.go delete mode 100644 plugins/mysql/README.md delete mode 100644 plugins/mysql/main.go diff --git a/Makefile b/Makefile index 0214ea0d5..4172d6980 100644 --- a/Makefile +++ b/Makefile @@ -97,39 +97,39 @@ README.html: md2roff # Binaries build: $(BINARY) -$(BINARY): main.go pkg/*/*.go +$(BINARY): main.go go build -o $(BINARY) -ldflags "-w -s $(VERSION_LDFLAGS)" linux: $(BINARY).amd64.linux -$(BINARY).amd64.linux: main.go pkg/*/*.go +$(BINARY).amd64.linux: main.go # Building linux 64-bit x86 binary. GOOS=linux GOARCH=amd64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)" linux386: $(BINARY).i386.linux -$(BINARY).i386.linux: main.go pkg/*/*.go +$(BINARY).i386.linux: main.go # Building linux 32-bit x86 binary. GOOS=linux GOARCH=386 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)" arm: arm64 armhf arm64: $(BINARY).arm64.linux -$(BINARY).arm64.linux: main.go pkg/*/*.go +$(BINARY).arm64.linux: main.go # Building linux 64-bit ARM binary. GOOS=linux GOARCH=arm64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)" armhf: $(BINARY).armhf.linux -$(BINARY).armhf.linux: main.go pkg/*/*.go +$(BINARY).armhf.linux: main.go # Building linux 32-bit ARM binary. GOOS=linux GOARCH=arm GOARM=6 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)" macos: $(BINARY).amd64.macos -$(BINARY).amd64.macos: main.go pkg/*/*.go +$(BINARY).amd64.macos: main.go # Building darwin 64-bit x86 binary. GOOS=darwin GOARCH=amd64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)" exe: $(BINARY).amd64.exe windows: $(BINARY).amd64.exe -$(BINARY).amd64.exe: main.go pkg/*/*.go +$(BINARY).amd64.exe: main.go # Building windows 64-bit x86 binary. GOOS=windows GOARCH=amd64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)" diff --git a/go.mod b/go.mod index b5cb3c398..169f31ca0 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,12 @@ -module github.com/davidnewhall/unifi-poller +module github.com/unifi-poller/unifi-poller go 1.13 require ( - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d - github.com/prometheus/client_golang v1.3.0 - github.com/prometheus/common v0.7.0 - github.com/spf13/pflag v1.0.5 - golift.io/cnfg v0.0.5 - golift.io/unifi v0.0.400 + github.com/unifi-poller/influxunifi v0.0.1 + github.com/unifi-poller/inputunifi v0.0.1 + github.com/unifi-poller/poller v0.0.1 + github.com/unifi-poller/promunifi v0.0.1 + golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 // indirect + gopkg.in/yaml.v2 v2.2.7 // indirect ) diff --git a/go.sum b/go.sum index f9e33d082..cec9ab94f 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,20 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/unifi-poller/influxunifi v0.0.0-20191229010055-eac7cb2786a8 h1:6tLYxh52e01ZiL+qxBRssEzXHqKjryog9Ez08hnRdbI= +github.com/unifi-poller/influxunifi v0.0.0-20191229010055-eac7cb2786a8/go.mod h1:wYuSwHJnuYHMQyLs9ZnDSoZkAj/otg229+8PytZ6lJw= +github.com/unifi-poller/influxunifi v0.0.1 h1:zHTa1Wf+2bke+qoLoRmgtFjWq/Yr0Cr+ZjtrtawefRM= +github.com/unifi-poller/influxunifi v0.0.1/go.mod h1:wYuSwHJnuYHMQyLs9ZnDSoZkAj/otg229+8PytZ6lJw= +github.com/unifi-poller/inputunifi v0.0.0-20191229005859-343b6711d445 h1:bsEkBa6xK1M9/g/rBrIc6qLeel4kqvtqnNyXLDhu4Uw= +github.com/unifi-poller/inputunifi v0.0.0-20191229005859-343b6711d445/go.mod h1:gmgiDi8RbaAJvtGf9ybDB+eguJzl/xyrpoqH5JUdWEk= +github.com/unifi-poller/inputunifi v0.0.1 h1:97s6pneYypvYV+RPgI5CgsRsrCYJyqqsVtaBoDprgsk= +github.com/unifi-poller/inputunifi v0.0.1/go.mod h1:gmgiDi8RbaAJvtGf9ybDB+eguJzl/xyrpoqH5JUdWEk= +github.com/unifi-poller/poller v0.0.1 h1:/SIsahlUEVJ+v9+C94spjV58+MIqR5DucVZqOstj2vM= +github.com/unifi-poller/poller v0.0.1/go.mod h1:sZfDL7wcVwenlkrm/92bsSuoKKUnjj0bwcSUCT+aA2s= +github.com/unifi-poller/promunifi v0.0.0-20191229005654-36c9f9b67ca7 h1:82q6vD+Ij8RmLoGng5/exRrnFRYm2/tpkKOVhEUH864= +github.com/unifi-poller/promunifi v0.0.0-20191229005654-36c9f9b67ca7/go.mod h1:U1fEJ/lCYTjkHmFhDBdEBMzIECo5Jz2G7ZBKtM7zkAw= +github.com/unifi-poller/promunifi v0.0.1 h1:KMZPE73VyA/BQDuL3Oo6m5+hAU0solGoZ/9m7dAJtoI= +github.com/unifi-poller/promunifi v0.0.1/go.mod h1:U1fEJ/lCYTjkHmFhDBdEBMzIECo5Jz2G7ZBKtM7zkAw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -76,7 +90,10 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golift.io/cnfg v0.0.5 h1:HnMU8Z9C/igKvir1dqaHx5BPuNGZrp99FCtdJyP2Z4I= golift.io/cnfg v0.0.5/go.mod h1:ScFDIJg/rJGHbRaed/i7g1lBhywEjB0JiP2uZr3xC3A= @@ -89,3 +106,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 24315b5d2..c39d81fcc 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,12 @@ package main import ( "log" - "github.com/davidnewhall/unifi-poller/pkg/poller" + "github.com/unifi-poller/poller" // Load input plugins! - _ "github.com/davidnewhall/unifi-poller/pkg/inputunifi" + _ "github.com/unifi-poller/inputunifi" // Load output plugins! - _ "github.com/davidnewhall/unifi-poller/pkg/influxunifi" - _ "github.com/davidnewhall/unifi-poller/pkg/promunifi" + _ "github.com/unifi-poller/influxunifi" + _ "github.com/unifi-poller/promunifi" ) // Keep it simple. diff --git a/pkg/influxunifi/README.md b/pkg/influxunifi/README.md deleted file mode 100644 index cbc606c01..000000000 --- a/pkg/influxunifi/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# influx - -This package provides the methods to turn UniFi measurements into influx -data-points with appropriate tags and fields. diff --git a/pkg/influxunifi/clients.go b/pkg/influxunifi/clients.go deleted file mode 100644 index 8ac00a4d7..000000000 --- a/pkg/influxunifi/clients.go +++ /dev/null @@ -1,95 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// batchClient generates Unifi Client datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchClient(r report, s *unifi.Client) { - tags := map[string]string{ - "mac": s.Mac, - "site_name": s.SiteName, - "source": s.SourceName, - "ap_name": s.ApName, - "gw_name": s.GwName, - "sw_name": s.SwName, - "oui": s.Oui, - "radio_name": s.RadioName, - "radio": s.Radio, - "radio_proto": s.RadioProto, - "name": s.Name, - "fixed_ip": s.FixedIP, - "sw_port": s.SwPort.Txt, - "os_class": s.OsClass.Txt, - "os_name": s.OsName.Txt, - "dev_cat": s.DevCat.Txt, - "dev_id": s.DevID.Txt, - "dev_vendor": s.DevVendor.Txt, - "dev_family": s.DevFamily.Txt, - "is_wired": s.IsWired.Txt, - "is_guest": s.IsGuest.Txt, - "use_fixedip": s.UseFixedIP.Txt, - "channel": s.Channel.Txt, - "vlan": s.Vlan.Txt, - } - fields := map[string]interface{}{ - "anomalies": s.Anomalies, - "ip": s.IP, - "essid": s.Essid, - "bssid": s.Bssid, - "channel": s.Channel.Val, - "hostname": s.Name, - "radio_desc": s.RadioDescription, - "satisfaction": s.Satisfaction.Val, - "bytes_r": s.BytesR, - "ccq": s.Ccq, - "noise": s.Noise, - "note": s.Note, - "roam_count": s.RoamCount, - "rssi": s.Rssi, - "rx_bytes": s.RxBytes, - "rx_bytes_r": s.RxBytesR, - "rx_packets": s.RxPackets, - "rx_rate": s.RxRate, - "signal": s.Signal, - "tx_bytes": s.TxBytes, - "tx_bytes_r": s.TxBytesR, - "tx_packets": s.TxPackets, - "tx_retries": s.TxRetries, - "tx_power": s.TxPower, - "tx_rate": s.TxRate, - "uptime": s.Uptime, - "wifi_tx_attempts": s.WifiTxAttempts, - "wired-rx_bytes": s.WiredRxBytes, - "wired-rx_bytes-r": s.WiredRxBytesR, - "wired-rx_packets": s.WiredRxPackets, - "wired-tx_bytes": s.WiredTxBytes, - "wired-tx_bytes-r": s.WiredTxBytesR, - "wired-tx_packets": s.WiredTxPackets, - } - - r.send(&metric{Table: "clients", Tags: tags, Fields: fields}) -} - -func (u *InfluxUnifi) batchClientDPI(r report, s *unifi.DPITable) { - for _, dpi := range s.ByApp { - r.send(&metric{ - Table: "clientdpi", - Tags: map[string]string{ - "category": unifi.DPICats.Get(dpi.Cat), - "application": unifi.DPIApps.GetApp(dpi.Cat, dpi.App), - "name": s.Name, - "mac": s.MAC, - "site_name": s.SiteName, - "source": s.SourceName, - }, - Fields: map[string]interface{}{ - "tx_packets": dpi.TxPackets, - "rx_packets": dpi.RxPackets, - "tx_bytes": dpi.TxBytes, - "rx_bytes": dpi.RxBytes, - }}, - ) - } -} diff --git a/pkg/influxunifi/ids.go b/pkg/influxunifi/ids.go deleted file mode 100644 index fbc6e0dda..000000000 --- a/pkg/influxunifi/ids.go +++ /dev/null @@ -1,42 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// batchIDS generates intrusion detection datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchIDS(r report, i *unifi.IDS) { - tags := map[string]string{ - "site_name": i.SiteName, - "source": i.SourceName, - "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, - } - - r.send(&metric{Table: "intrusion_detect", Tags: tags, Fields: fields}) -} diff --git a/pkg/influxunifi/influxdb.go b/pkg/influxunifi/influxdb.go deleted file mode 100644 index 61d63ce16..000000000 --- a/pkg/influxunifi/influxdb.go +++ /dev/null @@ -1,295 +0,0 @@ -// Package influxunifi provides the methods to turn UniFi measurements into influx -// data-points with appropriate tags and fields. -package influxunifi - -import ( - "crypto/tls" - "fmt" - "log" - "time" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - influx "github.com/influxdata/influxdb1-client/v2" - "golift.io/cnfg" -) - -const ( - defaultInterval = 30 * time.Second - minimumInterval = 10 * time.Second - defaultInfluxDB = "unifi" - defaultInfluxUser = "unifipoller" - defaultInfluxURL = "http://127.0.0.1:8086" -) - -// Config defines the data needed to store metrics in InfluxDB -type Config struct { - Interval cnfg.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"` - Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"` - VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` - URL string `json:"url,omitempty" toml:"url,omitempty" xml:"url" yaml:"url"` - User string `json:"user,omitempty" toml:"user,omitempty" xml:"user" yaml:"user"` - Pass string `json:"pass,omitempty" toml:"pass,omitempty" xml:"pass" yaml:"pass"` - DB string `json:"db,omitempty" toml:"db,omitempty" xml:"db" yaml:"db"` -} - -// InfluxDB allows the data to be nested in the config file. -type InfluxDB struct { - Config Config `json:"influxdb" toml:"influxdb" xml:"influxdb" yaml:"influxdb"` -} - -// InfluxUnifi is returned by New() after you provide a Config. -type InfluxUnifi struct { - Collector poller.Collect - influx influx.Client - LastCheck time.Time - *InfluxDB -} - -type metric struct { - Table string - Tags map[string]string - Fields map[string]interface{} -} - -func init() { - u := &InfluxUnifi{InfluxDB: &InfluxDB{}, LastCheck: time.Now()} - - poller.NewOutput(&poller.Output{ - Name: "influxdb", - Config: u.InfluxDB, - Method: u.Run, - }) -} - -// PollController runs forever, polling UniFi and pushing to InfluxDB -// This is started by Run() or RunBoth() after everything checks out. -func (u *InfluxUnifi) PollController() { - interval := u.Config.Interval.Round(time.Second) - ticker := time.NewTicker(interval) - log.Printf("[INFO] Everything checks out! Poller started, InfluxDB interval: %v", interval) - - for u.LastCheck = range ticker.C { - metrics, ok, err := u.Collector.Metrics() - if err != nil { - u.Collector.LogErrorf("%v", err) - - if !ok { - continue - } - } - - report, err := u.ReportMetrics(metrics) - if err != nil { - // XXX: reset and re-auth? not sure.. - u.Collector.LogErrorf("%v", err) - continue - } - - u.LogInfluxReport(report) - } -} - -// Run runs a ticker to poll the unifi server and update influxdb. -func (u *InfluxUnifi) Run(c poller.Collect) error { - var err error - - if u.Config.Disable { - return nil - } - - u.Collector = c - u.setConfigDefaults() - - u.influx, err = influx.NewHTTPClient(influx.HTTPConfig{ - Addr: u.Config.URL, - Username: u.Config.User, - Password: u.Config.Pass, - TLSConfig: &tls.Config{InsecureSkipVerify: !u.Config.VerifySSL}, - }) - if err != nil { - return err - } - - u.PollController() - - return nil -} - -func (u *InfluxUnifi) setConfigDefaults() { - if u.Config.URL == "" { - u.Config.URL = defaultInfluxURL - } - - if u.Config.User == "" { - u.Config.User = defaultInfluxUser - } - - if u.Config.Pass == "" { - u.Config.Pass = defaultInfluxUser - } - - if u.Config.DB == "" { - u.Config.DB = defaultInfluxDB - } - - if u.Config.Interval.Duration == 0 { - u.Config.Interval = cnfg.Duration{Duration: defaultInterval} - } else if u.Config.Interval.Duration < minimumInterval { - u.Config.Interval = cnfg.Duration{Duration: minimumInterval} - } - - u.Config.Interval = cnfg.Duration{Duration: u.Config.Interval.Duration.Round(time.Second)} -} - -// ReportMetrics batches all device and client data into influxdb data points. -// Call this after you've collected all the data you care about. -// Returns an error if influxdb calls fail, otherwise returns a report. -func (u *InfluxUnifi) ReportMetrics(m *poller.Metrics) (*Report, error) { - r := &Report{Metrics: m, ch: make(chan *metric), Start: time.Now()} - defer close(r.ch) - - var err error - - // Make a new Influx Points Batcher. - r.bp, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.Config.DB}) - - if err != nil { - return nil, fmt.Errorf("influx.NewBatchPoints: %v", err) - } - - go u.collect(r, r.ch) - // Batch all the points. - u.loopPoints(r) - r.wg.Wait() // wait for all points to finish batching! - - // Send all the points. - if err = u.influx.Write(r.bp); err != nil { - return nil, fmt.Errorf("influxdb.Write(points): %v", err) - } - - r.Elapsed = time.Since(r.Start) - - return r, nil -} - -// collect runs in a go routine and batches all the points. -func (u *InfluxUnifi) collect(r report, ch chan *metric) { - for m := range ch { - pt, err := influx.NewPoint(m.Table, m.Tags, m.Fields, r.metrics().TS) - if err != nil { - r.error(err) - } else { - r.batch(m, pt) - } - - r.done() - } -} - -// loopPoints kicks off 3 or 7 go routines to process metrics and send them -// to the collect routine through the metric channel. -func (u *InfluxUnifi) loopPoints(r report) { - m := r.metrics() - - r.add() - r.add() - r.add() - r.add() - r.add() - - go func() { - defer r.done() - - for _, s := range m.SitesDPI { - u.batchSiteDPI(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.Sites { - u.batchSite(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.ClientsDPI { - u.batchClientDPI(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.Clients { - u.batchClient(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.IDSList { - u.batchIDS(r, s) - } - }() - - u.loopDevicePoints(r) -} - -func (u *InfluxUnifi) loopDevicePoints(r report) { - m := r.metrics() - if m.Devices == nil { - return - } - - r.add() - r.add() - r.add() - r.add() - - go func() { - defer r.done() - - for _, s := range m.UAPs { - u.batchUAP(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.USGs { - u.batchUSG(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.USWs { - u.batchUSW(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.UDMs { - u.batchUDM(r, s) - } - }() -} - -// LogInfluxReport writes a log message after exporting to influxdb. -func (u *InfluxUnifi) LogInfluxReport(r *Report) { - idsMsg := fmt.Sprintf("IDS Events: %d, ", len(r.Metrics.IDSList)) - u.Collector.Logf("UniFi Metrics Recorded. Sites: %d, Clients: %d, "+ - "UAP: %d, USG/UDM: %d, USW: %d, %sPoints: %d, Fields: %d, Errs: %d, Elapsed: %v", - len(r.Metrics.Sites), len(r.Metrics.Clients), len(r.Metrics.UAPs), - len(r.Metrics.UDMs)+len(r.Metrics.USGs), len(r.Metrics.USWs), idsMsg, r.Total, - r.Fields, len(r.Errors), r.Elapsed.Round(time.Millisecond)) -} diff --git a/pkg/influxunifi/report.go b/pkg/influxunifi/report.go deleted file mode 100644 index 3fdf77a90..000000000 --- a/pkg/influxunifi/report.go +++ /dev/null @@ -1,64 +0,0 @@ -package influxunifi - -import ( - "sync" - "time" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - influx "github.com/influxdata/influxdb1-client/v2" -) - -// Report is returned to the calling procedure after everything is processed. -type Report struct { - Metrics *poller.Metrics - Errors []error - Total int - Fields int - Start time.Time - Elapsed time.Duration - ch chan *metric - wg sync.WaitGroup - bp influx.BatchPoints -} - -// report is an internal interface that can be mocked and overrridden for tests. -type report interface { - add() - done() - send(m *metric) - error(err error) - batch(m *metric, pt *influx.Point) - metrics() *poller.Metrics -} - -func (r *Report) metrics() *poller.Metrics { - return r.Metrics -} - -// 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 -} - -/* The following methods are not thread safe. */ - -func (r *Report) error(err error) { - r.Errors = append(r.Errors, err) -} - -func (r *Report) batch(m *metric, p *influx.Point) { - r.Total++ - r.Fields += len(m.Fields) - r.bp.AddPoint(p) -} diff --git a/pkg/influxunifi/site.go b/pkg/influxunifi/site.go deleted file mode 100644 index 1ae313c47..000000000 --- a/pkg/influxunifi/site.go +++ /dev/null @@ -1,78 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// batchSite generates Unifi Sites' datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchSite(r report, s *unifi.Site) { - for _, h := range s.Health { - tags := map[string]string{ - "name": s.Name, - "site_name": s.SiteName, - "source": s.SourceName, - "desc": s.Desc, - "status": h.Status, - "subsystem": h.Subsystem, - "wan_ip": h.WanIP, - "gw_name": h.GwName, - "lan_ip": h.LanIP, - } - fields := map[string]interface{}{ - "num_user": h.NumUser.Val, - "num_guest": h.NumGuest.Val, - "num_iot": h.NumIot.Val, - "tx_bytes-r": h.TxBytesR.Val, - "rx_bytes-r": h.RxBytesR.Val, - "num_ap": h.NumAp.Val, - "num_adopted": h.NumAdopted.Val, - "num_disabled": h.NumDisabled.Val, - "num_disconnected": h.NumDisconnected.Val, - "num_pending": h.NumPending.Val, - "num_gw": h.NumGw.Val, - "wan_ip": h.WanIP, - "num_sta": h.NumSta.Val, - "gw_cpu": h.GwSystemStats.CPU.Val, - "gw_mem": h.GwSystemStats.Mem.Val, - "gw_uptime": h.GwSystemStats.Uptime.Val, - "latency": h.Latency.Val, - "uptime": h.Uptime.Val, - "drops": h.Drops.Val, - "xput_up": h.XputUp.Val, - "xput_down": h.XputDown.Val, - "speedtest_ping": h.SpeedtestPing.Val, - "speedtest_lastrun": h.SpeedtestLastrun.Val, - "num_sw": h.NumSw.Val, - "remote_user_num_active": h.RemoteUserNumActive.Val, - "remote_user_num_inactive": h.RemoteUserNumInactive.Val, - "remote_user_rx_bytes": h.RemoteUserRxBytes.Val, - "remote_user_tx_bytes": h.RemoteUserTxBytes.Val, - "remote_user_rx_packets": h.RemoteUserRxPackets.Val, - "remote_user_tx_packets": h.RemoteUserTxPackets.Val, - "num_new_alarms": s.NumNewAlarms.Val, - } - - r.send(&metric{Table: "subsystems", Tags: tags, Fields: fields}) - } -} - -func (u *InfluxUnifi) batchSiteDPI(r report, s *unifi.DPITable) { - for _, dpi := range s.ByApp { - r.send(&metric{ - Table: "sitedpi", - Tags: map[string]string{ - "category": unifi.DPICats.Get(dpi.Cat), - "application": unifi.DPIApps.GetApp(dpi.Cat, dpi.App), - "site_name": s.SiteName, - "source": s.SourceName, - }, - Fields: map[string]interface{}{ - "tx_packets": dpi.TxPackets, - "rx_packets": dpi.RxPackets, - "tx_bytes": dpi.TxBytes, - "rx_bytes": dpi.RxBytes, - }}, - ) - } -} diff --git a/pkg/influxunifi/uap.go b/pkg/influxunifi/uap.go deleted file mode 100644 index 3966cb4a9..000000000 --- a/pkg/influxunifi/uap.go +++ /dev/null @@ -1,189 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// batchUAP generates Wireless-Access-Point datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchUAP(r report, s *unifi.UAP) { - if !s.Adopted.Val || s.Locating.Val { - return - } - - tags := map[string]string{ - "mac": s.Mac, - "site_name": s.SiteName, - "source": s.SourceName, - "name": s.Name, - "version": s.Version, - "model": s.Model, - "serial": s.Serial, - "type": s.Type, - } - fields := Combine(u.processUAPstats(s.Stat.Ap), u.batchSysStats(s.SysStats, s.SystemStats)) - fields["ip"] = s.IP - fields["bytes"] = s.Bytes.Val - fields["last_seen"] = s.LastSeen.Val - fields["rx_bytes"] = s.RxBytes.Val - fields["tx_bytes"] = s.TxBytes.Val - fields["uptime"] = s.Uptime.Val - fields["state"] = s.State - fields["user-num_sta"] = int(s.UserNumSta.Val) - fields["guest-num_sta"] = int(s.GuestNumSta.Val) - fields["num_sta"] = s.NumSta.Val - - r.send(&metric{Table: "uap", Tags: tags, Fields: fields}) - u.processRadTable(r, tags, s.RadioTable, s.RadioTableStats) - u.processVAPTable(r, tags, s.VapTable) -} - -func (u *InfluxUnifi) processUAPstats(ap *unifi.Ap) map[string]interface{} { - if ap == nil { - return map[string]interface{}{} - } - - // Accumulative Statistics. - return map[string]interface{}{ - "stat_user-rx_packets": ap.UserRxPackets.Val, - "stat_guest-rx_packets": ap.GuestRxPackets.Val, - "stat_rx_packets": ap.RxPackets.Val, - "stat_user-rx_bytes": ap.UserRxBytes.Val, - "stat_guest-rx_bytes": ap.GuestRxBytes.Val, - "stat_rx_bytes": ap.RxBytes.Val, - "stat_user-rx_errors": ap.UserRxErrors.Val, - "stat_guest-rx_errors": ap.GuestRxErrors.Val, - "stat_rx_errors": ap.RxErrors.Val, - "stat_user-rx_dropped": ap.UserRxDropped.Val, - "stat_guest-rx_dropped": ap.GuestRxDropped.Val, - "stat_rx_dropped": ap.RxDropped.Val, - "stat_user-rx_crypts": ap.UserRxCrypts.Val, - "stat_guest-rx_crypts": ap.GuestRxCrypts.Val, - "stat_rx_crypts": ap.RxCrypts.Val, - "stat_user-rx_frags": ap.UserRxFrags.Val, - "stat_guest-rx_frags": ap.GuestRxFrags.Val, - "stat_rx_frags": ap.RxFrags.Val, - "stat_user-tx_packets": ap.UserTxPackets.Val, - "stat_guest-tx_packets": ap.GuestTxPackets.Val, - "stat_tx_packets": ap.TxPackets.Val, - "stat_user-tx_bytes": ap.UserTxBytes.Val, - "stat_guest-tx_bytes": ap.GuestTxBytes.Val, - "stat_tx_bytes": ap.TxBytes.Val, - "stat_user-tx_errors": ap.UserTxErrors.Val, - "stat_guest-tx_errors": ap.GuestTxErrors.Val, - "stat_tx_errors": ap.TxErrors.Val, - "stat_user-tx_dropped": ap.UserTxDropped.Val, - "stat_guest-tx_dropped": ap.GuestTxDropped.Val, - "stat_tx_dropped": ap.TxDropped.Val, - "stat_user-tx_retries": ap.UserTxRetries.Val, - "stat_guest-tx_retries": ap.GuestTxRetries.Val, - } -} - -// processVAPTable creates points for Wifi Radios. This works with several types of UAP-capable devices. -func (u *InfluxUnifi) processVAPTable(r report, t map[string]string, vt unifi.VapTable) { - for _, s := range vt { - tags := map[string]string{ - "device_name": t["name"], - "site_name": t["site_name"], - "source": t["source"], - "ap_mac": s.ApMac, - "bssid": s.Bssid, - "id": s.ID, - "name": s.Name, - "radio_name": s.RadioName, - "radio": s.Radio, - "essid": s.Essid, - "site_id": s.SiteID, - "usage": s.Usage, - "state": s.State, - "is_guest": s.IsGuest.Txt, - } - fields := map[string]interface{}{ - "ccq": s.Ccq, - "mac_filter_rejections": s.MacFilterRejections, - "num_satisfaction_sta": s.NumSatisfactionSta.Val, - "avg_client_signal": s.AvgClientSignal.Val, - "satisfaction": s.Satisfaction.Val, - "satisfaction_now": s.SatisfactionNow.Val, - "num_sta": s.NumSta, - "channel": s.Channel.Val, - "rx_bytes": s.RxBytes.Val, - "rx_crypts": s.RxCrypts.Val, - "rx_dropped": s.RxDropped.Val, - "rx_errors": s.RxErrors.Val, - "rx_frags": s.RxFrags.Val, - "rx_nwids": s.RxNwids.Val, - "rx_packets": s.RxPackets.Val, - "tx_bytes": s.TxBytes.Val, - "tx_dropped": s.TxDropped.Val, - "tx_errors": s.TxErrors.Val, - "tx_packets": s.TxPackets.Val, - "tx_power": s.TxPower.Val, - "tx_retries": s.TxRetries.Val, - "tx_combined_retries": s.TxCombinedRetries.Val, - "tx_data_mpdu_bytes": s.TxDataMpduBytes.Val, - "tx_rts_retries": s.TxRtsRetries.Val, - "tx_success": s.TxSuccess.Val, - "tx_total": s.TxTotal.Val, - "tx_tcp_goodbytes": s.TxTCPStats.Goodbytes.Val, - "tx_tcp_lat_avg": s.TxTCPStats.LatAvg.Val, - "tx_tcp_lat_max": s.TxTCPStats.LatMax.Val, - "tx_tcp_lat_min": s.TxTCPStats.LatMin.Val, - "rx_tcp_goodbytes": s.RxTCPStats.Goodbytes.Val, - "rx_tcp_lat_avg": s.RxTCPStats.LatAvg.Val, - "rx_tcp_lat_max": s.RxTCPStats.LatMax.Val, - "rx_tcp_lat_min": s.RxTCPStats.LatMin.Val, - "wifi_tx_latency_mov_avg": s.WifiTxLatencyMov.Avg.Val, - "wifi_tx_latency_mov_max": s.WifiTxLatencyMov.Max.Val, - "wifi_tx_latency_mov_min": s.WifiTxLatencyMov.Min.Val, - "wifi_tx_latency_mov_total": s.WifiTxLatencyMov.Total.Val, - "wifi_tx_latency_mov_cuont": s.WifiTxLatencyMov.TotalCount.Val, - } - - r.send(&metric{Table: "uap_vaps", Tags: tags, Fields: fields}) - } -} - -func (u *InfluxUnifi) processRadTable(r report, t map[string]string, rt unifi.RadioTable, rts unifi.RadioTableStats) { - for _, p := range rt { - tags := map[string]string{ - "device_name": t["name"], - "site_name": t["site_name"], - "source": t["source"], - "channel": p.Channel.Txt, - "radio": p.Radio, - } - fields := map[string]interface{}{ - "current_antenna_gain": p.CurrentAntennaGain.Val, - "ht": p.Ht.Txt, - "max_txpower": p.MaxTxpower.Val, - "min_txpower": p.MinTxpower.Val, - "nss": p.Nss.Val, - "radio_caps": p.RadioCaps.Val, - } - - for _, t := range rts { - if t.Name == p.Name { - fields["ast_be_xmit"] = t.AstBeXmit.Val - fields["channel"] = t.Channel.Val - fields["cu_self_rx"] = t.CuSelfRx.Val - fields["cu_self_tx"] = t.CuSelfTx.Val - fields["cu_total"] = t.CuTotal.Val - fields["extchannel"] = t.Extchannel.Val - fields["gain"] = t.Gain.Val - fields["guest-num_sta"] = t.GuestNumSta.Val - fields["num_sta"] = t.NumSta.Val - fields["radio"] = t.Radio - fields["tx_packets"] = t.TxPackets.Val - fields["tx_power"] = t.TxPower.Val - fields["tx_retries"] = t.TxRetries.Val - fields["user-num_sta"] = t.UserNumSta.Val - - break - } - } - - r.send(&metric{Table: "uap_radios", Tags: tags, Fields: fields}) - } -} diff --git a/pkg/influxunifi/udm.go b/pkg/influxunifi/udm.go deleted file mode 100644 index 599bbbbee..000000000 --- a/pkg/influxunifi/udm.go +++ /dev/null @@ -1,133 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// Combine concatenates N maps. This will delete things if not used with caution. -func Combine(in ...map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}) - - for i := range in { - for k := range in[i] { - out[k] = in[i][k] - } - } - - return out -} - -// batchSysStats is used by all device types. -func (u *InfluxUnifi) batchSysStats(s unifi.SysStats, ss unifi.SystemStats) map[string]interface{} { - return map[string]interface{}{ - "loadavg_1": s.Loadavg1.Val, - "loadavg_5": s.Loadavg5.Val, - "loadavg_15": s.Loadavg15.Val, - "mem_used": s.MemUsed.Val, - "mem_buffer": s.MemBuffer.Val, - "mem_total": s.MemTotal.Val, - "cpu": ss.CPU.Val, - "mem": ss.Mem.Val, - "system_uptime": ss.Uptime.Val, - } -} - -// batchUDM generates Unifi Gateway datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchUDM(r report, s *unifi.UDM) { - if !s.Adopted.Val || s.Locating.Val { - return - } - - tags := map[string]string{ - "source": s.SourceName, - "mac": s.Mac, - "site_name": s.SiteName, - "name": s.Name, - "version": s.Version, - "model": s.Model, - "serial": s.Serial, - "type": s.Type, - } - fields := Combine( - u.batchUSGstat(s.SpeedtestStatus, s.Stat.Gw, s.Uplink), - u.batchSysStats(s.SysStats, s.SystemStats), - map[string]interface{}{ - "source": s.SourceName, - "ip": s.IP, - "bytes": s.Bytes.Val, - "last_seen": s.LastSeen.Val, - "license_state": s.LicenseState, - "guest-num_sta": s.GuestNumSta.Val, - "rx_bytes": s.RxBytes.Val, - "tx_bytes": s.TxBytes.Val, - "uptime": s.Uptime.Val, - "state": s.State.Val, - "user-num_sta": s.UserNumSta.Val, - "version": s.Version, - "num_desktop": s.NumDesktop.Val, - "num_handheld": s.NumHandheld.Val, - "num_mobile": s.NumMobile.Val, - }, - ) - - r.send(&metric{Table: "usg", Tags: tags, Fields: fields}) - u.batchNetTable(r, tags, s.NetworkTable) - u.batchUSGwans(r, tags, s.Wan1, s.Wan2) - - tags = map[string]string{ - "mac": s.Mac, - "site_name": s.SiteName, - "source": s.SourceName, - "name": s.Name, - "version": s.Version, - "model": s.Model, - "serial": s.Serial, - "type": s.Type, - } - fields = Combine( - u.batchUSWstat(s.Stat.Sw), - map[string]interface{}{ - "guest-num_sta": s.GuestNumSta.Val, - "ip": s.IP, - "bytes": s.Bytes.Val, - "last_seen": s.LastSeen.Val, - "rx_bytes": s.RxBytes.Val, - "tx_bytes": s.TxBytes.Val, - "uptime": s.Uptime.Val, - "state": s.State.Val, - }) - - r.send(&metric{Table: "usw", Tags: tags, Fields: fields}) - u.batchPortTable(r, tags, s.PortTable) - - if s.Stat.Ap == nil { - return // we're done now. the following code process UDM (non-pro) UAP data. - } - - tags = map[string]string{ - "mac": s.Mac, - "site_name": s.SiteName, - "source": s.SourceName, - "name": s.Name, - "version": s.Version, - "model": s.Model, - "serial": s.Serial, - "type": s.Type, - } - fields = u.processUAPstats(s.Stat.Ap) - fields["ip"] = s.IP - fields["bytes"] = s.Bytes.Val - fields["last_seen"] = s.LastSeen.Val - fields["rx_bytes"] = s.RxBytes.Val - fields["tx_bytes"] = s.TxBytes.Val - fields["uptime"] = s.Uptime.Val - fields["state"] = s.State - fields["user-num_sta"] = int(s.UserNumSta.Val) - fields["guest-num_sta"] = int(s.GuestNumSta.Val) - fields["num_sta"] = s.NumSta.Val - - r.send(&metric{Table: "uap", Tags: tags, Fields: fields}) - u.processRadTable(r, tags, *s.RadioTable, *s.RadioTableStats) - u.processVAPTable(r, tags, *s.VapTable) -} diff --git a/pkg/influxunifi/usg.go b/pkg/influxunifi/usg.go deleted file mode 100644 index 96306e880..000000000 --- a/pkg/influxunifi/usg.go +++ /dev/null @@ -1,140 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// batchUSG generates Unifi Gateway datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) { - if !s.Adopted.Val || s.Locating.Val { - return - } - - tags := map[string]string{ - "mac": s.Mac, - "site_name": s.SiteName, - "source": s.SourceName, - "name": s.Name, - "version": s.Version, - "model": s.Model, - "serial": s.Serial, - "type": s.Type, - } - fields := Combine( - u.batchSysStats(s.SysStats, s.SystemStats), - u.batchUSGstat(s.SpeedtestStatus, s.Stat.Gw, s.Uplink), - map[string]interface{}{ - "ip": s.IP, - "bytes": s.Bytes.Val, - "last_seen": s.LastSeen.Val, - "license_state": s.LicenseState, - "guest-num_sta": s.GuestNumSta.Val, - "rx_bytes": s.RxBytes.Val, - "tx_bytes": s.TxBytes.Val, - "uptime": s.Uptime.Val, - "state": s.State.Val, - "user-num_sta": s.UserNumSta.Val, - "version": s.Version, - "num_desktop": s.NumDesktop.Val, - "num_handheld": s.NumHandheld.Val, - "num_mobile": s.NumMobile.Val, - }, - ) - - r.send(&metric{Table: "usg", Tags: tags, Fields: fields}) - u.batchNetTable(r, tags, s.NetworkTable) - u.batchUSGwans(r, tags, s.Wan1, s.Wan2) -} - -func (u *InfluxUnifi) batchUSGstat(ss unifi.SpeedtestStatus, gw *unifi.Gw, ul unifi.Uplink) map[string]interface{} { - if gw == nil { - return map[string]interface{}{} - } - - return map[string]interface{}{ - "uplink_latency": ul.Latency.Val, - "uplink_speed": ul.Speed.Val, - "speedtest-status_latency": ss.Latency.Val, - "speedtest-status_runtime": ss.Runtime.Val, - "speedtest-status_ping": ss.StatusPing.Val, - "speedtest-status_xput_download": ss.XputDownload.Val, - "speedtest-status_xput_upload": ss.XputUpload.Val, - "lan-rx_bytes": gw.LanRxBytes.Val, - "lan-rx_packets": gw.LanRxPackets.Val, - "lan-tx_bytes": gw.LanTxBytes.Val, - "lan-tx_packets": gw.LanTxPackets.Val, - "lan-rx_dropped": gw.LanRxDropped.Val, - } -} - -func (u *InfluxUnifi) batchUSGwans(r report, tags map[string]string, wans ...unifi.Wan) { - for _, wan := range wans { - if !wan.Up.Val { - continue - } - - tags := map[string]string{ - "device_name": tags["name"], - "site_name": tags["site_name"], - "source": tags["source"], - "ip": wan.IP, - "purpose": wan.Name, - "mac": wan.Mac, - "ifname": wan.Ifname, - "type": wan.Type, - "up": wan.Up.Txt, - "enabled": wan.Enable.Txt, - } - fields := map[string]interface{}{ - "bytes-r": wan.BytesR.Val, - "full_duplex": wan.FullDuplex.Val, - "gateway": wan.Gateway, - "max_speed": wan.MaxSpeed.Val, - "rx_bytes": wan.RxBytes.Val, - "rx_bytes-r": wan.RxBytesR.Val, - "rx_dropped": wan.RxDropped.Val, - "rx_errors": wan.RxErrors.Val, - "rx_broadcast": wan.RxBroadcast.Val, - "rx_multicast": wan.RxMulticast.Val, - "rx_packets": wan.RxPackets.Val, - "speed": wan.Speed.Val, - "tx_bytes": wan.TxBytes.Val, - "tx_bytes-r": wan.TxBytesR.Val, - "tx_dropped": wan.TxDropped.Val, - "tx_errors": wan.TxErrors.Val, - "tx_packets": wan.TxPackets.Val, - "tx_broadcast": wan.TxBroadcast.Val, - "tx_multicast": wan.TxMulticast.Val, - } - - r.send(&metric{Table: "usg_wan_ports", Tags: tags, Fields: fields}) - } -} - -func (u *InfluxUnifi) batchNetTable(r report, tags map[string]string, nt unifi.NetworkTable) { - for _, p := range nt { - tags := map[string]string{ - "device_name": tags["name"], - "site_name": tags["site_name"], - "source": tags["source"], - "up": p.Up.Txt, - "enabled": p.Enabled.Txt, - "ip": p.IP, - "mac": p.Mac, - "name": p.Name, - "domain_name": p.DomainName, - "purpose": p.Purpose, - "is_guest": p.IsGuest.Txt, - } - 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, - } - - r.send(&metric{Table: "usg_networks", Tags: tags, Fields: fields}) - } -} diff --git a/pkg/influxunifi/usw.go b/pkg/influxunifi/usw.go deleted file mode 100644 index db717c1a6..000000000 --- a/pkg/influxunifi/usw.go +++ /dev/null @@ -1,114 +0,0 @@ -package influxunifi - -import ( - "golift.io/unifi" -) - -// batchUSW generates Unifi Switch datapoints for InfluxDB. -// These points can be passed directly to influx. -func (u *InfluxUnifi) batchUSW(r report, s *unifi.USW) { - if !s.Adopted.Val || s.Locating.Val { - return - } - - tags := map[string]string{ - "mac": s.Mac, - "site_name": s.SiteName, - "source": s.SourceName, - "name": s.Name, - "version": s.Version, - "model": s.Model, - "serial": s.Serial, - "type": s.Type, - } - fields := Combine( - u.batchUSWstat(s.Stat.Sw), - u.batchSysStats(s.SysStats, s.SystemStats), - map[string]interface{}{ - "guest-num_sta": s.GuestNumSta.Val, - "ip": s.IP, - "bytes": s.Bytes.Val, - "fan_level": s.FanLevel.Val, - "general_temperature": s.GeneralTemperature.Val, - "last_seen": s.LastSeen.Val, - "rx_bytes": s.RxBytes.Val, - "tx_bytes": s.TxBytes.Val, - "uptime": s.Uptime.Val, - "state": s.State.Val, - "user-num_sta": s.UserNumSta.Val, - }) - - r.send(&metric{Table: "usw", Tags: tags, Fields: fields}) - u.batchPortTable(r, tags, s.PortTable) -} - -func (u *InfluxUnifi) batchUSWstat(sw *unifi.Sw) map[string]interface{} { - if sw == nil { - return map[string]interface{}{} - } - - return map[string]interface{}{ - "stat_bytes": sw.Bytes.Val, - "stat_rx_bytes": sw.RxBytes.Val, - "stat_rx_crypts": sw.RxCrypts.Val, - "stat_rx_dropped": sw.RxDropped.Val, - "stat_rx_errors": sw.RxErrors.Val, - "stat_rx_frags": sw.RxFrags.Val, - "stat_rx_packets": sw.TxPackets.Val, - "stat_tx_bytes": sw.TxBytes.Val, - "stat_tx_dropped": sw.TxDropped.Val, - "stat_tx_errors": sw.TxErrors.Val, - "stat_tx_packets": sw.TxPackets.Val, - "stat_tx_retries": sw.TxRetries.Val, - } -} - -func (u *InfluxUnifi) batchPortTable(r report, t map[string]string, pt []unifi.Port) { - for _, p := range pt { - if !p.Up.Val || !p.Enable.Val { - continue // only record UP ports. - } - - tags := map[string]string{ - "site_name": t["site_name"], - "device_name": t["name"], - "source": t["source"], - "name": p.Name, - "poe_mode": p.PoeMode, - "port_poe": p.PortPoe.Txt, - "port_idx": p.PortIdx.Txt, - "port_id": t["name"] + " Port " + p.PortIdx.Txt, - "poe_enable": p.PoeEnable.Txt, - "flowctrl_rx": p.FlowctrlRx.Txt, - "flowctrl_tx": p.FlowctrlTx.Txt, - "media": p.Media, - } - 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, - } - - if p.PoeEnable.Val && p.PortPoe.Val { - fields["poe_current"] = p.PoeCurrent.Val - fields["poe_power"] = p.PoePower.Val - fields["poe_voltage"] = p.PoeVoltage.Val - } - - r.send(&metric{Table: "usw_ports", Tags: tags, Fields: fields}) - } -} diff --git a/pkg/inputunifi/collector.go b/pkg/inputunifi/collector.go deleted file mode 100644 index 195b2d51a..000000000 --- a/pkg/inputunifi/collector.go +++ /dev/null @@ -1,195 +0,0 @@ -package inputunifi - -import ( - "fmt" - "strings" - "time" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - "golift.io/unifi" -) - -func (u *InputUnifi) isNill(c *Controller) bool { - u.RLock() - defer u.RUnlock() - - return c.Unifi == nil -} - -// newDynamicCntrlr creates and saves a controller (with auth cookie) for repeated use. -// This is called when an unconfigured controller is requested. -func (u *InputUnifi) newDynamicCntrlr(url string) (bool, *Controller) { - u.Lock() - defer u.Unlock() - - c := u.dynamic[url] - if c != nil { - // it already exists. - return false, c - } - - ccopy := u.Default // copy defaults into new controller - c = &ccopy - u.dynamic[url] = c - c.Role = url - c.URL = url - - return true, c -} - -func (u *InputUnifi) dynamicController(url string) (*poller.Metrics, error) { - if !strings.HasPrefix(url, "http") { - return nil, fmt.Errorf("scrape filter match failed, and filter is not http URL") - } - - new, c := u.newDynamicCntrlr(url) - - if new { - u.Logf("Authenticating to Dynamic UniFi Controller: %s", url) - - if err := u.getUnifi(c); err != nil { - return nil, fmt.Errorf("authenticating to %s: %v", url, err) - } - } - - return u.collectController(c) -} - -func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) { - if u.isNill(c) { - u.Logf("Re-authenticating to UniFi Controller: %s", c.URL) - - if err := u.getUnifi(c); err != nil { - return nil, fmt.Errorf("re-authenticating to %s: %v", c.Role, err) - } - } - - return u.pollController(c) -} - -func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) { - var err error - - u.RLock() - defer u.RUnlock() - - m := &poller.Metrics{TS: time.Now()} // At this point, it's the Current Check. - - // Get the sites we care about. - if m.Sites, err = u.getFilteredSites(c); err != nil { - return m, fmt.Errorf("unifi.GetSites(%v): %v", c.URL, err) - } - - if c.SaveDPI { - if m.SitesDPI, err = c.Unifi.GetSiteDPI(m.Sites); err != nil { - return m, fmt.Errorf("unifi.GetSiteDPI(%v): %v", c.URL, err) - } - - if m.ClientsDPI, err = c.Unifi.GetClientsDPI(m.Sites); err != nil { - return m, fmt.Errorf("unifi.GetClientsDPI(%v): %v", c.URL, err) - } - } - - if c.SaveIDS { - m.IDSList, err = c.Unifi.GetIDS(m.Sites, time.Now().Add(2*time.Minute), time.Now()) - if err != nil { - return m, fmt.Errorf("unifi.GetIDS(%v): %v", c.URL, err) - } - } - - // Get all the points. - if m.Clients, err = c.Unifi.GetClients(m.Sites); err != nil { - return m, fmt.Errorf("unifi.GetClients(%v): %v", c.URL, err) - } - - if m.Devices, err = c.Unifi.GetDevices(m.Sites); err != nil { - return m, fmt.Errorf("unifi.GetDevices(%v): %v", c.URL, err) - } - - return u.augmentMetrics(c, m), nil -} - -// 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 *InputUnifi) augmentMetrics(c *Controller, metrics *poller.Metrics) *poller.Metrics { - if metrics == nil || metrics.Devices == nil || metrics.Clients == nil { - return metrics - } - - 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 { - if devices[c.Mac] = c.Name; c.Name == "" { - devices[c.Mac] = c.Hostname - } - - 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 - } - - for i := range metrics.ClientsDPI { - // Name on Client DPI data also comes blank, find it based on MAC address. - metrics.ClientsDPI[i].Name = devices[metrics.ClientsDPI[i].MAC] - if metrics.ClientsDPI[i].Name == "" { - metrics.ClientsDPI[i].Name = metrics.ClientsDPI[i].MAC - } - } - - if !*c.SaveSites { - metrics.Sites = nil - } - - return metrics -} - -// 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 *InputUnifi) getFilteredSites(c *Controller) (unifi.Sites, error) { - u.RLock() - defer u.RUnlock() - - sites, err := c.Unifi.GetSites() - if err != nil { - return nil, err - } else if len(c.Sites) < 1 || StringInSlice("all", c.Sites) { - return sites, nil - } - - i := 0 - - for _, s := range sites { - // Only include valid sites in the request filter. - if StringInSlice(s.Name, c.Sites) { - sites[i] = s - i++ - } - } - - return sites[:i], nil -} diff --git a/pkg/inputunifi/input.go b/pkg/inputunifi/input.go deleted file mode 100644 index d6540d83a..000000000 --- a/pkg/inputunifi/input.go +++ /dev/null @@ -1,198 +0,0 @@ -// Package inputunifi implements the poller.Input interface and bridges the gap between -// metrics from the unifi library, and the augments required to pump them into unifi-poller. -package inputunifi - -import ( - "fmt" - "os" - "strings" - - "sync" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - "golift.io/unifi" -) - -const ( - defaultURL = "https://127.0.0.1:8443" - defaultUser = "unifipoller" - defaultPass = "unifipoller" - defaultSite = "all" -) - -// InputUnifi contains the running data. -type InputUnifi struct { - *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"` - dynamic map[string]*Controller - sync.Mutex // to lock the map above. - poller.Logger -} - -// Controller represents the configuration for a UniFi Controller. -// Each polled controller may have its own configuration. -type Controller struct { - VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` - SaveIDS bool `json:"save_ids" toml:"save_ids" xml:"save_ids" yaml:"save_ids"` - SaveDPI bool `json:"save_dpi" toml:"save_dpi" xml:"save_dpi" yaml:"save_dpi"` - SaveSites *bool `json:"save_sites" toml:"save_sites" xml:"save_sites" yaml:"save_sites"` - Role string `json:"role" toml:"role" xml:"role,attr" yaml:"role"` - User string `json:"user" toml:"user" xml:"user" yaml:"user"` - Pass string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"` - URL string `json:"url" toml:"url" xml:"url" yaml:"url"` - Sites []string `json:"sites,omitempty" toml:"sites,omitempty" xml:"site" yaml:"sites"` - Unifi *unifi.Unifi `json:"-" toml:"-" xml:"-" yaml:"-"` -} - -// Config contains our configuration data -type Config struct { - sync.RWMutex // locks the Unifi struct member when re-authing to unifi. - Default Controller `json:"defaults" toml:"defaults" xml:"default" yaml:"defaults"` - Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"` - Dynamic bool `json:"dynamic" toml:"dynamic" xml:"dynamic,attr" yaml:"dynamic"` - Controllers []*Controller `json:"controllers" toml:"controller" xml:"controller" yaml:"controllers"` -} - -func init() { - u := &InputUnifi{} - - poller.NewInput(&poller.InputPlugin{ - Name: "unifi", - Input: u, // this library implements poller.Input interface for Metrics(). - Config: u, // Defines our config data interface. - }) -} - -// getUnifi (re-)authenticates to a unifi controller. -func (u *InputUnifi) getUnifi(c *Controller) error { - var err error - - u.Lock() - defer u.Unlock() - - if c.Unifi != nil { - c.Unifi.CloseIdleConnections() - } - - // Create an authenticated session to the Unifi Controller. - c.Unifi, err = unifi.NewUnifi(&unifi.Config{ - User: c.User, - Pass: c.Pass, - URL: c.URL, - VerifySSL: c.VerifySSL, - ErrorLog: u.LogErrorf, // Log all errors. - DebugLog: u.LogDebugf, // Log debug messages. - }) - if err != nil { - c.Unifi = nil - return fmt.Errorf("unifi controller: %v", err) - } - - u.LogDebugf("Authenticated with controller successfully, %s", c.URL) - - return nil -} - -// checkSites makes sure the list of provided sites exists on the controller. -// This only runs once during initialization. -func (u *InputUnifi) checkSites(c *Controller) error { - u.RLock() - defer u.RUnlock() - - if len(c.Sites) < 1 || c.Sites[0] == "" { - c.Sites = []string{"all"} - } - - u.LogDebugf("Checking Controller Sites List") - - sites, err := c.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 %s: %v", len(msg), c.Role, strings.Join(msg, ", ")) - - if StringInSlice("all", c.Sites) { - c.Sites = []string{"all"} - return nil - } - - keep := []string{} - -FIRST: - for _, s := range c.Sites { - for _, site := range sites { - if s == site.Name { - keep = append(keep, s) - continue FIRST - } - } - u.LogErrorf("Configured site not found on controller %s: %v", c.Role, s) - } - - if c.Sites = keep; len(keep) < 1 { - c.Sites = []string{"all"} - } - - return nil -} - -func (u *InputUnifi) dumpSitesJSON(c *Controller, path, name string, sites unifi.Sites) ([]byte, error) { - allJSON := []byte{} - - 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) - - body, err := c.Unifi.GetJSON(apiPath) - if err != nil { - return allJSON, err - } - - allJSON = append(allJSON, body...) - } - - return allJSON, nil -} - -func (u *InputUnifi) setDefaults(c *Controller) { - if c.SaveSites == nil { - t := true - c.SaveSites = &t - } - - if c.URL == "" { - c.URL = defaultURL - } - - if c.Role == "" { - c.Role = c.URL - } - - if c.Pass == "" { - c.Pass = defaultPass - } - - if c.User == "" { - c.User = defaultUser - } - - if len(c.Sites) < 1 { - c.Sites = []string{defaultSite} - } -} - -// 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 -} diff --git a/pkg/inputunifi/interface.go b/pkg/inputunifi/interface.go deleted file mode 100644 index daf21ae7f..000000000 --- a/pkg/inputunifi/interface.go +++ /dev/null @@ -1,140 +0,0 @@ -package inputunifi - -/* This file contains the three poller.Input interface methods. */ - -import ( - "fmt" - "os" - "strings" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - "golift.io/unifi" -) - -// Initialize gets called one time when starting up. -// Satisfies poller.Input interface. -func (u *InputUnifi) Initialize(l poller.Logger) error { - if u.Disable { - l.Logf("UniFi input plugin disabled!") - return nil - } - - if u.setDefaults(&u.Default); len(u.Controllers) < 1 && !u.Dynamic { - new := u.Default // copy defaults. - u.Controllers = []*Controller{&new} - } - - if len(u.Controllers) < 1 { - l.Logf("No controllers configured. Polling dynamic controllers only!") - } - - u.dynamic = make(map[string]*Controller) - u.Logger = l - - for _, c := range u.Controllers { - u.setDefaults(c) - - switch err := u.getUnifi(c); err { - case nil: - if err := u.checkSites(c); err != nil { - u.LogErrorf("checking sites on %s: %v", c.Role, err) - } - - u.Logf("Configured UniFi Controller at %s v%s as user %s. Sites: %v", - c.URL, c.Unifi.ServerVersion, c.User, c.Sites) - default: - u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Role, err) - } - } - - return nil -} - -// Metrics grabs all the measurements from a UniFi controller and returns them. -func (u *InputUnifi) Metrics() (*poller.Metrics, bool, error) { - return u.MetricsFrom(nil) -} - -// MetricsFrom grabs all the measurements from a UniFi controller and returns them. -func (u *InputUnifi) MetricsFrom(filter *poller.Filter) (*poller.Metrics, bool, error) { - if u.Disable { - return nil, false, nil - } - - errs := []string{} - metrics := &poller.Metrics{} - ok := false - - if filter != nil && filter.Path != "" { - if !u.Dynamic { - return metrics, false, fmt.Errorf("filter path requested but dynamic lookups disabled") - } - - // Attempt a dynamic metrics fetch from an unconfigured controller. - m, err := u.dynamicController(filter.Path) - - return m, err == nil && m != nil, err - } - - // Check if the request is for an existing, configured controller. - for _, c := range u.Controllers { - if filter != nil && !strings.EqualFold(c.Role, filter.Role) { - continue - } - - m, err := u.collectController(c) - if err != nil { - errs = append(errs, err.Error()) - } - - if m == nil { - continue - } - - ok = true - metrics = poller.AppendMetrics(metrics, m) - } - - if len(errs) > 0 { - return metrics, ok, fmt.Errorf(strings.Join(errs, ", ")) - } - - return metrics, ok, nil -} - -// RawMetrics returns API output from the first configured unifi controller. -func (u *InputUnifi) RawMetrics(filter *poller.Filter) ([]byte, error) { - if l := len(u.Controllers); filter.Unit >= l { - return nil, fmt.Errorf("control number %d not found, %d controller(s) configured (0 index)", filter.Unit, l) - } - - c := u.Controllers[filter.Unit] - if u.isNill(c) { - u.Logf("Re-authenticating to UniFi Controller: %s", c.URL) - - if err := u.getUnifi(c); err != nil { - return nil, fmt.Errorf("re-authenticating to %s: %v", c.Role, err) - } - } - - if err := u.checkSites(c); err != nil { - return nil, err - } - - sites, err := u.getFilteredSites(c) - if err != nil { - return nil, err - } - - switch filter.Kind { - case "d", "device", "devices": - return u.dumpSitesJSON(c, unifi.APIDevicePath, "Devices", sites) - case "client", "clients", "c": - return u.dumpSitesJSON(c, unifi.APIClientPath, "Clients", sites) - case "other", "o": - _, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", filter.Path) - return c.Unifi.GetJSON(filter.Path) - default: - return []byte{}, fmt.Errorf("must provide filter: devices, clients, other") - } -} diff --git a/pkg/poller/build_macos.go b/pkg/poller/build_macos.go deleted file mode 100644 index 9292f409e..000000000 --- a/pkg/poller/build_macos.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build darwin - -package poller - -// DefaultConfFile is where to find config if --config is not prvided. -const DefaultConfFile = "/usr/local/etc/unifi-poller/up.conf" - -// DefaultObjPath is the path to look for shared object libraries (plugins). -const DefaultObjPath = "/usr/local/lib/unifi-poller" diff --git a/pkg/poller/build_unix.go b/pkg/poller/build_unix.go deleted file mode 100644 index fd381e191..000000000 --- a/pkg/poller/build_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !windows,!darwin - -package poller - -// DefaultConfFile is where to find config if --config is not prvided. -const DefaultConfFile = "/etc/unifi-poller/up.conf" - -// DefaultObjPath is the path to look for shared object libraries (plugins). -const DefaultObjPath = "/usr/lib/unifi-poller" diff --git a/pkg/poller/build_windows.go b/pkg/poller/build_windows.go deleted file mode 100644 index 69d964e8e..000000000 --- a/pkg/poller/build_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build windows - -package poller - -// DefaultConfFile is where to find config if --config is not prvided. -const DefaultConfFile = `C:\ProgramData\unifi-poller\up.conf` - -// DefaultObjPath is useless in this context. Bummer. -const DefaultObjPath = "PLUGINS_DO_NOT_WORK_ON_WINDOWS_SOWWWWWY" diff --git a/pkg/poller/config.go b/pkg/poller/config.go deleted file mode 100644 index b40c89386..000000000 --- a/pkg/poller/config.go +++ /dev/null @@ -1,150 +0,0 @@ -package poller - -/* - I consider this file the pinacle example of how to allow a Go application to be configured from a file. - You can put your configuration into any file format: XML, YAML, JSON, TOML, and you can override any - struct member using an environment variable. The Duration type is also supported. All of the Config{} - and Duration{} types and methods are reusable in other projects. Just adjust the data in the struct to - meet your app's needs. See the New() procedure and Start() method in start.go for example usage. -*/ - -import ( - "os" - "path" - "plugin" - "strings" - "time" - - "github.com/spf13/pflag" - "golift.io/cnfg" - "golift.io/cnfg/cnfgfile" - "golift.io/unifi" -) - -const ( - // AppName is the name of the application. - AppName = "unifi-poller" - // ENVConfigPrefix is the prefix appended to an env variable tag name. - ENVConfigPrefix = "UP" -) - -// UnifiPoller contains the application startup data, and auth info for UniFi & Influx. -type UnifiPoller struct { - Flags *Flags - *Config -} - -// Flags represents the CLI args available and their settings. -type Flags struct { - ConfigFile string - DumpJSON string - ShowVer bool - *pflag.FlagSet -} - -// 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 - SitesDPI []*unifi.DPITable - ClientsDPI []*unifi.DPITable -} - -// Config represents the core library input data. -type Config struct { - *Poller `json:"poller" toml:"poller" xml:"poller" yaml:"poller"` -} - -// Poller is the global config values. -type Poller struct { - Plugins []string `json:"plugins" toml:"plugins" xml:"plugin" yaml:"plugins"` - Debug bool `json:"debug" toml:"debug" xml:"debug,attr" yaml:"debug"` - Quiet bool `json:"quiet,omitempty" toml:"quiet,omitempty" xml:"quiet,attr" yaml:"quiet"` -} - -// LoadPlugins reads-in dynamic shared libraries. -// Not used very often, if at all. -func (u *UnifiPoller) LoadPlugins() error { - for _, p := range u.Plugins { - name := strings.TrimSuffix(p, ".so") + ".so" - - if name == ".so" { - continue // Just ignore it. uhg. - } - - if _, err := os.Stat(name); os.IsNotExist(err) { - name = path.Join(DefaultObjPath, name) - } - - u.Logf("Loading Dynamic Plugin: %s", name) - - if _, err := plugin.Open(name); err != nil { - return err - } - } - - return nil -} - -// ParseConfigs parses the poller config and the config for each registered output plugin. -func (u *UnifiPoller) ParseConfigs() error { - // Parse core config. - if err := u.parseInterface(u.Config); err != nil { - return err - } - - // Load dynamic plugins. - if err := u.LoadPlugins(); err != nil { - return err - } - - if err := u.parseInputs(); err != nil { - return err - } - - return u.parseOutputs() -} - -// parseInterface parses the config file and environment variables into the provided interface. -func (u *UnifiPoller) parseInterface(i interface{}) error { - // Parse config file into provided interface. - if err := cnfgfile.Unmarshal(i, u.Flags.ConfigFile); err != nil { - return err - } - - // Parse environment variables into provided interface. - _, err := cnfg.UnmarshalENV(i, ENVConfigPrefix) - - return err -} - -// Parse input plugin configs. -func (u *UnifiPoller) parseInputs() error { - inputSync.Lock() - defer inputSync.Unlock() - - for _, i := range inputs { - if err := u.parseInterface(i.Config); err != nil { - return err - } - } - - return nil -} - -// Parse output plugin configs. -func (u *UnifiPoller) parseOutputs() error { - outputSync.Lock() - defer outputSync.Unlock() - - for _, o := range outputs { - if err := u.parseInterface(o.Config); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/poller/dumper.go b/pkg/poller/dumper.go deleted file mode 100644 index c78edabb0..000000000 --- a/pkg/poller/dumper.go +++ /dev/null @@ -1,33 +0,0 @@ -package poller - -import ( - "fmt" - "strconv" - "strings" -) - -// DumpJSONPayload prints raw json from the UniFi Controller. This is currently -// tied into the -j CLI arg, and is probably not very useful outside that context. -func (u *UnifiPoller) DumpJSONPayload() (err error) { - u.Config.Quiet = true - split := strings.SplitN(u.Flags.DumpJSON, " ", 2) - filter := &Filter{Kind: split[0]} - - if split2 := strings.Split(filter.Kind, ":"); len(split2) > 1 { - filter.Kind = split2[0] - filter.Unit, _ = strconv.Atoi(split2[1]) - } - - if len(split) > 1 { - filter.Path = split[1] - } - - m, err := inputs[0].RawMetrics(filter) - if err != nil { - return err - } - - fmt.Println(string(m)) - - return nil -} diff --git a/pkg/poller/inputs.go b/pkg/poller/inputs.go deleted file mode 100644 index 897a8d0d9..000000000 --- a/pkg/poller/inputs.go +++ /dev/null @@ -1,165 +0,0 @@ -package poller - -import ( - "fmt" - "strings" - "sync" - - "golift.io/unifi" -) - -var ( - inputs []*InputPlugin - inputSync sync.Mutex -) - -// Input plugins must implement this interface. -type Input interface { - Initialize(Logger) error // Called once on startup to initialize the plugin. - Metrics() (*Metrics, bool, error) // Called every time new metrics are requested. - MetricsFrom(*Filter) (*Metrics, bool, error) // Called every time new metrics are requested. - RawMetrics(*Filter) ([]byte, error) -} - -// InputPlugin describes an input plugin's consumable interface. -type InputPlugin struct { - Name string - Config interface{} // Each config is passed into an unmarshaller later. - Input -} - -// Filter is used for metrics filters. Many fields for lots of expansion. -type Filter struct { - Type string - Term string - Name string - Tags string - Role string - Kind string - Path string - Area int - Item int - Unit int - Sign int64 - Mass int64 - Rate float64 - Cost float64 - Free bool - True bool - Done bool - Stop bool -} - -// NewInput creates a metric input. This should be called by input plugins -// init() functions. -func NewInput(i *InputPlugin) { - inputSync.Lock() - defer inputSync.Unlock() - - if i == nil || i.Input == nil { - panic("nil output or method passed to poller.NewOutput") - } - - inputs = append(inputs, i) -} - -// InitializeInputs runs the passed-in initializer method for each input plugin. -func (u *UnifiPoller) InitializeInputs() error { - inputSync.Lock() - defer inputSync.Unlock() - - for _, input := range inputs { - // This must return, or the app locks up here. - if err := input.Initialize(u); err != nil { - return err - } - } - - return nil -} - -// Metrics aggregates all the measurements from all configured inputs and returns them. -func (u *UnifiPoller) Metrics() (*Metrics, bool, error) { - errs := []string{} - metrics := &Metrics{} - ok := false - - for _, input := range inputs { - m, _, err := input.Metrics() - if err != nil { - errs = append(errs, err.Error()) - } - - if m == nil { - continue - } - - ok = true - metrics = AppendMetrics(metrics, m) - } - - var err error - - if len(errs) > 0 { - err = fmt.Errorf(strings.Join(errs, ", ")) - } - - return metrics, ok, err -} - -// MetricsFrom aggregates all the measurements from filtered inputs and returns them. -func (u *UnifiPoller) MetricsFrom(filter *Filter) (*Metrics, bool, error) { - errs := []string{} - metrics := &Metrics{} - ok := false - - for _, input := range inputs { - if !strings.EqualFold(input.Name, filter.Name) { - continue - } - - m, _, err := input.MetricsFrom(filter) - if err != nil { - errs = append(errs, err.Error()) - } - - if m == nil { - continue - } - - ok = true - metrics = AppendMetrics(metrics, m) - } - - var err error - - if len(errs) > 0 { - err = fmt.Errorf(strings.Join(errs, ", ")) - } - - return metrics, ok, err -} - -// AppendMetrics combined the metrics from two sources. -func AppendMetrics(existing *Metrics, m *Metrics) *Metrics { - existing.SitesDPI = append(existing.SitesDPI, m.SitesDPI...) - existing.Sites = append(existing.Sites, m.Sites...) - existing.ClientsDPI = append(existing.ClientsDPI, m.ClientsDPI...) - existing.Clients = append(existing.Clients, m.Clients...) - existing.IDSList = append(existing.IDSList, m.IDSList...) - - if m.Devices == nil { - return existing - } - - if existing.Devices == nil { - existing.Devices = &unifi.Devices{} - } - - existing.UAPs = append(existing.UAPs, m.UAPs...) - existing.USGs = append(existing.USGs, m.USGs...) - existing.USWs = append(existing.USWs, m.USWs...) - existing.UDMs = append(existing.UDMs, m.UDMs...) - - return existing -} diff --git a/pkg/poller/logger.go b/pkg/poller/logger.go deleted file mode 100644 index fa983e5ff..000000000 --- a/pkg/poller/logger.go +++ /dev/null @@ -1,34 +0,0 @@ -package poller - -import ( - "fmt" - "log" -) - -const callDepth = 2 - -// Logger is passed into input packages so they may write logs. -type Logger interface { - Logf(m string, v ...interface{}) - LogErrorf(m string, v ...interface{}) - LogDebugf(m string, v ...interface{}) -} - -// Logf prints a log entry if quiet is false. -func (u *UnifiPoller) Logf(m string, v ...interface{}) { - if !u.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.Debug && !u.Quiet { - _ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...)) - } -} - -// LogErrorf prints an error log entry. -func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) { - _ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...)) -} diff --git a/pkg/poller/outputs.go b/pkg/poller/outputs.go deleted file mode 100644 index 0c73bfe7c..000000000 --- a/pkg/poller/outputs.go +++ /dev/null @@ -1,72 +0,0 @@ -package poller - -import ( - "fmt" - "sync" -) - -var ( - outputs []*Output - outputSync sync.Mutex -) - -// Collect is passed into output packages so they may collect metrics to output. -// Output packages must implement this interface. -type Collect interface { - Metrics() (*Metrics, bool, error) - MetricsFrom(*Filter) (*Metrics, bool, error) - Logger -} - -// Output defines the output data for a metric exporter like influx or prometheus. -// Output packages should call NewOutput with this struct in init(). -type Output struct { - Name string - Config interface{} // Each config is passed into an unmarshaller later. - Method func(Collect) error // Called on startup for each configured output. -} - -// NewOutput should be called by each output package's init function. -func NewOutput(o *Output) { - outputSync.Lock() - defer outputSync.Unlock() - - if o == nil || o.Method == nil { - panic("nil output or method passed to poller.NewOutput") - } - - outputs = append(outputs, o) -} - -// InitializeOutputs runs all the configured output plugins. -// If none exist, or they all exit an error is returned. -func (u *UnifiPoller) InitializeOutputs() error { - v := make(chan error) - defer close(v) - - var count int - - for _, o := range outputs { - count++ - - go func(o *Output) { - v <- o.Method(u) - }(o) - } - - if count < 1 { - return fmt.Errorf("no output plugins imported") - } - - for err := range v { - if err != nil { - return err - } - - if count--; count < 1 { - return fmt.Errorf("all output plugins have stopped, or none enabled") - } - } - - return nil -} diff --git a/pkg/poller/start.go b/pkg/poller/start.go deleted file mode 100644 index 34cba9dae..000000000 --- a/pkg/poller/start.go +++ /dev/null @@ -1,83 +0,0 @@ -// Package poller provides the CLI interface to setup unifi-poller. -package poller - -import ( - "fmt" - "log" - "os" - - "github.com/prometheus/common/version" - "github.com/spf13/pflag" -) - -// New returns a new poller struct. -func New() *UnifiPoller { - return &UnifiPoller{Config: &Config{Poller: &Poller{}}, Flags: &Flags{}} -} - -// 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.SetOutput(os.Stdout) - log.SetFlags(log.LstdFlags) - u.Flags.Parse(os.Args[1:]) - - if u.Flags.ShowVer { - fmt.Printf("%s v%s\n", AppName, version.Version) - return nil // don't run anything else w/ version request. - } - - if u.Flags.DumpJSON == "" { // do not print this when dumping JSON. - u.Logf("Loading Configuration File: %s", u.Flags.ConfigFile) - } - - // Parse config file and ENV variables. - if err := u.ParseConfigs(); err != nil { - return err - } - - return u.Run() -} - -// Parse turns CLI arguments into data structures. Called by Start() on startup. -func (f *Flags) 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 u.Flags.DumpJSON != "" { - if err := u.InitializeInputs(); err != nil { - return err - } - - return u.DumpJSONPayload() - } - - if u.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.Version, os.Getpid()) - - if err := u.InitializeInputs(); err != nil { - return err - } - - return u.InitializeOutputs() -} diff --git a/pkg/promunifi/README.md b/pkg/promunifi/README.md deleted file mode 100644 index d0ca905d7..000000000 --- a/pkg/promunifi/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# prometheus - -This package provides the interface to turn UniFi measurements into prometheus -exported metrics. Requires the poller package for actual UniFi data collection. diff --git a/pkg/promunifi/clients.go b/pkg/promunifi/clients.go deleted file mode 100644 index 844d899d8..000000000 --- a/pkg/promunifi/clients.go +++ /dev/null @@ -1,136 +0,0 @@ -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 - DPITxPackets *prometheus.Desc - DPIRxPackets *prometheus.Desc - DPITxBytes *prometheus.Desc - DPIRxBytes *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", "source", "wired"} - labelW := append([]string{"radio_name", "radio", "radio_proto", "channel", "essid", "bssid", "radio_desc"}, labels...) - labelDPI := []string{"name", "mac", "site_name", "source", "category", "application"} - - 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_ratio", "Client Connection Quality", labelW, nil), - Satisfaction: prometheus.NewDesc(ns+"satisfaction_ratio", "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), - DPITxPackets: prometheus.NewDesc(ns+"dpi_transmit_packets", "Client DPI Transmit Packets", labelDPI, nil), - DPIRxPackets: prometheus.NewDesc(ns+"dpi_receive_packets", "Client DPI Receive Packets", labelDPI, nil), - DPITxBytes: prometheus.NewDesc(ns+"dpi_transmit_bytes", "Client DPI Transmit Bytes", labelDPI, nil), - DPIRxBytes: prometheus.NewDesc(ns+"dpi_receive_bytes", "Client DPI Receive Bytes", labelDPI, nil), - } -} - -func (u *promUnifi) exportClientDPI(r report, s *unifi.DPITable) { - for _, dpi := range s.ByApp { - labelDPI := []string{s.Name, s.MAC, s.SiteName, s.SourceName, - unifi.DPICats.Get(dpi.Cat), unifi.DPIApps.GetApp(dpi.Cat, dpi.App)} - - // log.Println(labelDPI, dpi.Cat, dpi.App, dpi.TxBytes, dpi.RxBytes, dpi.TxPackets, dpi.RxPackets) - r.send([]*metric{ - {u.Client.DPITxPackets, gauge, dpi.TxPackets, labelDPI}, - {u.Client.DPIRxPackets, gauge, dpi.RxPackets, labelDPI}, - {u.Client.DPITxBytes, gauge, dpi.TxBytes, labelDPI}, - {u.Client.DPIRxBytes, gauge, dpi.RxBytes, labelDPI}, - }) - } -} - -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, c.SourceName, ""} - 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, counter, c.WiredRxBytes, labels}, - {u.Client.RxBytesR, gauge, c.WiredRxBytesR, labels}, - {u.Client.RxPackets, counter, c.WiredRxPackets, labels}, - {u.Client.TxBytes, counter, c.WiredTxBytes, labels}, - {u.Client.TxBytesR, gauge, c.WiredTxBytesR, labels}, - {u.Client.TxPackets, counter, c.WiredTxPackets, labels}, - }) - } else { - labels[len(labels)-1] = "false" - labelW[len(labelW)-1] = "false" - - r.send([]*metric{ - {u.Client.Anomalies, counter, c.Anomalies, labelW}, - {u.Client.CCQ, gauge, float64(c.Ccq) / 1000.0, labelW}, - {u.Client.Satisfaction, gauge, c.Satisfaction.Val / 100.0, labelW}, - {u.Client.Noise, gauge, c.Noise, labelW}, - {u.Client.RoamCount, counter, c.RoamCount, labelW}, - {u.Client.RSSI, gauge, c.Rssi, labelW}, - {u.Client.Signal, gauge, c.Signal, labelW}, - {u.Client.TxPower, gauge, c.TxPower, labelW}, - {u.Client.TxRate, gauge, c.TxRate * 1000, labelW}, - {u.Client.WifiTxAttempts, counter, c.WifiTxAttempts, labelW}, - {u.Client.RxRate, gauge, c.RxRate * 1000, labelW}, - {u.Client.TxRetries, counter, c.TxRetries, labels}, - {u.Client.TxBytes, counter, c.TxBytes, labels}, - {u.Client.TxBytesR, gauge, c.TxBytesR, labels}, - {u.Client.TxPackets, counter, c.TxPackets, labels}, - {u.Client.RxBytes, counter, c.RxBytes, labels}, - {u.Client.RxBytesR, gauge, c.RxBytesR, labels}, - {u.Client.RxPackets, counter, c.RxPackets, labels}, - {u.Client.BytesR, gauge, c.BytesR, labelW}, - }) - } - - r.send([]*metric{{u.Client.Uptime, gauge, c.Uptime, labelW}}) -} diff --git a/pkg/promunifi/collector.go b/pkg/promunifi/collector.go deleted file mode 100644 index 7fae2ccc8..000000000 --- a/pkg/promunifi/collector.go +++ /dev/null @@ -1,346 +0,0 @@ -// Package promunifi provides the bridge between unifi-poller metrics and prometheus. -package promunifi - -import ( - "fmt" - "net/http" - "reflect" - "strings" - "sync" - "time" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/version" - "golift.io/unifi" -) - -const ( - // channel buffer, fits at least one batch. - defaultBuffer = 50 - defaultHTTPListen = "0.0.0.0:9130" - // simply fewer letters. - counter = prometheus.CounterValue - gauge = prometheus.GaugeValue -) - -type promUnifi struct { - *Config `json:"prometheus" toml:"prometheus" xml:"prometheus" yaml:"prometheus"` - Client *uclient - Device *unifiDevice - UAP *uap - USG *usg - USW *usw - Site *site - // This interface is passed to the Collect() method. The Collect method uses - // this interface to retrieve the latest UniFi measurements and export them. - Collector poller.Collect -} - -// Config is the input (config file) data used to initialize this output plugin. -type Config struct { - // If non-empty, each of the collected metrics is prefixed by the - // provided string and an underscore ("_"). - Namespace string `json:"namespace" toml:"namespace" xml:"namespace" yaml:"namespace"` - HTTPListen string `json:"http_listen" toml:"http_listen" xml:"http_listen" yaml:"http_listen"` - // 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 `json:"report_errors" toml:"report_errors" xml:"report_errors" yaml:"report_errors"` - Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"` - // Buffer is a channel buffer. - // Default is probably 50. Seems fast there; try 1 to see if CPU usage goes down? - Buffer int `json:"buffer" toml:"buffer" xml:"buffer" yaml:"buffer"` -} - -type metric struct { - Desc *prometheus.Desc - ValueType prometheus.ValueType - Value interface{} - Labels []string -} - -// Report accumulates counters that are printed to a log line. -type Report struct { - *Config - Total int // Total count of metrics recorded. - Errors int // Total count of errors recording metrics. - Zeros int // Total count of metrics equal to zero. - Metrics *poller.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 -} - -// target is used for targeted (sometimes dynamic) metrics scrapes. -type target struct { - *poller.Filter - u *promUnifi -} - -func init() { - u := &promUnifi{Config: &Config{}} - - poller.NewOutput(&poller.Output{ - Name: "prometheus", - Config: u, - Method: u.Run, - }) -} - -// Run creates the collectors and starts the web server up. -// Should be run in a Go routine. Returns nil if not configured. -func (u *promUnifi) Run(c poller.Collect) error { - if u.Disable { - return nil - } - - u.Namespace = strings.Trim(strings.Replace(u.Namespace, "-", "_", -1), "_") - if u.Namespace == "" { - u.Namespace = strings.Replace(poller.AppName, "-", "", -1) - } - - if u.HTTPListen == "" { - u.HTTPListen = defaultHTTPListen - } - - if u.Buffer == 0 { - u.Buffer = defaultBuffer - } - - // Later can pass this in from poller by adding a method to the interface. - u.Collector = c - u.Client = descClient(u.Namespace + "_client_") - u.Device = descDevice(u.Namespace + "_device_") // stats for all device types. - u.UAP = descUAP(u.Namespace + "_device_") - u.USG = descUSG(u.Namespace + "_device_") - u.USW = descUSW(u.Namespace + "_device_") - u.Site = descSite(u.Namespace + "_site_") - mux := http.NewServeMux() - - prometheus.MustRegister(version.NewCollector(u.Namespace)) - prometheus.MustRegister(u) - c.Logf("Prometheus exported at https://%s/ - namespace: %s", u.HTTPListen, u.Namespace) - mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, - promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}, - )) - mux.HandleFunc("/scrape", u.ScrapeHandler) - mux.HandleFunc("/", u.DefaultHandler) - - return http.ListenAndServe(u.HTTPListen, mux) -} - -// ScrapeHandler allows prometheus to scrape a single source, instead of all sources. -func (u *promUnifi) ScrapeHandler(w http.ResponseWriter, r *http.Request) { - t := &target{u: u, Filter: &poller.Filter{ - Name: r.URL.Query().Get("input"), // "unifi" - Path: r.URL.Query().Get("path"), // url: "https://127.0.0.1:8443" - Role: r.URL.Query().Get("role"), // configured role in up.conf. - }} - - if t.Name == "" { - u.Collector.LogErrorf("input parameter missing on scrape from %v", r.RemoteAddr) - http.Error(w, `'input' parameter must be specified (try "unifi")`, 400) - - return - } - - if t.Role == "" && t.Path == "" { - u.Collector.LogErrorf("role and path parameters missing on scrape from %v", r.RemoteAddr) - http.Error(w, "'role' OR 'path' parameter must be specified: configured role OR unconfigured url", 400) - - return - } - - registry := prometheus.NewRegistry() - - registry.MustRegister(t) - promhttp.HandlerFor( - registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}, - ).ServeHTTP(w, r) -} - -func (u *promUnifi) DefaultHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - _, _ = w.Write([]byte(poller.AppName + "\n")) -} - -// Describe satisfies the prometheus Collector. This returns all of the -// metric descriptions that this packages produces. -func (t *target) Describe(ch chan<- *prometheus.Desc) { - t.u.Describe(ch) -} - -// 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 for a single controller poll. -func (t *target) Collect(ch chan<- prometheus.Metric) { - t.u.collect(ch, t.Filter) -} - -// 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) { - u.collect(ch, nil) -} - -func (u *promUnifi) collect(ch chan<- prometheus.Metric, filter *poller.Filter) { - var err error - - r := &Report{ - Config: u.Config, - ch: make(chan []*metric, u.Config.Buffer), - Start: time.Now()} - defer r.close() - - ok := false - - if filter == nil { - r.Metrics, ok, err = u.Collector.Metrics() - } else { - r.Metrics, ok, err = u.Collector.MetricsFrom(filter) - } - - r.Fetch = time.Since(r.Start) - - if err != nil { - r.error(ch, prometheus.NewInvalidDesc(err), fmt.Errorf("metric fetch failed")) - u.Collector.LogErrorf("metric fetch failed: %v", err) - - if !ok { - return - } - } - - if r.Metrics.Devices == nil { - r.Metrics.Devices = &unifi.Devices{} - } - - // Pass Report interface into our collecting and reporting methods. - go u.exportMetrics(r, ch, r.ch) - u.loopExports(r) -} - -// 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. -// This is where our channels connects to the prometheus channel. -func (u *promUnifi) exportMetrics(r report, ch chan<- prometheus.Metric, ourChan chan []*metric) { - descs := make(map[*prometheus.Desc]bool) // used as a counter - defer r.report(u.Collector, descs) - - for newMetrics := range ourChan { - 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() - } -} - -func (u *promUnifi) loopExports(r report) { - m := r.metrics() - - r.add() - r.add() - r.add() - r.add() - r.add() - r.add() - r.add() - r.add() - - go func() { - defer r.done() - - for _, s := range m.Sites { - u.exportSite(r, s) - } - }() - - go func() { - defer r.done() - - for _, s := range m.SitesDPI { - u.exportSiteDPI(r, s) - } - }() - - go func() { - defer r.done() - - for _, c := range m.Clients { - u.exportClient(r, c) - } - }() - - go func() { - defer r.done() - - for _, c := range m.ClientsDPI { - u.exportClientDPI(r, c) - } - }() - - go func() { - defer r.done() - - for _, d := range m.UAPs { - u.exportUAP(r, d) - } - }() - - go func() { - defer r.done() - - for _, d := range m.UDMs { - u.exportUDM(r, d) - } - }() - - go func() { - defer r.done() - - for _, d := range m.USGs { - u.exportUSG(r, d) - } - }() - - go func() { - defer r.done() - - for _, d := range m.USWs { - u.exportUSW(r, d) - } - }() -} diff --git a/pkg/promunifi/report.go b/pkg/promunifi/report.go deleted file mode 100644 index 3eb666389..000000000 --- a/pkg/promunifi/report.go +++ /dev/null @@ -1,80 +0,0 @@ -package promunifi - -import ( - "fmt" - "time" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - "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() *poller.Metrics - report(c poller.Collect, 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 -const oneDecimalPoint = 10.0 - -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() *poller.Metrics { - return r.Metrics -} - -func (r *Report) report(c poller.Collect, descs map[*prometheus.Desc]bool) { - m := r.Metrics - c.Logf("UniFi Measurements Exported. Site: %d, Client: %d, "+ - "UAP: %d, USG/UDM: %d, USW: %d, 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), - len(descs), r.Total, r.Errors, r.Zeros, - r.Fetch.Round(time.Millisecond/oneDecimalPoint), - r.Elapsed.Round(time.Millisecond/oneDecimalPoint)) -} - -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.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) -} diff --git a/pkg/promunifi/site.go b/pkg/promunifi/site.go deleted file mode 100644 index c515b8a4d..000000000 --- a/pkg/promunifi/site.go +++ /dev/null @@ -1,152 +0,0 @@ -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 - DPITxPackets *prometheus.Desc - DPIRxPackets *prometheus.Desc - DPITxBytes *prometheus.Desc - DPIRxBytes *prometheus.Desc -} - -func descSite(ns string) *site { - labels := []string{"subsystem", "status", "site_name", "source"} - labelDPI := []string{"category", "application", "site_name", "source"} - nd := prometheus.NewDesc - - return &site{ - NumUser: nd(ns+"users", "Number of Users", labels, nil), - NumGuest: nd(ns+"guests", "Number of Guests", labels, nil), - NumIot: nd(ns+"iots", "Number of IoT Devices", labels, nil), - TxBytesR: nd(ns+"transmit_rate_bytes", "Bytes Transmit Rate", labels, nil), - RxBytesR: nd(ns+"receive_rate_bytes", "Bytes Receive Rate", labels, nil), - NumAp: nd(ns+"aps", "Access Point Count", labels, nil), - NumAdopted: nd(ns+"adopted", "Adoption Count", labels, nil), - NumDisabled: nd(ns+"disabled", "Disabled Count", labels, nil), - NumDisconnected: nd(ns+"disconnected", "Disconnected Count", labels, nil), - NumPending: nd(ns+"pending", "Pending Count", labels, nil), - NumGw: nd(ns+"gateways", "Gateway Count", labels, nil), - NumSw: nd(ns+"switches", "Switch Count", labels, nil), - NumSta: nd(ns+"stations", "Station Count", labels, nil), - Latency: nd(ns+"latency_seconds", "Latency", labels, nil), - Uptime: nd(ns+"uptime_seconds", "Uptime", labels, nil), - Drops: nd(ns+"intenet_drops_total", "Internet (WAN) Disconnections", labels, nil), - XputUp: nd(ns+"xput_up_rate", "Speedtest Upload", labels, nil), - XputDown: nd(ns+"xput_down_rate", "Speedtest Download", labels, nil), - SpeedtestPing: nd(ns+"speedtest_ping", "Speedtest Ping", labels, nil), - RemoteUserNumActive: nd(ns+"remote_user_active", "Remote Users Active", labels, nil), - RemoteUserNumInactive: nd(ns+"remote_user_inactive", "Remote Users Inactive", labels, nil), - RemoteUserRxBytes: nd(ns+"remote_user_receive_bytes_total", "Remote Users Receive Bytes", labels, nil), - RemoteUserTxBytes: nd(ns+"remote_user_transmit_bytes_total", "Remote Users Transmit Bytes", labels, nil), - RemoteUserRxPackets: nd(ns+"remote_user_receive_packets_total", "Remote Users Receive Packets", labels, nil), - RemoteUserTxPackets: nd(ns+"remote_user_transmit_packets_total", "Remote Users Transmit Packets", labels, nil), - DPITxPackets: nd(ns+"dpi_transmit_packets", "Site DPI Transmit Packets", labelDPI, nil), - DPIRxPackets: nd(ns+"dpi_receive_packets", "Site DPI Receive Packets", labelDPI, nil), - DPITxBytes: nd(ns+"dpi_transmit_bytes", "Site DPI Transmit Bytes", labelDPI, nil), - DPIRxBytes: nd(ns+"dpi_receive_bytes", "Site DPI Receive Bytes", labelDPI, nil), - } -} - -func (u *promUnifi) exportSiteDPI(r report, s *unifi.DPITable) { - for _, dpi := range s.ByApp { - labelDPI := []string{unifi.DPICats.Get(dpi.Cat), unifi.DPIApps.GetApp(dpi.Cat, dpi.App), s.SiteName, s.SourceName} - - // log.Println(labelsDPI, dpi.Cat, dpi.App, dpi.TxBytes, dpi.RxBytes, dpi.TxPackets, dpi.RxPackets) - r.send([]*metric{ - {u.Site.DPITxPackets, gauge, dpi.TxPackets, labelDPI}, - {u.Site.DPIRxPackets, gauge, dpi.RxPackets, labelDPI}, - {u.Site.DPITxBytes, gauge, dpi.TxBytes, labelDPI}, - {u.Site.DPIRxBytes, gauge, dpi.RxBytes, labelDPI}, - }) - } -} - -func (u *promUnifi) exportSite(r report, s *unifi.Site) { - for _, h := range s.Health { - switch labels := []string{h.Subsystem, h.Status, s.SiteName, s.SourceName}; labels[0] { - case "www": - r.send([]*metric{ - {u.Site.TxBytesR, gauge, h.TxBytesR, labels}, - {u.Site.RxBytesR, gauge, h.RxBytesR, labels}, - {u.Site.Uptime, gauge, h.Uptime, labels}, - {u.Site.Latency, gauge, h.Latency.Val / 1000, labels}, - {u.Site.XputUp, gauge, h.XputUp, labels}, - {u.Site.XputDown, gauge, h.XputDown, labels}, - {u.Site.SpeedtestPing, gauge, h.SpeedtestPing, labels}, - {u.Site.Drops, counter, h.Drops, labels}, - }) - case "wlan": - r.send([]*metric{ - {u.Site.TxBytesR, gauge, h.TxBytesR, labels}, - {u.Site.RxBytesR, gauge, h.RxBytesR, labels}, - {u.Site.NumAdopted, gauge, h.NumAdopted, labels}, - {u.Site.NumDisconnected, gauge, h.NumDisconnected, labels}, - {u.Site.NumPending, gauge, h.NumPending, labels}, - {u.Site.NumUser, gauge, h.NumUser, labels}, - {u.Site.NumGuest, gauge, h.NumGuest, labels}, - {u.Site.NumIot, gauge, h.NumIot, labels}, - {u.Site.NumAp, gauge, h.NumAp, labels}, - {u.Site.NumDisabled, gauge, h.NumDisabled, labels}, - }) - case "wan": - r.send([]*metric{ - {u.Site.TxBytesR, gauge, h.TxBytesR, labels}, - {u.Site.RxBytesR, gauge, h.RxBytesR, labels}, - {u.Site.NumAdopted, gauge, h.NumAdopted, labels}, - {u.Site.NumDisconnected, gauge, h.NumDisconnected, labels}, - {u.Site.NumPending, gauge, h.NumPending, labels}, - {u.Site.NumGw, gauge, h.NumGw, labels}, - {u.Site.NumSta, gauge, h.NumSta, labels}, - }) - case "lan": - r.send([]*metric{ - {u.Site.TxBytesR, gauge, h.TxBytesR, labels}, - {u.Site.RxBytesR, gauge, h.RxBytesR, labels}, - {u.Site.NumAdopted, gauge, h.NumAdopted, labels}, - {u.Site.NumDisconnected, gauge, h.NumDisconnected, labels}, - {u.Site.NumPending, gauge, h.NumPending, labels}, - {u.Site.NumUser, gauge, h.NumUser, labels}, - {u.Site.NumGuest, gauge, h.NumGuest, labels}, - {u.Site.NumIot, gauge, h.NumIot, labels}, - {u.Site.NumSw, gauge, h.NumSw, labels}, - }) - case "vpn": - r.send([]*metric{ - {u.Site.RemoteUserNumActive, gauge, h.RemoteUserNumActive, labels}, - {u.Site.RemoteUserNumInactive, gauge, h.RemoteUserNumInactive, labels}, - {u.Site.RemoteUserRxBytes, counter, h.RemoteUserRxBytes, labels}, - {u.Site.RemoteUserTxBytes, counter, h.RemoteUserTxBytes, labels}, - {u.Site.RemoteUserRxPackets, counter, h.RemoteUserRxPackets, labels}, - {u.Site.RemoteUserTxPackets, counter, h.RemoteUserTxPackets, labels}, - }) - } - } -} diff --git a/pkg/promunifi/uap.go b/pkg/promunifi/uap.go deleted file mode 100644 index 713520622..000000000 --- a/pkg/promunifi/uap.go +++ /dev/null @@ -1,320 +0,0 @@ -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 - RadioExtchannel *prometheus.Desc - RadioGain *prometheus.Desc - RadioNumSta *prometheus.Desc - RadioTxPackets *prometheus.Desc - RadioTxRetries *prometheus.Desc -} - -func descUAP(ns string) *uap { - labelA := []string{"stat", "site_name", "name", "source"} // stat + labels[1:] - labelV := []string{"vap_name", "bssid", "radio", "radio_name", "essid", "usage", "site_name", "name", "source"} - labelR := []string{"radio_name", "radio", "site_name", "name", "source"} - nd := prometheus.NewDesc - - return &uap{ - // 3x each - stat table: total, guest, user - ApWifiTxDropped: nd(ns+"stat_wifi_transmt_dropped_total", "Wifi Transmissions Dropped", labelA, nil), - ApRxErrors: nd(ns+"stat_receive_errors_total", "Receive Errors", labelA, nil), - ApRxDropped: nd(ns+"stat_receive_dropped_total", "Receive Dropped", labelA, nil), - ApRxFrags: nd(ns+"stat_receive_frags_total", "Received Frags", labelA, nil), - ApRxCrypts: nd(ns+"stat_receive_crypts_total", "Receive Crypts", labelA, nil), - ApTxPackets: nd(ns+"stat_transmit_packets_total", "Transmit Packets", labelA, nil), - ApTxBytes: nd(ns+"stat_transmit_bytes_total", "Transmit Bytes", labelA, nil), - ApTxErrors: nd(ns+"stat_transmit_errors_total", "Transmit Errors", labelA, nil), - ApTxDropped: nd(ns+"stat_transmit_dropped_total", "Transmit Dropped", labelA, nil), - ApTxRetries: nd(ns+"stat_retries_tx_total", "Transmit Retries", labelA, nil), - ApRxPackets: nd(ns+"stat_receive_packets_total", "Receive Packets", labelA, nil), - ApRxBytes: nd(ns+"stat_receive_bytes_total", "Receive Bytes", labelA, nil), - WifiTxAttempts: nd(ns+"stat_wifi_transmit_attempts_total", "Wifi Transmission Attempts", labelA, nil), - MacFilterRejections: nd(ns+"stat_mac_filter_rejects_total", "MAC Filter Rejections", labelA, nil), - // N each - 1 per Virtual AP (VAP) - VAPCcq: nd(ns+"vap_ccq_ratio", "VAP Client Connection Quality", labelV, nil), - VAPMacFilterRejections: nd(ns+"vap_mac_filter_rejects_total", "VAP MAC Filter Rejections", labelV, nil), - VAPNumSatisfactionSta: nd(ns+"vap_satisfaction_stations", "VAP Number Satisifaction Stations", labelV, nil), - VAPAvgClientSignal: nd(ns+"vap_average_client_signal", "VAP Average Client Signal", labelV, nil), - VAPSatisfaction: nd(ns+"vap_satisfaction_ratio", "VAP Satisfaction", labelV, nil), - VAPSatisfactionNow: nd(ns+"vap_satisfaction_now_ratio", "VAP Satisfaction Now", labelV, nil), - VAPDNSAvgLatency: nd(ns+"vap_dns_latency_average_seconds", "VAP DNS Latency Average", labelV, nil), - VAPRxBytes: nd(ns+"vap_receive_bytes_total", "VAP Bytes Received", labelV, nil), - VAPRxCrypts: nd(ns+"vap_receive_crypts_total", "VAP Crypts Received", labelV, nil), - VAPRxDropped: nd(ns+"vap_receive_dropped_total", "VAP Dropped Received", labelV, nil), - VAPRxErrors: nd(ns+"vap_receive_errors_total", "VAP Errors Received", labelV, nil), - VAPRxFrags: nd(ns+"vap_receive_frags_total", "VAP Frags Received", labelV, nil), - VAPRxNwids: nd(ns+"vap_receive_nwids_total", "VAP Nwids Received", labelV, nil), - VAPRxPackets: nd(ns+"vap_receive_packets_total", "VAP Packets Received", labelV, nil), - VAPTxBytes: nd(ns+"vap_transmit_bytes_total", "VAP Bytes Transmitted", labelV, nil), - VAPTxDropped: nd(ns+"vap_transmit_dropped_total", "VAP Dropped Transmitted", labelV, nil), - VAPTxErrors: nd(ns+"vap_transmit_errors_total", "VAP Errors Transmitted", labelV, nil), - VAPTxPackets: nd(ns+"vap_transmit_packets_total", "VAP Packets Transmitted", labelV, nil), - VAPTxPower: nd(ns+"vap_transmit_power", "VAP Transmit Power", labelV, nil), - VAPTxRetries: nd(ns+"vap_transmit_retries_total", "VAP Retries Transmitted", labelV, nil), - VAPTxCombinedRetries: nd(ns+"vap_transmit_retries_combined_total", "VAP Retries Combined Tx", labelV, nil), - VAPTxDataMpduBytes: nd(ns+"vap_data_mpdu_transmit_bytes_total", "VAP Data MPDU Bytes Tx", labelV, nil), - VAPTxRtsRetries: nd(ns+"vap_transmit_rts_retries_total", "VAP RTS Retries Transmitted", labelV, nil), - VAPTxSuccess: nd(ns+"vap_transmit_success_total", "VAP Success Transmits", labelV, nil), - VAPTxTotal: nd(ns+"vap_transmit_total", "VAP Transmit Total", labelV, nil), - VAPTxGoodbytes: nd(ns+"vap_transmit_goodbyes", "VAP Goodbyes Transmitted", labelV, nil), - VAPTxLatAvg: nd(ns+"vap_transmit_latency_average_seconds", "VAP Latency Average Tx", labelV, nil), - VAPTxLatMax: nd(ns+"vap_transmit_latency_maximum_seconds", "VAP Latency Maximum Tx", labelV, nil), - VAPTxLatMin: nd(ns+"vap_transmit_latency_minimum_seconds", "VAP Latency Minimum Tx", labelV, nil), - VAPRxGoodbytes: nd(ns+"vap_receive_goodbyes", "VAP Goodbyes Received", labelV, nil), - VAPRxLatAvg: nd(ns+"vap_receive_latency_average_seconds", "VAP Latency Average Rx", labelV, nil), - VAPRxLatMax: nd(ns+"vap_receive_latency_maximum_seconds", "VAP Latency Maximum Rx", labelV, nil), - VAPRxLatMin: nd(ns+"vap_receive_latency_minimum_seconds", "VAP Latency Minimum Rx", labelV, nil), - VAPWifiTxLatencyMovAvg: nd(ns+"vap_transmit_latency_moving_avg_seconds", "VAP Latency Moving Avg Tx", labelV, nil), - VAPWifiTxLatencyMovMax: nd(ns+"vap_transmit_latency_moving_max_seconds", "VAP Latency Moving Min Tx", labelV, nil), - VAPWifiTxLatencyMovMin: nd(ns+"vap_transmit_latency_moving_min_seconds", "VAP Latency Moving Max Tx", labelV, nil), - VAPWifiTxLatencyMovTotal: nd(ns+"vap_transmit_latency_moving_total", "VAP Latency Moving Total Tramsit", labelV, nil), - VAPWifiTxLatencyMovCount: nd(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: nd(ns+"radio_current_antenna_gain", "Radio Current Antenna Gain", labelR, nil), - RadioHt: nd(ns+"radio_ht", "Radio HT", labelR, nil), - RadioMaxTxpower: nd(ns+"radio_max_transmit_power", "Radio Maximum Transmit Power", labelR, nil), - RadioMinTxpower: nd(ns+"radio_min_transmit_power", "Radio Minimum Transmit Power", labelR, nil), - RadioNss: nd(ns+"radio_nss", "Radio Nss", labelR, nil), - RadioRadioCaps: nd(ns+"radio_caps", "Radio Capabilities", labelR, nil), - RadioTxPower: nd(ns+"radio_transmit_power", "Radio Transmit Power", labelR, nil), - RadioAstBeXmit: nd(ns+"radio_ast_be_xmit", "Radio AstBe Transmit", labelR, nil), - RadioChannel: nd(ns+"radio_channel", "Radio Channel", labelR, nil), - RadioCuSelfRx: nd(ns+"radio_channel_utilization_receive_ratio", "Channel Utilization Rx", labelR, nil), - RadioCuSelfTx: nd(ns+"radio_channel_utilization_transmit_ratio", "Channel Utilization Tx", labelR, nil), - RadioExtchannel: nd(ns+"radio_ext_channel", "Radio Ext Channel", labelR, nil), - RadioGain: nd(ns+"radio_gain", "Radio Gain", labelR, nil), - RadioNumSta: nd(ns+"radio_stations", "Radio Total Station Count", append(labelR, "station_type"), nil), - RadioTxPackets: nd(ns+"radio_transmit_packets", "Radio Transmitted Packets", labelR, nil), - RadioTxRetries: nd(ns+"radio_transmit_retries", "Radio Transmit Retries", labelR, nil), - } -} - -func (u *promUnifi) exportUAP(r report, d *unifi.UAP) { - if !d.Adopted.Val || d.Locating.Val { - return - } - - labels := []string{d.Type, d.SiteName, d.Name, d.SourceName} - infoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID, d.Bytes.Txt, d.Uptime.Txt} - u.exportUAPstats(r, labels, d.Stat.Ap, d.BytesD, d.TxBytesD, d.RxBytesD, d.BytesR) - u.exportVAPtable(r, labels, d.VapTable) - u.exportBYTstats(r, labels, d.TxBytes, d.RxBytes) - u.exportSYSstats(r, labels, d.SysStats, d.SystemStats) - u.exportSTAcount(r, labels, d.UserNumSta, d.GuestNumSta) - u.exportRADtable(r, labels, d.RadioTable, d.RadioTableStats) - r.send([]*metric{ - {u.Device.Info, gauge, 1.0, append(labels, infoLabels...)}, - {u.Device.Uptime, gauge, d.Uptime, labels}, - }) -} - -// udm doesn't have these stats exposed yet, so pass 2 or 6 metrics. -func (u *promUnifi) exportUAPstats(r report, labels []string, ap *unifi.Ap, bytes ...unifi.FlexInt) { - if ap == nil { - return - } - - labelU := []string{"user", labels[1], labels[2], labels[3]} - labelG := []string{"guest", labels[1], labels[2], labels[3]} - r.send([]*metric{ - // ap only stuff. - {u.Device.BytesD, counter, bytes[0], labels}, // not sure if these 3 Ds are counters or gauges. - {u.Device.TxBytesD, counter, bytes[1], labels}, // not sure if these 3 Ds are counters or gauges. - {u.Device.RxBytesD, counter, bytes[2], labels}, // not sure if these 3 Ds are counters or gauges. - {u.Device.BytesR, gauge, bytes[3], labels}, // only UAP has this one, and those ^ weird. - // user - {u.UAP.ApWifiTxDropped, counter, ap.UserWifiTxDropped, labelU}, - {u.UAP.ApRxErrors, counter, ap.UserRxErrors, labelU}, - {u.UAP.ApRxDropped, counter, ap.UserRxDropped, labelU}, - {u.UAP.ApRxFrags, counter, ap.UserRxFrags, labelU}, - {u.UAP.ApRxCrypts, counter, ap.UserRxCrypts, labelU}, - {u.UAP.ApTxPackets, counter, ap.UserTxPackets, labelU}, - {u.UAP.ApTxBytes, counter, ap.UserTxBytes, labelU}, - {u.UAP.ApTxErrors, counter, ap.UserTxErrors, labelU}, - {u.UAP.ApTxDropped, counter, ap.UserTxDropped, labelU}, - {u.UAP.ApTxRetries, counter, ap.UserTxRetries, labelU}, - {u.UAP.ApRxPackets, counter, ap.UserRxPackets, labelU}, - {u.UAP.ApRxBytes, counter, ap.UserRxBytes, labelU}, - {u.UAP.WifiTxAttempts, counter, ap.UserWifiTxAttempts, labelU}, - {u.UAP.MacFilterRejections, counter, ap.UserMacFilterRejections, labelU}, - // guest - {u.UAP.ApWifiTxDropped, counter, ap.GuestWifiTxDropped, labelG}, - {u.UAP.ApRxErrors, counter, ap.GuestRxErrors, labelG}, - {u.UAP.ApRxDropped, counter, ap.GuestRxDropped, labelG}, - {u.UAP.ApRxFrags, counter, ap.GuestRxFrags, labelG}, - {u.UAP.ApRxCrypts, counter, ap.GuestRxCrypts, labelG}, - {u.UAP.ApTxPackets, counter, ap.GuestTxPackets, labelG}, - {u.UAP.ApTxBytes, counter, ap.GuestTxBytes, labelG}, - {u.UAP.ApTxErrors, counter, ap.GuestTxErrors, labelG}, - {u.UAP.ApTxDropped, counter, ap.GuestTxDropped, labelG}, - {u.UAP.ApTxRetries, counter, ap.GuestTxRetries, labelG}, - {u.UAP.ApRxPackets, counter, ap.GuestRxPackets, labelG}, - {u.UAP.ApRxBytes, counter, ap.GuestRxBytes, labelG}, - {u.UAP.WifiTxAttempts, counter, ap.GuestWifiTxAttempts, labelG}, - {u.UAP.MacFilterRejections, counter, ap.GuestMacFilterRejections, labelG}, - }) -} - -// UAP VAP Table -func (u *promUnifi) exportVAPtable(r report, labels []string, vt unifi.VapTable) { - // vap table stats - for _, v := range vt { - if !v.Up.Val { - continue - } - - labelV := []string{v.Name, v.Bssid, v.Radio, v.RadioName, v.Essid, v.Usage, labels[1], labels[2], labels[3]} - r.send([]*metric{ - {u.UAP.VAPCcq, gauge, float64(v.Ccq) / 1000.0, labelV}, - {u.UAP.VAPMacFilterRejections, counter, v.MacFilterRejections, labelV}, - {u.UAP.VAPNumSatisfactionSta, gauge, v.NumSatisfactionSta, labelV}, - {u.UAP.VAPAvgClientSignal, gauge, v.AvgClientSignal.Val, labelV}, - {u.UAP.VAPSatisfaction, gauge, v.Satisfaction.Val / 100.0, labelV}, - {u.UAP.VAPSatisfactionNow, gauge, v.SatisfactionNow.Val / 100.0, labelV}, - {u.UAP.VAPDNSAvgLatency, gauge, v.DNSAvgLatency.Val / 1000, labelV}, - {u.UAP.VAPRxBytes, counter, v.RxBytes, labelV}, - {u.UAP.VAPRxCrypts, counter, v.RxCrypts, labelV}, - {u.UAP.VAPRxDropped, counter, v.RxDropped, labelV}, - {u.UAP.VAPRxErrors, counter, v.RxErrors, labelV}, - {u.UAP.VAPRxFrags, counter, v.RxFrags, labelV}, - {u.UAP.VAPRxNwids, counter, v.RxNwids, labelV}, - {u.UAP.VAPRxPackets, counter, v.RxPackets, labelV}, - {u.UAP.VAPTxBytes, counter, v.TxBytes, labelV}, - {u.UAP.VAPTxDropped, counter, v.TxDropped, labelV}, - {u.UAP.VAPTxErrors, counter, v.TxErrors, labelV}, - {u.UAP.VAPTxPackets, counter, v.TxPackets, labelV}, - {u.UAP.VAPTxPower, gauge, v.TxPower, labelV}, - {u.UAP.VAPTxRetries, counter, v.TxRetries, labelV}, - {u.UAP.VAPTxCombinedRetries, counter, v.TxCombinedRetries, labelV}, - {u.UAP.VAPTxDataMpduBytes, counter, v.TxDataMpduBytes, labelV}, - {u.UAP.VAPTxRtsRetries, counter, v.TxRtsRetries, labelV}, - {u.UAP.VAPTxTotal, counter, v.TxTotal, labelV}, - {u.UAP.VAPTxGoodbytes, counter, v.TxTCPStats.Goodbytes, labelV}, - {u.UAP.VAPTxLatAvg, gauge, v.TxTCPStats.LatAvg.Val / 1000, labelV}, - {u.UAP.VAPTxLatMax, gauge, v.TxTCPStats.LatMax.Val / 1000, labelV}, - {u.UAP.VAPTxLatMin, gauge, v.TxTCPStats.LatMin.Val / 1000, labelV}, - {u.UAP.VAPRxGoodbytes, counter, v.RxTCPStats.Goodbytes, labelV}, - {u.UAP.VAPRxLatAvg, gauge, v.RxTCPStats.LatAvg.Val / 1000, labelV}, - {u.UAP.VAPRxLatMax, gauge, v.RxTCPStats.LatMax.Val / 1000, labelV}, - {u.UAP.VAPRxLatMin, gauge, v.RxTCPStats.LatMin.Val / 1000, labelV}, - {u.UAP.VAPWifiTxLatencyMovAvg, gauge, v.WifiTxLatencyMov.Avg.Val / 1000, labelV}, - {u.UAP.VAPWifiTxLatencyMovMax, gauge, v.WifiTxLatencyMov.Max.Val / 1000, labelV}, - {u.UAP.VAPWifiTxLatencyMovMin, gauge, v.WifiTxLatencyMov.Min.Val / 1000, labelV}, - {u.UAP.VAPWifiTxLatencyMovTotal, counter, v.WifiTxLatencyMov.Total, labelV}, // not sure if gauge or counter. - {u.UAP.VAPWifiTxLatencyMovCount, counter, v.WifiTxLatencyMov.TotalCount, labelV}, // not sure if gauge or counter. - }) - } -} - -// UAP Radio Table -func (u *promUnifi) exportRADtable(r report, labels []string, rt unifi.RadioTable, rts unifi.RadioTableStats) { - // radio table - for _, p := range rt { - labelR := []string{p.Name, p.Radio, labels[1], labels[2], labels[3]} - labelRUser := append(labelR, "user") - labelRGuest := append(labelR, "guest") - - r.send([]*metric{ - {u.UAP.RadioCurrentAntennaGain, gauge, p.CurrentAntennaGain, labelR}, - {u.UAP.RadioHt, gauge, p.Ht, labelR}, - {u.UAP.RadioMaxTxpower, gauge, p.MaxTxpower, labelR}, - {u.UAP.RadioMinTxpower, gauge, p.MinTxpower, labelR}, - {u.UAP.RadioNss, gauge, p.Nss, labelR}, - {u.UAP.RadioRadioCaps, gauge, 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, gauge, t.TxPower, labelR}, - {u.UAP.RadioAstBeXmit, gauge, t.AstBeXmit, labelR}, - {u.UAP.RadioChannel, gauge, t.Channel, labelR}, - {u.UAP.RadioCuSelfRx, gauge, t.CuSelfRx.Val / 100.0, labelR}, - {u.UAP.RadioCuSelfTx, gauge, t.CuSelfTx.Val / 100.0, labelR}, - {u.UAP.RadioExtchannel, gauge, t.Extchannel, labelR}, - {u.UAP.RadioGain, gauge, t.Gain, labelR}, - {u.UAP.RadioNumSta, gauge, t.GuestNumSta, labelRGuest}, - {u.UAP.RadioNumSta, gauge, t.UserNumSta, labelRUser}, - {u.UAP.RadioTxPackets, gauge, t.TxPackets, labelR}, - {u.UAP.RadioTxRetries, gauge, t.TxRetries, labelR}, - }) - - break - } - } -} diff --git a/pkg/promunifi/udm.go b/pkg/promunifi/udm.go deleted file mode 100644 index b74e1b9e5..000000000 --- a/pkg/promunifi/udm.go +++ /dev/null @@ -1,131 +0,0 @@ -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 { - Info *prometheus.Desc - 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 - TxBytesD *prometheus.Desc // ap only - RxBytesD *prometheus.Desc // ap only - Counter *prometheus.Desc - 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{"type", "site_name", "name", "source"} - infoLabels := []string{"version", "model", "serial", "mac", "ip", "id", "bytes", "uptime"} - - return &unifiDevice{ - Info: prometheus.NewDesc(ns+"info", "Device Information", append(labels, infoLabels...), nil), - Uptime: prometheus.NewDesc(ns+"uptime_seconds", "Device 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), - TxBytesD: prometheus.NewDesc(ns+"d_tranmsit_bytes", "Transmit Bytes D???", labels, nil), - RxBytesD: prometheus.NewDesc(ns+"d_receive_bytes", "Receive Bytes D???", labels, nil), - Counter: prometheus.NewDesc(ns+"stations", "Number of Stations", append(labels, "station_type"), 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_ratio", "System CPU % Utilized", labels, nil), - Mem: prometheus.NewDesc(ns+"memory_utilization_ratio", "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) { - if !d.Adopted.Val || d.Locating.Val { - return - } - - labels := []string{d.Type, d.SiteName, d.Name, d.SourceName} - infoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID, d.Bytes.Txt, d.Uptime.Txt} - // Shared data (all devices do this). - u.exportBYTstats(r, labels, d.TxBytes, d.RxBytes) - u.exportSYSstats(r, labels, d.SysStats, d.SystemStats) - u.exportSTAcount(r, labels, d.UserNumSta, d.GuestNumSta, d.NumDesktop, d.NumMobile, d.NumHandheld) - // Switch Data - u.exportUSWstats(r, labels, d.Stat.Sw) - u.exportPRTtable(r, labels, d.PortTable) - // Gateway Data - u.exportWANPorts(r, labels, d.Wan1, d.Wan2) - u.exportUSGstats(r, labels, d.Stat.Gw, d.SpeedtestStatus, d.Uplink) - // Dream Machine System Data. - r.send([]*metric{ - {u.Device.Info, gauge, 1.0, append(labels, infoLabels...)}, - {u.Device.Uptime, gauge, d.Uptime, labels}, - }) - - // Wireless Data - UDM (non-pro) only - if d.Stat.Ap != nil && d.VapTable != nil { - u.exportUAPstats(r, labels, d.Stat.Ap, d.BytesD, d.TxBytesD, d.RxBytesD, d.BytesR) - u.exportVAPtable(r, labels, *d.VapTable) - u.exportRADtable(r, labels, *d.RadioTable, *d.RadioTableStats) - } -} - -// shared by all -func (u *promUnifi) exportBYTstats(r report, labels []string, tx, rx unifi.FlexInt) { - r.send([]*metric{ - {u.Device.TotalTxBytes, counter, tx, labels}, - {u.Device.TotalRxBytes, counter, rx, labels}, - {u.Device.TotalBytes, counter, tx.Val + rx.Val, labels}, - }) -} - -// shared by all, pass 2 or 5 stats. -func (u *promUnifi) exportSTAcount(r report, labels []string, stas ...unifi.FlexInt) { - r.send([]*metric{ - {u.Device.Counter, gauge, stas[0], append(labels, "user")}, - {u.Device.Counter, gauge, stas[1], append(labels, "guest")}, - }) - - if len(stas) > 2 { - r.send([]*metric{ - {u.Device.Counter, gauge, stas[2], append(labels, "desktop")}, - {u.Device.Counter, gauge, stas[3], append(labels, "mobile")}, - {u.Device.Counter, gauge, stas[4], append(labels, "handheld")}, - }) - } -} - -// shared by all -func (u *promUnifi) exportSYSstats(r report, labels []string, s unifi.SysStats, ss unifi.SystemStats) { - r.send([]*metric{ - {u.Device.Loadavg1, gauge, s.Loadavg1, labels}, - {u.Device.Loadavg5, gauge, s.Loadavg5, labels}, - {u.Device.Loadavg15, gauge, s.Loadavg15, labels}, - {u.Device.MemUsed, gauge, s.MemUsed, labels}, - {u.Device.MemTotal, gauge, s.MemTotal, labels}, - {u.Device.MemBuffer, gauge, s.MemBuffer, labels}, - {u.Device.CPU, gauge, ss.CPU.Val / 100.0, labels}, - {u.Device.Mem, gauge, ss.Mem.Val / 100.0, labels}, - }) -} diff --git a/pkg/promunifi/usg.go b/pkg/promunifi/usg.go deleted file mode 100644 index 83891cc64..000000000 --- a/pkg/promunifi/usg.go +++ /dev/null @@ -1,144 +0,0 @@ -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 - UplinkLatency *prometheus.Desc - UplinkSpeed *prometheus.Desc - Runtime *prometheus.Desc - XputDownload *prometheus.Desc - XputUpload *prometheus.Desc -} - -func descUSG(ns string) *usg { - labels := []string{"port", "site_name", "name", "source"} - - 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), - UplinkLatency: prometheus.NewDesc(ns+"uplink_latency_seconds", "Uplink Latency", labels, nil), - UplinkSpeed: prometheus.NewDesc(ns+"uplink_speed_mbps", "Uplink Speed", 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) { - if !d.Adopted.Val || d.Locating.Val { - return - } - - labels := []string{d.Type, d.SiteName, d.Name, d.SourceName} - infoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID, d.Bytes.Txt, d.Uptime.Txt} - - // Gateway System Data. - u.exportWANPorts(r, labels, d.Wan1, d.Wan2) - u.exportBYTstats(r, labels, d.TxBytes, d.RxBytes) - u.exportSYSstats(r, labels, d.SysStats, d.SystemStats) - u.exportUSGstats(r, labels, d.Stat.Gw, d.SpeedtestStatus, d.Uplink) - u.exportSTAcount(r, labels, d.UserNumSta, d.GuestNumSta, d.NumDesktop, d.UserNumSta, d.GuestNumSta) - r.send([]*metric{ - {u.Device.Info, gauge, 1.0, append(labels, infoLabels...)}, - {u.Device.Uptime, gauge, d.Uptime, labels}, - }) -} - -// Gateway States -func (u *promUnifi) exportUSGstats(r report, labels []string, gw *unifi.Gw, st unifi.SpeedtestStatus, ul unifi.Uplink) { - if gw == nil { - return - } - - labelLan := []string{"lan", labels[1], labels[2], labels[3]} - labelWan := []string{"all", labels[1], labels[2], labels[3]} - - r.send([]*metric{ - {u.USG.LanRxPackets, counter, gw.LanRxPackets, labelLan}, - {u.USG.LanRxBytes, counter, gw.LanRxBytes, labelLan}, - {u.USG.LanTxPackets, counter, gw.LanTxPackets, labelLan}, - {u.USG.LanTxBytes, counter, gw.LanTxBytes, labelLan}, - {u.USG.LanRxDropped, counter, gw.LanRxDropped, labelLan}, - {u.USG.UplinkLatency, gauge, ul.Latency.Val / 1000, labelWan}, - {u.USG.UplinkSpeed, gauge, ul.Speed, labelWan}, - // Speed Test Stats - {u.USG.Latency, gauge, st.Latency.Val / 1000, labelWan}, - {u.USG.Runtime, gauge, st.Runtime, labelWan}, - {u.USG.XputDownload, gauge, st.XputDownload, labelWan}, - {u.USG.XputUpload, gauge, st.XputUpload, labelWan}, - }) -} - -// WAN Stats -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[1], labels[2], labels[3]} - - r.send([]*metric{ - {u.USG.WanRxPackets, counter, wan.RxPackets, labelWan}, - {u.USG.WanRxBytes, counter, wan.RxBytes, labelWan}, - {u.USG.WanRxDropped, counter, wan.RxDropped, labelWan}, - {u.USG.WanRxErrors, counter, wan.RxErrors, labelWan}, - {u.USG.WanTxPackets, counter, wan.TxPackets, labelWan}, - {u.USG.WanTxBytes, counter, wan.TxBytes, labelWan}, - {u.USG.WanRxBroadcast, counter, wan.RxBroadcast, labelWan}, - {u.USG.WanRxMulticast, counter, wan.RxMulticast, labelWan}, - {u.USG.WanSpeed, counter, wan.Speed.Val * 1000000, labelWan}, - {u.USG.WanTxBroadcast, counter, wan.TxBroadcast, labelWan}, - {u.USG.WanTxBytesR, counter, wan.TxBytesR, labelWan}, - {u.USG.WanTxDropped, counter, wan.TxDropped, labelWan}, - {u.USG.WanTxErrors, counter, wan.TxErrors, labelWan}, - {u.USG.WanTxMulticast, counter, wan.TxMulticast, labelWan}, - {u.USG.WanBytesR, gauge, wan.BytesR, labelWan}, - }) - } -} diff --git a/pkg/promunifi/usw.go b/pkg/promunifi/usw.go deleted file mode 100644 index 66d819d83..000000000 --- a/pkg/promunifi/usw.go +++ /dev/null @@ -1,194 +0,0 @@ -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{"site_name", "name", "source"} - labelP := []string{"port_id", "port_num", "port_name", "port_mac", "port_ip", "site_name", "name", "source"} - nd := prometheus.NewDesc - - return &usw{ - // This data may be derivable by sum()ing the port data. - SwRxPackets: nd(ns+"switch_receive_packets_total", "Switch Packets Received Total", labelS, nil), - SwRxBytes: nd(ns+"switch_receive_bytes_total", "Switch Bytes Received Total", labelS, nil), - SwRxErrors: nd(ns+"switch_receive_errors_total", "Switch Errors Received Total", labelS, nil), - SwRxDropped: nd(ns+"switch_receive_dropped_total", "Switch Dropped Received Total", labelS, nil), - SwRxCrypts: nd(ns+"switch_receive_crypts_total", "Switch Crypts Received Total", labelS, nil), - SwRxFrags: nd(ns+"switch_receive_frags_total", "Switch Frags Received Total", labelS, nil), - SwTxPackets: nd(ns+"switch_transmit_packets_total", "Switch Packets Transmit Total", labelS, nil), - SwTxBytes: nd(ns+"switch_transmit_bytes_total", "Switch Bytes Transmit Total", labelS, nil), - SwTxErrors: nd(ns+"switch_transmit_errors_total", "Switch Errors Transmit Total", labelS, nil), - SwTxDropped: nd(ns+"switch_transmit_dropped_total", "Switch Dropped Transmit Total", labelS, nil), - SwTxRetries: nd(ns+"switch_transmit_retries_total", "Switch Retries Transmit Total", labelS, nil), - SwRxMulticast: nd(ns+"switch_receive_multicast_total", "Switch Multicast Receive Total", labelS, nil), - SwRxBroadcast: nd(ns+"switch_receive_broadcast_total", "Switch Broadcast Receive Total", labelS, nil), - SwTxMulticast: nd(ns+"switch_transmit_multicast_total", "Switch Multicast Transmit Total", labelS, nil), - SwTxBroadcast: nd(ns+"switch_transmit_broadcast_total", "Switch Broadcast Transmit Total", labelS, nil), - SwBytes: nd(ns+"switch_bytes_total", "Switch Bytes Transferred Total", labelS, nil), - // per-port data - PoeCurrent: nd(pns+"poe_amperes", "POE Current", labelP, nil), - PoePower: nd(pns+"poe_watts", "POE Power", labelP, nil), - PoeVoltage: nd(pns+"poe_volts", "POE Voltage", labelP, nil), - RxBroadcast: nd(pns+"receive_broadcast_total", "Receive Broadcast", labelP, nil), - RxBytes: nd(pns+"receive_bytes_total", "Total Receive Bytes", labelP, nil), - RxBytesR: nd(pns+"receive_rate_bytes", "Receive Bytes Rate", labelP, nil), - RxDropped: nd(pns+"receive_dropped_total", "Total Receive Dropped", labelP, nil), - RxErrors: nd(pns+"receive_errors_total", "Total Receive Errors", labelP, nil), - RxMulticast: nd(pns+"receive_multicast_total", "Total Receive Multicast", labelP, nil), - RxPackets: nd(pns+"receive_packets_total", "Total Receive Packets", labelP, nil), - Satisfaction: nd(pns+"satisfaction_ratio", "Satisfaction", labelP, nil), - Speed: nd(pns+"port_speed_bps", "Speed", labelP, nil), - TxBroadcast: nd(pns+"transmit_broadcast_total", "Total Transmit Broadcast", labelP, nil), - TxBytes: nd(pns+"transmit_bytes_total", "Total Transmit Bytes", labelP, nil), - TxBytesR: nd(pns+"transmit_rate_bytes", "Transmit Bytes Rate", labelP, nil), - TxDropped: nd(pns+"transmit_dropped_total", "Total Transmit Dropped", labelP, nil), - TxErrors: nd(pns+"transmit_errors_total", "Total Transmit Errors", labelP, nil), - TxMulticast: nd(pns+"transmit_multicast_total", "Total Tranmist Multicast", labelP, nil), - TxPackets: nd(pns+"transmit_packets_total", "Total Transmit Packets", labelP, nil), - } -} - -func (u *promUnifi) exportUSW(r report, d *unifi.USW) { - if !d.Adopted.Val || d.Locating.Val { - return - } - - labels := []string{d.Type, d.SiteName, d.Name, d.SourceName} - infoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID, d.Bytes.Txt, d.Uptime.Txt} - - u.exportUSWstats(r, labels, d.Stat.Sw) - u.exportPRTtable(r, labels, d.PortTable) - u.exportBYTstats(r, labels, d.TxBytes, d.RxBytes) - u.exportSYSstats(r, labels, d.SysStats, d.SystemStats) - u.exportSTAcount(r, labels, d.UserNumSta, d.GuestNumSta) - r.send([]*metric{ - {u.Device.Info, gauge, 1.0, append(labels, infoLabels...)}, - {u.Device.Uptime, gauge, d.Uptime, labels}, - }) - - // Switch System Data. - if d.HasTemperature.Val { - r.send([]*metric{{u.Device.Temperature, gauge, d.GeneralTemperature, labels}}) - } - - if d.HasFan.Val { - r.send([]*metric{{u.Device.FanLevel, gauge, d.FanLevel, labels}}) - } - - if d.TotalMaxPower.Txt != "" { - r.send([]*metric{{u.Device.TotalMaxPower, gauge, d.TotalMaxPower, labels}}) - } -} - -// Switch Stats -func (u *promUnifi) exportUSWstats(r report, labels []string, sw *unifi.Sw) { - if sw == nil { - return - } - - labelS := labels[1:] - - r.send([]*metric{ - {u.USW.SwRxPackets, counter, sw.RxPackets, labelS}, - {u.USW.SwRxBytes, counter, sw.RxBytes, labelS}, - {u.USW.SwRxErrors, counter, sw.RxErrors, labelS}, - {u.USW.SwRxDropped, counter, sw.RxDropped, labelS}, - {u.USW.SwRxCrypts, counter, sw.RxCrypts, labelS}, - {u.USW.SwRxFrags, counter, sw.RxFrags, labelS}, - {u.USW.SwTxPackets, counter, sw.TxPackets, labelS}, - {u.USW.SwTxBytes, counter, sw.TxBytes, labelS}, - {u.USW.SwTxErrors, counter, sw.TxErrors, labelS}, - {u.USW.SwTxDropped, counter, sw.TxDropped, labelS}, - {u.USW.SwTxRetries, counter, sw.TxRetries, labelS}, - {u.USW.SwRxMulticast, counter, sw.RxMulticast, labelS}, - {u.USW.SwRxBroadcast, counter, sw.RxBroadcast, labelS}, - {u.USW.SwTxMulticast, counter, sw.TxMulticast, labelS}, - {u.USW.SwTxBroadcast, counter, sw.TxBroadcast, labelS}, - {u.USW.SwBytes, counter, sw.Bytes, labelS}, - }) -} - -// Switch Port Table -func (u *promUnifi) exportPRTtable(r report, labels []string, pt []unifi.Port) { - // Per-port data on a switch - for _, p := range pt { - if !p.Up.Val || !p.Enable.Val { - continue - } - - // Copy labels, and add four new ones. - labelP := []string{labels[2] + " Port " + p.PortIdx.Txt, p.PortIdx.Txt, - p.Name, p.Mac, p.IP, labels[1], labels[2], labels[3]} - - if p.PoeEnable.Val && p.PortPoe.Val { - r.send([]*metric{ - {u.USW.PoeCurrent, gauge, p.PoeCurrent, labelP}, - {u.USW.PoePower, gauge, p.PoePower, labelP}, - {u.USW.PoeVoltage, gauge, p.PoeVoltage, labelP}, - }) - } - - r.send([]*metric{ - {u.USW.RxBroadcast, counter, p.RxBroadcast, labelP}, - {u.USW.RxBytes, counter, p.RxBytes, labelP}, - {u.USW.RxBytesR, gauge, p.RxBytesR, labelP}, - {u.USW.RxDropped, counter, p.RxDropped, labelP}, - {u.USW.RxErrors, counter, p.RxErrors, labelP}, - {u.USW.RxMulticast, counter, p.RxMulticast, labelP}, - {u.USW.RxPackets, counter, p.RxPackets, labelP}, - {u.USW.Satisfaction, gauge, p.Satisfaction.Val / 100.0, labelP}, - {u.USW.Speed, gauge, p.Speed.Val * 1000000, labelP}, - {u.USW.TxBroadcast, counter, p.TxBroadcast, labelP}, - {u.USW.TxBytes, counter, p.TxBytes, labelP}, - {u.USW.TxBytesR, gauge, p.TxBytesR, labelP}, - {u.USW.TxDropped, counter, p.TxDropped, labelP}, - {u.USW.TxErrors, counter, p.TxErrors, labelP}, - {u.USW.TxMulticast, counter, p.TxMulticast, labelP}, - {u.USW.TxPackets, counter, p.TxPackets, labelP}, - }) - } -} diff --git a/plugins/mysql/README.md b/plugins/mysql/README.md deleted file mode 100644 index 9c32f7fe2..000000000 --- a/plugins/mysql/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# MYSQL Output Plugin Example - -The code here, and the dynamic plugin provided shows an example of how you can -write your own output for unifi-poller. This plugin records some very basic -data about clients on a unifi network into a mysql database. - -You could write outputs that do... anything. An example: They could compare current -connected clients to a previous list (in a db, or stored in memory), and send a -notification if it changes. The possibilities are endless. - -You must compile your plugin using the unifi-poller source for the version you're -using. In other words, to build a plugin for version 2.0.1, do this: -``` -mkdir -p $GOPATH/src/github.com/davidnewhall -cd $GOPATH/src/github.com/davidnewhall - -git clone git@github.com:davidnewhall/unifi-poller.git -cd unifi-poller - -git checkout v2.0.1 -make vendor - -cp -r plugins/ -GOOS=linux make plugins -``` -The plugin you copy in *must* have a `main.go` file for `make plugins` to build it. diff --git a/plugins/mysql/main.go b/plugins/mysql/main.go deleted file mode 100644 index 4776e0d78..000000000 --- a/plugins/mysql/main.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/davidnewhall/unifi-poller/pkg/poller" - "golift.io/cnfg" -) - -// mysqlConfig represents the data that is unmarshalled from the up.conf config file for this plugins. -type mysqlConfig struct { - Interval cnfg.Duration `json:"interval" toml:"interval" xml:"interval" yaml:"interval"` - Host string `json:"host" toml:"host" xml:"host" yaml:"host"` - User string `json:"user" toml:"user" xml:"user" yaml:"user"` - Pass string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"` - DB string `json:"db" toml:"db" xml:"db" yaml:"db"` - Table string `json:"table" toml:"table" xml:"table" yaml:"table"` - // Maps do not work with ENV VARIABLES yet, but may in the future. - Fields []string `json:"fields" toml:"fields" xml:"field" yaml:"fields"` -} - -// Pointers are ignored during ENV variable unmarshal, avoid pointers to your config. -// Only capital (exported) members are unmarshaled when passed into poller.NewOutput(). -type plugin struct { - Config mysqlConfig `json:"mysql" toml:"mysql" xml:"mysql" yaml:"mysql"` -} - -func init() { - u := &plugin{Config: mysqlConfig{}} - - poller.NewOutput(&poller.Output{ - Name: "mysql", - Config: u, // pass in the struct *above* your config (so it can see the struct tags). - Method: u.Run, - }) -} - -func main() { - fmt.Println("this is a unifi-poller plugin; not an application") -} - -func (a *plugin) Run(c poller.Collect) error { - c.Logf("mysql plugin is not finished") - return nil -}