diff --git a/integrations/influxunifi/examples/MANUAL.md b/integrations/influxunifi/examples/MANUAL.md
index cabf4db8..d12c9ed3 100644
--- a/integrations/influxunifi/examples/MANUAL.md
+++ b/integrations/influxunifi/examples/MANUAL.md
@@ -65,56 +65,16 @@ is provided so the application can be easily adapted to any environment.
`Config File Parameters`
-Additional parameters are added by output packages. Parameters can also be set
-using environment variables. See the GitHub wiki for more information!
+Configuration file (up.conf) parameters are documented in the wiki.
- >>> POLLER FIELDS FOLLOW - you may have multiple controllers:
+* [https://github.com/davidnewhall/unifi-poller/wiki/Configuration](https://github.com/davidnewhall/unifi-poller/wiki/Configuration)
- debug default: false
- This turns on time stamps and line numbers in logs, outputs a few extra
- lines of information while processing.
+`Shell Environment Parameters`
- quiet default: false
- Setting this to true will turn off per-device and per-interval logs. Only
- errors will be logged. Using this with debug=true adds line numbers to
- any error logs.
+This application can be fully configured using shell environment variables.
+Find documentation for this feature on the Docker Wiki page.
- >>> UNIFI CONTROLLER FIELDS FOLLOW - you may have multiple controllers:
-
- sites default: ["all"]
- This list of strings should represent the names of sites on the UniFi
- controller that will be polled for data. Pass `all` in the list to
- poll all sites. On startup, the application prints out all site names
- found in the controller; they're cryptic, but they have the human-name
- next to them. The cryptic names go into the config file `sites` list.
- The controller's first site is not cryptic and is named `default`.
-
- url default: https://127.0.0.1:8443
- This is the URL where the UniFi Controller is available.
-
- user default: influxdb
- Username used to authenticate with UniFi controller. This should be a
- special service account created on the control with read-only access.
-
- pass no default
- Password used to authenticate with UniFi controller. This can also be
- set in an environment variable instead of a configuration file.
-
- save_ids default: false
- Setting this parameter to true will enable collection of Intrusion
- Detection System data. IDS and IPS are the same data set. This is off
- by default because most controllers do not have this enabled. It also
- creates a lot of new metrics from controllers with a lot of IDS entries.
- IDS data does not contain metrics, so this doesn't work with Prometheus.
-
- save_sites default: true
- Setting this parameter to false will disable saving Network Site data.
- This data populates the Sites dashboard, and this setting affects influx
- and prometheus.
-
- verify_ssl default: false
- If your UniFi controller has a valid SSL certificate, you can enable
- this option to validate it. Otherwise, any SSL certificate is valid.
+* [https://github.com/davidnewhall/unifi-poller/wiki/Docker](https://github.com/davidnewhall/unifi-poller/wiki/Docker)
GO DURATION
---
diff --git a/integrations/influxunifi/examples/up.conf.example b/integrations/influxunifi/examples/up.conf.example
index 43aa9831..942c53cd 100644
--- a/integrations/influxunifi/examples/up.conf.example
+++ b/integrations/influxunifi/examples/up.conf.example
@@ -1,75 +1,95 @@
# UniFi Poller primary configuration file. TOML FORMAT #
-# commented lines are defaults, uncomment to change. #
########################################################
-
[poller]
-# Turns on line numbers, microsecond logging, and a per-device log.
-# The default is false, but I personally leave this on at home (four devices).
-# This may be noisy if you have a lot of devices. It adds one line per device.
-debug = false
+ # Turns on line numbers, microsecond logging, and a per-device log.
+ # The default is false, but I personally leave this on at home (four devices).
+ # This may be noisy if you have a lot of devices. It adds one line per device.
+ debug = false
-# Turns off per-interval logs. Only startup and error logs will be emitted.
-# Recommend enabling debug with this setting for better error logging.
-quiet = false
+ # Turns off per-interval logs. Only startup and error logs will be emitted.
+ # Recommend enabling debug with this setting for better error logging.
+ quiet = false
-# Load dynamic plugins. Advanced use; only sample mysql plugin provided by default.
-plugins = []
+ # Load dynamic plugins. Advanced use; only sample mysql plugin provided by default.
+ plugins = []
#### OUTPUTS
+ # If you don't use an output, you can disable it.
+
[prometheus]
-disable = false
-# This controls on which ip and port /metrics is exported when mode is "prometheus".
-# This has no effect in other modes. Must contain a colon and port.
-http_listen = "0.0.0.0:9130"
-report_errors = false
+ disable = false
+ # This controls on which ip and port /metrics is exported when mode is "prometheus".
+ # This has no effect in other modes. Must contain a colon and port.
+ http_listen = "0.0.0.0:9130"
+ report_errors = false
[influxdb]
-disable = false
-# InfluxDB does not require auth by default, so the user/password are probably unimportant.
-url = "http://127.0.0.1:8086"
-user = "unifi"
-pass = "unifi"
-# Be sure to create this database.
-db = "unifi"
-# If your InfluxDB uses a valid SSL cert, set this to true.
-verify_ssl = false
-# The UniFi Controller only updates traffic stats about every 30 seconds.
-# Setting this to something lower may lead to "zeros" in your data.
-# If you're getting zeros now, set this to "1m"
-interval = "30s"
+ disable = false
+ # InfluxDB does not require auth by default, so the user/password are probably unimportant.
+ url = "http://127.0.0.1:8086"
+ user = "unifipoller"
+ pass = "unifipoller"
+ # Be sure to create this database.
+ db = "unifi"
+ # If your InfluxDB uses a valid SSL cert, set this to true.
+ verify_ssl = false
+ # The UniFi Controller only updates traffic stats about every 30 seconds.
+ # Setting this to something lower may lead to "zeros" in your data.
+ # If you're getting zeros now, set this to "1m"
+ interval = "30s"
#### INPUTS
[unifi]
-disable = false
+ # Setting this to true and providing default credentials allows you to skip
+ # configuring controllers in this config file. Instead you configure them in
+ # your prometheus.yml config. Prometheus then sends the controller URL to
+ # unifi-poller when it performs the scrape. This is useful if you have many,
+ # or changing controllers. Most people can leave this off. See wiki for more.
+ dynamic = false
+
+# The following section contains the default credentials/configuration for any
+# dynamic controller (see above section), or the primary controller if you do not
+# provide one and dynamic is disabled. In other words, you can just add your
+# controller here and delete the following section. Either works.
+[unifi.defaults]
+ name = "https://127.0.0.1:8443"
+ url = "https://127.0.0.1:8443"
+ user = "unifipoller"
+ pass = "unifipoller"
+ sites = ["all"]
+ save_ids = false
+ save_sites = true
+ verify_ssl = false
# You may repeat the following section to poll additional controllers.
[[unifi.controller]]
-# Friendly name used in dashboards. Uses URL if left empty.
-name = ""
+ # Friendly name used in dashboards. Uses URL if left empty; which is fine.
+ # Avoid changing this later because it will live forever in your database.
+ name = ""
-url = "https://127.0.0.1:8443"
-# Make a read-only user in the UniFi Admin Settings.
-user = "influx"
-# You may also set env variable UNIFI_PASSWORD instead of putting this in the config.
-pass = "4BB9345C-2341-48D7-99F5-E01B583FF77F"
+ url = "https://127.0.0.1:8443"
+ # Make a read-only user in the UniFi Admin Settings.
+ user = "unifipoller"
+ # You may also set env variable UNIFI_PASSWORD instead of putting this in the config.
+ pass = "4BB9345C-2341-48D7-99F5-E01B583FF77F"
-# If the controller has more than one site, specify which sites to poll here.
-# Set this to ["default"] to poll only the first site on the controller.
-# A setting of ["all"] will poll all sites; this works if you only have 1 site too.
-sites = ["all"]
+ # If the controller has more than one site, specify which sites to poll here.
+ # Set this to ["default"] to poll only the first site on the controller.
+ # A setting of ["all"] will poll all sites; this works if you only have 1 site too.
+ sites = ["all"]
-# Enable collection of Intrusion Detection System Data (InfluxDB only).
-# Only useful if IDS or IPS are enabled on one of the sites.
-save_ids = false
+ # Enable collection of Intrusion Detection System Data (InfluxDB only).
+ # Only useful if IDS or IPS are enabled on one of the sites.
+ save_ids = false
-# Enable collection of site data. This data powers the Network Sites dashboard.
-# It's not valuable to everyone and setting this to false will save resources.
-save_sites = true
+ # Enable collection of site data. This data powers the Network Sites dashboard.
+ # It's not valuable to everyone and setting this to false will save resources.
+ save_sites = true
-# If your UniFi controller has a valid SSL certificate (like lets encrypt),
-# you can enable this option to validate it. Otherwise, any SSL certificate is
-# valid. If you don't know if you have a valid SSL cert, then you don't have one.
-verify_ssl = false
+ # If your UniFi controller has a valid SSL certificate (like lets encrypt),
+ # you can enable this option to validate it. Otherwise, any SSL certificate is
+ # valid. If you don't know if you have a valid SSL cert, then you don't have one.
+ verify_ssl = false
diff --git a/integrations/influxunifi/examples/up.json.example b/integrations/influxunifi/examples/up.json.example
index 12ba4a4f..a4e0a401 100644
--- a/integrations/influxunifi/examples/up.json.example
+++ b/integrations/influxunifi/examples/up.json.example
@@ -14,20 +14,30 @@
"influxdb": {
"disable": false,
"url": "http://127.0.0.1:8086",
- "user": "unifi",
- "pass": "unifi",
+ "user": "unifipoller",
+ "pass": "unifipoller",
"db": "unifi",
"verify_ssl": false,
"interval": "30s"
},
"unifi": {
- "disable": false,
+ "dynamic": false,
+ "defaults": {
+ "name": "https://127.0.0.1:8443",
+ "user": "unifipoller",
+ "pass": "unifipoller",
+ "url": "https://127.0.0.1:8443",
+ "sites": ["all"],
+ "save_ids": false,
+ "save_sites": true,
+ "verify_ssl": false
+ },
"controllers": [
{
"name": "",
- "user": "influx",
- "pass": "",
+ "user": "unifipoller",
+ "pass": "unifipoller",
"url": "https://127.0.0.1:8443",
"sites": ["all"],
"save_ids": false,
diff --git a/integrations/influxunifi/examples/up.xml.example b/integrations/influxunifi/examples/up.xml.example
index 14b2aa09..094f8347 100644
--- a/integrations/influxunifi/examples/up.xml.example
+++ b/integrations/influxunifi/examples/up.xml.example
@@ -18,22 +18,33 @@
30s
http://127.0.0.1:8086
- unifi
- unifi
+ unifipoller
+ unifipoller
unifi
false
-
+
+
+ all
+ unifipoller
+ unifipoller
+ https://127.0.0.1:8443
+ false
+ false
+ true
+
+
all
- influx
-
+ unifipoller
+ unifipoller
https://127.0.0.1:8443
false
false
true
+
diff --git a/integrations/influxunifi/examples/up.yaml.example b/integrations/influxunifi/examples/up.yaml.example
index bb8a4aa1..edf08506 100644
--- a/integrations/influxunifi/examples/up.yaml.example
+++ b/integrations/influxunifi/examples/up.yaml.example
@@ -18,17 +18,28 @@ influxdb:
disable: false
interval: "30s"
url: "http://127.0.0.1:8086"
- user: "unifi"
- pass: "unifi"
+ user: "unifipoller"
+ pass: "unifipoller"
db: "unifi"
verify_ssl: false
unifi:
- disable: false
+ dynamic: false
+ defaults:
+ name: "https://127.0.0.1:8443"
+ user: "unifipoller"
+ pass: "unifipoller"
+ url: "https://127.0.0.1:8443"
+ sites:
+ - all
+ verify_ssl: false
+ save_ids: false
+ save_sites: true
+
controllers:
- name: ""
- user: "influx"
- pass: ""
+ user: "unifipoller"
+ pass: "unifipoller"
url: "https://127.0.0.1:8443"
sites:
- all
diff --git a/integrations/influxunifi/pkg/inputunifi/collector.go b/integrations/influxunifi/pkg/inputunifi/collector.go
index 1539f244..90f1feb4 100644
--- a/integrations/influxunifi/pkg/inputunifi/collector.go
+++ b/integrations/influxunifi/pkg/inputunifi/collector.go
@@ -2,6 +2,7 @@ package inputunifi
import (
"fmt"
+ "strings"
"time"
"github.com/davidnewhall/unifi-poller/pkg/poller"
@@ -15,47 +16,43 @@ func (u *InputUnifi) isNill(c *Controller) bool {
return c.Unifi == nil
}
-func (u *InputUnifi) dynamicController(url string) (*poller.Metrics, bool, error) {
- c := u.Config.Default // copy defaults into new controller
+// 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.Config.Default // copy defaults into new controller
+ c = &ccopy
+ u.dynamic[url] = c
c.Name = url
c.URL = url
- u.Logf("Authenticating to Dynamic UniFi Controller: %s", url)
-
- if err := u.getUnifi(&c); err != nil {
- return nil, false, fmt.Errorf("authenticating to %s: %v", url, err)
- }
-
- metrics := &poller.Metrics{}
- ok, err := u.appendController(&c, metrics)
-
- return metrics, ok, err
+ return true, c
}
-func (u *InputUnifi) appendController(c *Controller, metrics *poller.Metrics) (bool, error) {
- m, err := u.collectController(c)
- if err != nil || m == nil {
- return false, err
+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")
}
- metrics.Sites = append(metrics.Sites, m.Sites...)
- metrics.Clients = append(metrics.Clients, m.Clients...)
- metrics.IDSList = append(metrics.IDSList, m.IDSList...)
+ new, c := u.newDynamicCntrlr(url)
- if m.Devices == nil {
- return true, nil
+ 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)
+ }
}
- if metrics.Devices == nil {
- metrics.Devices = &unifi.Devices{}
- }
-
- metrics.UAPs = append(metrics.UAPs, m.UAPs...)
- metrics.USGs = append(metrics.USGs, m.USGs...)
- metrics.USWs = append(metrics.USWs, m.USWs...)
- metrics.UDMs = append(metrics.UDMs, m.UDMs...)
-
- return true, nil
+ return u.collectController(c)
}
func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) {
@@ -67,11 +64,6 @@ func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) {
}
}
- m, err := u.pollController(c)
- if err == nil {
- return m, nil
- }
-
return u.pollController(c)
}
@@ -146,7 +138,7 @@ func (u *InputUnifi) augmentMetrics(c *Controller, metrics *poller.Metrics) *pol
metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
}
- if !c.SaveSites {
+ if !*c.SaveSites {
metrics.Sites = nil
}
diff --git a/integrations/influxunifi/pkg/inputunifi/input.go b/integrations/influxunifi/pkg/inputunifi/input.go
index 4a2881aa..a540964d 100644
--- a/integrations/influxunifi/pkg/inputunifi/input.go
+++ b/integrations/influxunifi/pkg/inputunifi/input.go
@@ -16,13 +16,15 @@ import (
const (
defaultURL = "https://127.0.0.1:8443"
defaultUser = "unifipoller"
- defaultPass = "unifipollerp4$$w0rd"
+ defaultPass = "unifipoller"
defaultSite = "all"
)
// InputUnifi contains the running data.
type InputUnifi struct {
- Config *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
+ Config *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
+ dynamic map[string]*Controller
+ sync.Mutex // to lock the map above.
poller.Logger
}
@@ -31,7 +33,7 @@ type InputUnifi struct {
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"`
- SaveSites bool `json:"save_sites" toml:"save_sites" xml:"save_sites" yaml:"save_sites"`
+ SaveSites *bool `json:"save_sites" toml:"save_sites" xml:"save_sites" yaml:"save_sites"`
Name string `json:"name" toml:"name" xml:"name,attr" yaml:"name"`
User string `json:"user" toml:"user" xml:"user" yaml:"user"`
Pass string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"`
@@ -44,8 +46,8 @@ type Controller struct {
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" yaml:"disable"`
- Dynamic bool `json:"dynamic" toml:"dynamic" xml:"dynamic" yaml:"dynamic"`
+ 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"`
}
@@ -157,6 +159,11 @@ func (u *InputUnifi) dumpSitesJSON(c *Controller, path, name string, sites unifi
}
func (u *InputUnifi) setDefaults(c *Controller) {
+ if c.SaveSites == nil {
+ t := true
+ c.SaveSites = &t
+ }
+
if c.URL == "" {
c.URL = defaultURL
}
diff --git a/integrations/influxunifi/pkg/inputunifi/interface.go b/integrations/influxunifi/pkg/inputunifi/interface.go
index 67f53cce..42745687 100644
--- a/integrations/influxunifi/pkg/inputunifi/interface.go
+++ b/integrations/influxunifi/pkg/inputunifi/interface.go
@@ -15,15 +15,20 @@ import (
// Satisfies poller.Input interface.
func (u *InputUnifi) Initialize(l poller.Logger) error {
if u.Config.Disable {
- l.Logf("unifi input disabled")
+ l.Logf("UniFi input plugin disabled!")
return nil
}
- if u.setDefaults(&u.Config.Default); len(u.Config.Controllers) < 1 {
+ if u.setDefaults(&u.Config.Default); len(u.Config.Controllers) < 1 && !u.Config.Dynamic {
new := u.Config.Default // copy defaults.
u.Config.Controllers = []*Controller{&new}
}
+ if len(u.Config.Controllers) < 1 {
+ l.Logf("No controllers configured. Polling dynamic controllers only!")
+ }
+
+ u.dynamic = make(map[string]*Controller)
u.Logger = l
for _, c := range u.Config.Controllers {
@@ -35,7 +40,7 @@ func (u *InputUnifi) Initialize(l poller.Logger) error {
u.LogErrorf("checking sites on %s: %v", c.Name, err)
}
- u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
+ 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.Name, err)
@@ -52,7 +57,7 @@ func (u *InputUnifi) Metrics() (*poller.Metrics, bool, error) {
// 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.Config.Disable || filter == nil || filter.Term == "" {
+ if u.Config.Disable {
return nil, false, nil
}
@@ -62,35 +67,48 @@ func (u *InputUnifi) MetricsFrom(filter *poller.Filter) (*poller.Metrics, bool,
// Check if the request is for an existing, configured controller.
for _, c := range u.Config.Controllers {
- if !strings.EqualFold(c.Name, filter.Term) {
+ if filter != nil && !strings.EqualFold(c.Name, filter.Term) {
continue
}
- exists, err := u.appendController(c, metrics)
+ m, err := u.collectController(c)
if err != nil {
errs = append(errs, err.Error())
}
- if exists {
- ok = true
+ if m == nil {
+ continue
}
+
+ ok = true
+ metrics = poller.AppendMetrics(metrics, m)
}
if len(errs) > 0 {
return metrics, ok, fmt.Errorf(strings.Join(errs, ", "))
}
- if u.Config.Dynamic && !ok && strings.HasPrefix(filter.Term, "http") {
- // Attempt to a dynamic metrics fetch from an unconfigured controller.
- return u.dynamicController(filter.Term)
+ if ok {
+ return metrics, true, nil
}
- return metrics, ok, nil
+ if filter != nil && !u.Config.Dynamic {
+ return metrics, false, fmt.Errorf("scrape filter match failed and dynamic lookups disabled")
+ }
+
+ // Attempt a dynamic metrics fetch from an unconfigured controller.
+ m, err := u.dynamicController(filter.Term)
+
+ return m, err == nil && m != nil, err
}
// RawMetrics returns API output from the first configured unifi controller.
func (u *InputUnifi) RawMetrics(filter *poller.Filter) ([]byte, error) {
- c := u.Config.Controllers[0] // We could pull the controller number from the filter.
+ if l := len(u.Config.Controllers); filter.Unit >= l {
+ return nil, fmt.Errorf("control number %d not found, %d controller(s) configured (0 index)", filter.Unit, l)
+ }
+
+ c := u.Config.Controllers[filter.Unit]
if u.isNill(c) {
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
@@ -108,14 +126,14 @@ func (u *InputUnifi) RawMetrics(filter *poller.Filter) ([]byte, error) {
return nil, err
}
- switch filter.Type {
+ 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.Term)
- return c.Unifi.GetJSON(filter.Term)
+ _, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", filter.Path)
+ return c.Unifi.GetJSON(filter.Path)
default:
return []byte{}, fmt.Errorf("must provide filter: devices, clients, other")
}
diff --git a/integrations/influxunifi/pkg/poller/dumper.go b/integrations/influxunifi/pkg/poller/dumper.go
index 80c7f728..c78edabb 100644
--- a/integrations/influxunifi/pkg/poller/dumper.go
+++ b/integrations/influxunifi/pkg/poller/dumper.go
@@ -2,19 +2,24 @@ package poller
import (
"fmt"
+ "strconv"
"strings"
)
-// DumpJSONPayload prints raw json from the UniFi Controller.
-// This only works with controller 0 (first one) in the config.
+// 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{Type: split[0]}
+ 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.Term = split[1]
+ filter.Path = split[1]
}
m, err := inputs[0].RawMetrics(filter)
diff --git a/integrations/influxunifi/pkg/poller/inputs.go b/integrations/influxunifi/pkg/poller/inputs.go
index 8633ca2c..a637239c 100644
--- a/integrations/influxunifi/pkg/poller/inputs.go
+++ b/integrations/influxunifi/pkg/poller/inputs.go
@@ -28,10 +28,26 @@ type InputPlugin struct {
Input
}
-// Filter is used for raw metrics filters.
+// 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
@@ -107,14 +123,14 @@ func (u *UnifiPoller) Metrics() (*Metrics, bool, error) {
return metrics, ok, err
}
-// MetricsFrom aggregates all the measurements from all configured inputs and returns them.
+// 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 input.Name != filter.Type {
+ if !strings.EqualFold(input.Name, filter.Name) {
continue
}
@@ -128,23 +144,7 @@ func (u *UnifiPoller) MetricsFrom(filter *Filter) (*Metrics, bool, error) {
}
ok = true
-
- metrics.Sites = append(metrics.Sites, m.Sites...)
- metrics.Clients = append(metrics.Clients, m.Clients...)
- metrics.IDSList = append(metrics.IDSList, m.IDSList...)
-
- if m.Devices == nil {
- continue
- }
-
- if metrics.Devices == nil {
- metrics.Devices = &unifi.Devices{}
- }
-
- metrics.UAPs = append(metrics.UAPs, m.UAPs...)
- metrics.USGs = append(metrics.USGs, m.USGs...)
- metrics.USWs = append(metrics.USWs, m.USWs...)
- metrics.UDMs = append(metrics.UDMs, m.UDMs...)
+ metrics = AppendMetrics(metrics, m)
}
var err error
@@ -155,3 +155,25 @@ func (u *UnifiPoller) MetricsFrom(filter *Filter) (*Metrics, bool, error) {
return metrics, ok, err
}
+
+// AppendMetrics combined the metrics from two sources.
+func AppendMetrics(existing *Metrics, m *Metrics) *Metrics {
+ existing.Sites = append(existing.Sites, m.Sites...)
+ existing.Clients = append(existing.Clients, m.Clients...)
+ existing.IDSList = append(existing.IDSList, m.IDSList...)
+
+ if m.Devices == nil {
+ return existing
+ }
+
+ if existing.Devices == nil {
+ existing.Devices = &unifi.Devices{}
+ }
+
+ existing.UAPs = append(existing.UAPs, m.UAPs...)
+ existing.USGs = append(existing.USGs, m.USGs...)
+ existing.USWs = append(existing.USWs, m.USWs...)
+ existing.UDMs = append(existing.UDMs, m.UDMs...)
+
+ return existing
+}
diff --git a/integrations/influxunifi/pkg/promunifi/collector.go b/integrations/influxunifi/pkg/promunifi/collector.go
index e4cf1e9c..bbfbda25 100644
--- a/integrations/influxunifi/pkg/promunifi/collector.go
+++ b/integrations/influxunifi/pkg/promunifi/collector.go
@@ -26,23 +26,18 @@ const (
)
type promUnifi struct {
- *Prometheus
- Client *uclient
- Device *unifiDevice
- UAP *uap
- USG *usg
- USW *usw
- Site *site
+ *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
}
-// Prometheus allows the data to be nested in the config file.
-type Prometheus struct {
- Config Config `json:"prometheus" toml:"prometheus" xml:"prometheus" yaml:"prometheus"`
-}
-
// 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
@@ -66,7 +61,7 @@ type metric struct {
// Report accumulates counters that are printed to a log line.
type Report struct {
- Config
+ *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.
@@ -78,17 +73,18 @@ type Report struct {
wg sync.WaitGroup
}
+// target is used for targeted (sometimes dynamic) metrics scrapes.
type target struct {
*poller.Filter
- *promUnifi
+ u *promUnifi
}
func init() {
- u := &promUnifi{Prometheus: &Prometheus{}}
+ u := &promUnifi{Config: &Config{}}
poller.NewOutput(&poller.Output{
Name: "prometheus",
- Config: u.Prometheus,
+ Config: u,
Method: u.Run,
})
}
@@ -96,51 +92,55 @@ func init() {
// 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.Config.Disable {
+ if u.Disable {
return nil
}
- u.Config.Namespace = strings.Trim(strings.Replace(u.Config.Namespace, "-", "_", -1), "_")
- if u.Config.Namespace == "" {
- u.Config.Namespace = strings.Replace(poller.AppName, "-", "", -1)
+ u.Namespace = strings.Trim(strings.Replace(u.Namespace, "-", "_", -1), "_")
+ if u.Namespace == "" {
+ u.Namespace = strings.Replace(poller.AppName, "-", "", -1)
}
- if u.Config.HTTPListen == "" {
- u.Config.HTTPListen = defaultHTTPListen
+ if u.HTTPListen == "" {
+ u.HTTPListen = defaultHTTPListen
}
+ // 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.Config.Namespace))
- prometheus.MustRegister(&promUnifi{
- Collector: c,
- Client: descClient(u.Config.Namespace + "_client_"),
- Device: descDevice(u.Config.Namespace + "_device_"), // stats for all device types.
- UAP: descUAP(u.Config.Namespace + "_device_"),
- USG: descUSG(u.Config.Namespace + "_device_"),
- USW: descUSW(u.Config.Namespace + "_device_"),
- Site: descSite(u.Config.Namespace + "_site_"),
- })
- c.Logf("Exporting Measurements for Prometheus at https://%s/metrics, namespace: %s",
- u.Config.HTTPListen, u.Config.Namespace)
- mux.Handle("/metrics", promhttp.HandlerFor(
- prometheus.DefaultGatherer, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError},
+ 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.Config.HTTPListen, mux)
+ 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{promUnifi: u, Filter: &poller.Filter{}}
- if t.Filter.Type = r.URL.Query().Get("input"); t.Filter.Type == "" {
+ t := &target{u: u, Filter: &poller.Filter{}}
+ if t.Name = r.URL.Query().Get("input"); 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.Filter.Term = r.URL.Query().Get("target"); t.Filter.Term == "" {
+ if t.Term = r.URL.Query().Get("target"); t.Term == "" {
+ u.Collector.LogErrorf("target parameter missing on scrape from %v", r.RemoteAddr)
http.Error(w, "'target' parameter must be specified, configured name, or unconfigured url", 400)
+
return
}
@@ -152,14 +152,15 @@ func (u *promUnifi) ScrapeHandler(w http.ResponseWriter, r *http.Request) {
).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.promUnifi.Describe(ch)
-}
-
-func (t *target) Collect(ch chan<- prometheus.Metric) {
- t.promUnifi.collect(ch, t.Filter)
+ t.u.Describe(ch)
}
// Describe satisfies the prometheus Collector. This returns all of the
@@ -178,6 +179,11 @@ func (u *promUnifi) Describe(ch chan<- *prometheus.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) {
@@ -187,27 +193,31 @@ func (u *promUnifi) Collect(ch chan<- prometheus.Metric) {
func (u *promUnifi) collect(ch chan<- prometheus.Metric, filter *poller.Filter) {
var err error
- ok := false
-
- r := &Report{Config: u.Config, ch: make(chan []*metric, buffer), Start: time.Now()}
+ r := &Report{
+ Config: u.Config,
+ ch: make(chan []*metric, 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(fmt.Errorf("metric fetch failed")), err)
+ r.error(ch, prometheus.NewInvalidDesc(err), fmt.Errorf("metric fetch failed"))
+ u.Collector.LogErrorf("metric fetch failed: %v", err)
if !ok {
return
}
}
- r.Fetch = time.Since(r.Start)
-
if r.Metrics.Devices == nil {
r.Metrics.Devices = &unifi.Devices{}
}
diff --git a/integrations/influxunifi/pkg/promunifi/report.go b/integrations/influxunifi/pkg/promunifi/report.go
index 9b6df74c..3eb66638 100644
--- a/integrations/influxunifi/pkg/promunifi/report.go
+++ b/integrations/influxunifi/pkg/promunifi/report.go
@@ -67,7 +67,7 @@ func (r *Report) export(m *metric, v float64) prometheus.Metric {
func (r *Report) error(ch chan<- prometheus.Metric, d *prometheus.Desc, v interface{}) {
r.Errors++
- if r.Config.ReportErrors {
+ if r.ReportErrors {
ch <- prometheus.NewInvalidMetric(d, fmt.Errorf("error: %v", v))
}
}