From dca41ce6a39230b370ea3e52cdf9325c6b2bc8e9 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 14 Jun 2019 21:13:55 -0700 Subject: [PATCH 1/5] Add site metrics to influxdb. --- Gopkg.lock | 6 +-- pkg/unifi-poller/config.go | 11 +++- pkg/unifi-poller/jsondebug.go | 19 ++++++- pkg/unifi-poller/unifi.go | 95 +++++++++++++++++---------------- pkg/unifi-poller/unifipoller.go | 6 ++- 5 files changed, 86 insertions(+), 51 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 50d3fbde..f43a439c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,12 +2,12 @@ [[projects]] - digest = "1:fda9365965d38b80007d47efbf516adbc65d266515f263e9c43336ab47ef0f69" + digest = "1:f42822e830b569d8527ad6e57585e6ccc094296bc9d648ccd256f95249453ce1" name = "github.com/golift/unifi" packages = ["."] pruneopts = "UT" - revision = "1d74eaae61aad0558126b6ade64753ebcc5982ef" - version = "v2.0.4" + revision = "fc5a69d27d7527038ae55ee5112bc755db380879" + version = "v2.1.0" [[projects]] branch = "master" diff --git a/pkg/unifi-poller/config.go b/pkg/unifi-poller/config.go index 98918935..53769af5 100644 --- a/pkg/unifi-poller/config.go +++ b/pkg/unifi-poller/config.go @@ -1,6 +1,7 @@ package unifipoller import ( + "strings" "time" "github.com/golift/unifi" @@ -39,6 +40,14 @@ type UnifiPoller struct { *Config } +// Metrics contains all the data from the controller. +type Metrics struct { + unifi.Sites + unifi.Clients + *unifi.Devices + influx.BatchPoints +} + // Config represents the data needed to poll a controller and report to influxdb. type Config struct { Interval Dur `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval"` @@ -60,7 +69,7 @@ type Dur struct{ value time.Duration } // UnmarshalTOML parses a duration type from a config file. func (v *Dur) UnmarshalTOML(data []byte) error { - unquoted := string(data[1 : len(data)-1]) + unquoted := strings.Trim(string(data), `"`) dur, err := time.ParseDuration(unquoted) if err == nil { v.value = dur diff --git a/pkg/unifi-poller/jsondebug.go b/pkg/unifi-poller/jsondebug.go index 66cf6a5f..a02f2730 100644 --- a/pkg/unifi-poller/jsondebug.go +++ b/pkg/unifi-poller/jsondebug.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "github.com/golift/unifi" "github.com/pkg/errors" @@ -24,13 +25,15 @@ func (u *UnifiPoller) DumpJSONPayload() (err error) { fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...) } // Log all errors to stderr. - switch sites, err := u.filterSites(u.Sites); { + switch sites, err := u.GetFilteredSites(); { case err != nil: return err case StringInSlice(u.DumpJSON, []string{"d", "device", "devices"}): return u.DumpDeviceJSON(sites) case StringInSlice(u.DumpJSON, []string{"client", "clients", "c"}): return u.DumpClientsJSON(sites) + case strings.HasPrefix(u.DumpJSON, "other "): + return u.DumpOtherJSON(sites) default: return errors.New("must provide filter: devices, clients") } @@ -58,6 +61,20 @@ func (u *UnifiPoller) DumpDeviceJSON(sites []unifi.Site) error { return nil } +// DumpOtherJSON prints the raw json for a user-provided path in a Unifi Controller. +func (u *UnifiPoller) DumpOtherJSON(sites []unifi.Site) error { + for _, s := range sites { + path := strings.SplitN(u.DumpJSON, " ", 2)[1] + if strings.Contains(path, "%s") { + path = fmt.Sprintf(path, s.Name) + } + if err := u.dumpJSON(path, "Other", s); err != nil { + return err + } + } + return nil +} + func (u *UnifiPoller) dumpJSON(path, what string, site unifi.Site) error { req, err := u.UniReq(path, "") if err != nil { diff --git a/pkg/unifi-poller/unifi.go b/pkg/unifi-poller/unifi.go index d06e1b9a..4014f8f1 100644 --- a/pkg/unifi-poller/unifi.go +++ b/pkg/unifi-poller/unifi.go @@ -40,95 +40,100 @@ FIRST: func (u *UnifiPoller) PollController() { log.Println("[INFO] Everything checks out! Poller started, interval:", u.Interval.value) ticker := time.NewTicker(u.Interval.value) - + var err error for range ticker.C { + m := &Metrics{} // Get the sites we care about. - sites, err := u.filterSites(u.Sites) - if err != nil { + if m.Sites, err = u.GetFilteredSites(); err != nil { logErrors([]error{err}, "uni.GetSites()") } // Get all the points. - clients, err := u.GetClients(sites) - if err != nil { + if m.Clients, err = u.GetClients(m.Sites); err != nil { logErrors([]error{err}, "uni.GetClients()") } - devices, err := u.GetDevices(sites) - if err != nil { + if m.Devices, err = u.GetDevices(m.Sites); err != nil { logErrors([]error{err}, "uni.GetDevices()") } + // Make a new Points Batcher. - bp, err := influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.InfluxDB}) + m.BatchPoints, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.InfluxDB}) if err != nil { logErrors([]error{err}, "influx.NewBatchPoints") continue } // Batch (and send) all the points. - if errs := batchPoints(devices, clients, bp); errs != nil && hasErr(errs) { + if errs := m.SendPoints(); errs != nil && hasErr(errs) { logErrors(errs, "asset.Points()") } - if err := u.Write(bp); err != nil { + if err := u.Write(m.BatchPoints); err != nil { logErrors([]error{err}, "infdb.Write(bp)") } // Talk about the data. var fieldcount, pointcount int - for _, p := range bp.Points() { + for _, p := range m.Points() { pointcount++ i, _ := p.Fields() fieldcount += len(i) } - u.Logf("Unifi Measurements Recorded. Sites: %d Clients: %d, "+ + u.Logf("Unifi Measurements Recorded. Sites: %d, Clients: %d, "+ "Wireless APs: %d, Gateways: %d, Switches: %d, Points: %d, Fields: %d", - len(sites), len(clients.UCLs), - len(devices.UAPs), len(devices.USGs), len(devices.USWs), pointcount, fieldcount) + len(m.Sites), len(m.Clients), len(m.UAPs), len(m.USGs), len(m.USWs), pointcount, fieldcount) } } -// batchPoints combines all device and client data into influxdb data points. -func batchPoints(devices *unifi.Devices, clients *unifi.Clients, bp influx.BatchPoints) (errs []error) { - process := func(asset Asset) error { - if asset == nil { - return nil - } - influxPoints, err := asset.Points() - if err != nil { - return err - } - bp.AddPoints(influxPoints) - return nil +// SendPoints combines all device and client data into influxdb data points. +// Call this after you've collected all the data you care about. +// This sends all the batched points to InfluxDB. +func (m *Metrics) SendPoints() (errs []error) { + for _, asset := range m.Sites { + errs = append(errs, m.processPoints(asset)) } - if devices != nil { - for _, asset := range devices.UAPs { - errs = append(errs, process(asset)) - } - for _, asset := range devices.USGs { - errs = append(errs, process(asset)) - } - for _, asset := range devices.USWs { - errs = append(errs, process(asset)) - } + for _, asset := range m.Clients { + errs = append(errs, m.processPoints(asset)) } - if clients != nil { - for _, asset := range clients.UCLs { - errs = append(errs, process(asset)) - } + if m.Devices == nil { + return + } + for _, asset := range m.UAPs { + errs = append(errs, m.processPoints(asset)) + } + for _, asset := range m.USGs { + errs = append(errs, m.processPoints(asset)) + } + for _, asset := range m.USWs { + errs = append(errs, m.processPoints(asset)) } return } -// filterSites returns a list of sites to fetch data for. -// Omits requested but unconfigured sites. -func (u *UnifiPoller) filterSites(filter []string) ([]unifi.Site, error) { +// processPoints is helper function for SendPoints. +func (m *Metrics) processPoints(asset Asset) error { + if asset == nil { + return nil + } + influxPoints, err := asset.Points() + if err != nil || influxPoints == nil { + return err + } + m.BatchPoints.AddPoints(influxPoints) + return nil +} + +// GetFilteredSites returns a list of sites to fetch data for. +// Omits requested but unconfigured sites. Grabs the full list from the +// controller and filters the sites provided in the config file. +func (u *UnifiPoller) GetFilteredSites() (unifi.Sites, error) { sites, err := u.GetSites() if err != nil { return nil, err - } else if len(filter) < 1 || StringInSlice("all", filter) { + } else if len(u.Sites) < 1 || StringInSlice("all", u.Sites) { return sites, nil } var i int for _, s := range sites { // Only include valid sites in the request filter. - if StringInSlice(s.Name, filter) { + if StringInSlice(s.Name, u.Sites) { sites[i] = s i++ } diff --git a/pkg/unifi-poller/unifipoller.go b/pkg/unifi-poller/unifipoller.go index 594dcbc6..4d754f34 100644 --- a/pkg/unifi-poller/unifipoller.go +++ b/pkg/unifi-poller/unifipoller.go @@ -101,7 +101,11 @@ func (u *UnifiPoller) GetUnifi() (err error) { if u.Debug && !u.Quiet { u.Unifi.DebugLog = log.Printf // Log debug messages. } - u.Logf("Authenticated to Unifi Controller at %s as user %s", u.UnifiBase, u.UnifiUser) + v, err := u.GetServer() + if err != nil { + v.ServerVersion = "unknown" + } + u.Logf("Authenticated to Unifi Controller at %s version %s as user %s", u.UnifiBase, v.ServerVersion, u.UnifiUser) if err = u.CheckSites(); err != nil { return err } From 320fc64533cdd370b9a0417046a41d20975ad166 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 14 Jun 2019 21:34:47 -0700 Subject: [PATCH 2/5] rename method --- pkg/unifi-poller/unifi.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/unifi-poller/unifi.go b/pkg/unifi-poller/unifi.go index 4014f8f1..1225bf0c 100644 --- a/pkg/unifi-poller/unifi.go +++ b/pkg/unifi-poller/unifi.go @@ -112,11 +112,11 @@ func (m *Metrics) processPoints(asset Asset) error { if asset == nil { return nil } - influxPoints, err := asset.Points() - if err != nil || influxPoints == nil { + points, err := asset.Points() + if err != nil || points == nil { return err } - m.BatchPoints.AddPoints(influxPoints) + m.BatchPoints.AddPoints(points) return nil } From df9fb0a6d687cd84c7431f542634bf79fdfbc456 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 14 Jun 2019 21:51:36 -0700 Subject: [PATCH 3/5] Add description. --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 345afe00..d9eb07f7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,24 @@ Collect your Unifi Controller Data and send it to an InfluxDB instance. Grafana dashboards included. Updated 2019. +## Description + +Ubiquiti makes networking devices like switches, gateways (routers) and wireless +access points. They have a line of equipment named UniFi that uses a controller +to keep stats and make it easy to configure the network devices. This controller +can be installed on Windows, macOS and Linux. Ubiquiti also provides a dedicated +hardware devices called a CloudKey that runs the controller software. + +Unifi-Poller is a smaller application that runs on macOS, Linux or Docker. It +polls your controller every 30 seconds for metric data and stores that data in +an Influx Database. A small setup with 2 APs, 1 switch, 1 gateway and 40 clients +produces nearly 3000 fields (metrics). + +This application requires your controller to be running all the time. If you run +a Unifi Controller, there's no excuse not to install Influx, Grafana and this app. +You'll have a plethora of data at your fingertips and the ability to craft custom +graphs to slice the data any way you choose. Good luck! + ## Installation [See the Wiki!](https://github.com/davidnewhall/unifi-poller/wiki/Installation) From 70b0929025a9dc8f438ac5ca07f305aa0bfdb5ce Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 14 Jun 2019 21:56:14 -0700 Subject: [PATCH 4/5] Add links to description. --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d9eb07f7..da1d454d 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ Grafana dashboards included. Updated 2019. ## Description -Ubiquiti makes networking devices like switches, gateways (routers) and wireless -access points. They have a line of equipment named UniFi that uses a controller -to keep stats and make it easy to configure the network devices. This controller -can be installed on Windows, macOS and Linux. Ubiquiti also provides a dedicated -hardware devices called a CloudKey that runs the controller software. +[Ubiquiti](https://www.ui.com) makes networking devices like switches, gateways +(routers) and wireless access points. They have a line of equipment named +[UniFi](https://www.ui.com/products/#unifi) that uses a +[controller](https://www.ui.com/download/unifi/) to keep stats and network +device configuration This controller can be installed on Windows, macOS and Linux. +Ubiquiti also provides a dedicated hardware devices called a CloudKey that runs +the controller software. Unifi-Poller is a smaller application that runs on macOS, Linux or Docker. It polls your controller every 30 seconds for metric data and stores that data in @@ -17,7 +19,9 @@ an Influx Database. A small setup with 2 APs, 1 switch, 1 gateway and 40 clients produces nearly 3000 fields (metrics). This application requires your controller to be running all the time. If you run -a Unifi Controller, there's no excuse not to install Influx, Grafana and this app. +a Unifi Controller, there's no excuse not to install +[Influx](https://github.com/davidnewhall/unifi-poller/wiki/InfluxDB), +[Grafana](https://github.com/davidnewhall/unifi-poller/wiki/Grafana) and this app. You'll have a plethora of data at your fingertips and the ability to craft custom graphs to slice the data any way you choose. Good luck! From 951c7113548db807e0dc33c9f9560c93fddbb5ac Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Fri, 14 Jun 2019 21:59:19 -0700 Subject: [PATCH 5/5] one more link --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index da1d454d..999283f0 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ Grafana dashboards included. Updated 2019. [Ubiquiti](https://www.ui.com) makes networking devices like switches, gateways (routers) and wireless access points. They have a line of equipment named [UniFi](https://www.ui.com/products/#unifi) that uses a -[controller](https://www.ui.com/download/unifi/) to keep stats and network -device configuration This controller can be installed on Windows, macOS and Linux. -Ubiquiti also provides a dedicated hardware devices called a CloudKey that runs -the controller software. +[controller](https://www.ui.com/download/unifi/) to keep stats and simplify network +device configuration. This controller can be installed on Windows, macOS and Linux. +Ubiquiti also provides a dedicated hardware device called a +[CloudKey](https://www.ui.com/unifi/unifi-cloud-key/) that runs the controller software. Unifi-Poller is a smaller application that runs on macOS, Linux or Docker. It polls your controller every 30 seconds for metric data and stores that data in