initial
This commit is contained in:
parent
f5830d903d
commit
5e203701b5
14
Makefile
14
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)"
|
||||
|
||||
|
|
|
|||
15
go.mod
15
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
|
||||
)
|
||||
|
|
|
|||
19
go.sum
19
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=
|
||||
|
|
|
|||
8
main.go
8
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.
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
# influx
|
||||
|
||||
This package provides the methods to turn UniFi measurements into influx
|
||||
data-points with appropriate tags and fields.
|
||||
|
|
@ -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,
|
||||
}},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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})
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
}},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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...))
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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}})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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},
|
||||
})
|
||||
}
|
||||
|
|
@ -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},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <your plugin> plugins/
|
||||
GOOS=linux make plugins
|
||||
```
|
||||
The plugin you copy in *must* have a `main.go` file for `make plugins` to build it.
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue