Merge pull request #155 from davidnewhall/dn2_dont_die
Prevent poller from dying from an error
This commit is contained in:
commit
740249d1de
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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...))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
Loading…
Reference in New Issue