diff --git a/README.md b/README.md index 8f150be2..4f470ac6 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ devices: port: 8999 user: prometheus2 password: password_to_second_router + wifiwave2: true - name: routers_srv_dns srv: record: _mikrotik._udp.example.com @@ -87,12 +88,18 @@ features: routes: true pools: true optics: true + wlanif: true + wlansta: true ``` If you add a devices with the `srv` parameter instead of `address` the exporter will perform a DNS query to obtain the SRV record and discover the devices dynamically. Also, you can specify a DNS server to use on the query. +Use the option `wifiwave2: true` for devices that have the `wifiwave2` package, +which replaces the `wireless` implementation, installed. This is necessary as `wifiwave2` has a slightly +different API and exposes a slightly smaller set of attributes (for example, no signal-to-noise, etc.) + ###### example output diff --git a/collector/wlanif_collector.go b/collector/wlanif_collector.go index 491d1c78..7167ec0d 100644 --- a/collector/wlanif_collector.go +++ b/collector/wlanif_collector.go @@ -11,8 +11,9 @@ import ( ) type wlanIFCollector struct { - props []string - descriptions map[string]*prometheus.Desc + props []string + propsWifiwave2 []string + descriptions map[string]*prometheus.Desc } func newWlanIFCollector() routerOSCollector { @@ -23,11 +24,15 @@ func newWlanIFCollector() routerOSCollector { func (c *wlanIFCollector) init() { c.props = []string{"channel", "registered-clients", "noise-floor", "overall-tx-ccq"} + // wifiwave2 has slightly different names + c.propsWifiwave2 = []string{"channel", "registered-peers"} labelNames := []string{"name", "address", "interface", "channel"} c.descriptions = make(map[string]*prometheus.Desc) for _, p := range c.props { c.descriptions[p] = descriptionForPropertyName("wlan_interface", p, labelNames) } + // add description for wifiwave2-specific properties to map to wireless ones + c.descriptions["registered-peers"] = descriptionForPropertyName("wlan_interface", "registered-clients", labelNames) } func (c *wlanIFCollector) describe(ch chan<- *prometheus.Desc) { @@ -53,7 +58,13 @@ func (c *wlanIFCollector) collect(ctx *collectorContext) error { } func (c *wlanIFCollector) fetchInterfaceNames(ctx *collectorContext) ([]string, error) { - reply, err := ctx.client.Run("/interface/wireless/print", "?disabled=false", "=.proplist=name") + cmd := "" + if ctx.device.Wifiwave2 { + cmd = "/interface/wifiwave/print" + } else { + cmd = "/interface/wireless/print" + } + reply, err := ctx.client.Run(cmd, "?disabled=false", "=.proplist=name") if err != nil { log.WithFields(log.Fields{ "device": ctx.device.Name, @@ -71,7 +82,16 @@ func (c *wlanIFCollector) fetchInterfaceNames(ctx *collectorContext) ([]string, } func (c *wlanIFCollector) collectForInterface(iface string, ctx *collectorContext) error { - reply, err := ctx.client.Run("/interface/wireless/monitor", fmt.Sprintf("=numbers=%s", iface), "=once=", "=.proplist="+strings.Join(c.props, ",")) + cmd := "" + var props []string + if ctx.device.Wifiwave2 { + cmd = "/interface/wifiwave/monitor" + props = c.propsWifiwave2 + } else { + cmd = "/interface/wireless/monitor" + props = c.props + } + reply, err := ctx.client.Run(cmd, fmt.Sprintf("=numbers=%s", iface), "=once=", "=.proplist="+strings.Join(props, ",")) if err != nil { log.WithFields(log.Fields{ "interface": iface, @@ -81,7 +101,7 @@ func (c *wlanIFCollector) collectForInterface(iface string, ctx *collectorContex return err } - for _, p := range c.props[1:] { + for _, p := range props[1:] { // there's always going to be only one sentence in reply, as we // have to explicitly specify the interface c.collectMetricForProperty(p, iface, reply.Re[0], ctx) diff --git a/collector/wlansta_collector.go b/collector/wlansta_collector.go index 04715716..411af8ab 100644 --- a/collector/wlansta_collector.go +++ b/collector/wlansta_collector.go @@ -9,9 +9,20 @@ import ( "gopkg.in/routeros.v2/proto" ) +// from https://forum.mikrotik.com/viewtopic.php?t=195124#p999722: +// wifiwave2 is an implementation of drivers from the manufacturer of the +// chipset, rather than an in-house written driver (which wireless is). So +// there are many small details that are missing or incomplete... + type wlanSTACollector struct { - props []string - descriptions map[string]*prometheus.Desc + // Both wifiwave2 and wireless have a similar, yet different API. They also + // expose a slightly different set of properties. + props []string + propsWirelessExtra []string + propsWirelessRXTX []string + propsWifiwave2Extra []string + propsWifiwave2RXTX []string + descriptions map[string]*prometheus.Desc } func newWlanSTACollector() routerOSCollector { @@ -21,13 +32,28 @@ func newWlanSTACollector() routerOSCollector { } func (c *wlanSTACollector) init() { - c.props = []string{"interface", "mac-address", "signal-to-noise", "signal-strength", "packets", "bytes", "frames"} + // common properties + c.props = []string{"interface", "mac-address"} + // wifiwave2 doesn't expose SNR, and uses different name for signal-strength + c.propsWirelessExtra = []string{"signal-to-noise", "signal-strength"} + // wireless exposes extra field "frames", not available in wifiwave2 + c.propsWirelessRXTX = []string{"packets", "bytes", "frames"} + c.propsWifiwave2Extra = []string{"signal"} + c.propsWifiwave2RXTX = []string{"packets", "bytes"} + // all metrics have the same label names labelNames := []string{"name", "address", "interface", "mac_address"} c.descriptions = make(map[string]*prometheus.Desc) - for _, p := range c.props[:len(c.props)-3] { + for _, p := range c.propsWirelessExtra { c.descriptions[p] = descriptionForPropertyName("wlan_station", p, labelNames) } - for _, p := range c.props[len(c.props)-3:] { + // normalize the metric name 'signal-strength' for the property "signal", so that dashboards + // that capture both wireless and wifiwave2 devices don't need to normalize + c.descriptions["signal"] = descriptionForPropertyName("wlan_station", "signal-strength", labelNames) + for _, p := range c.propsWirelessRXTX { + c.descriptions["tx_"+p] = descriptionForPropertyName("wlan_station", "tx_"+p, labelNames) + c.descriptions["rx_"+p] = descriptionForPropertyName("wlan_station", "rx_"+p, labelNames) + } + for _, p := range c.propsWifiwave2RXTX { c.descriptions["tx_"+p] = descriptionForPropertyName("wlan_station", "tx_"+p, labelNames) c.descriptions["rx_"+p] = descriptionForPropertyName("wlan_station", "rx_"+p, labelNames) } @@ -53,7 +79,24 @@ func (c *wlanSTACollector) collect(ctx *collectorContext) error { } func (c *wlanSTACollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) { - reply, err := ctx.client.Run("/interface/wireless/registration-table/print", "=.proplist="+strings.Join(c.props, ",")) + var cmd []string + var props []string = c.props + if ctx.device.Wifiwave2 { + props = append(props, c.propsWifiwave2Extra...) + props = append(props, c.propsWifiwave2RXTX...) + cmd = []string{ + "/interface/wifiwave2/registration-table/print", + "=.proplist=" + strings.Join(props, ","), + } + } else { + props = append(props, c.propsWirelessExtra...) + props = append(props, c.propsWirelessRXTX...) + cmd = []string{ + "/interface/wireless/registration-table/print", + "=.proplist=" + strings.Join(props, ","), + } + } + reply, err := ctx.client.Run(cmd...) if err != nil { log.WithFields(log.Fields{ "device": ctx.device.Name, @@ -69,11 +112,20 @@ func (c *wlanSTACollector) collectForStat(re *proto.Sentence, ctx *collectorCont iface := re.Map["interface"] mac := re.Map["mac-address"] - for _, p := range c.props[2 : len(c.props)-3] { - c.collectMetricForProperty(p, iface, mac, re, ctx) - } - for _, p := range c.props[len(c.props)-3:] { - c.collectMetricForTXRXCounters(p, iface, mac, re, ctx) + if ctx.device.Wifiwave2 { + for _, p := range c.propsWifiwave2Extra { + c.collectMetricForProperty(p, iface, mac, re, ctx) + } + for _, p := range c.propsWifiwave2RXTX { + c.collectMetricForTXRXCounters(p, iface, mac, re, ctx) + } + } else { + for _, p := range c.propsWirelessExtra { + c.collectMetricForProperty(p, iface, mac, re, ctx) + } + for _, p := range c.propsWirelessRXTX { + c.collectMetricForTXRXCounters(p, iface, mac, re, ctx) + } } } diff --git a/config/config.go b/config/config.go index 7905d256..6ae1488f 100644 --- a/config/config.go +++ b/config/config.go @@ -35,12 +35,13 @@ type Config struct { // Device represents a target device type Device struct { - Name string `yaml:"name"` - Address string `yaml:"address,omitempty"` - Srv SrvRecord `yaml:"srv,omitempty"` - User string `yaml:"user"` - Password string `yaml:"password"` - Port string `yaml:"port"` + Name string `yaml:"name"` + Address string `yaml:"address,omitempty"` + Srv SrvRecord `yaml:"srv,omitempty"` + User string `yaml:"user"` + Password string `yaml:"password"` + Port string `yaml:"port"` + Wifiwave2 bool `yaml:"wifiwave2"` } type SrvRecord struct {