Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support wireless metrics for wifiwave2 devices (#155) #161

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
30 changes: 25 additions & 5 deletions collector/wlanif_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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)
Expand Down
74 changes: 63 additions & 11 deletions collector/wlansta_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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,
Expand All @@ -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)
}
}
}

Expand Down
13 changes: 7 additions & 6 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down