diff --git a/collector/capsman_collector.go b/collector/capsman_collector.go new file mode 100644 index 0000000..9108373 --- /dev/null +++ b/collector/capsman_collector.go @@ -0,0 +1,127 @@ +package collector + +import ( + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "gopkg.in/routeros.v2/proto" +) + +type capsmanCollector struct { + props []string + descriptions map[string]*prometheus.Desc +} + +func newCapsmanCollector() routerOSCollector { + c := &capsmanCollector{} + c.init() + return c +} + +func (c *capsmanCollector) init() { + //"rx-signal", "tx-signal", + c.props = []string{"interface", "mac-address", "ssid", "uptime", "tx-signal", "rx-signal", "packets", "bytes"} + labelNames := []string{"name", "address", "interface", "mac_address", "ssid"} + c.descriptions = make(map[string]*prometheus.Desc) + for _, p := range c.props[3 : len(c.props)-2] { + c.descriptions[p] = descriptionForPropertyName("capsman_station", p, labelNames) + } + for _, p := range c.props[len(c.props)-2:] { + c.descriptions["tx_"+p] = descriptionForPropertyName("capsman_station", "tx_"+p, labelNames) + c.descriptions["rx_"+p] = descriptionForPropertyName("capsman_station", "rx_"+p, labelNames) + } +} + +func (c *capsmanCollector) describe(ch chan<- *prometheus.Desc) { + for _, d := range c.descriptions { + ch <- d + } +} + +func (c *capsmanCollector) collect(ctx *collectorContext) error { + stats, err := c.fetch(ctx) + if err != nil { + return err + } + + for _, re := range stats { + c.collectForStat(re, ctx) + } + + return nil +} + +func (c *capsmanCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) { + reply, err := ctx.client.Run("/caps-man/registration-table/print", "=.proplist="+strings.Join(c.props, ",")) + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "error": err, + }).Error("error fetching wlan station metrics") + return nil, err + } + + return reply.Re, nil +} + +func (c *capsmanCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) { + iface := re.Map["interface"] + mac := re.Map["mac-address"] + ssid := re.Map["ssid"] + + for _, p := range c.props[3 : len(c.props)-2] { + c.collectMetricForProperty(p, iface, mac, ssid, re, ctx) + } + for _, p := range c.props[len(c.props)-2:] { + c.collectMetricForTXRXCounters(p, iface, mac, ssid, re, ctx) + } +} + +func (c *capsmanCollector) collectMetricForProperty(property, iface, mac, ssid string, re *proto.Sentence, ctx *collectorContext) { + if re.Map[property] == "" { + return + } + p := re.Map[property] + i := strings.Index(p, "@") + if i > -1 { + p = p[:i] + } + var v float64 + var err error + if property != "uptime" { + v, err = strconv.ParseFloat(p, 64) + } else { + v, err = parseDuration(p) + } + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "property": property, + "value": re.Map[property], + "error": err, + }).Error("error parsing capsman station metric value") + return + } + + desc := c.descriptions[property] + ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, iface, mac, ssid) +} + +func (c *capsmanCollector) collectMetricForTXRXCounters(property, iface, mac, ssid string, re *proto.Sentence, ctx *collectorContext) { + tx, rx, err := splitStringToFloats(re.Map[property]) + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "property": property, + "value": re.Map[property], + "error": err, + }).Error("error parsing capsman station metric value") + return + } + desc_tx := c.descriptions["tx_"+property] + desc_rx := c.descriptions["rx_"+property] + ctx.ch <- prometheus.MustNewConstMetric(desc_tx, prometheus.CounterValue, tx, ctx.device.Name, ctx.device.Address, iface, mac, ssid) + ctx.ch <- prometheus.MustNewConstMetric(desc_rx, prometheus.CounterValue, rx, ctx.device.Name, ctx.device.Address, iface, mac, ssid) +} diff --git a/collector/collector.go b/collector/collector.go index 53424de..6858f92 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -139,6 +139,13 @@ func WithWlanSTA() Option { } } +// WithWlanIF enables wireless interface metrics +func WithCapsman() Option { + return func(c *collector) { + c.collectors = append(c.collectors, newCapsmanCollector()) + } +} + // WithWlanIF enables wireless interface metrics func WithWlanIF() Option { return func(c *collector) { diff --git a/collector/helper.go b/collector/helper.go index 726b3be..1ec50b5 100644 --- a/collector/helper.go +++ b/collector/helper.go @@ -13,11 +13,11 @@ import ( ) var durationRegex *regexp.Regexp -var durationParts [5]time.Duration +var durationParts [6]time.Duration func init() { - durationRegex = regexp.MustCompile(`(?:(\d*)w)?(?:(\d*)d)?(?:(\d*)h)?(?:(\d*)m)?(?:(\d*)s)?`) - durationParts = [5]time.Duration{time.Hour * 168, time.Hour * 24, time.Hour, time.Minute, time.Second} + durationRegex = regexp.MustCompile(`(?:(\d*)w)?(?:(\d*)d)?(?:(\d*)h)?(?:(\d*)m)?(?:(\d*)s)?(?:(\d*)ms)?`) + durationParts = [6]time.Duration{time.Hour * 168, time.Hour * 24, time.Hour, time.Minute, time.Second, time.Millisecond} } func metricStringCleanup(in string) string { @@ -39,7 +39,7 @@ func descriptionForPropertyNameHelpText(prefix, property string, labelNames []st func description(prefix, name, helpText string, labelNames []string) *prometheus.Desc { return prometheus.NewDesc( - prometheus.BuildFQName(namespace, prefix, name), + prometheus.BuildFQName(namespace, prefix, metricStringCleanup(name)), helpText, labelNames, nil, diff --git a/config/config.go b/config/config.go index 072be39..7905d25 100644 --- a/config/config.go +++ b/config/config.go @@ -24,6 +24,7 @@ type Config struct { Optics bool `yaml:"optics,omitempty"` W60G bool `yaml:"w60g,omitempty"` WlanSTA bool `yaml:"wlansta,omitempty"` + Capsman bool `yaml:"capsman,omitempty"` WlanIF bool `yaml:"wlanif,omitempty"` Monitor bool `yaml:"monitor,omitempty"` Ipsec bool `yaml:"ipsec,omitempty"` diff --git a/main.go b/main.go index 20d33c1..2d2b21e 100644 --- a/main.go +++ b/main.go @@ -50,6 +50,7 @@ var ( withW60G = flag.Bool("with-w60g", false, "retrieves w60g interface metrics") withWlanSTA = flag.Bool("with-wlansta", false, "retrieves connected wlan station metrics") withWlanIF = flag.Bool("with-wlanif", false, "retrieves wlan interface metrics") + withCapsman = flag.Bool("with-capsman", false, "retrieves capsman station metrics") withMonitor = flag.Bool("with-monitor", false, "retrieves ethernet interface monitor info") withIpsec = flag.Bool("with-ipsec", false, "retrieves ipsec metrics") withLte = flag.Bool("with-lte", false, "retrieves lte metrics") @@ -238,6 +239,10 @@ func collectorOptions() []collector.Option { opts = append(opts, collector.WithWlanSTA()) } + if *withCapsman || cfg.Features.Capsman { + opts = append(opts, collector.WithCapsman()) + } + if *withWlanIF || cfg.Features.WlanIF { opts = append(opts, collector.WithWlanIF()) }