Merge pull request #155 from davidnewhall/dn2_dont_die

Prevent poller from dying from an error
This commit is contained in:
David Newhall II 2019-12-11 12:35:38 -08:00 committed by GitHub
commit 740249d1de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 211 additions and 142 deletions

12
Gopkg.lock generated
View File

@ -27,7 +27,7 @@
[[projects]]
branch = "master"
digest = "1:50708c8fc92aec981df5c446581cf9f90ba9e2a5692118e0ce75d4534aaa14a2"
digest = "1:00e5ad58045d6d2a6c9e65d1809ff2594bc396e911712ae892a93976fdece115"
name = "github.com/influxdata/influxdb1-client"
packages = [
"models",
@ -35,7 +35,7 @@
"v2",
]
pruneopts = "UT"
revision = "fc22c7df067eefd070157f157893fbce961d6359"
revision = "8bf82d3c094dc06be9da8e5bf9d3589b6ea032ae"
[[projects]]
digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc"
@ -103,15 +103,15 @@
name = "golang.org/x/sys"
packages = ["windows"]
pruneopts = "UT"
revision = "ce4227a45e2eb77e5c847278dcc6a626742e2945"
revision = "ac6580df4449443a05718fd7858c1f91ad5f8d20"
[[projects]]
digest = "1:87738e338f505d3e3be1f80d36b53f3c4e73be9b7ad4ccae46abbe9ef04f3f71"
digest = "1:2883cea734f2766f41ff9c9d4aefccccc53e3d44f5c8b08893b9c218cf666722"
name = "golift.io/unifi"
packages = ["."]
pruneopts = "UT"
revision = "ba857a3a04311fed362cb43fa7bf4066bc3a7e55"
version = "v4.1.5"
revision = "a607fe940c6a563c6994f2c945394b19d2183b1c"
version = "v4.1.6"
[[projects]]
digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737"

View File

@ -7,8 +7,8 @@ import (
// 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.Stat.Ap == nil {
s.Stat.Ap = &unifi.Ap{}
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := map[string]string{
"mac": s.Mac,
@ -36,6 +36,9 @@ func (u *InfluxUnifi) batchUAP(r report, s *unifi.UAP) {
}
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,

View File

@ -33,11 +33,8 @@ func (u *InfluxUnifi) batchSysStats(s unifi.SysStats, ss unifi.SystemStats) map[
// 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.Stat.Sw == nil {
s.Stat.Sw = &unifi.Sw{}
}
if s.Stat.Gw == nil {
s.Stat.Gw = &unifi.Gw{}
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := map[string]string{
"mac": s.Mac,
@ -81,28 +78,18 @@ func (u *InfluxUnifi) batchUDM(r report, s *unifi.UDM) {
"serial": s.Serial,
"type": s.Type,
}
fields = 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,
"stat_bytes": s.Stat.Sw.Bytes.Val,
"stat_rx_bytes": s.Stat.Sw.RxBytes.Val,
"stat_rx_crypts": s.Stat.Sw.RxCrypts.Val,
"stat_rx_dropped": s.Stat.Sw.RxDropped.Val,
"stat_rx_errors": s.Stat.Sw.RxErrors.Val,
"stat_rx_frags": s.Stat.Sw.RxFrags.Val,
"stat_rx_packets": s.Stat.Sw.TxPackets.Val,
"stat_tx_bytes": s.Stat.Sw.TxBytes.Val,
"stat_tx_dropped": s.Stat.Sw.TxDropped.Val,
"stat_tx_errors": s.Stat.Sw.TxErrors.Val,
"stat_tx_packets": s.Stat.Sw.TxPackets.Val,
"stat_tx_retries": s.Stat.Sw.TxRetries.Val,
}
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)

View File

@ -7,8 +7,8 @@ import (
// 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.Stat.Gw == nil {
s.Stat.Gw = &unifi.Gw{}
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := map[string]string{
"mac": s.Mac,
@ -74,6 +74,9 @@ func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) {
*/
}
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,

View File

@ -7,9 +7,10 @@ import (
// 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.Stat.Sw == nil {
s.Stat.Sw = &unifi.Sw{}
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := map[string]string{
"mac": s.Mac,
"site_name": s.SiteName,
@ -19,35 +20,45 @@ func (u *InfluxUnifi) batchUSW(r report, s *unifi.USW) {
"serial": s.Serial,
"type": s.Type,
}
fields := Combine(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,
"stat_bytes": s.Stat.Sw.Bytes.Val,
"stat_rx_bytes": s.Stat.Sw.RxBytes.Val,
"stat_rx_crypts": s.Stat.Sw.RxCrypts.Val,
"stat_rx_dropped": s.Stat.Sw.RxDropped.Val,
"stat_rx_errors": s.Stat.Sw.RxErrors.Val,
"stat_rx_frags": s.Stat.Sw.RxFrags.Val,
"stat_rx_packets": s.Stat.Sw.TxPackets.Val,
"stat_tx_bytes": s.Stat.Sw.TxBytes.Val,
"stat_tx_dropped": s.Stat.Sw.TxDropped.Val,
"stat_tx_errors": s.Stat.Sw.TxErrors.Val,
"stat_tx_packets": s.Stat.Sw.TxPackets.Val,
"stat_tx_retries": s.Stat.Sw.TxRetries.Val,
}, u.batchSysStats(s.SysStats, s.SystemStats))
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 {

View File

@ -1,5 +1,13 @@
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 (
"encoding/json"
"encoding/xml"
@ -10,6 +18,7 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/BurntSushi/toml"
@ -45,8 +54,8 @@ type UnifiPoller struct {
Unifi *unifi.Unifi
Flag *Flag
Config *Config
errorCount int
LastCheck time.Time
sync.Mutex // locks the Unifi struct member when re-authing to unifi.
}
// Flag represents the CLI args available and their settings.
@ -110,44 +119,50 @@ func (c *Config) ParseFile(configFile string) error {
// ParseENV copies environment variables into configuration values.
// This is useful for Docker users that find it easier to pass ENV variables
// than a specific configuration file. Uses reflection to find struct tags.
// This method uses the json struct tag member to match environment variables.
// Use a custom tag name by changing "json" below, but that's overkill for this app.
func (c *Config) ParseENV() error {
t := reflect.TypeOf(Config{}) // Get tag names from the Config struct.
// Loop each Config struct member; get reflect tag & env var value; update config.
for i := 0; i < t.NumField(); i++ {
t := reflect.TypeOf(*c) // Get "types" from the Config struct.
for i := 0; i < t.NumField(); i++ { // Loop each Config struct member
tag := t.Field(i).Tag.Get("json") // Get the ENV variable name from "json" struct tag
tag = strings.Split(strings.ToUpper(tag), ",")[0] // Capitalize and remove ,omitempty suffix
env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS.
if tag == "" || env == "" {
continue // Skip if either are empty.
if tag == "" || env == "" { // Skip if either are empty.
continue
}
// Reflect and update the u.Config struct member at position i.
switch c := reflect.ValueOf(c).Elem().Field(i); c.Type().String() {
switch field := reflect.ValueOf(c).Elem().Field(i); field.Type().String() {
// Handle each member type appropriately (differently).
case "string":
// This is a reflect package method to update a struct member by index.
c.SetString(env)
field.SetString(env)
case "int":
val, err := strconv.Atoi(env)
if err != nil {
return fmt.Errorf("%s: %v", tag, err)
}
c.Set(reflect.ValueOf(val))
field.Set(reflect.ValueOf(val))
case "[]string":
c.Set(reflect.ValueOf(strings.Split(env, ",")))
field.Set(reflect.ValueOf(strings.Split(env, ",")))
case path.Base(t.PkgPath()) + ".Duration":
val, err := time.ParseDuration(env)
if err != nil {
return fmt.Errorf("%s: %v", tag, err)
}
c.Set(reflect.ValueOf(Duration{val}))
field.Set(reflect.ValueOf(Duration{val}))
case "bool":
val, err := strconv.ParseBool(env)
if err != nil {
return fmt.Errorf("%s: %v", tag, err)
}
c.SetBool(val)
field.SetBool(val)
}
// Add more types here if more types are added to the config struct.
}
return nil

View File

@ -35,9 +35,9 @@ func (u *UnifiPoller) DumpJSONPayload() (err error) {
case err != nil:
return err
case StringInSlice(u.Flag.DumpJSON, []string{"d", "device", "devices"}):
return u.dumpSitesJSON(unifi.DevicePath, "Devices", sites)
return u.dumpSitesJSON(unifi.APIDevicePath, "Devices", sites)
case StringInSlice(u.Flag.DumpJSON, []string{"client", "clients", "c"}):
return u.dumpSitesJSON(unifi.ClientPath, "Clients", sites)
return u.dumpSitesJSON(unifi.APIClientPath, "Clients", sites)
case strings.HasPrefix(u.Flag.DumpJSON, "other "):
apiPath := strings.SplitN(u.Flag.DumpJSON, " ", 2)[1]
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", apiPath)

View File

@ -8,15 +8,6 @@ import (
const callDepth = 2
// LogError logs an error and increments the error counter.
// Should be used in the poller loop.
func (u *UnifiPoller) LogError(err error, prefix string) {
if err != nil {
u.errorCount++
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] %v: %v", prefix, err))
}
}
// StringInSlice returns true if a string is in a slice.
func StringInSlice(str string, slice []string) bool {
for _, s := range slice {
@ -41,7 +32,7 @@ func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) {
}
}
// LogErrorf prints an error log entry. This is used for external library logging.
// LogErrorf prints an error log entry.
func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) {
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
}

View File

@ -12,6 +12,7 @@ func (u *UnifiPoller) GetInfluxDB() (err error) {
if u.Influx != nil {
return nil
}
u.Influx, err = influxunifi.New(&influxunifi.Config{
Database: u.Config.InfluxDB,
User: u.Config.InfluxUser,
@ -22,7 +23,9 @@ func (u *UnifiPoller) GetInfluxDB() (err error) {
if err != nil {
return fmt.Errorf("influxdb: %v", err)
}
u.Logf("Logging Measurements to InfluxDB at %s as user %s", u.Config.InfluxURL, u.Config.InfluxUser)
return nil
}
@ -35,16 +38,19 @@ func (u *UnifiPoller) CollectAndProcess() error {
if err := u.GetInfluxDB(); err != nil {
return err
}
metrics, err := u.CollectMetrics()
if err != nil {
return err
}
u.AugmentMetrics(metrics)
report, err := u.Influx.ReportMetrics(metrics)
if err != nil {
u.LogError(err, "processing metrics")
return err
}
u.LogInfluxReport(report)
return nil
}
@ -52,9 +58,11 @@ func (u *UnifiPoller) CollectAndProcess() error {
// LogInfluxReport writes a log message after exporting to influxdb.
func (u *UnifiPoller) LogInfluxReport(r *influxunifi.Report) {
idsMsg := ""
if u.Config.SaveIDS {
idsMsg = fmt.Sprintf("IDS Events: %d, ", len(r.Metrics.IDSList))
}
u.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),

View File

@ -15,7 +15,7 @@ const oneDecimalPoint = 10
// RunPrometheus starts the web server and registers the collector.
func (u *UnifiPoller) RunPrometheus() error {
u.Logf("Exporting Measurements at https://%s/metrics for Prometheus", u.Config.HTTPListen)
u.Logf("Exporting Measurements for Prometheus at https://%s/metrics", u.Config.HTTPListen)
http.Handle("/metrics", promhttp.Handler())
prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorCnfg{
Namespace: strings.Replace(u.Config.Namespace, "-", "", -1),
@ -23,6 +23,7 @@ func (u *UnifiPoller) RunPrometheus() error {
LoggingFn: u.LogExportReport,
ReportErrors: true, // XXX: Does this need to be configurable?
}))
return http.ListenAndServe(u.Config.HTTPListen, nil)
}
@ -34,8 +35,9 @@ func (u *UnifiPoller) ExportMetrics() (*metrics.Metrics, error) {
if err != nil {
u.LogErrorf("collecting metrics: %v", err)
u.Logf("Re-authenticating to UniFi Controller")
if err := u.Unifi.Login(); err != nil {
u.LogError(err, "re-authenticating")
if err := u.GetUnifi(); err != nil {
u.LogErrorf("re-authenticating: %v", err)
return nil, err
}

View File

@ -28,7 +28,10 @@ func New() *UnifiPoller {
SaveSites: true,
HTTPListen: defaultHTTPListen,
Namespace: appName,
}, Flag: &Flag{ConfigFile: DefaultConfFile},
},
Flag: &Flag{
ConfigFile: DefaultConfFile,
},
}
}
@ -36,6 +39,7 @@ func New() *UnifiPoller {
// 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.Flag.Parse(os.Args[1:])
@ -92,63 +96,44 @@ func (f *Flag) Parse(args []string) {
// 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 err := u.GetUnifi(); err != nil {
return err
switch err := u.GetUnifi(); err {
case nil:
u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
u.Config.UnifiBase, u.Unifi.ServerVersion, u.Config.UnifiUser, u.Config.Sites)
default:
u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %v", err)
}
u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
u.Config.UnifiBase, u.Unifi.ServerVersion, u.Config.UnifiUser, u.Config.Sites)
switch strings.ToLower(u.Config.Mode) {
default:
return u.PollController()
u.PollController()
return nil
case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda":
u.LastCheck = time.Now()
return u.CollectAndProcess()
case "both":
go u.PollController()
fallthrough
case "prometheus", "exporter":
return u.RunPrometheus()
case "both":
return u.RunBoth()
}
}
// RunBoth starts the prometheus exporter and influxdb exporter at the same time.
// This will likely double the amount of polls your controller receives.
func (u *UnifiPoller) RunBoth() error {
e := make(chan error)
defer close(e)
go func() {
e <- u.RunPrometheus()
}()
go func() {
e <- u.PollController()
}()
// If either method returns an error (even nil), bail out.
return <-e
}
// PollController runs forever, polling UniFi and pushing to InfluxDB
// This is started by Run() or RunBoth() after everything checks out.
func (u *UnifiPoller) PollController() error {
func (u *UnifiPoller) PollController() {
interval := u.Config.Interval.Round(time.Second)
log.Printf("[INFO] Everything checks out! Poller started, interval: %v", interval)
ticker := time.NewTicker(interval)
defer ticker.Stop()
log.Printf("[INFO] Everything checks out! Poller started, InfluxDB interval: %v", interval)
ticker := time.NewTicker(interval)
for u.LastCheck = range ticker.C {
// Some users need to re-auth every interval because the cookie times out.
if u.Config.ReAuth {
u.LogDebugf("Re-authenticating to UniFi Controller")
if err := u.Unifi.Login(); err != nil {
return err
if err := u.CollectAndProcess(); err != nil {
u.LogErrorf("%v", err)
if u.Unifi != nil {
u.Unifi.CloseIdleConnections()
u.Unifi = nil // trigger re-auth in unifi.go.
}
}
if err := u.CollectAndProcess(); err != nil {
return err
}
// check for errors from the unifi polls.
if u.errorCount > 0 {
return fmt.Errorf("too many errors, stopping poller")
}
}
return nil
}

View File

@ -11,6 +11,13 @@ import (
// GetUnifi returns a UniFi controller interface.
func (u *UnifiPoller) GetUnifi() (err error) {
u.Lock()
defer u.Unlock()
if u.Unifi != nil {
u.Unifi.CloseIdleConnections()
}
// Create an authenticated session to the Unifi Controller.
u.Unifi, err = unifi.NewUnifi(&unifi.Config{
User: u.Config.UnifiUser,
@ -21,8 +28,10 @@ func (u *UnifiPoller) GetUnifi() (err error) {
DebugLog: u.LogDebugf, // Log debug messages.
})
if err != nil {
u.Unifi = nil
return fmt.Errorf("unifi controller: %v", err)
}
u.LogDebugf("Authenticated with controller successfully")
return u.CheckSites()
@ -34,20 +43,26 @@ func (u *UnifiPoller) CheckSites() error {
if strings.Contains(strings.ToLower(u.Config.Mode), "lambda") {
return nil // Skip this in lambda mode.
}
u.LogDebugf("Checking Controller Sites List")
sites, err := u.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: %v", len(msg), strings.Join(msg, ", "))
if StringInSlice("all", u.Config.Sites) {
u.Config.Sites = []string{"all"}
return nil
}
FIRST:
for _, s := range u.Config.Sites {
for _, site := range sites {
@ -58,26 +73,44 @@ FIRST:
// This is fine, it may get added later.
u.LogErrorf("configured site not found on controller: %v", s)
}
return nil
}
// CollectMetrics grabs all the measurements from a UniFi controller and returns them.
func (u *UnifiPoller) CollectMetrics() (*metrics.Metrics, error) {
m := &metrics.Metrics{TS: u.LastCheck} // At this point, it's the Current Check.
var err error
if u.Unifi == nil || u.Config.ReAuth {
// Some users need to re-auth every interval because the cookie times out.
// Sometimes we hit this path when the controller dies.
u.Logf("Re-authenticating to UniFi Controller")
if err := u.GetUnifi(); err != nil {
return nil, err
}
}
m := &metrics.Metrics{TS: u.LastCheck} // At this point, it's the Current Check.
// Get the sites we care about.
m.Sites, err = u.GetFilteredSites()
u.LogError(err, "unifi.GetSites()")
if m.Sites, err = u.GetFilteredSites(); err != nil {
return m, fmt.Errorf("unifi.GetSites(): %v", err)
}
if u.Config.SaveIDS {
m.IDSList, err = u.Unifi.GetIDS(m.Sites, time.Now().Add(u.Config.Interval.Duration), time.Now())
u.LogError(err, "unifi.GetIDS()")
return m, fmt.Errorf("unifi.GetIDS(): %v", err)
}
// Get all the points.
m.Clients, err = u.Unifi.GetClients(m.Sites)
u.LogError(err, "unifi.GetClients()")
m.Devices, err = u.Unifi.GetDevices(m.Sites)
u.LogError(err, "unifi.GetDevices()")
return m, err
if m.Clients, err = u.Unifi.GetClients(m.Sites); err != nil {
return m, fmt.Errorf("unifi.GetClients(): %v", err)
}
if m.Devices, err = u.Unifi.GetDevices(m.Sites); err != nil {
return m, fmt.Errorf("unifi.GetDevices(): %v", err)
}
return m, nil
}
// AugmentMetrics is our middleware layer between collecting metrics and writing them.
@ -87,23 +120,29 @@ func (u *UnifiPoller) AugmentMetrics(metrics *metrics.Metrics) {
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
return
}
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 {
metrics.Clients[i].SwName = devices[c.SwMac]
@ -111,6 +150,7 @@ func (u *UnifiPoller) AugmentMetrics(metrics *metrics.Metrics) {
metrics.Clients[i].GwName = devices[c.GwMac]
metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
}
if !u.Config.SaveSites {
metrics.Sites = nil
}
@ -120,13 +160,15 @@ func (u *UnifiPoller) AugmentMetrics(metrics *metrics.Metrics) {
// Omits requested but unconfigured sites. Grabs the full list from the
// controller and returns the sites provided in the config file.
func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) {
var i int
sites, err := u.Unifi.GetSites()
if err != nil {
return nil, err
} else if len(u.Config.Sites) < 1 || StringInSlice("all", u.Config.Sites) {
return sites, nil
}
var i int
for _, s := range sites {
// Only include valid sites in the request filter.
if StringInSlice(s.Name, u.Config.Sites) {
@ -134,5 +176,6 @@ func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) {
i++
}
}
return sites[:i], nil
}

View File

@ -159,6 +159,9 @@ func descUAP(ns string) *uap {
}
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}
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)
@ -175,6 +178,9 @@ func (u *promUnifi) exportUAP(r report, d *unifi.UAP) {
// 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]}
labelG := []string{"guest", labels[1], labels[2]}
r.send([]*metric{

View File

@ -60,6 +60,9 @@ func descDevice(ns string) *unifiDevice {
// 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}
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).

View File

@ -69,6 +69,9 @@ func descUSG(ns string) *usg {
}
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}
infoLabels := []string{d.Version, d.Model, d.Serial, d.Mac, d.IP, d.ID, d.Bytes.Txt, d.Uptime.Txt}
// Gateway System Data.
@ -85,6 +88,9 @@ func (u *promUnifi) exportUSG(r report, d *unifi.USG) {
// 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]}
labelWan := []string{"all", labels[1], labels[2]}
r.send([]*metric{

View File

@ -91,6 +91,9 @@ func descUSW(ns string) *usw {
}
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}
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)
@ -116,6 +119,9 @@ func (u *promUnifi) exportUSW(r report, d *unifi.USW) {
// 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},