From 9eeeaebabd8dc5c56f252ab84e08a8a5a6572c05 Mon Sep 17 00:00:00 2001 From: davidnewhall2 Date: Sun, 1 Dec 2019 00:43:57 -0800 Subject: [PATCH] fixes --- pkg/influxunifi/clients.go | 2 + pkg/influxunifi/uap.go | 188 +++++++++++++++++++------------------ pkg/influxunifi/udm.go | 5 +- pkg/influxunifi/usg.go | 30 +++--- pkg/influxunifi/usw.go | 8 +- pkg/poller/influx.go | 6 ++ pkg/poller/start.go | 41 ++++---- 7 files changed, 151 insertions(+), 129 deletions(-) diff --git a/pkg/influxunifi/clients.go b/pkg/influxunifi/clients.go index f954da17..127ab7e2 100644 --- a/pkg/influxunifi/clients.go +++ b/pkg/influxunifi/clients.go @@ -37,6 +37,8 @@ func (u *InfluxUnifi) batchClient(r report, s *unifi.Client) { "ip": s.IP, "essid": s.Essid, "bssid": s.Bssid, + "channel": s.Channel.Val, + "hostname": s.Name, "radio_desc": s.RadioDescription, "satisfaction": s.Satisfaction.Val, "bytes_r": s.BytesR, diff --git a/pkg/influxunifi/uap.go b/pkg/influxunifi/uap.go index dc7c6214..81d4be17 100644 --- a/pkg/influxunifi/uap.go +++ b/pkg/influxunifi/uap.go @@ -31,7 +31,9 @@ func (u *InfluxUnifi) batchUAP(r report, s *unifi.UAP) { fields["guest-num_sta"] = int(s.GuestNumSta.Val) fields["num_sta"] = s.NumSta.Val r.send(&metric{Table: "uap", Tags: tags, Fields: fields}) - u.processVAPs(r, tags, s.VapTable, s.RadioTable, s.RadioTableStats) + u.processRadTable(r, tags, s.RadioTable, s.RadioTableStats) + u.processVAPTable(r, tags, s.VapTable) + } func (u *InfluxUnifi) processUAPstats(ap *unifi.Ap) map[string]interface{} { @@ -72,99 +74,101 @@ func (u *InfluxUnifi) processUAPstats(ap *unifi.Ap) map[string]interface{} { } } -// processVAPs creates points for Wifi Radios. This works with several types of UAP-capable devices. -func (u *InfluxUnifi) processVAPs(r report, tags map[string]string, vt unifi.VapTable, rt unifi.RadioTable, rts unifi.RadioTableStats) { - // Loop each virtual AP (ESSID) and extract data for it - // from radio_tables and radio_table_stats. +// processVAPTable creates points for Wifi Radios. This works with several types of UAP-capable devices. +func (u *InfluxUnifi) processVAPTable(r report, t map[string]string, vt unifi.VapTable) { for _, s := range vt { - t := make(map[string]string) // tags - f := make(map[string]interface{}) // fields - t["device_name"] = tags["name"] - t["site_name"] = tags["site_name"] - t["ap_mac"] = s.ApMac - t["bssid"] = s.Bssid - t["id"] = s.ID - t["name"] = s.Name - t["radio_name"] = s.RadioName - t["essid"] = s.Essid - t["site_id"] = s.SiteID - t["usage"] = s.Usage - t["state"] = s.State - t["is_guest"] = s.IsGuest.Txt - - f["ccq"] = s.Ccq - f["mac_filter_rejections"] = s.MacFilterRejections - f["num_satisfaction_sta"] = s.NumSatisfactionSta.Val - f["avg_client_signal"] = s.AvgClientSignal.Val - f["satisfaction"] = s.Satisfaction.Val - f["satisfaction_now"] = s.SatisfactionNow.Val - f["rx_bytes"] = s.RxBytes.Val - f["rx_crypts"] = s.RxCrypts.Val - f["rx_dropped"] = s.RxDropped.Val - f["rx_errors"] = s.RxErrors.Val - f["rx_frags"] = s.RxFrags.Val - f["rx_nwids"] = s.RxNwids.Val - f["rx_packets"] = s.RxPackets.Val - f["tx_bytes"] = s.TxBytes.Val - f["tx_dropped"] = s.TxDropped.Val - f["tx_errors"] = s.TxErrors.Val - f["tx_packets"] = s.TxPackets.Val - f["tx_power"] = s.TxPower.Val - f["tx_retries"] = s.TxRetries.Val - f["tx_combined_retries"] = s.TxCombinedRetries.Val - f["tx_data_mpdu_bytes"] = s.TxDataMpduBytes.Val - f["tx_rts_retries"] = s.TxRtsRetries.Val - f["tx_success"] = s.TxSuccess.Val - f["tx_total"] = s.TxTotal.Val - f["tx_tcp_goodbytes"] = s.TxTCPStats.Goodbytes.Val - f["tx_tcp_lat_avg"] = s.TxTCPStats.LatAvg.Val - f["tx_tcp_lat_max"] = s.TxTCPStats.LatMax.Val - f["tx_tcp_lat_min"] = s.TxTCPStats.LatMin.Val - f["rx_tcp_goodbytes"] = s.RxTCPStats.Goodbytes.Val - f["rx_tcp_lat_avg"] = s.RxTCPStats.LatAvg.Val - f["rx_tcp_lat_max"] = s.RxTCPStats.LatMax.Val - f["rx_tcp_lat_min"] = s.RxTCPStats.LatMin.Val - f["wifi_tx_latency_mov_avg"] = s.WifiTxLatencyMov.Avg.Val - f["wifi_tx_latency_mov_max"] = s.WifiTxLatencyMov.Max.Val - f["wifi_tx_latency_mov_min"] = s.WifiTxLatencyMov.Min.Val - f["wifi_tx_latency_mov_total"] = s.WifiTxLatencyMov.Total.Val - f["wifi_tx_latency_mov_cuont"] = s.WifiTxLatencyMov.TotalCount.Val - - // XXX: This is busted. It needs its own table.... - for _, p := range rt { - if p.Name != s.RadioName { - continue - } - t["channel"] = p.Channel.Txt - t["radio"] = p.Radio - f["current_antenna_gain"] = p.CurrentAntennaGain.Val - f["ht"] = p.Ht.Txt - f["max_txpower"] = p.MaxTxpower.Val - f["min_txpower"] = p.MinTxpower.Val - f["nss"] = p.Nss.Val - f["radio_caps"] = p.RadioCaps.Val - f["tx_power"] = p.TxPower.Val + tags := map[string]string{ + "device_name": t["name"], + "site_name": t["site_name"], + "ap_mac": s.ApMac, + "bssid": s.Bssid, + "id": s.ID, + "name": s.Name, + "radio_name": s.RadioName, + "essid": s.Essid, + "site_id": s.SiteID, + "usage": s.Usage, + "state": s.State, + "is_guest": s.IsGuest.Txt, } - - for _, p := range rts { - if p.Name != s.RadioName { - continue - } - f["ast_be_xmit"] = p.AstBeXmit.Val - f["channel"] = p.Channel.Val - f["cu_self_rx"] = p.CuSelfRx.Val - f["cu_self_tx"] = p.CuSelfTx.Val - f["cu_total"] = p.CuTotal.Val - f["extchannel"] = p.Extchannel.Val - f["gain"] = p.Gain.Val - f["guest-num_sta"] = p.GuestNumSta.Val - f["num_sta"] = p.NumSta.Val - f["radio"] = p.Radio - f["tx_packets"] = p.TxPackets.Val - f["tx_power"] = p.TxPower.Val - f["tx_retries"] = p.TxRetries.Val - f["user-num_sta"] = p.UserNumSta.Val + fields := map[string]interface{}{ + "ccq": s.Ccq, + "mac_filter_rejections": s.MacFilterRejections, + "num_satisfaction_sta": s.NumSatisfactionSta.Val, + "avg_client_signal": s.AvgClientSignal.Val, + "satisfaction": s.Satisfaction.Val, + "satisfaction_now": s.SatisfactionNow.Val, + "rx_bytes": s.RxBytes.Val, + "rx_crypts": s.RxCrypts.Val, + "rx_dropped": s.RxDropped.Val, + "rx_errors": s.RxErrors.Val, + "rx_frags": s.RxFrags.Val, + "rx_nwids": s.RxNwids.Val, + "rx_packets": s.RxPackets.Val, + "tx_bytes": s.TxBytes.Val, + "tx_dropped": s.TxDropped.Val, + "tx_errors": s.TxErrors.Val, + "tx_packets": s.TxPackets.Val, + "tx_power": s.TxPower.Val, + "tx_retries": s.TxRetries.Val, + "tx_combined_retries": s.TxCombinedRetries.Val, + "tx_data_mpdu_bytes": s.TxDataMpduBytes.Val, + "tx_rts_retries": s.TxRtsRetries.Val, + "tx_success": s.TxSuccess.Val, + "tx_total": s.TxTotal.Val, + "tx_tcp_goodbytes": s.TxTCPStats.Goodbytes.Val, + "tx_tcp_lat_avg": s.TxTCPStats.LatAvg.Val, + "tx_tcp_lat_max": s.TxTCPStats.LatMax.Val, + "tx_tcp_lat_min": s.TxTCPStats.LatMin.Val, + "rx_tcp_goodbytes": s.RxTCPStats.Goodbytes.Val, + "rx_tcp_lat_avg": s.RxTCPStats.LatAvg.Val, + "rx_tcp_lat_max": s.RxTCPStats.LatMax.Val, + "rx_tcp_lat_min": s.RxTCPStats.LatMin.Val, + "wifi_tx_latency_mov_avg": s.WifiTxLatencyMov.Avg.Val, + "wifi_tx_latency_mov_max": s.WifiTxLatencyMov.Max.Val, + "wifi_tx_latency_mov_min": s.WifiTxLatencyMov.Min.Val, + "wifi_tx_latency_mov_total": s.WifiTxLatencyMov.Total.Val, + "wifi_tx_latency_mov_cuont": s.WifiTxLatencyMov.TotalCount.Val, } - r.send(&metric{Table: "uap_vaps", Tags: t, Fields: f}) + r.send(&metric{Table: "uap_vaps", Tags: tags, Fields: fields}) + } +} + +func (u *InfluxUnifi) processRadTable(r report, t map[string]string, rt unifi.RadioTable, rts unifi.RadioTableStats) { + for _, p := range rt { + tags := map[string]string{ + "device_name": t["name"], + "site_name": t["site_name"], + "channel": p.Channel.Txt, + "radio": p.Radio, + } + fields := map[string]interface{}{ + "current_antenna_gain": p.CurrentAntennaGain.Val, + "ht": p.Ht.Txt, + "max_txpower": p.MaxTxpower.Val, + "min_txpower": p.MinTxpower.Val, + "nss": p.Nss.Val, + "radio_caps": p.RadioCaps.Val, + } + for _, t := range rts { + if t.Name == p.Name { + fields["ast_be_xmit"] = t.AstBeXmit.Val + fields["channel"] = t.Channel.Val + fields["cu_self_rx"] = t.CuSelfRx.Val + fields["cu_self_tx"] = t.CuSelfTx.Val + fields["cu_total"] = t.CuTotal.Val + fields["extchannel"] = t.Extchannel.Val + fields["gain"] = t.Gain.Val + fields["guest-num_sta"] = t.GuestNumSta.Val + fields["num_sta"] = t.NumSta.Val + fields["radio"] = t.Radio + fields["tx_packets"] = t.TxPackets.Val + fields["tx_power"] = t.TxPower.Val + fields["tx_retries"] = t.TxRetries.Val + fields["user-num_sta"] = t.UserNumSta.Val + break + } + } + r.send(&metric{Table: "uap_radios", Tags: tags, Fields: fields}) } } diff --git a/pkg/influxunifi/udm.go b/pkg/influxunifi/udm.go index cbe43f1f..c88eed34 100644 --- a/pkg/influxunifi/udm.go +++ b/pkg/influxunifi/udm.go @@ -74,7 +74,7 @@ func (u *InfluxUnifi) batchUDM(r report, s *unifi.UDM) { "lan-tx_packets": s.Stat.Gw.LanTxPackets.Val, }, u.batchSysStats(s.SysStats, s.SystemStats)) r.send(&metric{Table: "usg", Tags: tags, Fields: fields}) - u.batchNetworkTable(r, tags, s.NetworkTable) + u.batchNetTable(r, tags, s.NetworkTable) u.batchUSGwans(r, tags, s.Wan1, s.Wan2) tags = map[string]string{ @@ -136,5 +136,6 @@ func (u *InfluxUnifi) batchUDM(r report, s *unifi.UDM) { fields["guest-num_sta"] = int(s.GuestNumSta.Val) fields["num_sta"] = s.NumSta.Val r.send(&metric{Table: "uap", Tags: tags, Fields: fields}) - u.processVAPs(r, tags, *s.VapTable, *s.RadioTable, *s.RadioTableStats) + u.processRadTable(r, tags, *s.RadioTable, *s.RadioTableStats) + u.processVAPTable(r, tags, *s.VapTable) } diff --git a/pkg/influxunifi/usg.go b/pkg/influxunifi/usg.go index db3df2b7..420f24d0 100644 --- a/pkg/influxunifi/usg.go +++ b/pkg/influxunifi/usg.go @@ -19,7 +19,7 @@ func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) { "serial": s.Serial, "type": s.Type, } - fields := map[string]interface{}{ + fields := Combine(map[string]interface{}{ "ip": s.IP, "bytes": s.Bytes.Val, "last_seen": s.LastSeen.Val, @@ -33,6 +33,7 @@ func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) { "version": s.Version, "num_desktop": s.NumDesktop.Val, "num_handheld": s.NumHandheld.Val, + "uplink_latency": s.Uplink.Latency.Val, "num_mobile": s.NumMobile.Val, "speedtest-status_latency": s.SpeedtestStatus.Latency.Val, "speedtest-status_runtime": s.SpeedtestStatus.Runtime.Val, @@ -43,7 +44,10 @@ func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) { "lan-rx_packets": s.Stat.Gw.LanRxPackets.Val, "lan-tx_bytes": s.Stat.Gw.LanTxBytes.Val, "lan-tx_packets": s.Stat.Gw.LanTxPackets.Val, - } + }, u.batchSysStats(s.SysStats, s.SystemStats)) + r.send(&metric{Table: "usg", Tags: tags, Fields: fields}) + u.batchNetTable(r, tags, s.NetworkTable) + u.batchUSGwans(r, tags, s.Wan1, s.Wan2) /* for _, p := range s.PortTable { t := map[string]string{ @@ -73,10 +77,6 @@ func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) { r.send(&metric{Table: "usg_ports", Tags: t, Fields: f}) } */ - fields = Combine(fields, u.batchSysStats(s.SysStats, s.SystemStats)) - r.send(&metric{Table: "usg", Tags: tags, Fields: fields}) - u.batchNetworkTable(r, tags, s.NetworkTable) - u.batchUSGwans(r, tags, s.Wan1, s.Wan2) } func (u *InfluxUnifi) batchUSGwans(r report, tags map[string]string, wans ...unifi.Wan) { @@ -87,27 +87,26 @@ func (u *InfluxUnifi) batchUSGwans(r report, tags map[string]string, wans ...uni tags := map[string]string{ "device_name": tags["name"], "site_name": tags["site_name"], - "wan_name": wan.Name, + "ip": wan.IP, + "purpose": wan.Name, + "mac": wan.Mac, + "ifname": wan.Ifname, + "type": wan.Type, + "up": wan.Up.Txt, + "enabled": wan.Enable.Txt, } fields := map[string]interface{}{ "bytes-r": wan.BytesR.Val, - "enable": wan.Enable.Val, "full_duplex": wan.FullDuplex.Val, "gateway": wan.Gateway, - "ifname": wan.Ifname, - "ip": wan.IP, - "mac": wan.Mac, "max_speed": wan.MaxSpeed.Val, - "name": wan.Name, "rx_bytes": wan.RxBytes.Val, "rx_bytes-r": wan.RxBytesR.Val, "rx_dropped": wan.RxDropped.Val, "rx_errors": wan.RxErrors.Val, "rx_multicast": wan.RxMulticast.Val, "rx_packets": wan.RxPackets.Val, - "type": wan.Type, "speed": wan.Speed.Val, - "up": wan.Up.Val, "tx_bytes": wan.TxBytes.Val, "tx_bytes-r": wan.TxBytesR.Val, "tx_dropped": wan.TxDropped.Val, @@ -118,7 +117,7 @@ func (u *InfluxUnifi) batchUSGwans(r report, tags map[string]string, wans ...uni } } -func (u *InfluxUnifi) batchNetworkTable(r report, tags map[string]string, nt unifi.NetworkTable) { +func (u *InfluxUnifi) batchNetTable(r report, tags map[string]string, nt unifi.NetworkTable) { for _, p := range nt { tags := map[string]string{ "device_name": tags["name"], @@ -130,6 +129,7 @@ func (u *InfluxUnifi) batchNetworkTable(r report, tags map[string]string, nt uni "name": p.Name, "domain_name": p.DomainName, "purpose": p.Purpose, + "is_guest": p.IsGuest.Txt, } fields := map[string]interface{}{ "num_sta": p.NumSta.Val, diff --git a/pkg/influxunifi/usw.go b/pkg/influxunifi/usw.go index ce669ca1..a62f5f81 100644 --- a/pkg/influxunifi/usw.go +++ b/pkg/influxunifi/usw.go @@ -48,19 +48,19 @@ func (u *InfluxUnifi) batchUSW(r report, s *unifi.USW) { u.batchPortTable(r, tags, s.PortTable) } -func (u *InfluxUnifi) batchPortTable(r report, tags map[string]string, pt []unifi.Port) { +func (u *InfluxUnifi) batchPortTable(r report, t map[string]string, pt []unifi.Port) { for _, p := range pt { if !p.Up.Val || !p.Enable.Val { continue // only record UP ports. } tags := map[string]string{ - "site_name": tags["site_name"], - "device_name": tags["name"], + "site_name": t["site_name"], + "device_name": t["name"], "name": p.Name, "poe_mode": p.PoeMode, "port_poe": p.PortPoe.Txt, "port_idx": p.PortIdx.Txt, - "port_id": tags["name"] + " Port " + p.PortIdx.Txt, + "port_id": t["name"] + " Port " + p.PortIdx.Txt, "poe_enable": p.PoeEnable.Txt, "flowctrl_rx": p.FlowctrlRx.Txt, "flowctrl_tx": p.FlowctrlTx.Txt, diff --git a/pkg/poller/influx.go b/pkg/poller/influx.go index 6dbc15c3..2abd83ad 100644 --- a/pkg/poller/influx.go +++ b/pkg/poller/influx.go @@ -9,6 +9,9 @@ import ( // GetInfluxDB returns an InfluxDB interface. func (u *UnifiPoller) GetInfluxDB() (err error) { + if u.Influx != nil { + return nil + } u.Influx, err = influxunifi.New(&influxunifi.Config{ Database: u.Config.InfluxDB, User: u.Config.InfluxUser, @@ -29,6 +32,9 @@ func (u *UnifiPoller) GetInfluxDB() (err error) { // 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 { + if err := u.GetInfluxDB(); err != nil { + return err + } metrics, err := u.CollectMetrics() if err != nil { return err diff --git a/pkg/poller/start.go b/pkg/poller/start.go index ea68ba0b..1e1719c8 100644 --- a/pkg/poller/start.go +++ b/pkg/poller/start.go @@ -100,43 +100,52 @@ func (u *UnifiPoller) Run() error { 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() + case "both": + return u.RunBoth() } } +// RunBoth starts the prometheus exporter and influxdb exporter at the same time. +// This will likely double the amount of polls your controller receives. +func (u *UnifiPoller) RunBoth() error { + e := make(chan error) + defer close(e) + go func() { + e <- u.RunPrometheus() + }() + go func() { + e <- u.PollController() + }() + // If either method returns an error (even nil), bail out. + return <-e +} + // PollController runs forever, polling UniFi and pushing to InfluxDB -// This is started by Run() after everything checks out. +// This is started by Run() or RunBoth() 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 + // Some users need to re-auth every interval because the cookie times out. 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 := u.Unifi.Login(); err != nil { + return err } } - if err == nil { - // Only run this if the authentication procedure didn't return error. - _ = u.CollectAndProcess() + if err := u.CollectAndProcess(); err != nil { + return err } + // check for errors from the unifi polls. if u.errorCount > 0 { return fmt.Errorf("too many errors, stopping poller") }