fixes
This commit is contained in:
parent
f12d31c019
commit
3a84c97270
|
|
@ -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
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -18,22 +18,33 @@
|
|||
<influxdb disable="false">
|
||||
<interval>30s</interval>
|
||||
<url>http://127.0.0.1:8086</url>
|
||||
<user>unifi</user>
|
||||
<pass>unifi</pass>
|
||||
<user>unifipoller</user>
|
||||
<pass>unifipoller</pass>
|
||||
<db>unifi</db>
|
||||
<verify_ssl>false</verify_ssl>
|
||||
</influxdb>
|
||||
|
||||
<unifi disable="false">
|
||||
<unifi dynamic="false">
|
||||
<default name="https://127.0.0.1:8443">
|
||||
<site>all</site>
|
||||
<user>unifipoller</user>
|
||||
<pass>unifipoller</pass>
|
||||
<url>https://127.0.0.1:8443</url>
|
||||
<verify_ssl>false</verify_ssl>
|
||||
<save_ids>false</save_ids>
|
||||
<save_sites>true</save_sites>
|
||||
</default>
|
||||
|
||||
<!-- Repeat this stanza to poll additional controllers. -->
|
||||
<controller name="">
|
||||
<site>all</site>
|
||||
<user>influx</user>
|
||||
<pass></pass>
|
||||
<user>unifipoller</user>
|
||||
<pass>unifipoller</pass>
|
||||
<url>https://127.0.0.1:8443</url>
|
||||
<verify_ssl>false</verify_ssl>
|
||||
<save_ids>false</save_ids>
|
||||
<save_sites>true</save_sites>
|
||||
</controller>
|
||||
|
||||
</unifi>
|
||||
</poller>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue