all stats should be reporting

This commit is contained in:
Cody Lee 2020-10-11 11:32:45 -05:00
parent ceeb63870e
commit 51916c19e7
10 changed files with 1047 additions and 62 deletions

View File

@ -4,3 +4,165 @@ import (
"github.com/unifi-poller/unifi"
)
// reportClient generates Unifi Client datapoints for InfluxDB.
// These points can be passed directly to influx.
func (u *DatadogUnifi) reportClient(r report, s *unifi.Client) { // nolint: funlen
tags := []string{
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("ap_name", s.ApName),
tag("gw_name", s.GwName),
tag("sw_name", s.SwName),
tag("oui", s.Oui),
tag("radio_name", s.RadioName),
tag("radio", s.Radio),
tag("radio_proto", s.RadioProto),
tag("name", s.Name),
tag("fixed_ip", s.FixedIP),
tag("sw_port", s.SwPort.Txt),
tag("os_class", s.OsClass.Txt),
tag("os_name", s.OsName.Txt),
tag("dev_cat", s.DevCat.Txt),
tag("dev_id", s.DevID.Txt),
tag("dev_vendor", s.DevVendor.Txt),
tag("dev_family", s.DevFamily.Txt),
tag("is_wired", s.IsWired.Txt),
tag("is_guest", s.IsGuest.Txt),
tag("use_fixedip", s.UseFixedIP.Txt),
tag("channel", s.Channel.Txt),
tag("vlan", s.Vlan.Txt),
tag("hostname", s.Name),
tag("radio_desc", s.RadioDescription),
tag("ip", s.IP),
tag("essid", s.Essid),
tag("bssid", s.Bssid),
}
data := map[string]float64{
"anomalies": float64(s.Anomalies),
"channel": s.Channel.Val,
"satisfaction": s.Satisfaction.Val,
"bytes_r": float64(s.BytesR),
"ccq": float64(s.Ccq),
"noise": float64(s.Noise),
"roam_count": float64(s.RoamCount),
"rssi": float64(s.Rssi),
"rx_bytes": float64(s.RxBytes),
"rx_bytes_r": float64(s.RxBytesR),
"rx_packets": float64(s.RxPackets),
"rx_rate": float64(s.RxRate),
"signal": float64(s.Signal),
"tx_bytes": float64(s.TxBytes),
"tx_bytes_r": float64(s.TxBytesR),
"tx_packets": float64(s.TxPackets),
"tx_retries": float64(s.TxRetries),
"tx_power": float64(s.TxPower),
"tx_rate": float64(s.TxRate),
"uptime": float64(s.Uptime),
"wifi_tx_attempts": float64(s.WifiTxAttempts),
"wired-rx_bytes": float64(s.WiredRxBytes),
"wired-rx_bytes-r": float64(s.WiredRxBytesR),
"wired-rx_packets": float64(s.WiredRxPackets),
"wired-tx_bytes": float64(s.WiredTxBytes),
"wired-tx_bytes-r": float64(s.WiredTxBytesR),
"wired-tx_packets": float64(s.WiredTxPackets),
}
metricName := metricNamespace("clients")
reportGaugeForMap(r, metricName, data, tags)
}
// totalsDPImap: controller, site, name (app/cat name), dpi.
type totalsDPImap map[string]map[string]map[string]unifi.DPIData
func (u *DatadogUnifi) reportClientDPI(r report, s *unifi.DPITable, appTotal, catTotal totalsDPImap) {
for _, dpi := range s.ByApp {
category := unifi.DPICats.Get(dpi.Cat)
application := unifi.DPIApps.GetApp(dpi.Cat, dpi.App)
fillDPIMapTotals(appTotal, application, s.SourceName, s.SiteName, dpi)
fillDPIMapTotals(catTotal, category, s.SourceName, s.SiteName, dpi)
tags := []string{
tag("category", category),
tag("application", application),
tag("name", s.Name),
tag("mac", s.MAC),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
}
data := map[string]float64{
"tx_packets": float64(dpi.TxPackets),
"rx_packets": float64(dpi.RxPackets),
"tx_bytes": float64(dpi.TxBytes),
"rx_bytes": float64(dpi.RxBytes),
}
metricName := metricNamespace("clientdpi")
reportGaugeForMap(r, metricName, data, tags)
}
}
// fillDPIMapTotals fills in totals for categories and applications. maybe clients too.
// This allows less processing in InfluxDB to produce total transfer data per cat or app.
func fillDPIMapTotals(m totalsDPImap, name, controller, site string, dpi unifi.DPIData) {
if m[controller] == nil {
m[controller] = make(map[string]map[string]unifi.DPIData)
}
if m[controller][site] == nil {
m[controller][site] = make(map[string]unifi.DPIData)
}
existing := m[controller][site][name]
existing.TxPackets += dpi.TxPackets
existing.RxPackets += dpi.RxPackets
existing.TxBytes += dpi.TxBytes
existing.RxBytes += dpi.RxBytes
m[controller][site][name] = existing
}
func reportClientDPItotals(r report, appTotal, catTotal totalsDPImap) {
type all []struct {
kind string
val totalsDPImap
}
// This produces 7000+ metrics per site. Disabled for now.
if appTotal != nil {
appTotal = nil
}
// This can allow us to aggregate other data types later, like `name` or `mac`, or anything else unifi adds.
a := all{
// This produces 7000+ metrics per site. Disabled for now.
{
kind: "application",
val: appTotal,
},
{
kind: "category",
val: catTotal,
},
}
for _, k := range a {
for controller, s := range k.val {
for site, c := range s {
for name, m := range c {
tags := []string{
tag("site_name", site),
tag("source", controller),
tag("name", name),
}
data := map[string]float64{
"tx_packets": float64(m.TxPackets),
"rx_packets": float64(m.RxPackets),
"tx_bytes": float64(m.TxBytes),
"rx_bytes": float64(m.RxBytes),
}
metricName := metricNamespace("clientdpi.totals")
reportGaugeForMap(r, metricName, data, tags)
}
}
}
}
}

View File

@ -3,35 +3,35 @@
package datadogunifi
import (
"crypto/tls"
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/DataDog/datadog-go/statsd"
"github.com/pkg/errors"
"github.com/unifi-poller/poller"
"github.com/unifi-poller/unifi"
"golift.io/cnfg"
)
const (
defaultInterval = 30 * time.Second
minimumInterval = 10 * time.Second
)
// Config defines the data needed to store metrics in Datadog.
type Config struct {
// Required Config
// Interval controls the collection and reporting interval
Interval cnfg.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval,omitempty" yaml:"interval,omitempty"`
// Disable when true disables this output plugin
Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"`
// Address determines how to talk to the Datadog agent
Address string `json:"address" toml:"address" xml:"address,attr" yaml:"address"`
Address string `json:"address" toml:"address" xml:"address,attr" yaml:"address"`
// Optional Statsd Options - mirrored from statsd.Options
// Namespace to prepend to all metrics, events and service checks name.
Namespace *string `json:"namespace" toml:"namespace" xml:"namespace,attr" yaml:"namespace"`
// Tags are global tags to be applied to every metrics, events and service checks.
@ -48,7 +48,7 @@ type Config struct {
// protocol used when creating the client: 2048 for UDP and 512 for UDS.
BufferPoolSize *int `json:"buffer_pool_size" toml:"buffer_pool_size" xml:"buffer_pool_size,attr" yaml:"buffer_pool_size"`
// BufferFlushInterval is the interval after which the current buffer will get flushed.
BufferFlushInterval *time.Duration `json:"buffer_flush_interval" toml:"buffer_flush_interval" xml:"buffer_flush_interval,attr" yaml:"buffer_flush_interval"`
BufferFlushInterval *cnfg.Duration `json:"buffer_flush_interval" toml:"buffer_flush_interval" xml:"buffer_flush_interval,attr" yaml:"buffer_flush_interval"`
// BufferShardCount is the number of buffer "shards" that will be used.
// Those shards allows the use of multiple buffers at the same time to reduce
// lock contention.
@ -58,10 +58,7 @@ type Config struct {
// protocol used when creating the client: 2048 for UDP and 512 for UDS.
SenderQueueSize *int `json:"sender_queue_size" toml:"sender_queue_size" xml:"sender_queue_size,attr" yaml:"sender_queue_size"`
// WriteTimeoutUDS is the timeout after which a UDS packet is dropped.
WriteTimeoutUDS *time.Duration `json:"write_timeout_uds" toml:"write_timeout_uds" xml:"write_timeout_uds,attr" yaml:"write_timeout_uds"`
// Telemetry is a set of metrics automatically injected by the client in the
// dogstatsd stream to be able to monitor the client itself.
Telemetry *bool `json:"telemetry" toml:"telemetry" xml:"telemetry,attr" yaml:"telemetry"`
WriteTimeoutUDS *cnfg.Duration `json:"write_timeout_uds" toml:"write_timeout_uds" xml:"write_timeout_uds,attr" yaml:"write_timeout_uds"`
// ReceiveMode determins the behavior of the client when receiving to many
// metrics. The client will either drop the metrics if its buffers are
// full (ChannelMode mode) or block the caller until the metric can be
@ -86,10 +83,6 @@ type Config struct {
ChannelModeBufferSize *int `json:"channel_mode_buffer_size" toml:"channel_mode_buffer_size" xml:"channel_mode_buffer_size,attr" yaml:"channel_mode_buffer_size"`
// AggregationFlushInterval is the interval for the aggregator to flush metrics
AggregationFlushInterval *time.Duration `json:"aggregation_flush_interval" toml:"aggregation_flush_interval" xml:"aggregation_flush_interval,attr" yaml:"aggregation_flush_interval"`
// [beta] Aggregation enables/disables client side aggregation
Aggregation *bool `json:"aggregation" toml:"aggregation" xml:"aggregation,attr" yaml:"aggregation"`
// TelemetryAddr specify a different endpoint for telemetry metrics.
TelemetryAddr *string `json:"telemetry_address" toml:"telemetry_address" xml:"telemetry_address,attr" yaml:"telemetry_address"`
}
// Datadog allows the data to be context aware with configuration
@ -101,22 +94,16 @@ type Datadog struct {
// DatadogUnifi is returned by New() after you provide a Config.
type DatadogUnifi struct {
Collector poller.Collect
datadog statsd.ClientInterface
datadog statsd.ClientInterface
LastCheck time.Time
*Datadog
}
type metric struct {
Name string
Tags map[string]string
Fields map[string]interface{}
}
func init() { // nolint: gochecknoinits
func init() { // nolint: gochecknoinits
u := &DatadogUnifi{Datadog: &Datadog{}, LastCheck: time.Now()}
poller.NewOutput(&poller.Output{
Name: "datadog",
Name: "datadog",
Config: u.Datadog,
Method: u.Run,
})
@ -131,17 +118,62 @@ func (u *DatadogUnifi) setConfigDefaults() {
u.Interval = cnfg.Duration{Duration: u.Interval.Duration.Round(time.Second)}
u.options = make([]statsd.Option)
u.options = make([]statsd.Option, 0)
if u.Namespace != nil {
u.options = append(u.options, statsd.WithNamespace(*u.Namespace))
}
if u.Tags != nil && len(u.Tags) > 0 {
u.options = append(u.options, statsd.WithTags(u.Tags))
}
}
if u.MaxBytesPerPayload != nil {
u.options = append(u.options, statsd.WithMaxBytesPerPayload(*u.MaxBytesPerPayload))
}
if u.MaxMessagesPerPayload != nil {
u.options = append(u.options, statsd.WithMaxMessagesPerPayload(*u.MaxMessagesPerPayload))
}
if u.BufferPoolSize != nil {
u.options = append(u.options, statsd.WithBufferPoolSize(*u.BufferPoolSize))
}
if u.BufferFlushInterval != nil {
u.options = append(u.options, statsd.WithBufferFlushInterval((*u.BufferFlushInterval).Duration))
}
if u.BufferShardCount != nil {
u.options = append(u.options, statsd.WithBufferShardCount(*u.BufferShardCount))
}
if u.SenderQueueSize != nil {
u.options = append(u.options, statsd.WithSenderQueueSize(*u.SenderQueueSize))
}
if u.WriteTimeoutUDS != nil {
u.options = append(u.options, statsd.WithWriteTimeoutUDS((*u.WriteTimeoutUDS).Duration))
}
if u.ReceiveMode != nil {
switch *u.ReceiveMode {
case statsd.ChannelMode:
u.options = append(u.options, statsd.WithChannelMode())
case statsd.MutexMode:
u.options = append(u.options, statsd.WithMutexMode())
}
}
if u.ChannelModeBufferSize != nil {
u.options = append(u.options, statsd.WithChannelModeBufferSize(*u.ChannelModeBufferSize))
}
if u.AggregationFlushInterval != nil {
u.options = append(u.options, statsd.WithAggregationInterval(*u.AggregationFlushInterval))
}
}
// Run runs a ticker to poll the unifi server and update Datadog.
func (u *DatadogUnifi) Run(c poller.Collect) error {
@ -153,18 +185,18 @@ func (u *DatadogUnifi) Run(c poller.Collect) error {
u.Collector = c
u.setConfigDefaults()
u.datadog, err := statsd.New(u.Address, u.options...)
var err error
u.datadog, err = statsd.New(u.Address, u.options...)
if err != nil {
return err
}
u.PollController()
return nil
}
// PollController runs fover, polling UniFi and pushing to Datadog
// PollController runs forever, polling UniFi and pushing to Datadog
// This is started by Run() or RunBoth() after everything is validated.
func (u *DatadogUnifi) PollController() {
interval := u.Interval.Round(time.Second)
@ -197,53 +229,41 @@ func (u *DatadogUnifi) PollController() {
// Call this after you've collected all the data you care about.
// Returns an error if datadog statsd calls fail, otherwise returns a report.
func (u *DatadogUnifi) ReportMetrics(m *poller.Metrics) (*Report, error) {
r := &Report{Metrics: m, ch: make(chan *metric), Start: time.Now()}
r := &Report{Metrics: m, Start: time.Now()}
// batch all the points.
u.loopPoints(r)
r.End = time.Now()
r.Elapsed = r.End.Sub(r.Start)
return r, nil
}
// collect runs in a go routine and batches all the points.
func (u *InfluxUnifi) collect(r report, ch chan *metric) {
for m := range ch {
pt, err := influx.NewPoint(m.Table, m.Tags, m.Fields, r.metrics().TS)
if err == nil {
r.batch(m, pt)
}
r.error(err)
r.done()
}
}
// loopPoints kicks off 3 or 7 go routines to process metrics and send them
// to the collect routine through the metric channel.
// loopPoints collects all the data to immediately report to Datadog.
func (u *DatadogUnifi) loopPoints(r report) {
m := r.metrics()
for _, s := range m.SitesDPI {
u.batchSiteDPI(r, s)
u.reportSiteDPI(r, s)
}
for _, s := range m.Sites {
u.batchSite(r, s)
u.reportSite(r, s)
}
appTotal := make(totalsDPImap)
catTotal := make(totalsDPImap)
for _, s := range m.ClientsDPI {
u.batchClientDPI(r, s, appTotal, catTotal)
u.reportClientDPI(r, s, appTotal, catTotal)
}
reportClientDPItotals(r, appTotal, catTotal)
for _, s := range m.Clients {
u.batchClient(r, s)
u.reportClient(r, s)
}
for _, s := range m.IDSList {
u.batchIDS(r, s)
u.reportIDS(r, s)
}
u.loopDevicePoints(r)
@ -257,19 +277,19 @@ func (u *DatadogUnifi) loopDevicePoints(r report) {
}
for _, s := range m.UAPs {
u.batchUAP(r, s)
u.reportUAP(r, s)
}
for _, s := range m.USGs {
u.batchUSG(r, s)
u.reportUSG(r, s)
}
for _, s := range m.USWs {
u.batchUSW(r, s)
u.reportUSW(r, s)
}
for _, s := range m.UDMs {
u.batchUDM(r, s)
u.reportUDM(r, s)
}
}
@ -277,7 +297,6 @@ func (u *DatadogUnifi) loopDevicePoints(r report) {
func (u *DatadogUnifi) LogDatadogReport(r *Report) {
m := r.Metrics
idsMsg := fmt.Sprintf("IDS Events: %d, ", len(m.IDSList))
u.Collector.Logf("UniFi Metrics Recorded. Sites: %d, Clients: %d, "+
"UAP: %d, USG/UDM: %d, USW: %d, %sPoints: %d, Fields: %d, Errs: %d, Elapsed: %v",
len(m.Sites), len(m.Clients), len(m.UAPs),

View File

@ -0,0 +1,31 @@
package datadogunifi
import (
"github.com/unifi-poller/unifi"
)
// reportIDS generates intrusion detection datapoints for Datadog.
// These points can be passed directly to datadog.
func (u *DatadogUnifi) reportIDS(r report, i *unifi.IDS) {
tags := []string{
tag("site_name", i.SiteName),
tag("source", i.SourceName),
tag("in_iface", i.InIface),
tag("event_type", i.EventType),
tag("proto", i.Proto),
tag("app_proto", i.AppProto),
tag("usgip", i.USGIP),
tag("country_code", i.SourceIPGeo.CountryCode),
tag("country_name", i.SourceIPGeo.CountryName),
tag("city", i.SourceIPGeo.City),
tag("organization", i.SourceIPGeo.Organization),
tag("srcipASN", i.SrcIPASN),
tag("usgipASN", i.USGIPASN),
tag("alert_category", i.InnerAlertCategory),
tag("subsystem", i.Subsystem),
tag("catname", i.Catname),
}
metricName := metricNamespace("intrusion_detect")
r.reportCount(metricName("count"), 1, tags)
}

View File

@ -0,0 +1,21 @@
package datadogunifi
import (
"fmt"
)
func tag(name string, value interface{}) string {
return fmt.Sprintf("%s:%v", name, value)
}
func metricNamespace(namespace string) func(string) string {
return func(name string) string {
return fmt.Sprintf("%s.%s", namespace, name)
}
}
func reportGaugeForMap(r report, metricName func(string) string, data map[string]float64, tags []string) {
for name, value := range data {
r.reportGauge(metricName(name), value, tags)
}
}

View File

@ -0,0 +1,76 @@
package datadogunifi
import (
"time"
"github.com/DataDog/datadog-go/statsd"
"github.com/unifi-poller/poller"
)
type Report struct {
Metrics *poller.Metrics
Errors []error
Total int
Fields int
Start time.Time
End time.Time
Elapsed time.Duration
client statsd.ClientInterface
}
type report interface {
error(err error)
metrics() *poller.Metrics
reportGauge(name string, value float64, tags []string) error
reportCount(name string, value int64, tags []string) error
reportDistribution(name string, value float64, tags []string) error
reportTiming(name string, value time.Duration, tags []string) error
reportEvent(title string, message string, tags []string) error
reportServiceCheck(name string, status statsd.ServiceCheckStatus, message string, tags []string) error
}
func (r *Report) metrics() *poller.Metrics {
return r.Metrics
}
func (r *Report) error(err error) {
if err != nil {
r.Errors = append(r.Errors, err)
}
}
func (r *Report) reportGauge(name string, value float64, tags []string) error {
return r.client.Gauge(name, value, tags, 1.0)
}
func (r *Report) reportCount(name string, value int64, tags []string) error {
return r.client.Count(name, value, tags, 1.0)
}
func (r *Report) reportDistribution(name string, value float64, tags []string) error {
return r.client.Distribution(name, value, tags, 1.0)
}
func (r *Report) reportTiming(name string, value time.Duration, tags []string) error {
return r.client.Timing(name, value, tags, 1.0)
}
func (r *Report) reportEvent(title string, message string, tags []string) error {
return r.client.Event(&statsd.Event{
Title: title,
Text: message,
Timestamp: time.Now(),
Tags: tags,
})
}
func (r *Report) reportServiceCheck(name string, status statsd.ServiceCheckStatus, message string, tags []string) error {
return r.client.ServiceCheck(&statsd.ServiceCheck{
Name: name,
Status: status,
Timestamp: time.Now(),
Message: message,
Tags: tags,
})
}

View File

@ -4,5 +4,77 @@ import (
"github.com/unifi-poller/unifi"
)
// batchSite generates Unifi Sites' datapoints for Datadog.
// These points can be passed directly to datadog
// reportSite generates Unifi Sites' datapoints for Datadog.
// These points can be passed directly to Datadog.
func (u *DatadogUnifi) reportSite(r report, s *unifi.Site) {
metricName := metricNamespace("subsystems")
for _, h := range s.Health {
tags := []string{
tag("name", s.Name),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("desc", s.Desc),
tag("status", h.Status),
tag("subsystem", h.Subsystem),
tag("wan_ip", h.WanIP),
tag("gw_name", h.GwName),
tag("lan_ip", h.LanIP),
}
data := map[string]float64{
"num_user": h.NumUser.Val,
"num_guest": h.NumGuest.Val,
"num_iot": h.NumIot.Val,
"tx_bytes-r": h.TxBytesR.Val,
"rx_bytes-r": h.RxBytesR.Val,
"num_ap": h.NumAp.Val,
"num_adopted": h.NumAdopted.Val,
"num_disabled": h.NumDisabled.Val,
"num_disconnected": h.NumDisconnected.Val,
"num_pending": h.NumPending.Val,
"num_gw": h.NumGw.Val,
"num_sta": h.NumSta.Val,
"gw_cpu": h.GwSystemStats.CPU.Val,
"gw_mem": h.GwSystemStats.Mem.Val,
"gw_uptime": h.GwSystemStats.Uptime.Val,
"latency": h.Latency.Val,
"uptime": h.Uptime.Val,
"drops": h.Drops.Val,
"xput_up": h.XputUp.Val,
"xput_down": h.XputDown.Val,
"speedtest_ping": h.SpeedtestPing.Val,
"speedtest_lastrun": h.SpeedtestLastrun.Val,
"num_sw": h.NumSw.Val,
"remote_user_num_active": h.RemoteUserNumActive.Val,
"remote_user_num_inactive": h.RemoteUserNumInactive.Val,
"remote_user_rx_bytes": h.RemoteUserRxBytes.Val,
"remote_user_tx_bytes": h.RemoteUserTxBytes.Val,
"remote_user_rx_packets": h.RemoteUserRxPackets.Val,
"remote_user_tx_packets": h.RemoteUserTxPackets.Val,
"num_new_alarms": s.NumNewAlarms.Val,
}
for name, value := range data {
r.reportGauge(metricName(name), value, tags)
}
}
}
func (u *DatadogUnifi) reportSiteDPI(r report, s *unifi.DPITable) {
for _, dpi := range s.ByApp {
metricName := metricNamespace("sitedpi")
tags := []string{
tag("category", unifi.DPICats.Get(dpi.Cat)),
tag("application", unifi.DPIApps.GetApp(dpi.Cat, dpi.App)),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
}
r.reportCount(metricName("tx_packets"), dpi.TxPackets, tags)
r.reportCount(metricName("rx_packets"), dpi.RxPackets, tags)
r.reportCount(metricName("tx_bytes"), dpi.TxBytes, tags)
r.reportCount(metricName("rx_bytes"), dpi.RxBytes, tags)
}
}

View File

@ -0,0 +1,199 @@
package datadogunifi
import (
"github.com/unifi-poller/unifi"
)
// reportUAP generates Wireless-Access-Point datapoints for InfluxDB.
// These points can be passed directly to influx.
func (u *DatadogUnifi) reportUAP(r report, s *unifi.UAP) {
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := []string{
tag("ip", s.IP),
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("name", s.Name),
tag("version", s.Version),
tag("model", s.Model),
tag("serial", s.Serial),
tag("type", s.Type),
}
metricName := metricNamespace("uap")
u.reportUAPstats(s.Stat.Ap, r, metricName, tags)
u.reportSysStats(r, metricName, s.SysStats, s.SystemStats, tags)
data := map[string]float64{
"bytes": s.Bytes.Val,
"last_seen": s.LastSeen.Val,
"rx_bytes": s.RxBytes.Val,
"tx_bytes": s.TxBytes.Val,
"uptime": s.Uptime.Val,
"user-num_sta": s.UserNumSta.Val,
"guest-num_sta": s.GuestNumSta.Val,
"num_sta": s.NumSta.Val,
}
reportGaugeForMap(r, metricName, data, tags)
u.reportRadTable(r, s.Name, s.SiteName, s.SourceName, s.RadioTable, s.RadioTableStats)
u.reportVAPTable(r, s.Name, s.SiteName, s.SourceName, s.VapTable)
u.reportPortTable(r, s.Name, s.SiteName, s.SourceName, s.Type, s.PortTable)
}
func (u *DatadogUnifi) reportUAPstats(ap *unifi.Ap, r report, metricName func(string) string, tags []string) {
if ap == nil {
return
}
// Accumulative Statistics.
data := map[string]float64{
"stat_user-rx_packets": ap.UserRxPackets.Val,
"stat_guest-rx_packets": ap.GuestRxPackets.Val,
"stat_rx_packets": ap.RxPackets.Val,
"stat_user-rx_bytes": ap.UserRxBytes.Val,
"stat_guest-rx_bytes": ap.GuestRxBytes.Val,
"stat_rx_bytes": ap.RxBytes.Val,
"stat_user-rx_errors": ap.UserRxErrors.Val,
"stat_guest-rx_errors": ap.GuestRxErrors.Val,
"stat_rx_errors": ap.RxErrors.Val,
"stat_user-rx_dropped": ap.UserRxDropped.Val,
"stat_guest-rx_dropped": ap.GuestRxDropped.Val,
"stat_rx_dropped": ap.RxDropped.Val,
"stat_user-rx_crypts": ap.UserRxCrypts.Val,
"stat_guest-rx_crypts": ap.GuestRxCrypts.Val,
"stat_rx_crypts": ap.RxCrypts.Val,
"stat_user-rx_frags": ap.UserRxFrags.Val,
"stat_guest-rx_frags": ap.GuestRxFrags.Val,
"stat_rx_frags": ap.RxFrags.Val,
"stat_user-tx_packets": ap.UserTxPackets.Val,
"stat_guest-tx_packets": ap.GuestTxPackets.Val,
"stat_tx_packets": ap.TxPackets.Val,
"stat_user-tx_bytes": ap.UserTxBytes.Val,
"stat_guest-tx_bytes": ap.GuestTxBytes.Val,
"stat_tx_bytes": ap.TxBytes.Val,
"stat_user-tx_errors": ap.UserTxErrors.Val,
"stat_guest-tx_errors": ap.GuestTxErrors.Val,
"stat_tx_errors": ap.TxErrors.Val,
"stat_user-tx_dropped": ap.UserTxDropped.Val,
"stat_guest-tx_dropped": ap.GuestTxDropped.Val,
"stat_tx_dropped": ap.TxDropped.Val,
"stat_user-tx_retries": ap.UserTxRetries.Val,
"stat_guest-tx_retries": ap.GuestTxRetries.Val,
}
reportGaugeForMap(r, metricName, data, tags)
}
// reportVAPTable creates points for Wifi Radios. This works with several types of UAP-capable devices.
func (u *DatadogUnifi) reportVAPTable(r report, deviceName string, siteName string, source string, vt unifi.VapTable) { // nolint: funlen
for _, s := range vt {
tags := []string{
tag("device_name", deviceName),
tag("site_name", siteName),
tag("source", source),
tag("ap_mac", s.ApMac),
tag("bssid", s.Bssid),
tag("id", s.ID),
tag("name", s.Name),
tag("radio_name", s.RadioName),
tag("radio", s.Radio),
tag("essid", s.Essid),
tag("site_id", s.SiteID),
tag("usage", s.Usage),
tag("state", s.State),
tag("is_guest", s.IsGuest.Txt),
}
data := map[string]float64{
"ccq": float64(s.Ccq),
"mac_filter_rejections": float64(s.MacFilterRejections),
"num_satisfaction_sta": s.NumSatisfactionSta.Val,
"avg_client_signal": s.AvgClientSignal.Val,
"satisfaction": s.Satisfaction.Val,
"satisfaction_now": s.SatisfactionNow.Val,
"num_sta": float64(s.NumSta),
"channel": s.Channel.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,
}
metricName := metricNamespace("uap_vaps")
reportGaugeForMap(r, metricName, data, tags)
}
}
func (u *DatadogUnifi) reportRadTable(r report, deviceName string, siteName string, source string, rt unifi.RadioTable, rts unifi.RadioTableStats) {
for _, p := range rt {
tags := []string{
tag("device_name", deviceName),
tag("site_name", siteName),
tag("source", source),
tag("channel", p.Channel.Txt),
tag("radio", p.Radio),
}
data := map[string]float64{
"current_antenna_gain": p.CurrentAntennaGain.Val,
"ht": p.Ht.Val,
"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 {
data["ast_be_xmit"] = t.AstBeXmit.Val
data["channel"] = t.Channel.Val
data["cu_self_rx"] = t.CuSelfRx.Val
data["cu_self_tx"] = t.CuSelfTx.Val
data["cu_total"] = t.CuTotal.Val
data["extchannel"] = t.Extchannel.Val
data["gain"] = t.Gain.Val
data["guest-num_sta"] = t.GuestNumSta.Val
data["num_sta"] = t.NumSta.Val
data["tx_packets"] = t.TxPackets.Val
data["tx_power"] = t.TxPower.Val
data["tx_retries"] = t.TxRetries.Val
data["user-num_sta"] = t.UserNumSta.Val
break
}
}
metricName := metricNamespace("uap_radios")
reportGaugeForMap(r, metricName, data, tags)
}
}

View File

@ -0,0 +1,141 @@
package datadogunifi
import (
"fmt"
"github.com/unifi-poller/unifi"
)
// reportSysStats is used by all device types.
func (u *DatadogUnifi) reportSysStats(r report, metricName func(string) string, s unifi.SysStats, ss unifi.SystemStats, tags []string) {
data := map[string]float64{
"loadavg_1": s.Loadavg1.Val,
"loadavg_5": s.Loadavg5.Val,
"loadavg_15": s.Loadavg15.Val,
"mem_used": s.MemUsed.Val,
"mem_buffer": s.MemBuffer.Val,
"mem_total": s.MemTotal.Val,
"cpu": ss.CPU.Val,
"mem": ss.Mem.Val,
"system_uptime": ss.Uptime.Val,
}
for name, value := range data {
r.reportGauge(metricName(name), value, tags)
}
}
func (u *DatadogUnifi) reportUDMtemps(r report, metricName func(string) string, tags []string, temps []unifi.Temperature) {
for _, t := range temps {
name := fmt.Sprintf("temp_%s", t.Name)
r.reportGauge(metricName(name), t.Value, tags)
}
}
// reportUDM generates Unifi Gateway datapoints for InfluxDB.
// These points can be passed directly to influx.
func (u *DatadogUnifi) reportUDM(r report, s *unifi.UDM) { // nolint: funlen
if !s.Adopted.Val || s.Locating.Val {
return
}
metricName := metricNamespace("usg")
tags := []string{
tag("source", s.SourceName),
tag("ip", s.IP),
tag("license_state", s.LicenseState),
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("name", s.Name),
tag("version", s.Version),
tag("model", s.Model),
tag("serial", s.Serial),
tag("type", s.Type),
}
u.reportUDMtemps(r, metricName, tags, s.Temperatures)
u.reportUSGstats(r, metricName, tags, s.SpeedtestStatus, s.Stat.Gw, s.Uplink)
u.reportSysStats(r, metricName, s.SysStats, s.SystemStats, tags)
data := map[string]float64{
"bytes": s.Bytes.Val,
"last_seen": s.LastSeen.Val,
"guest-num_sta": s.GuestNumSta.Val,
"rx_bytes": s.RxBytes.Val,
"tx_bytes": s.TxBytes.Val,
"uptime": s.Uptime.Val,
"state": s.State.Val,
"user-num_sta": s.UserNumSta.Val,
"num_desktop": s.NumDesktop.Val,
"num_handheld": s.NumHandheld.Val,
"num_mobile": s.NumMobile.Val,
}
for name, value := range data {
r.reportGauge(metricName(name), value, tags)
}
u.reportNetTable(r, s.Name, s.SiteName, s.SourceName, s.NetworkTable)
u.reportUSGwans(r, s.Name, s.SiteName, s.SourceName, s.Wan1, s.Wan2)
tags = []string{
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("name", s.Name),
tag("version", s.Version),
tag("model", s.Model),
tag("serial", s.Serial),
tag("type", s.Type),
tag("ip", s.IP),
}
metricName = metricNamespace("usw")
u.reportUSWstat(r, metricName, tags, s.Stat.Sw)
data = map[string]float64{
"guest-num_sta": s.GuestNumSta.Val,
"bytes": s.Bytes.Val,
"last_seen": s.LastSeen.Val,
"rx_bytes": s.RxBytes.Val,
"tx_bytes": s.TxBytes.Val,
"uptime": s.Uptime.Val,
}
for name, value := range data {
r.reportGauge(metricName(name), value, tags)
}
u.reportPortTable(r, s.Name, s.SiteName, s.SourceName, s.Type, s.PortTable) // udm has a usw in it.
if s.Stat.Ap == nil {
return // we're done now. the following code process UDM (non-pro) UAP data.
}
tags = []string{
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("name", s.Name),
tag("version", s.Version),
tag("model", s.Model),
tag("serial", s.Serial),
tag("type", s.Type),
}
metricName = metricNamespace("uap")
u.reportUAPstats(s.Stat.Ap, r, metricName, tags)
data = map[string]float64{
"bytes": s.Bytes.Val,
"last_seen": s.LastSeen.Val,
"rx_bytes": s.RxBytes.Val,
"tx_bytes": s.TxBytes.Val,
"uptime": s.Uptime.Val,
"state": s.State.Val,
"user-num_sta": s.UserNumSta.Val,
"guest-num_sta": s.GuestNumSta.Val,
"num_sta": s.NumSta.Val,
}
for name, value := range data {
r.reportGauge(metricName(name), value, tags)
}
u.reportRadTable(r, s.Name, s.SiteName, s.SourceName, *s.RadioTable, *s.RadioTableStats)
u.reportVAPTable(r, s.Name, s.SiteName, s.SourceName, *s.VapTable)
}

View File

@ -0,0 +1,145 @@
package datadogunifi
import (
"github.com/unifi-poller/unifi"
)
// reportUSG generates Unifi Gateway datapoints for Datadog.
// These points can be passed directly to datadog.
func (u *DatadogUnifi) reportUSG(r report, s *unifi.USG) {
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := []string{
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("name", s.Name),
tag("version", s.Version),
tag("model", s.Model),
tag("serial", s.Serial),
tag("type", s.Type),
tag("ip", s.IP),
tag("license_state", s.LicenseState),
}
metricName := metricNamespace("usg")
u.reportSysStats(r, metricName, s.SysStats, s.SystemStats, tags)
u.reportUSGstats(r, metricName, tags, s.SpeedtestStatus, s.Stat.Gw, s.Uplink)
data := map[string]float64{
"bytes": s.Bytes.Val,
"last_seen": s.LastSeen.Val,
"guest-num_sta": s.GuestNumSta.Val,
"rx_bytes": s.RxBytes.Val,
"tx_bytes": s.TxBytes.Val,
"uptime": s.Uptime.Val,
"state": s.State.Val,
"user-num_sta": s.UserNumSta.Val,
"num_desktop": s.NumDesktop.Val,
"num_handheld": s.NumHandheld.Val,
"num_mobile": s.NumMobile.Val,
}
reportGaugeForMap(r, metricName, data, tags)
u.reportNetTable(r, s.Name, s.SiteName, s.SourceName, s.NetworkTable)
u.reportUSGwans(r, s.Name, s.SiteName, s.SourceName, s.Wan1, s.Wan2)
}
func (u *DatadogUnifi) reportUSGstats(r report, metricName func(string) string, tags []string, ss unifi.SpeedtestStatus, gw *unifi.Gw, ul unifi.Uplink) {
if gw == nil {
return
}
data := map[string]float64{
"uplink_latency": ul.Latency.Val,
"uplink_speed": ul.Speed.Val,
"speedtest-status_latency": ss.Latency.Val,
"speedtest-status_runtime": ss.Runtime.Val,
"speedtest-status_rundate": ss.Rundate.Val,
"speedtest-status_ping": ss.StatusPing.Val,
"speedtest-status_xput_download": ss.XputDownload.Val,
"speedtest-status_xput_upload": ss.XputUpload.Val,
"lan-rx_bytes": gw.LanRxBytes.Val,
"lan-rx_packets": gw.LanRxPackets.Val,
"lan-tx_bytes": gw.LanTxBytes.Val,
"lan-tx_packets": gw.LanTxPackets.Val,
"lan-rx_dropped": gw.LanRxDropped.Val,
}
reportGaugeForMap(r, metricName, data, tags)
}
func (u *DatadogUnifi) reportUSGwans(r report, deviceName string, siteName string, source string, wans ...unifi.Wan) {
for _, wan := range wans {
if !wan.Up.Val {
continue
}
tags := []string{
tag("device_name", deviceName),
tag("site_name", siteName),
tag("source", source),
tag("ip", wan.IP),
tag("purpose", wan.Name),
tag("mac", wan.Mac),
tag("ifname", wan.Ifname),
tag("type", wan.Type),
tag("up", wan.Up.Txt),
tag("enabled", wan.Enable.Txt),
tag("gateway", wan.Gateway),
}
fullDuplex := float64(0)
if wan.FullDuplex.Val {
fullDuplex = 1
}
data := map[string]float64{
"bytes-r": wan.BytesR.Val,
"full_duplex": fullDuplex,
"max_speed": wan.MaxSpeed.Val,
"rx_bytes": wan.RxBytes.Val,
"rx_bytes-r": wan.RxBytesR.Val,
"rx_dropped": wan.RxDropped.Val,
"rx_errors": wan.RxErrors.Val,
"rx_broadcast": wan.RxBroadcast.Val,
"rx_multicast": wan.RxMulticast.Val,
"rx_packets": wan.RxPackets.Val,
"speed": wan.Speed.Val,
"tx_bytes": wan.TxBytes.Val,
"tx_bytes-r": wan.TxBytesR.Val,
"tx_dropped": wan.TxDropped.Val,
"tx_errors": wan.TxErrors.Val,
"tx_packets": wan.TxPackets.Val,
"tx_broadcast": wan.TxBroadcast.Val,
"tx_multicast": wan.TxMulticast.Val,
}
metricName := metricNamespace("usg_wan_ports")
reportGaugeForMap(r, metricName, data, tags)
}
}
func (u *DatadogUnifi) reportNetTable(r report, deviceName string, siteName string, source string, nt unifi.NetworkTable) {
for _, p := range nt {
tags := []string{
tag("device_name", deviceName),
tag("site_name", siteName),
tag("source", source),
tag("up", p.Up.Txt),
tag("enabled", p.Enabled.Txt),
tag("ip", p.IP),
tag("mac", p.Mac),
tag("name", p.Name),
tag("domain_name", p.DomainName),
tag("purpose", p.Purpose),
tag("is_guest", p.IsGuest.Txt),
}
data := map[string]float64{
"num_sta": p.NumSta.Val,
"rx_bytes": p.RxBytes.Val,
"rx_packets": p.RxPackets.Val,
"tx_bytes": p.TxBytes.Val,
"tx_packets": p.TxPackets.Val,
}
metricName := metricNamespace("usg_networks")
reportGaugeForMap(r, metricName, data, tags)
}
}

View File

@ -0,0 +1,119 @@
package datadogunifi
import (
"fmt"
"github.com/unifi-poller/unifi"
)
// reportUSW generates Unifi Switch datapoints for Datadog.
// These points can be passed directly to datadog.
func (u *DatadogUnifi) reportUSW(r report, s *unifi.USW) {
if !s.Adopted.Val || s.Locating.Val {
return
}
tags := []string{
tag("mac", s.Mac),
tag("site_name", s.SiteName),
tag("source", s.SourceName),
tag("name", s.Name),
tag("version", s.Version),
tag("model", s.Model),
tag("serial", s.Serial),
tag("type", s.Type),
tag("ip", s.IP),
}
metricName := metricNamespace("usw")
u.reportUSWstat(r, metricName, tags, s.Stat.Sw)
u.reportSysStats(r, metricName, s.SysStats, s.SystemStats, tags)
data := map[string]float64{
"guest-num_sta": s.GuestNumSta.Val,
"bytes": s.Bytes.Val,
"fan_level": s.FanLevel.Val,
"general_temperature": s.GeneralTemperature.Val,
"last_seen": s.LastSeen.Val,
"rx_bytes": s.RxBytes.Val,
"tx_bytes": s.TxBytes.Val,
"uptime": s.Uptime.Val,
"state": s.State.Val,
"user-num_sta": s.UserNumSta.Val,
}
reportGaugeForMap(r, metricName, data, tags)
u.reportPortTable(r, s.Name, s.SiteName, s.SourceName, s.Type, s.PortTable)
}
func (u *DatadogUnifi) reportUSWstat(r report, metricName func(string) string, tags []string, sw *unifi.Sw) {
if sw == nil {
return
}
data := map[string]float64{
"stat_bytes": sw.Bytes.Val,
"stat_rx_bytes": sw.RxBytes.Val,
"stat_rx_crypts": sw.RxCrypts.Val,
"stat_rx_dropped": sw.RxDropped.Val,
"stat_rx_errors": sw.RxErrors.Val,
"stat_rx_frags": sw.RxFrags.Val,
"stat_rx_packets": sw.TxPackets.Val,
"stat_tx_bytes": sw.TxBytes.Val,
"stat_tx_dropped": sw.TxDropped.Val,
"stat_tx_errors": sw.TxErrors.Val,
"stat_tx_packets": sw.TxPackets.Val,
"stat_tx_retries": sw.TxRetries.Val,
}
reportGaugeForMap(r, metricName, data, tags)
}
func (u *DatadogUnifi) reportPortTable(r report, deviceName string, siteName string, source string, typeTag string, pt []unifi.Port) {
for _, p := range pt {
if !p.Up.Val || !p.Enable.Val {
continue // only record UP ports.
}
tags := []string{
tag("site_name", siteName),
tag("device_name", deviceName),
tag("source", source),
tag("type", typeTag),
tag("name", p.Name),
tag("poe_mode", p.PoeMode),
tag("port_poe", p.PortPoe.Txt),
tag("port_idx", p.PortIdx.Txt),
tag("port_id", fmt.Sprintf("%s_port_%s", deviceName, p.PortIdx.Txt)),
tag("poe_enable", p.PoeEnable.Txt),
tag("flowctrl_rx", p.FlowctrlRx.Txt),
tag("flowctrl_tx", p.FlowctrlTx.Txt),
tag("media", p.Media),
}
data := map[string]float64{
"dbytes_r": p.BytesR.Val,
"rx_broadcast": p.RxBroadcast.Val,
"rx_bytes": p.RxBytes.Val,
"rx_bytes-r": p.RxBytesR.Val,
"rx_dropped": p.RxDropped.Val,
"rx_errors": p.RxErrors.Val,
"rx_multicast": p.RxMulticast.Val,
"rx_packets": p.RxPackets.Val,
"speed": p.Speed.Val,
"stp_pathcost": p.StpPathcost.Val,
"tx_broadcast": p.TxBroadcast.Val,
"tx_bytes": p.TxBytes.Val,
"tx_bytes-r": p.TxBytesR.Val,
"tx_dropped": p.TxDropped.Val,
"tx_errors": p.TxErrors.Val,
"tx_multicast": p.TxMulticast.Val,
"tx_packets": p.TxPackets.Val,
}
if p.PoeEnable.Val && p.PortPoe.Val {
data["poe_current"] = p.PoeCurrent.Val
data["poe_power"] = p.PoePower.Val
data["poe_voltage"] = p.PoeVoltage.Val
}
metricName := metricNamespace("usw_ports")
reportGaugeForMap(r, metricName, data, tags)
}
}