diff --git a/integrations/inputunifi/.metadata.sh b/integrations/inputunifi/.metadata.sh
index 4b7241e8..a6c7f5a4 100755
--- a/integrations/inputunifi/.metadata.sh
+++ b/integrations/inputunifi/.metadata.sh
@@ -35,7 +35,7 @@ URL="${SOURCE_URL}"
# This parameter is passed in as -X to go build. Used to override the Version variable in a package.
# This makes a path like github.com/user/hello-world/helloworld.Version=1.3.3
# Name the Version-containing library the same as the github repo, without dashes.
-VERSION_PATH="${IMPORT_PATH}/$(echo ${BINARY} | tr -d -- -).Version"
+VERSION_PATH="${IMPORT_PATH}/poller.Version"
# Dynamic. Recommend not changing.
VVERSION=$(git describe --abbrev=0 --tags $(git rev-list --tags --max-count=1))
diff --git a/integrations/inputunifi/Gopkg.lock b/integrations/inputunifi/Gopkg.lock
index ebe365dd..9bc268e6 100644
--- a/integrations/inputunifi/Gopkg.lock
+++ b/integrations/inputunifi/Gopkg.lock
@@ -9,6 +9,22 @@
revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005"
version = "v0.3.1"
+[[projects]]
+ digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
+ name = "github.com/beorn7/perks"
+ packages = ["quantile"]
+ pruneopts = "UT"
+ revision = "37c8de3658fcb183f997c4e13e8337516ab753e6"
+ version = "v1.0.1"
+
+[[projects]]
+ digest = "1:573ca21d3669500ff845bdebee890eb7fc7f0f50c59f2132f2a0c6b03d85086a"
+ name = "github.com/golang/protobuf"
+ packages = ["proto"]
+ pruneopts = "UT"
+ revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
+ version = "v1.3.2"
+
[[projects]]
branch = "master"
digest = "1:50708c8fc92aec981df5c446581cf9f90ba9e2a5692118e0ce75d4534aaa14a2"
@@ -21,6 +37,58 @@
pruneopts = "UT"
revision = "fc22c7df067eefd070157f157893fbce961d6359"
+[[projects]]
+ digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc"
+ name = "github.com/matttproud/golang_protobuf_extensions"
+ packages = ["pbutil"]
+ pruneopts = "UT"
+ revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
+ version = "v1.0.1"
+
+[[projects]]
+ digest = "1:eb04f69c8991e52eff33c428bd729e04208bf03235be88e4df0d88497c6861b9"
+ name = "github.com/prometheus/client_golang"
+ packages = [
+ "prometheus",
+ "prometheus/internal",
+ "prometheus/promhttp",
+ ]
+ pruneopts = "UT"
+ revision = "170205fb58decfd011f1550d4cfb737230d7ae4f"
+ version = "v1.1.0"
+
+[[projects]]
+ branch = "master"
+ digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4"
+ name = "github.com/prometheus/client_model"
+ packages = ["go"]
+ pruneopts = "UT"
+ revision = "14fe0d1b01d4d5fc031dd4bec1823bd3ebbe8016"
+
+[[projects]]
+ digest = "1:f119e3205d3a1f0f19dbd7038eb37528e2c6f0933269dc344e305951fb87d632"
+ name = "github.com/prometheus/common"
+ packages = [
+ "expfmt",
+ "internal/bitbucket.org/ww/goautoneg",
+ "model",
+ ]
+ pruneopts = "UT"
+ revision = "287d3e634a1e550c9e463dd7e5a75a422c614505"
+ version = "v0.7.0"
+
+[[projects]]
+ digest = "1:ec0ff4bd619a67065e34d6477711ed0117e335f99059a4c508e0fe21cfe7b304"
+ name = "github.com/prometheus/procfs"
+ packages = [
+ ".",
+ "internal/fs",
+ "internal/util",
+ ]
+ pruneopts = "UT"
+ revision = "6d489fc7f1d9cd890a250f3ea3431b1744b9623f"
+ version = "v0.0.8"
+
[[projects]]
digest = "1:524b71991fc7d9246cc7dc2d9e0886ccb97648091c63e30eef619e6862c955dd"
name = "github.com/spf13/pflag"
@@ -30,20 +98,28 @@
version = "v1.0.5"
[[projects]]
- digest = "1:e74d5f03545d51228b9539aaffc5eb8a692fcb22f38fa60253437b1fc063a73b"
+ branch = "master"
+ digest = "1:68fe4216878f16dd6ef33413365fbbe8d2eb781177c7adab874cfc752ce96a7e"
+ name = "golang.org/x/sys"
+ packages = ["windows"]
+ pruneopts = "UT"
+ revision = "6d18c012aee9febd81bbf9806760c8c4480e870d"
+
+[[projects]]
+ digest = "1:0d815236933294be05901215b375de3a7c990abcb769069eccca32a825a15862"
name = "golift.io/unifi"
packages = ["."]
pruneopts = "UT"
- revision = "2bdbccee871d4f36a4e1efa3463386ae70095033"
- version = "v4.1.3"
+ revision = "68fa5c1f82da4567d8c192c52397984551cb08b9"
+ version = "v4.1.4"
[[projects]]
- digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
+ digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
- revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
- version = "v2.2.2"
+ revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce"
+ version = "v2.2.7"
[solve-meta]
analyzer-name = "dep"
@@ -51,6 +127,8 @@
input-imports = [
"github.com/BurntSushi/toml",
"github.com/influxdata/influxdb1-client/v2",
+ "github.com/prometheus/client_golang/prometheus",
+ "github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/spf13/pflag",
"golift.io/unifi",
"gopkg.in/yaml.v2",
diff --git a/integrations/inputunifi/examples/MANUAL.md b/integrations/inputunifi/examples/MANUAL.md
index c9ecd0de..4648bc4c 100644
--- a/integrations/inputunifi/examples/MANUAL.md
+++ b/integrations/inputunifi/examples/MANUAL.md
@@ -75,7 +75,8 @@ is provided so the application can be easily adapted to any environment.
interval default: 30s
How often to poll the controller for updated client and device data.
- The UniFi Controller only updates traffic stats about every 30 seconds.
+ The UniFi Controller only updates traffic stats about every 30-60 seconds.
+ Only works if "mode" (below) is "influx" - other modes do not use interval.
debug default: false
This turns on time stamps and line numbers in logs, outputs a few extra
@@ -89,10 +90,10 @@ is provided so the application can be easily adapted to any environment.
mode default: "influx"
* Value: influx
This default mode runs this application as a daemon. It will poll
- the controller at the configured interval. Providing an invalid value
- will run in this default mode.
+ the controller at the configured interval and report measurements to
+ InfluxDB. Providing an invalid value will run in this default mode.
- * Value: influxlambda - (the only other available option right now)
+ * Value: influxlambda
Setting this value will invoke a run-once mode where the application
immediately polls the controller and reports the metrics to InfluxDB.
Then it exits. This mode is useful in an AWS Lambda or a crontab where
@@ -101,6 +102,16 @@ is provided so the application can be easily adapted to any environment.
This mode can also be combined with a "test database" in InfluxDB to
give yourself a "test config file" you may run ad-hoc to test changes.
+ * Value: prometheus
+ In this mode the application opens an http interface and exports the
+ measurements at /metrics for collection by prometheus. Enabling this
+ mode disables InfluxDB usage entirely.
+
+ http_listen default: 0.0.0.0:9130
+ This option controls the IP and port the http listener uses when the
+ mode is set to prometheus. This setting has no effect when other modes
+ are in use. Metrics become available at the /metrics URI.
+
influx_url default: http://127.0.0.1:8086
This is the URL where the Influx web server is available.
diff --git a/integrations/inputunifi/examples/up.conf.example b/integrations/inputunifi/examples/up.conf.example
index ae6497e7..0f1b3041 100644
--- a/integrations/inputunifi/examples/up.conf.example
+++ b/integrations/inputunifi/examples/up.conf.example
@@ -8,7 +8,8 @@
sites = ["all"]
# The UniFi Controller only updates traffic stats about every 30 seconds.
-# Setting this to something lower may lead to "zeros" in your data. You've been warned.
+# Setting this to something lower may lead to "zeros" in your data.
+# If you're getting zeros now, set this to "1m"
interval = "30s"
# Turns on line numbers, microsecond logging, and a per-device log.
@@ -24,13 +25,22 @@ quiet = false
# an invalid mode will also result in "influx". In this default mode the application
# runs as a daemon and polls the controller at the configured interval.
#
-# There is only one other option at this time: "influxlambda"
+# There are two other options at this time: "influxlambda" and "prometheus"
#
-# Lambda mode makes the application exit after collecting and reporting metrics
+# Mode "influxlambda" makes the application exit after collecting and reporting metrics
# to InfluxDB one time. This mode requires an external process like an AWS Lambda
# or a simple crontab to keep the timings accurate on UniFi Poller run intervals.
+#
+# Mode "prometheus" opens an HTTP server on port 9130 and exports the metrics at
+# /metrics for polling collection by a prometheus server. This disables influxdb.
+# IMPORTANT: The prometheus mode is still beta.
+# Please help us test and provide your feedback on the github repo!
mode = "influx"
+# 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"
+
# InfluxDB does not require auth by default, so the user/password are probably unimportant.
influx_url = "http://127.0.0.1:8086"
influx_user = "unifi"
diff --git a/integrations/inputunifi/examples/up.json.example b/integrations/inputunifi/examples/up.json.example
index e59ec7c3..e9961505 100644
--- a/integrations/inputunifi/examples/up.json.example
+++ b/integrations/inputunifi/examples/up.json.example
@@ -4,6 +4,7 @@
"debug": false,
"quiet": false,
"mode": "influx",
+ "http_listen": "0.0.0.0:9130",
"influx_url": "http://127.0.0.1:8086",
"influx_user": "unifi",
"influx_pass": "unifi",
diff --git a/integrations/inputunifi/examples/up.xml.example b/integrations/inputunifi/examples/up.xml.example
index 87f1cac5..3022948b 100644
--- a/integrations/inputunifi/examples/up.xml.example
+++ b/integrations/inputunifi/examples/up.xml.example
@@ -18,7 +18,8 @@
30s
@@ -40,14 +41,25 @@
# an invalid mode will also result in "influx". In this default mode the application
# runs as a daemon and polls the controller at the configured interval.
#
- # There is only one other option at this time: "influxlambda"
+ # There are two other options at this time: "influxlambda" and "prometheus"
#
- # Lambda mode makes the application exit after collecting and reporting metrics
+ # Mode "influxlambda" makes the application exit after collecting and reporting metrics
# to InfluxDB one time. This mode requires an external process like an AWS Lambda
# or a simple crontab to keep the timings accurate on UniFi Poller run intervals.
+ #
+ # Mode "prometheus" opens an HTTP server on port 9130 and exports the metrics at
+ # /metrics for polling collection by a prometheus server. This disables influxdb.
+ # IMPORTANT: The prometheus mode is still beta.
+ # Please help us test and provide your feedback on the github repo!
-->
influx
+
+ 0.0.0.0:9130
+
diff --git a/integrations/inputunifi/examples/up.yaml.example b/integrations/inputunifi/examples/up.yaml.example
index 1042c4e1..4dfe2e67 100644
--- a/integrations/inputunifi/examples/up.yaml.example
+++ b/integrations/inputunifi/examples/up.yaml.example
@@ -9,7 +9,9 @@ sites:
- all
# The UniFi Controller only updates traffic stats about every 30 seconds.
-# Setting this to something lower may lead to "zeros" in your data. You've been warned.
+# Setting this to something lower may lead to "zeros" in your data.
+# If you're getting zeros now, set this to "1m"
+# Only has effect if "mode" (below) is "influx" - other modes do not use interval.
interval: "30s"
# Turns on line numbers, microsecond logging, and a per-device log.
@@ -25,13 +27,22 @@ quiet: false
# an invalid mode will also result in "influx". In this default mode the application
# runs as a daemon and polls the controller at the configured interval.
#
-# There is only one other option at this time: "influxlambda"
+# There are two other options at this time: "influxlambda" and "prometheus"
#
-# Lambda mode makes the application exit after collecting and reporting metrics
+# Mode "influxlambda" makes the application exit after collecting and reporting metrics
# to InfluxDB one time. This mode requires an external process like an AWS Lambda
# or a simple crontab to keep the timings accurate on UniFi Poller run intervals.
+#
+# Mode "prometheus" opens an HTTP server on port 9130 and exports the metrics at
+# /metrics for polling collection by a prometheus server. This disables influxdb.
+# IMPORTANT: The prometheus mode is still beta.
+# Please help us test and provide your feedback on the github repo!
mode: "influx"
+# 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"
+
# InfluxDB does not require auth by default, so the user/password are probably unimportant.
influx_url: "http://127.0.0.1:8086"
influx_user: "unifi"
diff --git a/integrations/inputunifi/influxunifi/README.md b/integrations/inputunifi/influxunifi/README.md
new file mode 100644
index 00000000..cbc606c0
--- /dev/null
+++ b/integrations/inputunifi/influxunifi/README.md
@@ -0,0 +1,4 @@
+# influx
+
+This package provides the methods to turn UniFi measurements into influx
+data-points with appropriate tags and fields.
diff --git a/integrations/inputunifi/unifipoller/influx_clients.go b/integrations/inputunifi/influxunifi/clients.go
similarity index 99%
rename from integrations/inputunifi/unifipoller/influx_clients.go
rename to integrations/inputunifi/influxunifi/clients.go
index 3ecb8ce0..5a57331b 100644
--- a/integrations/inputunifi/unifipoller/influx_clients.go
+++ b/integrations/inputunifi/influxunifi/clients.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
"time"
diff --git a/integrations/inputunifi/unifipoller/influx_ids.go b/integrations/inputunifi/influxunifi/ids.go
similarity index 98%
rename from integrations/inputunifi/unifipoller/influx_ids.go
rename to integrations/inputunifi/influxunifi/ids.go
index 2e78cc28..bf444f5f 100644
--- a/integrations/inputunifi/unifipoller/influx_ids.go
+++ b/integrations/inputunifi/influxunifi/ids.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
influx "github.com/influxdata/influxdb1-client/v2"
diff --git a/integrations/inputunifi/influxunifi/metrics.go b/integrations/inputunifi/influxunifi/metrics.go
new file mode 100644
index 00000000..3919c8c7
--- /dev/null
+++ b/integrations/inputunifi/influxunifi/metrics.go
@@ -0,0 +1,69 @@
+// Package influx provides the methods to turn UniFi measurements into influx
+// data-points with appropriate tags and fields.
+package influxunifi
+
+import (
+ "github.com/davidnewhall/unifi-poller/metrics"
+ client "github.com/influxdata/influxdb1-client/v2"
+)
+
+// Metrics contains all the data from the controller and an influx endpoint to send it to.
+type Metrics struct {
+ *metrics.Metrics
+ client.BatchPoints
+}
+
+// ProcessPoints batches all device and client data into influxdb data points.
+// Call this after you've collected all the data you care about.
+// This function is sorta weird and returns a slice of errors. The reasoning is
+// that some points may process while others fail, so we attempt to process them
+// all. This is (usually) run in a loop, so we can't really exit on error,
+// we just log the errors and tally them on a counter. In reality, this never
+// returns any errors because we control the data going in; cool right? But we
+// still check&log it in case the data going is skewed up and causes errors!
+func (m *Metrics) ProcessPoints() []error {
+ errs := []error{}
+ processPoints := func(m *Metrics, p []*client.Point, err error) {
+ switch {
+ case err != nil:
+ errs = append(errs, err)
+ case p == nil:
+ default:
+ m.AddPoints(p)
+ }
+ }
+
+ for _, asset := range m.Sites {
+ pts, err := SitePoints(asset, m.TS)
+ processPoints(m, pts, err)
+ }
+ for _, asset := range m.Clients {
+ pts, err := ClientPoints(asset, m.TS)
+ processPoints(m, pts, err)
+ }
+ for _, asset := range m.IDSList {
+ pts, err := IDSPoints(asset) // no m.TS.
+ processPoints(m, pts, err)
+ }
+
+ if m.Devices == nil {
+ return errs
+ }
+ for _, asset := range m.Devices.UAPs {
+ pts, err := UAPPoints(asset, m.TS)
+ processPoints(m, pts, err)
+ }
+ for _, asset := range m.Devices.USGs {
+ pts, err := USGPoints(asset, m.TS)
+ processPoints(m, pts, err)
+ }
+ for _, asset := range m.Devices.USWs {
+ pts, err := USWPoints(asset, m.TS)
+ processPoints(m, pts, err)
+ }
+ for _, asset := range m.Devices.UDMs {
+ pts, err := UDMPoints(asset, m.TS)
+ processPoints(m, pts, err)
+ }
+ return errs
+}
diff --git a/integrations/inputunifi/unifipoller/influx_site.go b/integrations/inputunifi/influxunifi/site.go
similarity index 99%
rename from integrations/inputunifi/unifipoller/influx_site.go
rename to integrations/inputunifi/influxunifi/site.go
index 5cd45964..0bd647ac 100644
--- a/integrations/inputunifi/unifipoller/influx_site.go
+++ b/integrations/inputunifi/influxunifi/site.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
"strings"
diff --git a/integrations/inputunifi/unifipoller/influx_uap.go b/integrations/inputunifi/influxunifi/uap.go
similarity index 99%
rename from integrations/inputunifi/unifipoller/influx_uap.go
rename to integrations/inputunifi/influxunifi/uap.go
index 968682f6..1744b0c0 100644
--- a/integrations/inputunifi/unifipoller/influx_uap.go
+++ b/integrations/inputunifi/influxunifi/uap.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
"time"
diff --git a/integrations/inputunifi/unifipoller/influx_udm.go b/integrations/inputunifi/influxunifi/udm.go
similarity index 99%
rename from integrations/inputunifi/unifipoller/influx_udm.go
rename to integrations/inputunifi/influxunifi/udm.go
index 2062c32c..fa968115 100644
--- a/integrations/inputunifi/unifipoller/influx_udm.go
+++ b/integrations/inputunifi/influxunifi/udm.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
"time"
diff --git a/integrations/inputunifi/unifipoller/influx_usg.go b/integrations/inputunifi/influxunifi/usg.go
similarity index 99%
rename from integrations/inputunifi/unifipoller/influx_usg.go
rename to integrations/inputunifi/influxunifi/usg.go
index 4549247a..606011c0 100644
--- a/integrations/inputunifi/unifipoller/influx_usg.go
+++ b/integrations/inputunifi/influxunifi/usg.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
"strings"
diff --git a/integrations/inputunifi/unifipoller/influx_usw.go b/integrations/inputunifi/influxunifi/usw.go
similarity index 99%
rename from integrations/inputunifi/unifipoller/influx_usw.go
rename to integrations/inputunifi/influxunifi/usw.go
index f4c743f1..225a4340 100644
--- a/integrations/inputunifi/unifipoller/influx_usw.go
+++ b/integrations/inputunifi/influxunifi/usw.go
@@ -1,4 +1,4 @@
-package unifipoller
+package influxunifi
import (
"time"
diff --git a/integrations/inputunifi/init/docker/docker-compose.env.example b/integrations/inputunifi/init/docker/docker-compose.env.example
index 6c48b3cf..1222c0c8 100644
--- a/integrations/inputunifi/init/docker/docker-compose.env.example
+++ b/integrations/inputunifi/init/docker/docker-compose.env.example
@@ -9,4 +9,4 @@ UP_BRANCH=stable
UP_UNIFI_USER=influx
UP_UNIFI_PASS=
UP_UNIFI_URL=https://127.0.0.1:8443
-UP_DEBUG_MODE=false
+UP_DEBUG=false
diff --git a/integrations/inputunifi/init/docker/docker-compose.yml b/integrations/inputunifi/init/docker/docker-compose.yml
index f9f6a77e..fcdbdddf 100644
--- a/integrations/inputunifi/init/docker/docker-compose.yml
+++ b/integrations/inputunifi/init/docker/docker-compose.yml
@@ -48,7 +48,7 @@ services:
- UP_UNIFI_USER=${UP_UNIFI_USER}
- UP_UNIFI_PASS=${UP_UNIFI_PASS}
- UP_UNIFI_URL=${UP_UNIFI_URL}
- - UP_DEBUG_MODE=${UP_DEBUG_MODE}
+ - UP_DEBUG=${UP_DEBUG}
volumes:
influxdb-storage:
chronograf-storage:
diff --git a/integrations/inputunifi/main.go b/integrations/inputunifi/main.go
index 768d4af6..f98e65c4 100644
--- a/integrations/inputunifi/main.go
+++ b/integrations/inputunifi/main.go
@@ -3,12 +3,12 @@ package main
import (
"log"
- "github.com/davidnewhall/unifi-poller/unifipoller"
+ "github.com/davidnewhall/unifi-poller/poller"
)
// Keep it simple.
func main() {
- if err := unifipoller.Start(); err != nil {
+ if err := poller.New().Start(); err != nil {
log.Fatalln("[ERROR]", err)
}
}
diff --git a/integrations/inputunifi/metrics/metrics.go b/integrations/inputunifi/metrics/metrics.go
new file mode 100644
index 00000000..2d38f54e
--- /dev/null
+++ b/integrations/inputunifi/metrics/metrics.go
@@ -0,0 +1,16 @@
+package metrics
+
+import (
+ "time"
+
+ "golift.io/unifi"
+)
+
+// Metrics is a type shared by the exporting and reporting packages.
+type Metrics struct {
+ TS time.Time
+ unifi.Sites
+ unifi.IDSList
+ unifi.Clients
+ *unifi.Devices
+}
diff --git a/integrations/inputunifi/unifipoller/build_macos.go b/integrations/inputunifi/poller/build_macos.go
similarity index 88%
rename from integrations/inputunifi/unifipoller/build_macos.go
rename to integrations/inputunifi/poller/build_macos.go
index d3eb3f50..1ab32471 100644
--- a/integrations/inputunifi/unifipoller/build_macos.go
+++ b/integrations/inputunifi/poller/build_macos.go
@@ -1,6 +1,6 @@
// +build darwin
-package unifipoller
+package poller
// DefaultConfFile is where to find config is --config is not prvided.
const DefaultConfFile = "/usr/local/etc/unifi-poller/up.conf"
diff --git a/integrations/inputunifi/unifipoller/build_unix.go b/integrations/inputunifi/poller/build_unix.go
similarity index 88%
rename from integrations/inputunifi/unifipoller/build_unix.go
rename to integrations/inputunifi/poller/build_unix.go
index 5fd6bdca..c1001ac9 100644
--- a/integrations/inputunifi/unifipoller/build_unix.go
+++ b/integrations/inputunifi/poller/build_unix.go
@@ -1,6 +1,6 @@
// +build !windows,!darwin
-package unifipoller
+package poller
// DefaultConfFile is where to find config is --config is not prvided.
const DefaultConfFile = "/etc/unifi-poller/up.conf"
diff --git a/integrations/inputunifi/unifipoller/build_windows.go b/integrations/inputunifi/poller/build_windows.go
similarity index 88%
rename from integrations/inputunifi/unifipoller/build_windows.go
rename to integrations/inputunifi/poller/build_windows.go
index 529a6570..5c31504f 100644
--- a/integrations/inputunifi/unifipoller/build_windows.go
+++ b/integrations/inputunifi/poller/build_windows.go
@@ -1,6 +1,6 @@
// +build windows
-package unifipoller
+package poller
// DefaultConfFile is where to find config is --config is not prvided.
const DefaultConfFile = `C:\ProgramData\unifi-poller\up.conf`
diff --git a/integrations/inputunifi/unifipoller/config.go b/integrations/inputunifi/poller/config.go
similarity index 78%
rename from integrations/inputunifi/unifipoller/config.go
rename to integrations/inputunifi/poller/config.go
index 56516dda..743d3996 100644
--- a/integrations/inputunifi/unifipoller/config.go
+++ b/integrations/inputunifi/poller/config.go
@@ -1,4 +1,4 @@
-package unifipoller
+package poller
import (
"encoding/json"
@@ -24,6 +24,7 @@ var Version = "development"
const (
// App defaults in case they're missing from the config.
+ appName = "unifi-poller"
defaultInterval = 30 * time.Second
defaultInfluxDB = "unifi"
defaultInfluxUser = "unifi"
@@ -31,6 +32,7 @@ const (
defaultInfluxURL = "http://127.0.0.1:8086"
defaultUnifiUser = "influx"
defaultUnifiURL = "https://127.0.0.1:8443"
+ defaultHTTPListen = "0.0.0.0:9130"
)
// ENVConfigPrefix is the prefix appended to an env variable tag
@@ -55,36 +57,28 @@ type Flag struct {
*pflag.FlagSet
}
-// Metrics contains all the data from the controller and an influx endpoint to send it to.
-type Metrics struct {
- TS time.Time
- unifi.Sites
- unifi.IDSList
- unifi.Clients
- *unifi.Devices
- influx.BatchPoints
-}
-
// Config represents the data needed to poll a controller and report to influxdb.
// This is all of the data stored in the config file.
// Any with explicit defaults have omitempty on json and toml tags.
type Config struct {
- Interval Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval" env:"POLLING_INTERVAL"`
- Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug" env:"DEBUG_MODE"`
- Quiet bool `json:"quiet,omitempty" toml:"quiet,omitempty" xml:"quiet" yaml:"quiet" env:"QUIET_MODE"`
- VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl" env:"VERIFY_SSL"`
- CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids" env:"COLLECT_IDS"`
- ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate" env:"REAUTHENTICATE"`
- InfxBadSSL bool `json:"influx_insecure_ssl" toml:"influx_insecure_ssl" xml:"influx_insecure_ssl" yaml:"influx_insecure_ssl" env:"INFLUX_INSECURE_SSL"`
- Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode" env:"POLLING_MODE"`
- InfluxURL string `json:"influx_url,omitempty" toml:"influx_url,omitempty" xml:"influx_url" yaml:"influx_url" env:"INFLUX_URL"`
- InfluxUser string `json:"influx_user,omitempty" toml:"influx_user,omitempty" xml:"influx_user" yaml:"influx_user" env:"INFLUX_USER"`
- InfluxPass string `json:"influx_pass,omitempty" toml:"influx_pass,omitempty" xml:"influx_pass" yaml:"influx_pass" env:"INFLUX_PASS"`
- InfluxDB string `json:"influx_db,omitempty" toml:"influx_db,omitempty" xml:"influx_db" yaml:"influx_db" env:"INFLUX_DB"`
- UnifiUser string `json:"unifi_user,omitempty" toml:"unifi_user,omitempty" xml:"unifi_user" yaml:"unifi_user" env:"UNIFI_USER"`
- UnifiPass string `json:"unifi_pass,omitempty" toml:"unifi_pass,omitempty" xml:"unifi_pass" yaml:"unifi_pass" env:"UNIFI_PASS"`
- UnifiBase string `json:"unifi_url,omitempty" toml:"unifi_url,omitempty" xml:"unifi_url" yaml:"unifi_url" env:"UNIFI_URL"`
- Sites []string `json:"sites,omitempty" toml:"sites,omitempty" xml:"sites" yaml:"sites" env:"POLL_SITES"`
+ Interval Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
+ Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug"`
+ Quiet bool `json:"quiet,omitempty" toml:"quiet,omitempty" xml:"quiet" yaml:"quiet"`
+ VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
+ CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids"`
+ ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate"`
+ InfxBadSSL bool `json:"influx_insecure_ssl" toml:"influx_insecure_ssl" xml:"influx_insecure_ssl" yaml:"influx_insecure_ssl"`
+ Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode"`
+ HTTPListen string `json:"http_listen" toml:"http_listen" xml:"http_listen" yaml:"http_listen"`
+ Namespace string `json:"namespace" toml:"namespace" xml:"namespace" yaml:"namespace"`
+ InfluxURL string `json:"influx_url,omitempty" toml:"influx_url,omitempty" xml:"influx_url" yaml:"influx_url"`
+ InfluxUser string `json:"influx_user,omitempty" toml:"influx_user,omitempty" xml:"influx_user" yaml:"influx_user"`
+ InfluxPass string `json:"influx_pass,omitempty" toml:"influx_pass,omitempty" xml:"influx_pass" yaml:"influx_pass"`
+ InfluxDB string `json:"influx_db,omitempty" toml:"influx_db,omitempty" xml:"influx_db" yaml:"influx_db"`
+ UnifiUser string `json:"unifi_user,omitempty" toml:"unifi_user,omitempty" xml:"unifi_user" yaml:"unifi_user"`
+ UnifiPass string `json:"unifi_pass,omitempty" toml:"unifi_pass,omitempty" xml:"unifi_pass" yaml:"unifi_pass"`
+ UnifiBase string `json:"unifi_url,omitempty" toml:"unifi_url,omitempty" xml:"unifi_url" yaml:"unifi_url"`
+ Sites []string `json:"sites,omitempty" toml:"sites,omitempty" xml:"sites" yaml:"sites"`
}
// Duration is used to UnmarshalTOML into a time.Duration value.
@@ -119,8 +113,8 @@ 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++ {
- tag := t.Field(i).Tag.Get("env") // Get the ENV variable name from "env" struct tag
- env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS.
+ tag := strings.ToUpper(t.Field(i).Tag.Get("json")) // Get the ENV variable name from capitalized "json" struct tag
+ env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS.
if tag == "" || env == "" {
continue // Skip if either are empty.
}
@@ -153,5 +147,6 @@ func (c *Config) ParseENV() error {
c.SetBool(val)
}
}
+
return nil
}
diff --git a/integrations/inputunifi/unifipoller/dumper.go b/integrations/inputunifi/poller/dumper.go
similarity index 98%
rename from integrations/inputunifi/unifipoller/dumper.go
rename to integrations/inputunifi/poller/dumper.go
index 01d821ef..da825485 100644
--- a/integrations/inputunifi/unifipoller/dumper.go
+++ b/integrations/inputunifi/poller/dumper.go
@@ -1,4 +1,4 @@
-package unifipoller
+package poller
import (
"fmt"
@@ -20,14 +20,17 @@ func (u *UnifiPoller) DumpJSONPayload() (err error) {
if err != nil {
return err
}
+
fmt.Fprintf(os.Stderr, "[INFO] Authenticated to UniFi Controller @ %v as user %v",
u.Config.UnifiBase, u.Config.UnifiUser)
if err := u.CheckSites(); err != nil {
return err
}
+
u.Unifi.ErrorLog = func(m string, v ...interface{}) {
fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...)
} // Log all errors to stderr.
+
switch sites, err := u.GetFilteredSites(); {
case err != nil:
return err
diff --git a/integrations/inputunifi/unifipoller/helpers.go b/integrations/inputunifi/poller/helpers.go
similarity index 75%
rename from integrations/inputunifi/unifipoller/helpers.go
rename to integrations/inputunifi/poller/helpers.go
index 8adf54be..71421b16 100644
--- a/integrations/inputunifi/unifipoller/helpers.go
+++ b/integrations/inputunifi/poller/helpers.go
@@ -1,4 +1,4 @@
-package unifipoller
+package poller
import (
"fmt"
@@ -6,12 +6,14 @@ import (
"strings"
)
+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(2, fmt.Sprintf("[ERROR] %v: %v", prefix, err))
+ _ = log.Output(callDepth, fmt.Sprintf("[ERROR] %v: %v", prefix, err))
}
}
@@ -28,18 +30,18 @@ func StringInSlice(str string, slice []string) bool {
// Logf prints a log entry if quiet is false.
func (u *UnifiPoller) Logf(m string, v ...interface{}) {
if !u.Config.Quiet {
- _ = log.Output(2, fmt.Sprintf("[INFO] "+m, v...))
+ _ = log.Output(callDepth, fmt.Sprintf("[INFO] "+m, v...))
}
}
// LogDebugf prints a debug log entry if debug is true and quite is false
func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) {
if u.Config.Debug && !u.Config.Quiet {
- _ = log.Output(2, fmt.Sprintf("[DEBUG] "+m, v...))
+ _ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...))
}
}
// LogErrorf prints an error log entry. This is used for external library logging.
func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) {
- _ = log.Output(2, fmt.Sprintf("[ERROR] "+m, v...))
+ _ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
}
diff --git a/integrations/inputunifi/poller/influx.go b/integrations/inputunifi/poller/influx.go
new file mode 100644
index 00000000..d428bdb8
--- /dev/null
+++ b/integrations/inputunifi/poller/influx.go
@@ -0,0 +1,80 @@
+package poller
+
+import (
+ "crypto/tls"
+ "fmt"
+
+ "github.com/davidnewhall/unifi-poller/influxunifi"
+ "github.com/davidnewhall/unifi-poller/metrics"
+ influx "github.com/influxdata/influxdb1-client/v2"
+)
+
+// GetInfluxDB returns an InfluxDB interface.
+func (u *UnifiPoller) GetInfluxDB() (err error) {
+ u.Influx, err = influx.NewHTTPClient(influx.HTTPConfig{
+ Addr: u.Config.InfluxURL,
+ Username: u.Config.InfluxUser,
+ Password: u.Config.InfluxPass,
+ TLSConfig: &tls.Config{InsecureSkipVerify: u.Config.InfxBadSSL},
+ })
+ 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
+}
+
+// CollectAndProcess collects measurements and then reports them to InfluxDB
+// Can be called once or in a ticker loop. This function and all the ones below
+// handle their own logging. An error is returned so the calling function may
+// determine if there was a read or write error and act on it. This is currently
+// called in two places in this library. One returns an error, one does not.
+func (u *UnifiPoller) CollectAndProcess() error {
+ metrics, err := u.CollectMetrics()
+ if err != nil {
+ return err
+ }
+ u.AugmentMetrics(metrics)
+ err = u.ReportMetrics(metrics)
+ u.LogError(err, "processing metrics")
+ return err
+}
+
+// ReportMetrics batches all the metrics and writes them to InfluxDB.
+// This creates an InfluxDB writer, and returns an error if the write fails.
+func (u *UnifiPoller) ReportMetrics(metrics *metrics.Metrics) error {
+ // Batch (and send) all the points.
+ m := &influxunifi.Metrics{Metrics: metrics}
+ // Make a new Influx Points Batcher.
+ var err error
+ m.BatchPoints, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.Config.InfluxDB})
+ if err != nil {
+ return fmt.Errorf("influx.NewBatchPoints: %v", err)
+ }
+ for _, err := range m.ProcessPoints() {
+ u.LogError(err, "influx.ProcessPoints")
+ }
+ if err = u.Influx.Write(m.BatchPoints); err != nil {
+ return fmt.Errorf("influxdb.Write(points): %v", err)
+ }
+ u.LogInfluxReport(m)
+ return nil
+}
+
+// LogInfluxReport writes a log message after exporting to influxdb.
+func (u *UnifiPoller) LogInfluxReport(m *influxunifi.Metrics) {
+ var fields, points int
+ for _, p := range m.Points() {
+ points++
+ i, _ := p.Fields()
+ fields += len(i)
+ }
+ idsMsg := ""
+ if u.Config.CollectIDS {
+ idsMsg = fmt.Sprintf("IDS Events: %d, ", len(m.IDSList))
+ }
+ u.Logf("UniFi Measurements Recorded. Sites: %d, Clients: %d, "+
+ "Wireless APs: %d, Gateways: %d, Switches: %d, %sPoints: %d, Fields: %d",
+ len(m.Sites), len(m.Clients), len(m.UAPs),
+ len(m.UDMs)+len(m.USGs), len(m.USWs), idsMsg, points, fields)
+}
diff --git a/integrations/inputunifi/poller/prometheus.go b/integrations/inputunifi/poller/prometheus.go
new file mode 100644
index 00000000..ae2daa63
--- /dev/null
+++ b/integrations/inputunifi/poller/prometheus.go
@@ -0,0 +1,69 @@
+package poller
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/davidnewhall/unifi-poller/metrics"
+ "github.com/davidnewhall/unifi-poller/promunifi"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+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)
+ http.Handle("/metrics", promhttp.Handler())
+ prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorCnfg{
+ Namespace: strings.Replace(u.Config.Namespace, "-", "", -1),
+ CollectFn: u.ExportMetrics,
+ LoggingFn: u.LogExportReport,
+ ReportErrors: true, // XXX: Does this need to be configurable?
+ }))
+ return http.ListenAndServe(u.Config.HTTPListen, nil)
+}
+
+// ExportMetrics updates the internal metrics provided via
+// HTTP at /metrics for prometheus collection.
+// This is run by Prometheus as CollectFn.
+func (u *UnifiPoller) ExportMetrics() (*metrics.Metrics, error) {
+ m, err := u.CollectMetrics()
+ 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")
+ return nil, err
+ }
+
+ if m, err = u.CollectMetrics(); err != nil {
+ u.LogErrorf("collecting metrics: %v", err)
+ return nil, err
+ }
+ }
+
+ u.AugmentMetrics(m)
+ return m, nil
+}
+
+// LogExportReport is called after prometheus exports metrics.
+// This is run by Prometheus as LoggingFn
+func (u *UnifiPoller) LogExportReport(report *promunifi.Report) {
+ m := report.Metrics
+ idsMsg := ""
+ if u.Config.CollectIDS {
+ idsMsg = fmt.Sprintf(", IDS Events: %d, ", len(m.IDSList))
+ }
+
+ u.Logf("UniFi Measurements Exported. Site: %d, Client: %d, "+
+ "UAP: %d, USG/UDM: %d, USW: %d%s, Descs: %d, "+
+ "Metrics: %d, Errs: %d, 0s: %d, Reqs/Total: %v / %v",
+ len(m.Sites), len(m.Clients), len(m.UAPs), len(m.UDMs)+len(m.USGs), len(m.USWs),
+ idsMsg, report.Descs, report.Total, report.Errors, report.Zeros,
+ report.Fetch.Round(time.Millisecond/oneDecimalPoint),
+ report.Elapsed.Round(time.Millisecond/oneDecimalPoint))
+}
diff --git a/integrations/inputunifi/poller/start.go b/integrations/inputunifi/poller/start.go
new file mode 100644
index 00000000..11d70228
--- /dev/null
+++ b/integrations/inputunifi/poller/start.go
@@ -0,0 +1,144 @@
+// Package poller provides the CLI interface to setup unifi-poller.
+package poller
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/spf13/pflag"
+)
+
+// New returns a new poller struct preloaded with default values.
+// No need to call this if you call Start.c
+func New() *UnifiPoller {
+ return &UnifiPoller{
+ Config: &Config{
+ InfluxURL: defaultInfluxURL,
+ InfluxUser: defaultInfluxUser,
+ InfluxPass: defaultInfluxPass,
+ InfluxDB: defaultInfluxDB,
+ UnifiUser: defaultUnifiUser,
+ UnifiPass: defaultUnifiUser,
+ UnifiBase: defaultUnifiURL,
+ Interval: Duration{defaultInterval},
+ Sites: []string{"all"},
+ HTTPListen: defaultHTTPListen,
+ Namespace: appName,
+ }, Flag: &Flag{},
+ }
+}
+
+// Start begins the application from a CLI.
+// Parses cli flags, parses config file, parses env vars, sets up logging, then:
+// - dumps a json payload OR - executes Run().
+func (u *UnifiPoller) Start() error {
+ log.SetFlags(log.LstdFlags)
+ u.Flag.Parse(os.Args[1:])
+
+ if u.Flag.ShowVer {
+ fmt.Printf("%s v%s\n", appName, Version)
+ return nil // don't run anything else w/ version request.
+ }
+
+ if u.Flag.DumpJSON == "" { // do not print this when dumping JSON.
+ u.Logf("Loading Configuration File: %s", u.Flag.ConfigFile)
+ }
+
+ // Parse config file.
+ if err := u.Config.ParseFile(u.Flag.ConfigFile); err != nil {
+ u.Flag.Usage()
+ return err
+ }
+
+ // Update Config with ENV variable overrides.
+ if err := u.Config.ParseENV(); err != nil {
+ return err
+ }
+
+ if u.Flag.DumpJSON != "" {
+ return u.DumpJSONPayload()
+ }
+
+ if u.Config.Debug {
+ log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
+ u.LogDebugf("Debug Logging Enabled")
+ }
+
+ log.Printf("[INFO] UniFi Poller v%v Starting Up! PID: %d", Version, os.Getpid())
+ return u.Run()
+}
+
+// Parse turns CLI arguments into data structures. Called by Start() on startup.
+func (f *Flag) Parse(args []string) {
+ f.FlagSet = pflag.NewFlagSet(appName, pflag.ExitOnError)
+ f.Usage = func() {
+ fmt.Printf("Usage: %s [--config=/path/to/up.conf] [--version]", appName)
+ f.PrintDefaults()
+ }
+
+ f.StringVarP(&f.DumpJSON, "dumpjson", "j", "",
+ "This debug option prints a json payload and exits. See man page for more info.")
+ f.StringVarP(&f.ConfigFile, "config", "c", DefaultConfFile, "Poller config file path.")
+ f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit.")
+ _ = f.FlagSet.Parse(args) // pflag.ExitOnError means this will never return error.
+}
+
+// Run picks a mode and executes the associated functions. This will do one of three things:
+// 1. Start the collector routine that polls unifi and reports to influx on an interval. (default)
+// 2. Run the collector one time and report the metrics to influxdb. (lambda)
+// 3. Start a web server and wait for Prometheus to poll the application for metrics.
+func (u *UnifiPoller) Run() error {
+ if err := u.GetUnifi(); err != nil {
+ return 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:
+ if err := u.GetInfluxDB(); err != nil {
+ return err
+ }
+ return u.PollController()
+
+ case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda":
+ if err := u.GetInfluxDB(); err != nil {
+ return err
+ }
+ u.LastCheck = time.Now()
+ return u.CollectAndProcess()
+
+ case "prometheus", "exporter":
+ return u.RunPrometheus()
+ }
+}
+
+// PollController runs forever, polling UniFi and pushing to InfluxDB
+// This is started by Run() after everything checks out.
+func (u *UnifiPoller) PollController() error {
+ 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()
+ for u.LastCheck = range ticker.C {
+ var err error
+ if u.Config.ReAuth {
+ u.LogDebugf("Re-authenticating to UniFi Controller")
+ // Some users need to re-auth every interval because the cookie times out.
+ if err = u.Unifi.Login(); err != nil {
+ u.LogError(err, "re-authenticating")
+ }
+ }
+ if err == nil {
+ // Only run this if the authentication procedure didn't return error.
+ _ = u.CollectAndProcess()
+ }
+ if u.errorCount > 0 {
+ return fmt.Errorf("too many errors, stopping poller")
+ }
+ }
+ return nil
+}
diff --git a/integrations/inputunifi/poller/unifi.go b/integrations/inputunifi/poller/unifi.go
new file mode 100644
index 00000000..54135923
--- /dev/null
+++ b/integrations/inputunifi/poller/unifi.go
@@ -0,0 +1,135 @@
+package poller
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/davidnewhall/unifi-poller/metrics"
+ "golift.io/unifi"
+)
+
+// GetUnifi returns a UniFi controller interface.
+func (u *UnifiPoller) GetUnifi() (err error) {
+ // Create an authenticated session to the Unifi Controller.
+ u.Unifi, err = unifi.NewUnifi(&unifi.Config{
+ User: u.Config.UnifiUser,
+ Pass: u.Config.UnifiPass,
+ URL: u.Config.UnifiBase,
+ VerifySSL: u.Config.VerifySSL,
+ ErrorLog: u.LogErrorf, // Log all errors.
+ DebugLog: u.LogDebugf, // Log debug messages.
+ })
+ if err != nil {
+ return fmt.Errorf("unifi controller: %v", err)
+ }
+ u.LogDebugf("Authenticated with controller successfully")
+
+ return u.CheckSites()
+}
+
+// CheckSites makes sure the list of provided sites exists on the controller.
+// This does not run in Lambda (run-once) mode.
+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 {
+ if s == site.Name {
+ continue 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
+ // Get the sites we care about.
+ m.Sites, err = u.GetFilteredSites()
+ u.LogError(err, "unifi.GetSites()")
+ if u.Config.CollectIDS {
+ m.IDSList, err = u.Unifi.GetIDS(m.Sites, time.Now().Add(u.Config.Interval.Duration), time.Now())
+ u.LogError(err, "unifi.GetIDS()")
+ }
+ // 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
+}
+
+// AugmentMetrics is our middleware layer between collecting metrics and writing them.
+// This is where we can manipuate the returned data or make arbitrary decisions.
+// This function currently adds parent device names to client metrics.
+func (u *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]
+ metrics.Clients[i].ApName = devices[c.ApMac]
+ metrics.Clients[i].GwName = devices[c.GwMac]
+ metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
+ }
+}
+
+// GetFilteredSites returns a list of sites to fetch data for.
+// Omits requested but unconfigured sites. Grabs the full list from the
+// controller and returns the sites provided in the config file.
+func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) {
+ 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) {
+ sites[i] = s
+ i++
+ }
+ }
+ return sites[:i], nil
+}
diff --git a/integrations/inputunifi/promunifi/README.md b/integrations/inputunifi/promunifi/README.md
new file mode 100644
index 00000000..d0ca905d
--- /dev/null
+++ b/integrations/inputunifi/promunifi/README.md
@@ -0,0 +1,4 @@
+# prometheus
+
+This package provides the interface to turn UniFi measurements into prometheus
+exported metrics. Requires the poller package for actual UniFi data collection.
diff --git a/integrations/inputunifi/promunifi/clients.go b/integrations/inputunifi/promunifi/clients.go
new file mode 100644
index 00000000..ac12bdf8
--- /dev/null
+++ b/integrations/inputunifi/promunifi/clients.go
@@ -0,0 +1,129 @@
+package promunifi
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+type uclient struct {
+ Anomalies *prometheus.Desc
+ BytesR *prometheus.Desc
+ CCQ *prometheus.Desc
+ Satisfaction *prometheus.Desc
+ Noise *prometheus.Desc
+ RoamCount *prometheus.Desc
+ RSSI *prometheus.Desc
+ RxBytes *prometheus.Desc
+ RxBytesR *prometheus.Desc
+ RxPackets *prometheus.Desc
+ RxRate *prometheus.Desc
+ Signal *prometheus.Desc
+ TxBytes *prometheus.Desc
+ TxBytesR *prometheus.Desc
+ TxPackets *prometheus.Desc
+ TxRetries *prometheus.Desc
+ TxPower *prometheus.Desc
+ TxRate *prometheus.Desc
+ Uptime *prometheus.Desc
+ WifiTxAttempts *prometheus.Desc
+ WiredRxBytes *prometheus.Desc
+ WiredRxBytesR *prometheus.Desc
+ WiredRxPackets *prometheus.Desc
+ WiredTxBytes *prometheus.Desc
+ WiredTxBytesR *prometheus.Desc
+ WiredTxPackets *prometheus.Desc
+ DpiStatsApp *prometheus.Desc
+ DpiStatsCat *prometheus.Desc
+ DpiStatsRxBytes *prometheus.Desc
+ DpiStatsRxPackets *prometheus.Desc
+ DpiStatsTxBytes *prometheus.Desc
+ DpiStatsTxPackets *prometheus.Desc
+}
+
+func descClient(ns string) *uclient {
+ labels := []string{"name", "mac", "site_name", "gw_name", "sw_name", "vlan", "ip", "oui", "network", "sw_port", "ap_name", "wired"}
+ labelW := append([]string{"radio_name", "radio", "radio_proto", "channel", "essid", "bssid", "radio_desc"}, labels...)
+ return &uclient{
+ Anomalies: prometheus.NewDesc(ns+"anomalies", "Client Anomalies", labelW, nil),
+ BytesR: prometheus.NewDesc(ns+"transfer_rate_bytes", "Client Data Rate", labelW, nil),
+ CCQ: prometheus.NewDesc(ns+"ccq_percent", "Client Connection Quality", labelW, nil),
+ Satisfaction: prometheus.NewDesc(ns+"satisfaction_percent", "Client Satisfaction", labelW, nil),
+ Noise: prometheus.NewDesc(ns+"noise_db", "Client AP Noise", labelW, nil),
+ RoamCount: prometheus.NewDesc(ns+"roam_count_total", "Client Roam Counter", labelW, nil),
+ RSSI: prometheus.NewDesc(ns+"rssi_db", "Client RSSI", labelW, nil),
+ RxBytes: prometheus.NewDesc(ns+"receive_bytes_total", "Client Receive Bytes", labels, nil),
+ RxBytesR: prometheus.NewDesc(ns+"receive_rate_bytes", "Client Receive Data Rate", labels, nil),
+ RxPackets: prometheus.NewDesc(ns+"receive_packets_total", "Client Receive Packets", labels, nil),
+ RxRate: prometheus.NewDesc(ns+"radio_receive_rate_bps", "Client Receive Rate", labelW, nil),
+ Signal: prometheus.NewDesc(ns+"radio_signal_db", "Client Signal Strength", labelW, nil),
+ TxBytes: prometheus.NewDesc(ns+"transmit_bytes_total", "Client Transmit Bytes", labels, nil),
+ TxBytesR: prometheus.NewDesc(ns+"transmit_rate_bytes", "Client Transmit Data Rate", labels, nil),
+ TxPackets: prometheus.NewDesc(ns+"transmit_packets_total", "Client Transmit Packets", labels, nil),
+ TxRetries: prometheus.NewDesc(ns+"transmit_retries_total", "Client Transmit Retries", labels, nil),
+ TxPower: prometheus.NewDesc(ns+"radio_transmit_power_dbm", "Client Transmit Power", labelW, nil),
+ TxRate: prometheus.NewDesc(ns+"radio_transmit_rate_bps", "Client Transmit Rate", labelW, nil),
+ WifiTxAttempts: prometheus.NewDesc(ns+"wifi_attempts_transmit_total", "Client Wifi Transmit Attempts", labelW, nil),
+ Uptime: prometheus.NewDesc(ns+"uptime_seconds", "Client Uptime", labelW, nil),
+ /* needs more "looking into"
+ DpiStatsApp: prometheus.NewDesc(ns+"dpi_stats_app", "Client DPI Stats App", labels, nil),
+ DpiStatsCat: prometheus.NewDesc(ns+"dpi_stats_cat", "Client DPI Stats Cat", labels, nil),
+ DpiStatsRxBytes: prometheus.NewDesc(ns+"dpi_stats_receive_bytes_total", "Client DPI Stats Receive Bytes", labels, nil),
+ DpiStatsRxPackets: prometheus.NewDesc(ns+"dpi_stats_receive_packets_total", "Client DPI Stats Receive Packets", labels, nil),
+ DpiStatsTxBytes: prometheus.NewDesc(ns+"dpi_stats_transmit_bytes_total", "Client DPI Stats Transmit Bytes", labels, nil),
+ DpiStatsTxPackets: prometheus.NewDesc(ns+"dpi_stats_transmit_packets_total", "Client DPI Stats Transmit Packets", labels, nil),
+ */
+ }
+}
+
+func (u *promUnifi) exportClient(r report, c *unifi.Client) {
+ labels := []string{c.Name, c.Mac, c.SiteName, c.GwName, c.SwName, c.Vlan.Txt, c.IP, c.Oui, c.Network, c.SwPort.Txt, c.ApName, ""}
+ labelW := append([]string{c.RadioName, c.Radio, c.RadioProto, c.Channel.Txt, c.Essid, c.Bssid, c.RadioDescription}, labels...)
+
+ if c.IsWired.Val {
+ labels[len(labels)-1] = "true"
+ labelW[len(labelW)-1] = "true"
+ r.send([]*metric{
+ {u.Client.RxBytes, prometheus.CounterValue, c.WiredRxBytes, labels},
+ {u.Client.RxBytesR, prometheus.GaugeValue, c.WiredRxBytesR, labels},
+ {u.Client.RxPackets, prometheus.CounterValue, c.WiredRxPackets, labels},
+ {u.Client.TxBytes, prometheus.CounterValue, c.WiredTxBytes, labels},
+ {u.Client.TxBytesR, prometheus.GaugeValue, c.WiredTxBytesR, labels},
+ {u.Client.TxPackets, prometheus.CounterValue, c.WiredTxPackets, labels},
+ })
+ } else {
+ labels[len(labels)-1] = "false"
+ labelW[len(labelW)-1] = "false"
+ r.send([]*metric{
+ {u.Client.Anomalies, prometheus.CounterValue, c.Anomalies, labelW},
+ {u.Client.CCQ, prometheus.GaugeValue, c.Ccq / 10, labelW},
+ {u.Client.Satisfaction, prometheus.GaugeValue, c.Satisfaction, labelW},
+ {u.Client.Noise, prometheus.GaugeValue, c.Noise, labelW},
+ {u.Client.RoamCount, prometheus.CounterValue, c.RoamCount, labelW},
+ {u.Client.RSSI, prometheus.GaugeValue, c.Rssi, labelW},
+ {u.Client.Signal, prometheus.GaugeValue, c.Signal, labelW},
+ {u.Client.TxPower, prometheus.GaugeValue, c.TxPower, labelW},
+ {u.Client.TxRate, prometheus.GaugeValue, c.TxRate * 1000, labelW},
+ {u.Client.WifiTxAttempts, prometheus.CounterValue, c.WifiTxAttempts, labelW},
+ {u.Client.RxRate, prometheus.GaugeValue, c.RxRate * 1000, labelW},
+ {u.Client.TxRetries, prometheus.CounterValue, c.TxRetries, labels},
+ {u.Client.TxBytes, prometheus.CounterValue, c.TxBytes, labels},
+ {u.Client.TxBytesR, prometheus.GaugeValue, c.TxBytesR, labels},
+ {u.Client.TxPackets, prometheus.CounterValue, c.TxPackets, labels},
+ {u.Client.RxBytes, prometheus.CounterValue, c.RxBytes, labels},
+ {u.Client.RxBytesR, prometheus.GaugeValue, c.RxBytesR, labels},
+ {u.Client.RxPackets, prometheus.CounterValue, c.RxPackets, labels},
+ {u.Client.BytesR, prometheus.GaugeValue, c.BytesR, labelW},
+ })
+ }
+ r.send([]*metric{
+ {u.Client.Uptime, prometheus.GaugeValue, c.Uptime, labelW},
+ /* needs more "looking into"
+ {u.Client.DpiStatsApp, prometheus.GaugeValue, c.DpiStats.App, labels},
+ {u.Client.DpiStatsCat, prometheus.GaugeValue, c.DpiStats.Cat, labels},
+ {u.Client.DpiStatsRxBytes, prometheus.CounterValue, c.DpiStats.RxBytes, labels},
+ {u.Client.DpiStatsRxPackets, prometheus.CounterValue, c.DpiStats.RxPackets, labels},
+ {u.Client.DpiStatsTxBytes, prometheus.CounterValue, c.DpiStats.TxBytes, labels},
+ {u.Client.DpiStatsTxPackets, prometheus.CounterValue, c.DpiStats.TxPackets, labels},
+ */
+ })
+}
diff --git a/integrations/inputunifi/promunifi/collector.go b/integrations/inputunifi/promunifi/collector.go
new file mode 100644
index 00000000..25cfd370
--- /dev/null
+++ b/integrations/inputunifi/promunifi/collector.go
@@ -0,0 +1,150 @@
+// Package promunifi provides the bridge between unifi metrics and prometheus.
+package promunifi
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/davidnewhall/unifi-poller/metrics"
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+// channel buffer, fits at least one batch.
+const buffer = 50
+
+// UnifiCollectorCnfg defines the data needed to collect and report UniFi Metrics.
+type UnifiCollectorCnfg struct {
+ // If non-empty, each of the collected metrics is prefixed by the
+ // provided string and an underscore ("_").
+ Namespace string
+ // If true, any error encountered during collection is reported as an
+ // invalid metric (see NewInvalidMetric). Otherwise, errors are ignored
+ // and the collected metrics will be incomplete. Possibly, no metrics
+ // will be collected at all.
+ ReportErrors bool
+ // This function is passed to the Collect() method. The Collect method runs
+ // this function to retrieve the latest UniFi measurements and export them.
+ CollectFn func() (*metrics.Metrics, error)
+ // Provide a logger function if you want to run a routine *after* prometheus checks in.
+ LoggingFn func(*Report)
+}
+
+type promUnifi struct {
+ Config UnifiCollectorCnfg
+ Client *uclient
+ Device *unifiDevice
+ UAP *uap
+ USG *usg
+ USW *usw
+ Site *site
+}
+
+type metric struct {
+ Desc *prometheus.Desc
+ ValueType prometheus.ValueType
+ Value interface{}
+ Labels []string
+}
+
+// Report is passed into LoggingFn to log the export metrics to stdout (outside this package).
+type Report struct {
+ Total int // Total count of metrics recorded.
+ Errors int // Total count of errors recording metrics.
+ Zeros int // Total count of metrics equal to zero.
+ Descs int // Total count of unique metrics descriptions.
+ Metrics *metrics.Metrics // Metrics collected and recorded.
+ Elapsed time.Duration // Duration elapsed collecting and exporting.
+ Fetch time.Duration // Duration elapsed making controller requests.
+ Start time.Time // Time collection began.
+ ch chan []*metric
+ wg sync.WaitGroup
+ cf UnifiCollectorCnfg
+}
+
+// NewUnifiCollector returns a prometheus collector that will export any available
+// UniFi metrics. You must provide a collection function in the opts.
+func NewUnifiCollector(opts UnifiCollectorCnfg) prometheus.Collector {
+ if opts.CollectFn == nil {
+ panic("nil collector function")
+ }
+ if opts.Namespace = strings.Trim(opts.Namespace, "_") + "_"; opts.Namespace == "_" {
+ opts.Namespace = ""
+ }
+ return &promUnifi{
+ Config: opts,
+ Client: descClient(opts.Namespace + "client_"),
+ Device: descDevice(opts.Namespace + "device_"), // stats for all device types.
+ UAP: descUAP(opts.Namespace + "device_"),
+ USG: descUSG(opts.Namespace + "device_"),
+ USW: descUSW(opts.Namespace + "device_"),
+ Site: descSite(opts.Namespace + "site_"),
+ }
+}
+
+// Describe satisfies the prometheus Collector. This returns all of the
+// metric descriptions that this packages produces.
+func (u *promUnifi) Describe(ch chan<- *prometheus.Desc) {
+ for _, f := range []interface{}{u.Client, u.Device, u.UAP, u.USG, u.USW, u.Site} {
+ v := reflect.Indirect(reflect.ValueOf(f))
+ // Loop each struct member and send it to the provided channel.
+ for i := 0; i < v.NumField(); i++ {
+ desc, ok := v.Field(i).Interface().(*prometheus.Desc)
+ if ok && desc != nil {
+ ch <- desc
+ }
+ }
+ }
+}
+
+// Collect satisfies the prometheus Collector. This runs the input method to get
+// the current metrics (from another package) then exports them for prometheus.
+func (u *promUnifi) Collect(ch chan<- prometheus.Metric) {
+ var err error
+ r := &Report{cf: u.Config, ch: make(chan []*metric, buffer), Start: time.Now()}
+ defer r.close()
+
+ if r.Metrics, err = r.cf.CollectFn(); err != nil {
+ r.error(ch, prometheus.NewInvalidDesc(fmt.Errorf("metric fetch failed")), err)
+ return
+ }
+ r.Fetch = time.Since(r.Start)
+ if r.Metrics.Devices == nil {
+ r.Metrics.Devices = &unifi.Devices{}
+ }
+
+ // Pass Report interface into our collecting and reporting methods.
+ go u.exportMetrics(r, ch)
+ for _, f := range []func(report){u.loopClients, u.loopSites, u.loopUAPs, u.loopUSWs, u.loopUSGs, u.loopUDMs} {
+ r.add()
+ go f(r) // in loops.go.
+ }
+}
+
+// This is closely tied to the method above with a sync.WaitGroup.
+// This method runs in a go routine and exits when the channel closes.
+func (u *promUnifi) exportMetrics(r report, ch chan<- prometheus.Metric) {
+ descs := make(map[*prometheus.Desc]bool) // used as a counter
+ defer r.report(descs)
+ for newMetrics := range r.channel() {
+ for _, m := range newMetrics {
+ descs[m.Desc] = true
+ switch v := m.Value.(type) {
+ case unifi.FlexInt:
+ ch <- r.export(m, v.Val)
+ case float64:
+ ch <- r.export(m, v)
+ case int64:
+ ch <- r.export(m, float64(v))
+ case int:
+ ch <- r.export(m, float64(v))
+ default:
+ r.error(ch, m.Desc, fmt.Sprintf("not a number: %v", m.Value))
+ }
+ }
+ r.done()
+ }
+}
diff --git a/integrations/inputunifi/promunifi/loops.go b/integrations/inputunifi/promunifi/loops.go
new file mode 100644
index 00000000..9e086ec3
--- /dev/null
+++ b/integrations/inputunifi/promunifi/loops.go
@@ -0,0 +1,47 @@
+package promunifi
+
+// This file contains all the loop methods for each device type, clients and sites.
+// Moved them here to consolate clutter from the other files. Also, if these change,
+// they usually all change at once since they're pretty much the same code.
+
+func (u *promUnifi) loopSites(r report) {
+ defer r.done()
+ for _, s := range r.metrics().Sites {
+ u.exportSite(r, s)
+ }
+}
+
+func (u *promUnifi) loopUAPs(r report) {
+ defer r.done()
+ for _, d := range r.metrics().UAPs {
+ u.exportUAP(r, d)
+ }
+}
+
+func (u *promUnifi) loopUDMs(r report) {
+ defer r.done()
+ for _, d := range r.metrics().UDMs {
+ u.exportUDM(r, d)
+ }
+}
+
+func (u *promUnifi) loopUSGs(r report) {
+ defer r.done()
+ for _, d := range r.metrics().USGs {
+ u.exportUSG(r, d)
+ }
+}
+
+func (u *promUnifi) loopUSWs(r report) {
+ defer r.done()
+ for _, d := range r.metrics().USWs {
+ u.exportUSW(r, d)
+ }
+}
+
+func (u *promUnifi) loopClients(r report) {
+ defer r.done()
+ for _, c := range r.metrics().Clients {
+ u.exportClient(r, c)
+ }
+}
diff --git a/integrations/inputunifi/promunifi/report.go b/integrations/inputunifi/promunifi/report.go
new file mode 100644
index 00000000..016d3db8
--- /dev/null
+++ b/integrations/inputunifi/promunifi/report.go
@@ -0,0 +1,78 @@
+package promunifi
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/davidnewhall/unifi-poller/metrics"
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+// This file contains the report interface.
+// This interface can be mocked and overridden for tests.
+
+// report is an internal interface used to "process metrics"
+type report interface {
+ add()
+ done()
+ send([]*metric)
+ metrics() *metrics.Metrics
+ channel() chan []*metric
+ report(descs map[*prometheus.Desc]bool)
+ export(m *metric, v float64) prometheus.Metric
+ error(ch chan<- prometheus.Metric, d *prometheus.Desc, v interface{})
+}
+
+// satisfy gomnd
+const one = 1
+
+func (r *Report) add() {
+ r.wg.Add(one)
+}
+
+func (r *Report) done() {
+ r.wg.Add(-one)
+}
+
+func (r *Report) send(m []*metric) {
+ r.wg.Add(one)
+ r.ch <- m
+}
+
+func (r *Report) metrics() *metrics.Metrics {
+ return r.Metrics
+}
+
+func (r *Report) channel() chan []*metric {
+ return r.ch
+}
+
+func (r *Report) report(descs map[*prometheus.Desc]bool) {
+ if r.cf.LoggingFn == nil {
+ return
+ }
+ r.Descs = len(descs)
+ r.cf.LoggingFn(r)
+}
+
+func (r *Report) export(m *metric, v float64) prometheus.Metric {
+ r.Total++
+ if v == 0 {
+ r.Zeros++
+ }
+ return prometheus.MustNewConstMetric(m.Desc, m.ValueType, v, m.Labels...)
+}
+
+func (r *Report) error(ch chan<- prometheus.Metric, d *prometheus.Desc, v interface{}) {
+ r.Errors++
+ if r.cf.ReportErrors {
+ ch <- prometheus.NewInvalidMetric(d, fmt.Errorf("error: %v", v))
+ }
+}
+
+// close is not part of the interface.
+func (r *Report) close() {
+ r.wg.Wait()
+ r.Elapsed = time.Since(r.Start)
+ close(r.ch)
+}
diff --git a/integrations/inputunifi/promunifi/site.go b/integrations/inputunifi/promunifi/site.go
new file mode 100644
index 00000000..0acc3cec
--- /dev/null
+++ b/integrations/inputunifi/promunifi/site.go
@@ -0,0 +1,132 @@
+package promunifi
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+type site struct {
+ NumUser *prometheus.Desc
+ NumGuest *prometheus.Desc
+ NumIot *prometheus.Desc
+ TxBytesR *prometheus.Desc
+ RxBytesR *prometheus.Desc
+ NumAp *prometheus.Desc
+ NumAdopted *prometheus.Desc
+ NumDisabled *prometheus.Desc
+ NumDisconnected *prometheus.Desc
+ NumPending *prometheus.Desc
+ NumGw *prometheus.Desc
+ NumSw *prometheus.Desc
+ NumSta *prometheus.Desc
+ Latency *prometheus.Desc
+ Drops *prometheus.Desc
+ Uptime *prometheus.Desc
+ XputUp *prometheus.Desc
+ XputDown *prometheus.Desc
+ SpeedtestPing *prometheus.Desc
+ RemoteUserNumActive *prometheus.Desc
+ RemoteUserNumInactive *prometheus.Desc
+ RemoteUserRxBytes *prometheus.Desc
+ RemoteUserTxBytes *prometheus.Desc
+ RemoteUserRxPackets *prometheus.Desc
+ RemoteUserTxPackets *prometheus.Desc
+}
+
+func descSite(ns string) *site {
+ labels := []string{"subsystem", "status", "site_name"}
+ return &site{
+ NumUser: prometheus.NewDesc(ns+"users", "Number of Users", labels, nil),
+ NumGuest: prometheus.NewDesc(ns+"guests", "Number of Guests", labels, nil),
+ NumIot: prometheus.NewDesc(ns+"iots", "Number of IoT Devices", labels, nil),
+ TxBytesR: prometheus.NewDesc(ns+"transmit_rate_bytes", "Bytes Transmit Rate", labels, nil),
+ RxBytesR: prometheus.NewDesc(ns+"receive_rate_bytes", "Bytes Receive Rate", labels, nil),
+ NumAp: prometheus.NewDesc(ns+"aps", "Access Point Count", labels, nil),
+ NumAdopted: prometheus.NewDesc(ns+"adopted", "Adoption Count", labels, nil),
+ NumDisabled: prometheus.NewDesc(ns+"disabled", "Disabled Count", labels, nil),
+ NumDisconnected: prometheus.NewDesc(ns+"disconnected", "Disconnected Count", labels, nil),
+ NumPending: prometheus.NewDesc(ns+"pending", "Pending Count", labels, nil),
+ NumGw: prometheus.NewDesc(ns+"gateways", "Gateway Count", labels, nil),
+ NumSw: prometheus.NewDesc(ns+"switches", "Switch Count", labels, nil),
+ NumSta: prometheus.NewDesc(ns+"stations", "Station Count", labels, nil),
+ Latency: prometheus.NewDesc(ns+"latency_seconds", "Latency", labels, nil),
+ Uptime: prometheus.NewDesc(ns+"uptime_seconds", "Uptime", labels, nil),
+ Drops: prometheus.NewDesc(ns+"intenet_drops_total", "Internet (WAN) Disconnections", labels, nil),
+ XputUp: prometheus.NewDesc(ns+"xput_up_rate", "Speedtest Upload", labels, nil),
+ XputDown: prometheus.NewDesc(ns+"xput_down_rate", "Speedtest Download", labels, nil),
+ SpeedtestPing: prometheus.NewDesc(ns+"speedtest_ping", "Speedtest Ping", labels, nil),
+ RemoteUserNumActive: prometheus.NewDesc(ns+"remote_user_active", "Remote Users Active", labels, nil),
+ RemoteUserNumInactive: prometheus.NewDesc(ns+"remote_user_inactive", "Remote Users Inactive", labels, nil),
+ RemoteUserRxBytes: prometheus.NewDesc(ns+"remote_user_receive_bytes_total", "Remote Users Receive Bytes", labels, nil),
+ RemoteUserTxBytes: prometheus.NewDesc(ns+"remote_user_transmit_bytes_total", "Remote Users Transmit Bytes", labels, nil),
+ RemoteUserRxPackets: prometheus.NewDesc(ns+"remote_user_receive_packets_total", "Remote Users Receive Packets", labels, nil),
+ RemoteUserTxPackets: prometheus.NewDesc(ns+"remote_user_transmit_packets_total", "Remote Users Transmit Packets", labels, nil),
+ }
+}
+
+func (u *promUnifi) exportSite(r report, s *unifi.Site) {
+ for _, h := range s.Health {
+ labels := []string{h.Subsystem, h.Status, s.SiteName}
+ switch h.Subsystem {
+ case "www":
+ r.send([]*metric{
+ {u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
+ {u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
+ {u.Site.Uptime, prometheus.GaugeValue, h.Latency, labels},
+ {u.Site.Latency, prometheus.GaugeValue, h.Latency.Val / 1000, labels},
+ {u.Site.XputUp, prometheus.GaugeValue, h.XputUp, labels},
+ {u.Site.XputDown, prometheus.GaugeValue, h.XputDown, labels},
+ {u.Site.SpeedtestPing, prometheus.GaugeValue, h.SpeedtestPing, labels},
+ {u.Site.Drops, prometheus.CounterValue, h.Drops, labels},
+ })
+
+ case "wlan":
+ r.send([]*metric{
+ {u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
+ {u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
+ {u.Site.NumAdopted, prometheus.GaugeValue, h.NumAdopted, labels},
+ {u.Site.NumDisconnected, prometheus.GaugeValue, h.NumDisconnected, labels},
+ {u.Site.NumPending, prometheus.GaugeValue, h.NumPending, labels},
+ {u.Site.NumUser, prometheus.GaugeValue, h.NumUser, labels},
+ {u.Site.NumGuest, prometheus.GaugeValue, h.NumGuest, labels},
+ {u.Site.NumIot, prometheus.GaugeValue, h.NumIot, labels},
+ {u.Site.NumAp, prometheus.GaugeValue, h.NumAp, labels},
+ {u.Site.NumDisabled, prometheus.GaugeValue, h.NumDisabled, labels},
+ })
+
+ case "wan":
+ r.send([]*metric{
+ {u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
+ {u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
+ {u.Site.NumAdopted, prometheus.GaugeValue, h.NumAdopted, labels},
+ {u.Site.NumDisconnected, prometheus.GaugeValue, h.NumDisconnected, labels},
+ {u.Site.NumPending, prometheus.GaugeValue, h.NumPending, labels},
+ {u.Site.NumGw, prometheus.GaugeValue, h.NumGw, labels},
+ {u.Site.NumSta, prometheus.GaugeValue, h.NumSta, labels},
+ })
+
+ case "lan":
+ r.send([]*metric{
+ {u.Site.TxBytesR, prometheus.GaugeValue, h.TxBytesR, labels},
+ {u.Site.RxBytesR, prometheus.GaugeValue, h.RxBytesR, labels},
+ {u.Site.NumAdopted, prometheus.GaugeValue, h.NumAdopted, labels},
+ {u.Site.NumDisconnected, prometheus.GaugeValue, h.NumDisconnected, labels},
+ {u.Site.NumPending, prometheus.GaugeValue, h.NumPending, labels},
+ {u.Site.NumUser, prometheus.GaugeValue, h.NumUser, labels},
+ {u.Site.NumGuest, prometheus.GaugeValue, h.NumGuest, labels},
+ {u.Site.NumIot, prometheus.GaugeValue, h.NumIot, labels},
+ {u.Site.NumSw, prometheus.GaugeValue, h.NumSw, labels},
+ })
+
+ case "vpn":
+ r.send([]*metric{
+ {u.Site.RemoteUserNumActive, prometheus.GaugeValue, h.RemoteUserNumActive, labels},
+ {u.Site.RemoteUserNumInactive, prometheus.GaugeValue, h.RemoteUserNumInactive, labels},
+ {u.Site.RemoteUserRxBytes, prometheus.CounterValue, h.RemoteUserRxBytes, labels},
+ {u.Site.RemoteUserTxBytes, prometheus.CounterValue, h.RemoteUserTxBytes, labels},
+ {u.Site.RemoteUserRxPackets, prometheus.CounterValue, h.RemoteUserRxPackets, labels},
+ {u.Site.RemoteUserTxPackets, prometheus.CounterValue, h.RemoteUserTxPackets, labels},
+ })
+ }
+ }
+}
diff --git a/integrations/inputunifi/promunifi/uap.go b/integrations/inputunifi/promunifi/uap.go
new file mode 100644
index 00000000..ed8507df
--- /dev/null
+++ b/integrations/inputunifi/promunifi/uap.go
@@ -0,0 +1,337 @@
+package promunifi
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+type uap struct {
+ // Ap Traffic Stats
+ ApWifiTxDropped *prometheus.Desc
+ ApRxErrors *prometheus.Desc
+ ApRxDropped *prometheus.Desc
+ ApRxFrags *prometheus.Desc
+ ApRxCrypts *prometheus.Desc
+ ApTxPackets *prometheus.Desc
+ ApTxBytes *prometheus.Desc
+ ApTxErrors *prometheus.Desc
+ ApTxDropped *prometheus.Desc
+ ApTxRetries *prometheus.Desc
+ ApRxPackets *prometheus.Desc
+ ApRxBytes *prometheus.Desc
+ WifiTxAttempts *prometheus.Desc
+ MacFilterRejections *prometheus.Desc
+ // VAP Stats
+ VAPCcq *prometheus.Desc
+ VAPMacFilterRejections *prometheus.Desc
+ VAPNumSatisfactionSta *prometheus.Desc
+ VAPAvgClientSignal *prometheus.Desc
+ VAPSatisfaction *prometheus.Desc
+ VAPSatisfactionNow *prometheus.Desc
+ VAPDNSAvgLatency *prometheus.Desc
+ VAPRxBytes *prometheus.Desc
+ VAPRxCrypts *prometheus.Desc
+ VAPRxDropped *prometheus.Desc
+ VAPRxErrors *prometheus.Desc
+ VAPRxFrags *prometheus.Desc
+ VAPRxNwids *prometheus.Desc
+ VAPRxPackets *prometheus.Desc
+ VAPTxBytes *prometheus.Desc
+ VAPTxDropped *prometheus.Desc
+ VAPTxErrors *prometheus.Desc
+ VAPTxPackets *prometheus.Desc
+ VAPTxPower *prometheus.Desc
+ VAPTxRetries *prometheus.Desc
+ VAPTxCombinedRetries *prometheus.Desc
+ VAPTxDataMpduBytes *prometheus.Desc
+ VAPTxRtsRetries *prometheus.Desc
+ VAPTxSuccess *prometheus.Desc
+ VAPTxTotal *prometheus.Desc
+ VAPTxGoodbytes *prometheus.Desc
+ VAPTxLatAvg *prometheus.Desc
+ VAPTxLatMax *prometheus.Desc
+ VAPTxLatMin *prometheus.Desc
+ VAPRxGoodbytes *prometheus.Desc
+ VAPRxLatAvg *prometheus.Desc
+ VAPRxLatMax *prometheus.Desc
+ VAPRxLatMin *prometheus.Desc
+ VAPWifiTxLatencyMovAvg *prometheus.Desc
+ VAPWifiTxLatencyMovMax *prometheus.Desc
+ VAPWifiTxLatencyMovMin *prometheus.Desc
+ VAPWifiTxLatencyMovTotal *prometheus.Desc
+ VAPWifiTxLatencyMovCount *prometheus.Desc
+ // Radio Stats
+ RadioCurrentAntennaGain *prometheus.Desc
+ RadioHt *prometheus.Desc
+ RadioMaxTxpower *prometheus.Desc
+ RadioMinTxpower *prometheus.Desc
+ RadioNss *prometheus.Desc
+ RadioRadioCaps *prometheus.Desc
+ RadioTxPower *prometheus.Desc
+ RadioAstBeXmit *prometheus.Desc
+ RadioChannel *prometheus.Desc
+ RadioCuSelfRx *prometheus.Desc
+ RadioCuSelfTx *prometheus.Desc
+ RadioCuTotal *prometheus.Desc
+ RadioExtchannel *prometheus.Desc
+ RadioGain *prometheus.Desc
+ RadioGuestNumSta *prometheus.Desc
+ RadioNumSta *prometheus.Desc
+ RadioUserNumSta *prometheus.Desc
+ RadioTxPackets *prometheus.Desc
+ RadioTxRetries *prometheus.Desc
+}
+
+func descUAP(ns string) *uap {
+ // labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
+ labelA := []string{"stat", "site_name", "name"} // stat + labels[6:]
+ labelV := []string{"vap_name", "bssid", "radio", "radio_name", "essid", "usage", "site_name", "name"}
+ labelR := []string{"radio_name", "radio", "site_name", "name"}
+ return &uap{
+ // 3x each - stat table: total, guest, user
+ ApWifiTxDropped: prometheus.NewDesc(ns+"stat_wifi_transmt_dropped_total", "Wifi Transmissions Dropped", labelA, nil),
+ ApRxErrors: prometheus.NewDesc(ns+"stat_receive_errors_total", "Receive Errors", labelA, nil),
+ ApRxDropped: prometheus.NewDesc(ns+"stat_receive_dropped_total", "Receive Dropped", labelA, nil),
+ ApRxFrags: prometheus.NewDesc(ns+"stat_receive_frags_total", "Received Frags", labelA, nil),
+ ApRxCrypts: prometheus.NewDesc(ns+"stat_receive_crypts_total", "Receive Crypts", labelA, nil),
+ ApTxPackets: prometheus.NewDesc(ns+"stat_transmit_packets_total", "Transmit Packets", labelA, nil),
+ ApTxBytes: prometheus.NewDesc(ns+"stat_transmit_bytes_total", "Transmit Bytes", labelA, nil),
+ ApTxErrors: prometheus.NewDesc(ns+"stat_transmit_errors_total", "Transmit Errors", labelA, nil),
+ ApTxDropped: prometheus.NewDesc(ns+"stat_transmit_dropped_total", "Transmit Dropped", labelA, nil),
+ ApTxRetries: prometheus.NewDesc(ns+"stat_retries_tx_total", "Transmit Retries", labelA, nil),
+ ApRxPackets: prometheus.NewDesc(ns+"stat_receive_packets_total", "Receive Packets", labelA, nil),
+ ApRxBytes: prometheus.NewDesc(ns+"stat_receive_bytes_total", "Receive Bytes", labelA, nil),
+ WifiTxAttempts: prometheus.NewDesc(ns+"stat_wifi_transmit_attempts_total", "Wifi Transmission Attempts", labelA, nil),
+ MacFilterRejections: prometheus.NewDesc(ns+"stat_mac_filter_rejects_total", "MAC Filter Rejections", labelA, nil),
+ // N each - 1 per Virtual AP (VAP)
+ VAPCcq: prometheus.NewDesc(ns+"vap_ccq", "VAP Client Connection Quality", labelV, nil),
+ VAPMacFilterRejections: prometheus.NewDesc(ns+"vap_mac_filter_rejects_total", "VAP MAC Filter Rejections", labelV, nil),
+ VAPNumSatisfactionSta: prometheus.NewDesc(ns+"vap_satisfaction_stations", "VAP Number Satisifaction Stations", labelV, nil),
+ VAPAvgClientSignal: prometheus.NewDesc(ns+"vap_average_client_signal", "VAP Average Client Signal", labelV, nil),
+ VAPSatisfaction: prometheus.NewDesc(ns+"vap_satisfaction_percent", "VAP Satisfaction", labelV, nil),
+ VAPSatisfactionNow: prometheus.NewDesc(ns+"vap_satisfaction_now_percent", "VAP Satisfaction Now", labelV, nil),
+ VAPDNSAvgLatency: prometheus.NewDesc(ns+"vap_dns_latency_average_seconds", "VAP DNS Latency Average", labelV, nil),
+ VAPRxBytes: prometheus.NewDesc(ns+"vap_receive_bytes_total", "VAP Bytes Received", labelV, nil),
+ VAPRxCrypts: prometheus.NewDesc(ns+"vap_receive_crypts_total", "VAP Crypts Received", labelV, nil),
+ VAPRxDropped: prometheus.NewDesc(ns+"vap_receive_dropped_total", "VAP Dropped Received", labelV, nil),
+ VAPRxErrors: prometheus.NewDesc(ns+"vap_receive_errors_total", "VAP Errors Received", labelV, nil),
+ VAPRxFrags: prometheus.NewDesc(ns+"vap_receive_frags_total", "VAP Frags Received", labelV, nil),
+ VAPRxNwids: prometheus.NewDesc(ns+"vap_receive_nwids_total", "VAP Nwids Received", labelV, nil),
+ VAPRxPackets: prometheus.NewDesc(ns+"vap_receive_packets_total", "VAP Packets Received", labelV, nil),
+ VAPTxBytes: prometheus.NewDesc(ns+"vap_transmit_bytes_total", "VAP Bytes Transmitted", labelV, nil),
+ VAPTxDropped: prometheus.NewDesc(ns+"vap_transmit_dropped_total", "VAP Dropped Transmitted", labelV, nil),
+ VAPTxErrors: prometheus.NewDesc(ns+"vap_transmit_errors_total", "VAP Errors Transmitted", labelV, nil),
+ VAPTxPackets: prometheus.NewDesc(ns+"vap_transmit_packets_total", "VAP Packets Transmitted", labelV, nil),
+ VAPTxPower: prometheus.NewDesc(ns+"vap_transmit_power", "VAP Transmit Power", labelV, nil),
+ VAPTxRetries: prometheus.NewDesc(ns+"vap_transmit_retries_total", "VAP Retries Transmitted", labelV, nil),
+ VAPTxCombinedRetries: prometheus.NewDesc(ns+"vap_transmit_retries_combined_total", "VAP Retries Combined Transmitted", labelV, nil),
+ VAPTxDataMpduBytes: prometheus.NewDesc(ns+"vap_data_mpdu_transmit_bytes_total", "VAP Data MPDU Bytes Transmitted", labelV, nil),
+ VAPTxRtsRetries: prometheus.NewDesc(ns+"vap_transmit_rts_retries_total", "VAP RTS Retries Transmitted", labelV, nil),
+ VAPTxSuccess: prometheus.NewDesc(ns+"vap_transmit_success_total", "VAP Success Transmits", labelV, nil),
+ VAPTxTotal: prometheus.NewDesc(ns+"vap_transmit_total", "VAP Transmit Total", labelV, nil),
+ VAPTxGoodbytes: prometheus.NewDesc(ns+"vap_transmit_goodbyes", "VAP Goodbyes Transmitted", labelV, nil),
+ VAPTxLatAvg: prometheus.NewDesc(ns+"vap_transmit_latency_average_seconds", "VAP Latency Average Transmit", labelV, nil),
+ VAPTxLatMax: prometheus.NewDesc(ns+"vap_transmit_latency_maximum_seconds", "VAP Latency Maximum Transmit", labelV, nil),
+ VAPTxLatMin: prometheus.NewDesc(ns+"vap_transmit_latency_minimum_seconds", "VAP Latency Minimum Transmit", labelV, nil),
+ VAPRxGoodbytes: prometheus.NewDesc(ns+"vap_receive_goodbyes", "VAP Goodbyes Received", labelV, nil),
+ VAPRxLatAvg: prometheus.NewDesc(ns+"vap_receive_latency_average_seconds", "VAP Latency Average Receive", labelV, nil),
+ VAPRxLatMax: prometheus.NewDesc(ns+"vap_receive_latency_maximum_seconds", "VAP Latency Maximum Receive", labelV, nil),
+ VAPRxLatMin: prometheus.NewDesc(ns+"vap_receive_latency_minimum_seconds", "VAP Latency Minimum Receive", labelV, nil),
+ VAPWifiTxLatencyMovAvg: prometheus.NewDesc(ns+"vap_transmit_latency_moving_avg_seconds", "VAP Latency Moving Average Tramsit", labelV, nil),
+ VAPWifiTxLatencyMovMax: prometheus.NewDesc(ns+"vap_transmit_latency_moving_max_seconds", "VAP Latency Moving Maximum Tramsit", labelV, nil),
+ VAPWifiTxLatencyMovMin: prometheus.NewDesc(ns+"vap_transmit_latency_moving_min_seconds", "VAP Latency Moving Minimum Tramsit", labelV, nil),
+ VAPWifiTxLatencyMovTotal: prometheus.NewDesc(ns+"vap_transmit_latency_moving_total", "VAP Latency Moving Total Tramsit", labelV, nil),
+ VAPWifiTxLatencyMovCount: prometheus.NewDesc(ns+"vap_transmit_latency_moving_count", "VAP Latency Moving Count Tramsit", labelV, nil),
+ // N each - 1 per Radio. 1-4 radios per AP usually
+ RadioCurrentAntennaGain: prometheus.NewDesc(ns+"radio_current_antenna_gain", "Radio Current Antenna Gain", labelR, nil),
+ RadioHt: prometheus.NewDesc(ns+"radio_ht", "Radio HT", labelR, nil),
+ RadioMaxTxpower: prometheus.NewDesc(ns+"radio_max_transmit_power", "Radio Maximum Transmit Power", labelR, nil),
+ RadioMinTxpower: prometheus.NewDesc(ns+"radio_min_transmit_power", "Radio Minimum Transmit Power", labelR, nil),
+ RadioNss: prometheus.NewDesc(ns+"radio_nss", "Radio Nss", labelR, nil),
+ RadioRadioCaps: prometheus.NewDesc(ns+"radio_caps", "Radio Capabilities", labelR, nil),
+ RadioTxPower: prometheus.NewDesc(ns+"radio_transmit_power", "Radio Transmit Power", labelR, nil),
+ RadioAstBeXmit: prometheus.NewDesc(ns+"radio_ast_be_xmit", "Radio AstBe Transmit", labelR, nil),
+ RadioChannel: prometheus.NewDesc(ns+"radio_channel", "Radio Channel", labelR, nil),
+ RadioCuSelfRx: prometheus.NewDesc(ns+"radio_channel_utilization_receive", "Radio Channel Utilization Receive", labelR, nil),
+ RadioCuSelfTx: prometheus.NewDesc(ns+"radio_channel_utilization_transmit", "Radio Channel Utilization Transmit", labelR, nil),
+ RadioCuTotal: prometheus.NewDesc(ns+"radio_channel_utilization_total", "Radio Channel Utilization", labelR, nil),
+ RadioExtchannel: prometheus.NewDesc(ns+"radio_ext_channel", "Radio Ext Channel", labelR, nil),
+ RadioGain: prometheus.NewDesc(ns+"radio_gain", "Radio Gain", labelR, nil),
+ RadioGuestNumSta: prometheus.NewDesc(ns+"radio_guest_stations", "Radio Guest Station Count", labelR, nil),
+ RadioNumSta: prometheus.NewDesc(ns+"radio_stations", "Radio Total Station Count", labelR, nil),
+ RadioUserNumSta: prometheus.NewDesc(ns+"radio_user_stations", "Radio User Station Count", labelR, nil),
+ RadioTxPackets: prometheus.NewDesc(ns+"radio_transmit_packets_total", "Radio Transmitted Packets", labelR, nil),
+ RadioTxRetries: prometheus.NewDesc(ns+"radio_transmit_retries_total", "Radio Transmit Retries", labelR, nil),
+ }
+}
+
+func (u *promUnifi) exportUAP(r report, d *unifi.UAP) {
+ labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
+ // Wireless System Data.
+ r.send([]*metric{
+ {u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
+ {u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
+ {u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
+ {u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
+ {u.Device.BytesD, prometheus.CounterValue, d.BytesD, labels}, // not sure if these 3 Ds are counters or gauges.
+ {u.Device.TxBytesD, prometheus.CounterValue, d.TxBytesD, labels}, // not sure if these 3 Ds are counters or gauges.
+ {u.Device.RxBytesD, prometheus.CounterValue, d.RxBytesD, labels}, // not sure if these 3 Ds are counters or gauges.
+ {u.Device.BytesR, prometheus.GaugeValue, d.BytesR, labels},
+ {u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
+ {u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
+ {u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
+ {u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
+ {u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
+ {u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
+ {u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
+ {u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
+ {u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
+ {u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
+ {u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
+ })
+
+ u.exportUAPstats(r, labels, d.Stat.Ap)
+ u.exportVAPtable(r, labels, d.VapTable)
+ u.exportRadtable(r, labels, d.RadioTable, d.RadioTableStats)
+}
+
+func (u *promUnifi) exportUAPstats(r report, labels []string, ap *unifi.Ap) {
+ // labelA := append([]string{"all"}, labels[2:]...)
+ labelU := append([]string{"user"}, labels[6:]...)
+ labelG := append([]string{"guest"}, labels[6:]...)
+ r.send([]*metric{
+ /* // all
+ {u.UAP.ApWifiTxDropped, prometheus.CounterValue, ap.WifiTxDropped, labelA},
+ {u.UAP.ApRxErrors, prometheus.CounterValue, ap.RxErrors, labelA},
+ {u.UAP.ApRxDropped, prometheus.CounterValue, ap.RxDropped, labelA},
+ {u.UAP.ApRxFrags, prometheus.CounterValue, ap.RxFrags, labelA},
+ {u.UAP.ApRxCrypts, prometheus.CounterValue, ap.RxCrypts, labelA},
+ {u.UAP.ApTxPackets, prometheus.CounterValue, ap.TxPackets, labelA},
+ {u.UAP.ApTxBytes, prometheus.CounterValue, ap.TxBytes, labelA},
+ {u.UAP.ApTxErrors, prometheus.CounterValue, ap.TxErrors, labelA},
+ {u.UAP.ApTxDropped, prometheus.CounterValue, ap.TxDropped, labelA},
+ {u.UAP.ApTxRetries, prometheus.CounterValue, ap.TxRetries, labelA},
+ {u.UAP.ApRxPackets, prometheus.CounterValue, ap.RxPackets, labelA},
+ {u.UAP.ApRxBytes, prometheus.CounterValue, ap.RxBytes, labelA},
+ {u.UAP.WifiTxAttempts, prometheus.CounterValue, ap.WifiTxAttempts, labelA},
+ {u.UAP.MacFilterRejections, prometheus.CounterValue, ap.MacFilterRejections, labelA},
+ */
+ // user
+ {u.UAP.ApWifiTxDropped, prometheus.CounterValue, ap.UserWifiTxDropped, labelU},
+ {u.UAP.ApRxErrors, prometheus.CounterValue, ap.UserRxErrors, labelU},
+ {u.UAP.ApRxDropped, prometheus.CounterValue, ap.UserRxDropped, labelU},
+ {u.UAP.ApRxFrags, prometheus.CounterValue, ap.UserRxFrags, labelU},
+ {u.UAP.ApRxCrypts, prometheus.CounterValue, ap.UserRxCrypts, labelU},
+ {u.UAP.ApTxPackets, prometheus.CounterValue, ap.UserTxPackets, labelU},
+ {u.UAP.ApTxBytes, prometheus.CounterValue, ap.UserTxBytes, labelU},
+ {u.UAP.ApTxErrors, prometheus.CounterValue, ap.UserTxErrors, labelU},
+ {u.UAP.ApTxDropped, prometheus.CounterValue, ap.UserTxDropped, labelU},
+ {u.UAP.ApTxRetries, prometheus.CounterValue, ap.UserTxRetries, labelU},
+ {u.UAP.ApRxPackets, prometheus.CounterValue, ap.UserRxPackets, labelU},
+ {u.UAP.ApRxBytes, prometheus.CounterValue, ap.UserRxBytes, labelU},
+ {u.UAP.WifiTxAttempts, prometheus.CounterValue, ap.UserWifiTxAttempts, labelU},
+ {u.UAP.MacFilterRejections, prometheus.CounterValue, ap.UserMacFilterRejections, labelU},
+ // guest
+ {u.UAP.ApWifiTxDropped, prometheus.CounterValue, ap.GuestWifiTxDropped, labelG},
+ {u.UAP.ApRxErrors, prometheus.CounterValue, ap.GuestRxErrors, labelG},
+ {u.UAP.ApRxDropped, prometheus.CounterValue, ap.GuestRxDropped, labelG},
+ {u.UAP.ApRxFrags, prometheus.CounterValue, ap.GuestRxFrags, labelG},
+ {u.UAP.ApRxCrypts, prometheus.CounterValue, ap.GuestRxCrypts, labelG},
+ {u.UAP.ApTxPackets, prometheus.CounterValue, ap.GuestTxPackets, labelG},
+ {u.UAP.ApTxBytes, prometheus.CounterValue, ap.GuestTxBytes, labelG},
+ {u.UAP.ApTxErrors, prometheus.CounterValue, ap.GuestTxErrors, labelG},
+ {u.UAP.ApTxDropped, prometheus.CounterValue, ap.GuestTxDropped, labelG},
+ {u.UAP.ApTxRetries, prometheus.CounterValue, ap.GuestTxRetries, labelG},
+ {u.UAP.ApRxPackets, prometheus.CounterValue, ap.GuestRxPackets, labelG},
+ {u.UAP.ApRxBytes, prometheus.CounterValue, ap.GuestRxBytes, labelG},
+ {u.UAP.WifiTxAttempts, prometheus.CounterValue, ap.GuestWifiTxAttempts, labelG},
+ {u.UAP.MacFilterRejections, prometheus.CounterValue, ap.GuestMacFilterRejections, labelG},
+ })
+}
+
+func (u *promUnifi) exportVAPtable(r report, labels []string, vt unifi.VapTable) {
+ // vap table stats
+ for _, v := range vt {
+ if !v.Up.Val {
+ continue
+ }
+ labelV := append([]string{v.Name, v.Bssid, v.Radio, v.RadioName, v.Essid, v.Usage}, labels[6:]...)
+
+ r.send([]*metric{
+ {u.UAP.VAPCcq, prometheus.GaugeValue, v.Ccq / 10, labelV},
+ {u.UAP.VAPMacFilterRejections, prometheus.CounterValue, v.MacFilterRejections, labelV},
+ {u.UAP.VAPNumSatisfactionSta, prometheus.GaugeValue, v.NumSatisfactionSta, labelV},
+ {u.UAP.VAPAvgClientSignal, prometheus.GaugeValue, v.AvgClientSignal, labelV},
+ {u.UAP.VAPSatisfaction, prometheus.GaugeValue, v.Satisfaction, labelV},
+ {u.UAP.VAPSatisfactionNow, prometheus.GaugeValue, v.SatisfactionNow, labelV},
+ {u.UAP.VAPDNSAvgLatency, prometheus.GaugeValue, v.DNSAvgLatency.Val / 1000, labelV},
+ {u.UAP.VAPRxBytes, prometheus.CounterValue, v.RxBytes, labelV},
+ {u.UAP.VAPRxCrypts, prometheus.CounterValue, v.RxCrypts, labelV},
+ {u.UAP.VAPRxDropped, prometheus.CounterValue, v.RxDropped, labelV},
+ {u.UAP.VAPRxErrors, prometheus.CounterValue, v.RxErrors, labelV},
+ {u.UAP.VAPRxFrags, prometheus.CounterValue, v.RxFrags, labelV},
+ {u.UAP.VAPRxNwids, prometheus.CounterValue, v.RxNwids, labelV},
+ {u.UAP.VAPRxPackets, prometheus.CounterValue, v.RxPackets, labelV},
+ {u.UAP.VAPTxBytes, prometheus.CounterValue, v.TxBytes, labelV},
+ {u.UAP.VAPTxDropped, prometheus.CounterValue, v.TxDropped, labelV},
+ {u.UAP.VAPTxErrors, prometheus.CounterValue, v.TxErrors, labelV},
+ {u.UAP.VAPTxPackets, prometheus.CounterValue, v.TxPackets, labelV},
+ {u.UAP.VAPTxPower, prometheus.GaugeValue, v.TxPower, labelV},
+ {u.UAP.VAPTxRetries, prometheus.CounterValue, v.TxRetries, labelV},
+ {u.UAP.VAPTxCombinedRetries, prometheus.CounterValue, v.TxCombinedRetries, labelV},
+ {u.UAP.VAPTxDataMpduBytes, prometheus.CounterValue, v.TxDataMpduBytes, labelV},
+ {u.UAP.VAPTxRtsRetries, prometheus.CounterValue, v.TxRtsRetries, labelV},
+ {u.UAP.VAPTxTotal, prometheus.CounterValue, v.TxTotal, labelV},
+ {u.UAP.VAPTxGoodbytes, prometheus.CounterValue, v.TxTCPStats.Goodbytes, labelV},
+ {u.UAP.VAPTxLatAvg, prometheus.GaugeValue, v.TxTCPStats.LatAvg.Val / 1000, labelV},
+ {u.UAP.VAPTxLatMax, prometheus.GaugeValue, v.TxTCPStats.LatMax.Val / 1000, labelV},
+ {u.UAP.VAPTxLatMin, prometheus.GaugeValue, v.TxTCPStats.LatMin.Val / 1000, labelV},
+ {u.UAP.VAPRxGoodbytes, prometheus.CounterValue, v.RxTCPStats.Goodbytes, labelV},
+ {u.UAP.VAPRxLatAvg, prometheus.GaugeValue, v.RxTCPStats.LatAvg.Val / 1000, labelV},
+ {u.UAP.VAPRxLatMax, prometheus.GaugeValue, v.RxTCPStats.LatMax.Val / 1000, labelV},
+ {u.UAP.VAPRxLatMin, prometheus.GaugeValue, v.RxTCPStats.LatMin.Val / 1000, labelV},
+ {u.UAP.VAPWifiTxLatencyMovAvg, prometheus.GaugeValue, v.WifiTxLatencyMov.Avg.Val / 1000, labelV},
+ {u.UAP.VAPWifiTxLatencyMovMax, prometheus.GaugeValue, v.WifiTxLatencyMov.Max.Val / 1000, labelV},
+ {u.UAP.VAPWifiTxLatencyMovMin, prometheus.GaugeValue, v.WifiTxLatencyMov.Min.Val / 1000, labelV},
+ {u.UAP.VAPWifiTxLatencyMovTotal, prometheus.CounterValue, v.WifiTxLatencyMov.Total, labelV}, // not sure if gauge or counter.
+ {u.UAP.VAPWifiTxLatencyMovCount, prometheus.CounterValue, v.WifiTxLatencyMov.TotalCount, labelV}, // not sure if gauge or counter.
+ })
+ }
+}
+
+func (u *promUnifi) exportRadtable(r report, labels []string, rt unifi.RadioTable, rts unifi.RadioTableStats) {
+ // radio table
+ for _, p := range rt {
+ labelR := append([]string{p.Name, p.Radio}, labels[6:]...)
+ r.send([]*metric{
+ {u.UAP.RadioCurrentAntennaGain, prometheus.GaugeValue, p.CurrentAntennaGain, labelR},
+ {u.UAP.RadioHt, prometheus.GaugeValue, p.Ht, labelR},
+ {u.UAP.RadioMaxTxpower, prometheus.GaugeValue, p.MaxTxpower, labelR},
+ {u.UAP.RadioMinTxpower, prometheus.GaugeValue, p.MinTxpower, labelR},
+ {u.UAP.RadioNss, prometheus.GaugeValue, p.Nss, labelR},
+ {u.UAP.RadioRadioCaps, prometheus.GaugeValue, p.RadioCaps, labelR},
+ })
+
+ // combine radio table with radio stats table.
+ for _, t := range rts {
+ if t.Name != p.Name {
+ continue
+ }
+ r.send([]*metric{
+ {u.UAP.RadioTxPower, prometheus.GaugeValue, t.TxPower, labelR},
+ {u.UAP.RadioAstBeXmit, prometheus.GaugeValue, t.AstBeXmit, labelR},
+ {u.UAP.RadioChannel, prometheus.GaugeValue, t.Channel, labelR},
+ {u.UAP.RadioCuSelfRx, prometheus.GaugeValue, t.CuSelfRx, labelR},
+ {u.UAP.RadioCuSelfTx, prometheus.GaugeValue, t.CuSelfTx, labelR},
+ {u.UAP.RadioCuTotal, prometheus.GaugeValue, t.CuTotal, labelR},
+ {u.UAP.RadioExtchannel, prometheus.GaugeValue, t.Extchannel, labelR},
+ {u.UAP.RadioGain, prometheus.GaugeValue, t.Gain, labelR},
+ {u.UAP.RadioGuestNumSta, prometheus.GaugeValue, t.GuestNumSta, labelR},
+ {u.UAP.RadioNumSta, prometheus.GaugeValue, t.NumSta, labelR},
+ {u.UAP.RadioUserNumSta, prometheus.GaugeValue, t.UserNumSta, labelR},
+ {u.UAP.RadioTxPackets, prometheus.CounterValue, t.TxPackets, labelR},
+ {u.UAP.RadioTxRetries, prometheus.CounterValue, t.TxRetries, labelR},
+ })
+ }
+ }
+}
diff --git a/integrations/inputunifi/promunifi/udm.go b/integrations/inputunifi/promunifi/udm.go
new file mode 100644
index 00000000..9a17b41b
--- /dev/null
+++ b/integrations/inputunifi/promunifi/udm.go
@@ -0,0 +1,107 @@
+package promunifi
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+// These are shared by all four device types: UDM, UAP, USG, USW
+type unifiDevice struct {
+ Uptime *prometheus.Desc
+ Temperature *prometheus.Desc // sw only
+ TotalMaxPower *prometheus.Desc // sw only
+ FanLevel *prometheus.Desc // sw only
+ TotalTxBytes *prometheus.Desc
+ TotalRxBytes *prometheus.Desc
+ TotalBytes *prometheus.Desc
+ BytesR *prometheus.Desc // ap only
+ BytesD *prometheus.Desc // ap only
+ Bytes *prometheus.Desc // ap only
+ TxBytesD *prometheus.Desc // ap only
+ RxBytesD *prometheus.Desc // ap only
+ NumSta *prometheus.Desc
+ UserNumSta *prometheus.Desc
+ GuestNumSta *prometheus.Desc
+ NumDesktop *prometheus.Desc // gw only
+ NumMobile *prometheus.Desc // gw only
+ NumHandheld *prometheus.Desc // gw only
+ Loadavg1 *prometheus.Desc
+ Loadavg5 *prometheus.Desc
+ Loadavg15 *prometheus.Desc
+ MemBuffer *prometheus.Desc
+ MemTotal *prometheus.Desc
+ MemUsed *prometheus.Desc
+ CPU *prometheus.Desc
+ Mem *prometheus.Desc
+}
+
+func descDevice(ns string) *unifiDevice {
+ labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
+ return &unifiDevice{
+ Uptime: prometheus.NewDesc(ns+"uptime", "Uptime", labels, nil),
+ Temperature: prometheus.NewDesc(ns+"temperature_celsius", "Temperature", labels, nil),
+ TotalMaxPower: prometheus.NewDesc(ns+"max_power_total", "Total Max Power", labels, nil),
+ FanLevel: prometheus.NewDesc(ns+"fan_level", "Fan Level", labels, nil),
+ TotalTxBytes: prometheus.NewDesc(ns+"transmit_bytes_total", "Total Transmitted Bytes", labels, nil),
+ TotalRxBytes: prometheus.NewDesc(ns+"receive_bytes_total", "Total Received Bytes", labels, nil),
+ TotalBytes: prometheus.NewDesc(ns+"bytes_total", "Total Bytes Transferred", labels, nil),
+ BytesR: prometheus.NewDesc(ns+"rate_bytes", "Transfer Rate", labels, nil),
+ BytesD: prometheus.NewDesc(ns+"d_bytes", "Total Bytes D???", labels, nil),
+ Bytes: prometheus.NewDesc(ns+"transferred_bytes_total", "Bytes Transferred", labels, nil),
+ TxBytesD: prometheus.NewDesc(ns+"d_tranmsit_bytes", "Transmit Bytes D???", labels, nil),
+ RxBytesD: prometheus.NewDesc(ns+"d_receive_bytes", "Receive Bytes D???", labels, nil),
+ NumSta: prometheus.NewDesc(ns+"stations", "Number of Stations", labels, nil),
+ UserNumSta: prometheus.NewDesc(ns+"user_stations", "Number of User Stations", labels, nil),
+ GuestNumSta: prometheus.NewDesc(ns+"guest_stations", "Number of Guest Stations", labels, nil),
+ NumDesktop: prometheus.NewDesc(ns+"desktops", "Number of Desktops", labels, nil),
+ NumMobile: prometheus.NewDesc(ns+"mobile", "Number of Mobiles", labels, nil),
+ NumHandheld: prometheus.NewDesc(ns+"handheld", "Number of Handhelds", labels, nil),
+ Loadavg1: prometheus.NewDesc(ns+"load_average_1", "System Load Average 1 Minute", labels, nil),
+ Loadavg5: prometheus.NewDesc(ns+"load_average_5", "System Load Average 5 Minutes", labels, nil),
+ Loadavg15: prometheus.NewDesc(ns+"load_average_15", "System Load Average 15 Minutes", labels, nil),
+ MemUsed: prometheus.NewDesc(ns+"memory_used_bytes", "System Memory Used", labels, nil),
+ MemTotal: prometheus.NewDesc(ns+"memory_installed_bytes", "System Installed Memory", labels, nil),
+ MemBuffer: prometheus.NewDesc(ns+"memory_buffer_bytes", "System Memory Buffer", labels, nil),
+ CPU: prometheus.NewDesc(ns+"cpu_utilization_percent", "System CPU % Utilized", labels, nil),
+ Mem: prometheus.NewDesc(ns+"memory_utilization_percent", "System Memory % Utilized", labels, nil),
+ }
+}
+
+// UDM is a collection of stats from USG, USW and UAP. It has no unique stats.
+func (u *promUnifi) exportUDM(r report, d *unifi.UDM) {
+ labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
+ // Dream Machine System Data.
+ r.send([]*metric{
+ {u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
+ {u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
+ {u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
+ {u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
+ {u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
+ {u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
+ {u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
+ {u.Device.NumDesktop, prometheus.GaugeValue, d.NumDesktop, labels},
+ {u.Device.NumMobile, prometheus.GaugeValue, d.NumMobile, labels},
+ {u.Device.NumHandheld, prometheus.GaugeValue, d.NumHandheld, labels},
+ {u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
+ {u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
+ {u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
+ {u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
+ {u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
+ {u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
+ {u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
+ {u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
+ })
+
+ // Switch Data
+ u.exportUSWstats(r, labels, d.Stat.Sw)
+ u.exportPortTable(r, labels, d.PortTable)
+ // Gateway Data
+ u.exportUSGstats(r, labels, d.Stat.Gw, d.SpeedtestStatus)
+ u.exportWANPorts(r, labels, d.Wan1, d.Wan2)
+ // Wireless Data - UDM (non-pro) only
+ if d.Stat.Ap != nil && d.VapTable != nil {
+ u.exportUAPstats(r, labels, d.Stat.Ap)
+ u.exportVAPtable(r, labels, *d.VapTable)
+ u.exportRadtable(r, labels, *d.RadioTable, *d.RadioTableStats)
+ }
+}
diff --git a/integrations/inputunifi/promunifi/usg.go b/integrations/inputunifi/promunifi/usg.go
new file mode 100644
index 00000000..b8764522
--- /dev/null
+++ b/integrations/inputunifi/promunifi/usg.go
@@ -0,0 +1,145 @@
+package promunifi
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+type usg struct {
+ WanRxPackets *prometheus.Desc
+ WanRxBytes *prometheus.Desc
+ WanRxDropped *prometheus.Desc
+ WanRxErrors *prometheus.Desc
+ WanTxPackets *prometheus.Desc
+ WanTxBytes *prometheus.Desc
+ LanRxPackets *prometheus.Desc
+ LanRxBytes *prometheus.Desc
+ LanRxDropped *prometheus.Desc
+ LanTxPackets *prometheus.Desc
+ LanTxBytes *prometheus.Desc
+ WanRxBroadcast *prometheus.Desc
+ WanRxBytesR *prometheus.Desc
+ WanRxMulticast *prometheus.Desc
+ WanSpeed *prometheus.Desc
+ WanTxBroadcast *prometheus.Desc
+ WanTxBytesR *prometheus.Desc
+ WanTxDropped *prometheus.Desc
+ WanTxErrors *prometheus.Desc
+ WanTxMulticast *prometheus.Desc
+ WanBytesR *prometheus.Desc
+ Latency *prometheus.Desc
+ Runtime *prometheus.Desc
+ XputDownload *prometheus.Desc
+ XputUpload *prometheus.Desc
+}
+
+func descUSG(ns string) *usg {
+ // labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
+ // labelWan := append([]string{"port"}, labels[6:]...)
+ labels := []string{"port", "site_name", "name"}
+ return &usg{
+ WanRxPackets: prometheus.NewDesc(ns+"wan_receive_packets_total", "WAN Receive Packets Total", labels, nil),
+ WanRxBytes: prometheus.NewDesc(ns+"wan_receive_bytes_total", "WAN Receive Bytes Total", labels, nil),
+ WanRxDropped: prometheus.NewDesc(ns+"wan_receive_dropped_total", "WAN Receive Dropped Total", labels, nil),
+ WanRxErrors: prometheus.NewDesc(ns+"wan_receive_errors_total", "WAN Receive Errors Total", labels, nil),
+ WanTxPackets: prometheus.NewDesc(ns+"wan_transmit_packets_total", "WAN Transmit Packets Total", labels, nil),
+ WanTxBytes: prometheus.NewDesc(ns+"wan_transmit_bytes_total", "WAN Transmit Bytes Total", labels, nil),
+ WanRxBroadcast: prometheus.NewDesc(ns+"wan_receive_broadcast_total", "WAN Receive Broadcast Total", labels, nil),
+ WanRxBytesR: prometheus.NewDesc(ns+"wan_receive_rate_bytes", "WAN Receive Bytes Rate", labels, nil),
+ WanRxMulticast: prometheus.NewDesc(ns+"wan_receive_multicast_total", "WAN Receive Multicast Total", labels, nil),
+ WanSpeed: prometheus.NewDesc(ns+"wan_speed_bps", "WAN Speed", labels, nil),
+ WanTxBroadcast: prometheus.NewDesc(ns+"wan_transmit_broadcast_total", "WAN Transmit Broadcast Total", labels, nil),
+ WanTxBytesR: prometheus.NewDesc(ns+"wan_transmit_rate_bytes", "WAN Transmit Bytes Rate", labels, nil),
+ WanTxDropped: prometheus.NewDesc(ns+"wan_transmit_dropped_total", "WAN Transmit Dropped Total", labels, nil),
+ WanTxErrors: prometheus.NewDesc(ns+"wan_transmit_errors_total", "WAN Transmit Errors Total", labels, nil),
+ WanTxMulticast: prometheus.NewDesc(ns+"wan_transmit_multicast_total", "WAN Transmit Multicast Total", labels, nil),
+ WanBytesR: prometheus.NewDesc(ns+"wan_rate_bytes", "WAN Transfer Rate", labels, nil),
+ LanRxPackets: prometheus.NewDesc(ns+"lan_receive_packets_total", "LAN Receive Packets Total", labels, nil),
+ LanRxBytes: prometheus.NewDesc(ns+"lan_receive_bytes_total", "LAN Receive Bytes Total", labels, nil),
+ LanRxDropped: prometheus.NewDesc(ns+"lan_receive_dropped_total", "LAN Receive Dropped Total", labels, nil),
+ LanTxPackets: prometheus.NewDesc(ns+"lan_transmit_packets_total", "LAN Transmit Packets Total", labels, nil),
+ LanTxBytes: prometheus.NewDesc(ns+"lan_transmit_bytes_total", "LAN Transmit Bytes Total", labels, nil),
+ Latency: prometheus.NewDesc(ns+"speedtest_latency_seconds", "Speedtest Latency", labels, nil),
+ Runtime: prometheus.NewDesc(ns+"speedtest_runtime", "Speedtest Run Time", labels, nil),
+ XputDownload: prometheus.NewDesc(ns+"speedtest_download", "Speedtest Download Rate", labels, nil),
+ XputUpload: prometheus.NewDesc(ns+"speedtest_upload", "Speedtest Upload Rate", labels, nil),
+ }
+}
+
+func (u *promUnifi) exportUSG(r report, d *unifi.USG) {
+ labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
+ // Gateway System Data.
+ r.send([]*metric{
+ {u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
+ {u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
+ {u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
+ {u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
+ {u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
+ {u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
+ {u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
+ {u.Device.NumDesktop, prometheus.GaugeValue, d.NumDesktop, labels},
+ {u.Device.NumMobile, prometheus.GaugeValue, d.NumMobile, labels},
+ {u.Device.NumHandheld, prometheus.GaugeValue, d.NumHandheld, labels},
+ {u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
+ {u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
+ {u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
+ {u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
+ {u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
+ {u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
+ {u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
+ {u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
+ })
+ u.exportWANPorts(r, labels, d.Wan1, d.Wan2)
+ u.exportUSGstats(r, labels, d.Stat.Gw, d.SpeedtestStatus)
+}
+
+func (u *promUnifi) exportUSGstats(r report, labels []string, gw *unifi.Gw, st unifi.SpeedtestStatus) {
+ labelLan := []string{"lan", labels[6], labels[7]}
+ labelWan := []string{"all", labels[6], labels[7]}
+ r.send([]*metric{
+ /* // Combined Port Stats - not really needed. sum() the others instead.
+ {u.USG.WanRxPackets, prometheus.CounterValue, gw.WanRxPackets, labelWan},
+ {u.USG.WanRxBytes, prometheus.CounterValue, gw.WanRxBytes, labelWan},
+ {u.USG.WanRxDropped, prometheus.CounterValue, gw.WanRxDropped, labelWan},
+ {u.USG.WanTxPackets, prometheus.CounterValue, gw.WanTxPackets, labelWan},
+ {u.USG.WanTxBytes, prometheus.CounterValue, gw.WanTxBytes, labelWan},
+ {u.USG.WanRxErrors, prometheus.CounterValue, gw.WanRxErrors, labelWan},
+ */
+ {u.USG.LanRxPackets, prometheus.CounterValue, gw.LanRxPackets, labelLan},
+ {u.USG.LanRxBytes, prometheus.CounterValue, gw.LanRxBytes, labelLan},
+ {u.USG.LanTxPackets, prometheus.CounterValue, gw.LanTxPackets, labelLan},
+ {u.USG.LanTxBytes, prometheus.CounterValue, gw.LanTxBytes, labelLan},
+ {u.USG.LanRxDropped, prometheus.CounterValue, gw.LanRxDropped, labelLan},
+ // Speed Test Stats
+ {u.USG.Latency, prometheus.GaugeValue, st.Latency.Val / 1000, labelWan},
+ {u.USG.Runtime, prometheus.GaugeValue, st.Runtime, labelWan},
+ {u.USG.XputDownload, prometheus.GaugeValue, st.XputDownload, labelWan},
+ {u.USG.XputUpload, prometheus.GaugeValue, st.XputUpload, labelWan},
+ })
+}
+
+func (u *promUnifi) exportWANPorts(r report, labels []string, wans ...unifi.Wan) {
+ for _, wan := range wans {
+ if !wan.Up.Val {
+ continue // only record UP interfaces.
+ }
+ labelWan := []string{wan.Name, labels[6], labels[7]}
+ r.send([]*metric{
+ {u.USG.WanRxPackets, prometheus.CounterValue, wan.RxPackets, labelWan},
+ {u.USG.WanRxBytes, prometheus.CounterValue, wan.RxBytes, labelWan},
+ {u.USG.WanRxDropped, prometheus.CounterValue, wan.RxDropped, labelWan},
+ {u.USG.WanRxErrors, prometheus.CounterValue, wan.RxErrors, labelWan},
+ {u.USG.WanTxPackets, prometheus.CounterValue, wan.TxPackets, labelWan},
+ {u.USG.WanTxBytes, prometheus.CounterValue, wan.TxBytes, labelWan},
+ {u.USG.WanRxBroadcast, prometheus.CounterValue, wan.RxBroadcast, labelWan},
+ {u.USG.WanRxMulticast, prometheus.CounterValue, wan.RxMulticast, labelWan},
+ {u.USG.WanSpeed, prometheus.CounterValue, wan.Speed.Val * 1000000, labelWan},
+ {u.USG.WanTxBroadcast, prometheus.CounterValue, wan.TxBroadcast, labelWan},
+ {u.USG.WanTxBytesR, prometheus.CounterValue, wan.TxBytesR, labelWan},
+ {u.USG.WanTxDropped, prometheus.CounterValue, wan.TxDropped, labelWan},
+ {u.USG.WanTxErrors, prometheus.CounterValue, wan.TxErrors, labelWan},
+ {u.USG.WanTxMulticast, prometheus.CounterValue, wan.TxMulticast, labelWan},
+ {u.USG.WanBytesR, prometheus.GaugeValue, wan.BytesR, labelWan},
+ })
+ }
+}
diff --git a/integrations/inputunifi/promunifi/usw.go b/integrations/inputunifi/promunifi/usw.go
new file mode 100644
index 00000000..88f7e190
--- /dev/null
+++ b/integrations/inputunifi/promunifi/usw.go
@@ -0,0 +1,181 @@
+package promunifi
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "golift.io/unifi"
+)
+
+type usw struct {
+ // Switch "total" traffic stats
+ SwRxPackets *prometheus.Desc
+ SwRxBytes *prometheus.Desc
+ SwRxErrors *prometheus.Desc
+ SwRxDropped *prometheus.Desc
+ SwRxCrypts *prometheus.Desc
+ SwRxFrags *prometheus.Desc
+ SwTxPackets *prometheus.Desc
+ SwTxBytes *prometheus.Desc
+ SwTxErrors *prometheus.Desc
+ SwTxDropped *prometheus.Desc
+ SwTxRetries *prometheus.Desc
+ SwRxMulticast *prometheus.Desc
+ SwRxBroadcast *prometheus.Desc
+ SwTxMulticast *prometheus.Desc
+ SwTxBroadcast *prometheus.Desc
+ SwBytes *prometheus.Desc
+ // Port data.
+ PoeCurrent *prometheus.Desc
+ PoePower *prometheus.Desc
+ PoeVoltage *prometheus.Desc
+ RxBroadcast *prometheus.Desc
+ RxBytes *prometheus.Desc
+ RxBytesR *prometheus.Desc
+ RxDropped *prometheus.Desc
+ RxErrors *prometheus.Desc
+ RxMulticast *prometheus.Desc
+ RxPackets *prometheus.Desc
+ Satisfaction *prometheus.Desc
+ Speed *prometheus.Desc
+ TxBroadcast *prometheus.Desc
+ TxBytes *prometheus.Desc
+ TxBytesR *prometheus.Desc
+ TxDropped *prometheus.Desc
+ TxErrors *prometheus.Desc
+ TxMulticast *prometheus.Desc
+ TxPackets *prometheus.Desc
+}
+
+func descUSW(ns string) *usw {
+ pns := ns + "port_"
+ // labels := []string{"ip", "version", "model", "serial", "type", "mac", "site_name", "name"}
+ labelS := []string{"site_name", "name"} // labels[6:]
+ labelP := []string{"port_num", "port_name", "port_mac", "port_ip", "site_name", "name"}
+ return &usw{
+ SwRxPackets: prometheus.NewDesc(ns+"switch_receive_packets_total", "Switch Packets Received Total", labelS, nil),
+ SwRxBytes: prometheus.NewDesc(ns+"switch_receive_bytes_total", "Switch Bytes Received Total", labelS, nil),
+ SwRxErrors: prometheus.NewDesc(ns+"switch_receive_errors_total", "Switch Errors Received Total", labelS, nil),
+ SwRxDropped: prometheus.NewDesc(ns+"switch_receive_dropped_total", "Switch Dropped Received Total", labelS, nil),
+ SwRxCrypts: prometheus.NewDesc(ns+"switch_receive_crypts_total", "Switch Crypts Received Total", labelS, nil),
+ SwRxFrags: prometheus.NewDesc(ns+"switch_receive_frags_total", "Switch Frags Received Total", labelS, nil),
+ SwTxPackets: prometheus.NewDesc(ns+"switch_transmit_packets_total", "Switch Packets Transmit Total", labelS, nil),
+ SwTxBytes: prometheus.NewDesc(ns+"switch_transmit_bytes_total", "Switch Bytes Transmit Total", labelS, nil),
+ SwTxErrors: prometheus.NewDesc(ns+"switch_transmit_errors_total", "Switch Errors Transmit Total", labelS, nil),
+ SwTxDropped: prometheus.NewDesc(ns+"switch_transmit_dropped_total", "Switch Dropped Transmit Total", labelS, nil),
+ SwTxRetries: prometheus.NewDesc(ns+"switch_transmit_retries_total", "Switch Retries Transmit Total", labelS, nil),
+ SwRxMulticast: prometheus.NewDesc(ns+"switch_receive_multicast_total", "Switch Multicast Receive Total", labelS, nil),
+ SwRxBroadcast: prometheus.NewDesc(ns+"switch_receive_broadcast_total", "Switch Broadcast Receive Total", labelS, nil),
+ SwTxMulticast: prometheus.NewDesc(ns+"switch_transmit_multicast_total", "Switch Multicast Transmit Total", labelS, nil),
+ SwTxBroadcast: prometheus.NewDesc(ns+"switch_transmit_broadcast_total", "Switch Broadcast Transmit Total", labelS, nil),
+ SwBytes: prometheus.NewDesc(ns+"switch_bytes_total", "Switch Bytes Transferred Total", labelS, nil),
+ // per-port data
+ PoeCurrent: prometheus.NewDesc(pns+"poe_amperes", "POE Current", labelP, nil),
+ PoePower: prometheus.NewDesc(pns+"poe_watts", "POE Power", labelP, nil),
+ PoeVoltage: prometheus.NewDesc(pns+"poe_volts", "POE Voltage", labelP, nil),
+ RxBroadcast: prometheus.NewDesc(pns+"receive_broadcast_total", "Receive Broadcast", labelP, nil),
+ RxBytes: prometheus.NewDesc(pns+"receive_bytes_total", "Total Receive Bytes", labelP, nil),
+ RxBytesR: prometheus.NewDesc(pns+"receive_rate_bytes", "Receive Bytes Rate", labelP, nil),
+ RxDropped: prometheus.NewDesc(pns+"receive_dropped_total", "Total Receive Dropped", labelP, nil),
+ RxErrors: prometheus.NewDesc(pns+"receive_errors_total", "Total Receive Errors", labelP, nil),
+ RxMulticast: prometheus.NewDesc(pns+"receive_multicast_total", "Total Receive Multicast", labelP, nil),
+ RxPackets: prometheus.NewDesc(pns+"receive_packets_total", "Total Receive Packets", labelP, nil),
+ Satisfaction: prometheus.NewDesc(pns+"satisfaction_percent", "Satisfaction", labelP, nil),
+ Speed: prometheus.NewDesc(pns+"port_speed_bps", "Speed", labelP, nil),
+ TxBroadcast: prometheus.NewDesc(pns+"transmit_broadcast_total", "Total Transmit Broadcast", labelP, nil),
+ TxBytes: prometheus.NewDesc(pns+"transmit_bytes_total", "Total Transmit Bytes", labelP, nil),
+ TxBytesR: prometheus.NewDesc(pns+"transmit_rate_bytes", "Transmit Bytes Rate", labelP, nil),
+ TxDropped: prometheus.NewDesc(pns+"transmit_dropped_total", "Total Transmit Dropped", labelP, nil),
+ TxErrors: prometheus.NewDesc(pns+"transmit_errors_total", "Total Transmit Errors", labelP, nil),
+ TxMulticast: prometheus.NewDesc(pns+"transmit_multicast_total", "Total Tranmist Multicast", labelP, nil),
+ TxPackets: prometheus.NewDesc(pns+"transmit_packets_total", "Total Transmit Packets", labelP, nil),
+ }
+}
+
+func (u *promUnifi) exportUSW(r report, d *unifi.USW) {
+ labels := []string{d.IP, d.Version, d.Model, d.Serial, d.Type, d.Mac, d.SiteName, d.Name}
+ if d.HasTemperature.Val {
+ r.send([]*metric{{u.Device.Temperature, prometheus.GaugeValue, d.GeneralTemperature, labels}})
+ }
+ if d.HasFan.Val {
+ r.send([]*metric{{u.Device.FanLevel, prometheus.GaugeValue, d.FanLevel, labels}})
+ }
+
+ // Switch System Data.
+ r.send([]*metric{
+ {u.Device.Uptime, prometheus.GaugeValue, d.Uptime, labels},
+ {u.Device.TotalMaxPower, prometheus.GaugeValue, d.TotalMaxPower, labels},
+ {u.Device.TotalTxBytes, prometheus.CounterValue, d.TxBytes, labels},
+ {u.Device.TotalRxBytes, prometheus.CounterValue, d.RxBytes, labels},
+ {u.Device.TotalBytes, prometheus.CounterValue, d.Bytes, labels},
+ {u.Device.NumSta, prometheus.GaugeValue, d.NumSta, labels},
+ {u.Device.UserNumSta, prometheus.GaugeValue, d.UserNumSta, labels},
+ {u.Device.GuestNumSta, prometheus.GaugeValue, d.GuestNumSta, labels},
+ {u.Device.Loadavg1, prometheus.GaugeValue, d.SysStats.Loadavg1, labels},
+ {u.Device.Loadavg5, prometheus.GaugeValue, d.SysStats.Loadavg5, labels},
+ {u.Device.Loadavg15, prometheus.GaugeValue, d.SysStats.Loadavg15, labels},
+ {u.Device.MemUsed, prometheus.GaugeValue, d.SysStats.MemUsed, labels},
+ {u.Device.MemTotal, prometheus.GaugeValue, d.SysStats.MemTotal, labels},
+ {u.Device.MemBuffer, prometheus.GaugeValue, d.SysStats.MemBuffer, labels},
+ {u.Device.CPU, prometheus.GaugeValue, d.SystemStats.CPU, labels},
+ {u.Device.Mem, prometheus.GaugeValue, d.SystemStats.Mem, labels},
+ })
+ u.exportPortTable(r, labels, d.PortTable)
+ u.exportUSWstats(r, labels, d.Stat.Sw)
+}
+
+func (u *promUnifi) exportUSWstats(r report, labels []string, sw *unifi.Sw) {
+ labelS := labels[6:]
+ r.send([]*metric{
+ {u.USW.SwRxPackets, prometheus.CounterValue, sw.RxPackets, labelS},
+ {u.USW.SwRxBytes, prometheus.CounterValue, sw.RxBytes, labelS},
+ {u.USW.SwRxErrors, prometheus.CounterValue, sw.RxErrors, labelS},
+ {u.USW.SwRxDropped, prometheus.CounterValue, sw.RxDropped, labelS},
+ {u.USW.SwRxCrypts, prometheus.CounterValue, sw.RxCrypts, labelS},
+ {u.USW.SwRxFrags, prometheus.CounterValue, sw.RxFrags, labelS},
+ {u.USW.SwTxPackets, prometheus.CounterValue, sw.TxPackets, labelS},
+ {u.USW.SwTxBytes, prometheus.CounterValue, sw.TxBytes, labelS},
+ {u.USW.SwTxErrors, prometheus.CounterValue, sw.TxErrors, labelS},
+ {u.USW.SwTxDropped, prometheus.CounterValue, sw.TxDropped, labelS},
+ {u.USW.SwTxRetries, prometheus.CounterValue, sw.TxRetries, labelS},
+ {u.USW.SwRxMulticast, prometheus.CounterValue, sw.RxMulticast, labelS},
+ {u.USW.SwRxBroadcast, prometheus.CounterValue, sw.RxBroadcast, labelS},
+ {u.USW.SwTxMulticast, prometheus.CounterValue, sw.TxMulticast, labelS},
+ {u.USW.SwTxBroadcast, prometheus.CounterValue, sw.TxBroadcast, labelS},
+ {u.USW.SwBytes, prometheus.CounterValue, sw.Bytes, labelS},
+ })
+}
+
+func (u *promUnifi) exportPortTable(r report, labels []string, pt []unifi.Port) {
+ // Per-port data on a switch
+ for _, p := range pt {
+ if !p.Up.Val {
+ continue
+ }
+ // Copy labels, and add four new ones.
+ labelP := []string{p.PortIdx.Txt, p.Name, p.Mac, p.IP, labels[6], labels[7]}
+ if p.PoeEnable.Val && p.PortPoe.Val {
+ r.send([]*metric{
+ {u.USW.PoeCurrent, prometheus.GaugeValue, p.PoeCurrent, labelP},
+ {u.USW.PoePower, prometheus.GaugeValue, p.PoePower, labelP},
+ {u.USW.PoeVoltage, prometheus.GaugeValue, p.PoeVoltage, labelP},
+ })
+ }
+
+ r.send([]*metric{
+ {u.USW.RxBroadcast, prometheus.CounterValue, p.RxBroadcast, labelP},
+ {u.USW.RxBytes, prometheus.CounterValue, p.RxBytes, labelP},
+ {u.USW.RxBytesR, prometheus.GaugeValue, p.RxBytesR, labelP},
+ {u.USW.RxDropped, prometheus.CounterValue, p.RxDropped, labelP},
+ {u.USW.RxErrors, prometheus.CounterValue, p.RxErrors, labelP},
+ {u.USW.RxMulticast, prometheus.CounterValue, p.RxMulticast, labelP},
+ {u.USW.RxPackets, prometheus.CounterValue, p.RxPackets, labelP},
+ {u.USW.Satisfaction, prometheus.GaugeValue, p.Satisfaction, labelP},
+ {u.USW.Speed, prometheus.GaugeValue, p.Speed.Val * 1000000, labelP},
+ {u.USW.TxBroadcast, prometheus.CounterValue, p.TxBroadcast, labelP},
+ {u.USW.TxBytes, prometheus.CounterValue, p.TxBytes, labelP},
+ {u.USW.TxBytesR, prometheus.GaugeValue, p.TxBytesR, labelP},
+ {u.USW.TxDropped, prometheus.CounterValue, p.TxDropped, labelP},
+ {u.USW.TxErrors, prometheus.CounterValue, p.TxErrors, labelP},
+ {u.USW.TxMulticast, prometheus.CounterValue, p.TxMulticast, labelP},
+ })
+ }
+}
diff --git a/integrations/inputunifi/unifipoller/start.go b/integrations/inputunifi/unifipoller/start.go
deleted file mode 100644
index 334119a1..00000000
--- a/integrations/inputunifi/unifipoller/start.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package unifipoller
-
-import (
- "crypto/tls"
- "fmt"
- "log"
- "os"
- "strings"
- "time"
-
- influx "github.com/influxdata/influxdb1-client/v2"
- "github.com/spf13/pflag"
- "golift.io/unifi"
-)
-
-// Start begins the application from a CLI.
-// Parses flags, parses config and executes Run().
-func Start() error {
- log.SetFlags(log.LstdFlags)
- up := &UnifiPoller{
- Flag: &Flag{},
- Config: &Config{
- // Preload our defaults.
- InfluxURL: defaultInfluxURL,
- InfluxUser: defaultInfluxUser,
- InfluxPass: defaultInfluxPass,
- InfluxDB: defaultInfluxDB,
- UnifiUser: defaultUnifiUser,
- UnifiPass: os.Getenv("UNIFI_PASSWORD"), // deprecated name.
- UnifiBase: defaultUnifiURL,
- Interval: Duration{defaultInterval},
- Sites: []string{"all"},
- }}
- up.Flag.Parse(os.Args[1:])
- if up.Flag.ShowVer {
- fmt.Printf("unifi-poller v%s\n", Version)
- return nil // don't run anything else w/ version request.
- }
- if up.Flag.DumpJSON == "" { // do not print this when dumping JSON.
- up.Logf("Loading Configuration File: %s", up.Flag.ConfigFile)
- }
- // Parse config file.
- if err := up.Config.ParseFile(up.Flag.ConfigFile); err != nil {
- up.Flag.Usage()
- return err
- }
- // Update Config with ENV variable overrides.
- if err := up.Config.ParseENV(); err != nil {
- return err
- }
- return up.Run()
-}
-
-// Parse turns CLI arguments into data structures. Called by Start() on startup.
-func (f *Flag) Parse(args []string) {
- f.FlagSet = pflag.NewFlagSet("unifi-poller", pflag.ExitOnError)
- f.Usage = func() {
- fmt.Println("Usage: unifi-poller [--config=/path/to/up.conf] [--version]")
- f.PrintDefaults()
- }
- f.StringVarP(&f.DumpJSON, "dumpjson", "j", "",
- "This debug option prints a json payload and exits. See man page for more info.")
- f.StringVarP(&f.ConfigFile, "config", "c", DefaultConfFile, "Poller config file path.")
- f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit.")
- _ = f.FlagSet.Parse(args) // pflag.ExitOnError means this will never return error.
-}
-
-// Run invokes all the application logic and routines.
-func (u *UnifiPoller) Run() (err error) {
- if u.Flag.DumpJSON != "" {
- return u.DumpJSONPayload()
- }
- if u.Config.Debug {
- log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
- u.LogDebugf("Debug Logging Enabled")
- }
- log.Printf("[INFO] UniFi Poller v%v Starting Up! PID: %d", Version, os.Getpid())
- if err = u.GetUnifi(); err != nil {
- return 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)
- if err = u.GetInfluxDB(); err != nil {
- return err
- }
- u.Logf("Logging Measurements to InfluxDB at %s as user %s", u.Config.InfluxURL, u.Config.InfluxUser)
- switch strings.ToLower(u.Config.Mode) {
- case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda":
- u.LogDebugf("Lambda Mode Enabled")
- u.LastCheck = time.Now()
- return u.CollectAndReport()
- default:
- return u.PollController()
- }
-}
-
-// GetInfluxDB returns an InfluxDB interface.
-func (u *UnifiPoller) GetInfluxDB() (err error) {
- u.Influx, err = influx.NewHTTPClient(influx.HTTPConfig{
- Addr: u.Config.InfluxURL,
- Username: u.Config.InfluxUser,
- Password: u.Config.InfluxPass,
- TLSConfig: &tls.Config{InsecureSkipVerify: u.Config.InfxBadSSL},
- })
- if err != nil {
- return fmt.Errorf("influxdb: %v", err)
- }
- return nil
-}
-
-// GetUnifi returns a UniFi controller interface.
-func (u *UnifiPoller) GetUnifi() (err error) {
- // Create an authenticated session to the Unifi Controller.
- u.Unifi, err = unifi.NewUnifi(&unifi.Config{
- User: u.Config.UnifiUser,
- Pass: u.Config.UnifiPass,
- URL: u.Config.UnifiBase,
- VerifySSL: u.Config.VerifySSL,
- ErrorLog: u.LogErrorf, // Log all errors.
- DebugLog: u.LogDebugf, // Log debug messages.
- })
- if err != nil {
- return fmt.Errorf("unifi controller: %v", err)
- }
- u.LogDebugf("Authenticated with controller successfully")
- return u.CheckSites()
-}
diff --git a/integrations/inputunifi/unifipoller/unifi.go b/integrations/inputunifi/unifipoller/unifi.go
deleted file mode 100644
index 57fd8c51..00000000
--- a/integrations/inputunifi/unifipoller/unifi.go
+++ /dev/null
@@ -1,250 +0,0 @@
-package unifipoller
-
-import (
- "fmt"
- "log"
- "strings"
- "time"
-
- influx "github.com/influxdata/influxdb1-client/v2"
- "golift.io/unifi"
-)
-
-// CheckSites makes sure the list of provided sites exists on the controller.
-// This does not run in Lambda (run-once) mode.
-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 {
- if s == site.Name {
- continue FIRST
- }
- }
- // This is fine, it may get added later.
- u.LogErrorf("configured site not found on controller: %v", s)
- }
- return nil
-}
-
-// PollController runs forever, polling UniFi, and pushing to influx.
-// This is started by Run() after everything checks out.
-func (u *UnifiPoller) PollController() error {
- interval := u.Config.Interval.Round(time.Second)
- log.Println("[INFO] Everything checks out! Poller started, interval:", interval)
- ticker := time.NewTicker(interval)
- for u.LastCheck = range ticker.C {
- var err error
- if u.Config.ReAuth {
- u.LogDebugf("Re-authenticating to UniFi Controller")
- // Some users need to re-auth every interval because the cookie times out.
- if err = u.Unifi.Login(); err != nil {
- u.LogError(err, "re-authenticating")
- }
- }
- if err == nil {
- // Only run this if the authentication procedure didn't return error.
- _ = u.CollectAndReport()
- }
- if u.errorCount > 0 {
- return fmt.Errorf("controller or influxdb errors, stopping poller")
- }
- }
- return nil
-}
-
-// CollectAndReport collects measurements and reports them to influxdb.
-// Can be called once or in a ticker loop. This function and all the ones below
-// handle their own logging. An error is returned so the calling function may
-// determine if there was a read or write error and act on it. This is currently
-// called in two places in this library. One returns an error, one does not.
-func (u *UnifiPoller) CollectAndReport() error {
- metrics, err := u.CollectMetrics()
- if err != nil {
- return err
- }
- if err := u.AugmentMetrics(metrics); err != nil {
- return err
- }
- err = u.ReportMetrics(metrics)
- u.LogError(err, "reporting metrics")
- return err
-}
-
-// CollectMetrics grabs all the measurements from a UniFi controller and returns them.
-// This also creates an InfluxDB writer, and returns an error if that fails.
-func (u *UnifiPoller) CollectMetrics() (*Metrics, error) {
- m := &Metrics{TS: u.LastCheck} // At this point, it's the Current Check.
- var err error
- // Get the sites we care about.
- m.Sites, err = u.GetFilteredSites()
- u.LogError(err, "unifi.GetSites()")
- if u.Config.CollectIDS {
- // Check back in time since twice the interval. Dups are discarded by InfluxDB.
- m.IDSList, err = u.Unifi.GetIDS(m.Sites, time.Now().Add(2*u.Config.Interval.Duration), time.Now())
- u.LogError(err, "unifi.GetIDS()")
- }
- // 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()")
- // Make a new Influx Points Batcher.
- m.BatchPoints, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.Config.InfluxDB})
- u.LogError(err, "influx.NewBatchPoints")
- return m, err
-}
-
-// AugmentMetrics is our middleware layer between collecting metrics and writing them.
-// This is where we can manipuate the returned data or make arbitrary decisions.
-// This function currently adds parent device names to client metrics.
-func (u *UnifiPoller) AugmentMetrics(metrics *Metrics) error {
- if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
- return fmt.Errorf("nil metrics, augment impossible")
- }
- 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]
- metrics.Clients[i].ApName = devices[c.ApMac]
- metrics.Clients[i].GwName = devices[c.GwMac]
- metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
- }
- return nil
-}
-
-// ReportMetrics batches all the metrics and writes them to InfluxDB.
-// Returns an error if the write to influx fails.
-func (u *UnifiPoller) ReportMetrics(metrics *Metrics) error {
- // Batch (and send) all the points.
- for _, err := range metrics.ProcessPoints() {
- u.LogError(err, "asset.Points()")
- }
- err := u.Influx.Write(metrics.BatchPoints)
- if err != nil {
- return fmt.Errorf("influxdb.Write(points): %v", err)
- }
- var fields, points int
- for _, p := range metrics.Points() {
- points++
- i, _ := p.Fields()
- fields += len(i)
- }
- idsMsg := ""
- if u.Config.CollectIDS {
- idsMsg = fmt.Sprintf("IDS Events: %d, ", len(metrics.IDSList))
- }
- u.Logf("UniFi Measurements Recorded. Sites: %d, Clients: %d, "+
- "Wireless APs: %d, Gateways: %d, Switches: %d, %sPoints: %d, Fields: %d",
- len(metrics.Sites), len(metrics.Clients), len(metrics.UAPs),
- len(metrics.UDMs)+len(metrics.USGs), len(metrics.USWs), idsMsg, points, fields)
- return nil
-}
-
-// ProcessPoints batches all device and client data into influxdb data points.
-// Call this after you've collected all the data you care about.
-// This function is sorta weird and returns a slice of errors. The reasoning is
-// that some points may process while others fail, so we attempt to process them
-// all. This is (usually) run in a loop, so we can't really exit on error,
-// we just log the errors and tally them on a counter. In reality, this never
-// returns any errors because we control the data going in; cool right? But we
-// still check&log it in case the data going is skewed up and causes errors!
-func (m *Metrics) ProcessPoints() []error {
- errs := []error{}
- processPoints := func(m *Metrics, p []*influx.Point, err error) {
- switch {
- case err != nil:
- errs = append(errs, err)
- case p == nil:
- default:
- m.BatchPoints.AddPoints(p)
- }
- }
-
- for _, asset := range m.Sites {
- pts, err := SitePoints(asset, m.TS)
- processPoints(m, pts, err)
- }
- for _, asset := range m.Clients {
- pts, err := ClientPoints(asset, m.TS)
- processPoints(m, pts, err)
- }
- for _, asset := range m.IDSList {
- pts, err := IDSPoints(asset) // no m.TS.
- processPoints(m, pts, err)
- }
-
- if m.Devices == nil {
- return errs
- }
- for _, asset := range m.Devices.UAPs {
- pts, err := UAPPoints(asset, m.TS)
- processPoints(m, pts, err)
- }
- for _, asset := range m.Devices.USGs {
- pts, err := USGPoints(asset, m.TS)
- processPoints(m, pts, err)
- }
- for _, asset := range m.Devices.USWs {
- pts, err := USWPoints(asset, m.TS)
- processPoints(m, pts, err)
- }
- for _, asset := range m.Devices.UDMs {
- pts, err := UDMPoints(asset, m.TS)
- processPoints(m, pts, err)
- }
- return errs
-}
-
-// GetFilteredSites returns a list of sites to fetch data for.
-// Omits requested but unconfigured sites. Grabs the full list from the
-// controller and returns the sites provided in the config file.
-func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) {
- 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) {
- sites[i] = s
- i++
- }
- }
- return sites[:i], nil
-}