diff --git a/collector/collector.go b/collector/collector.go index 4ee01b9d..4af11570 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -84,6 +84,13 @@ func WithDHCPv6() Option { } } +// WithPOE enables PoE metrics +func WithPOE() Option { + return func(c *collector) { + c.collectors = append(c.collectors, newPOECollector()) + } +} + // WithPools enables IP(v6) pool metrics func WithPools() Option { return func(c *collector) { diff --git a/collector/poe_collector.go b/collector/poe_collector.go new file mode 100644 index 00000000..45327708 --- /dev/null +++ b/collector/poe_collector.go @@ -0,0 +1,122 @@ +package collector + +import ( + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "gopkg.in/routeros.v2/proto" +) + +type poeCollector struct { + currentDesc *prometheus.Desc + powerDesc *prometheus.Desc + voltageDesc *prometheus.Desc + props []string +} + +func newPOECollector() routerOSCollector { + const prefix = "poe" + + labelNames := []string{"name", "address", "interface"} + return &poeCollector{ + currentDesc: description(prefix, "current", "current in mA", labelNames), + powerDesc: description(prefix, "wattage", "Power in W", labelNames), + voltageDesc: description(prefix, "voltage", "Voltage in V", labelNames), + props: []string{"poe-out-current", "poe-out-voltage", "poe-out-power"}, + } +} + +func (c *poeCollector) describe(ch chan<- *prometheus.Desc) { + ch <- c.currentDesc + ch <- c.powerDesc + ch <- c.voltageDesc +} + +func (c *poeCollector) collect(ctx *collectorContext) error { + reply, err := ctx.client.Run("/interface/ethernet/poe/print", "=.proplist=name") + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "error": err, + }).Error("error fetching interface poe metrics") + return err + } + + ifaces := make([]string, 0) + for _, iface := range reply.Re { + n := iface.Map["name"] + ifaces = append(ifaces, n) + } + + if len(ifaces) == 0 { + return nil + } + + return c.collectPOEMetricsForInterfaces(ifaces, ctx) +} + +func (c *poeCollector) collectPOEMetricsForInterfaces(ifaces []string, ctx *collectorContext) error { + reply, err := ctx.client.Run("/interface/ethernet/poe/monitor", + "=numbers="+strings.Join(ifaces, ","), + "=once=", + "=.proplist=name,"+strings.Join(c.props, ",")) + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "error": err, + }).Error("error fetching interface poe monitor metrics") + return err + } + + for _, se := range reply.Re { + name, ok := se.Map["name"] + if !ok { + continue + } + + c.collectMetricsForInterface(name, se, ctx) + } + + return nil +} + +func (c *poeCollector) collectMetricsForInterface(name string, se *proto.Sentence, ctx *collectorContext) { + for _, prop := range c.props { + v, ok := se.Map[prop] + if !ok { + continue + } + + value, err := strconv.ParseFloat(v, 64) + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "interface": name, + "property": prop, + "error": err, + }).Error("error parsing interface poe monitor metric") + return + } + + ctx.ch <- prometheus.MustNewConstMetric(c.descForKey(prop), prometheus.GaugeValue, value, ctx.device.Name, ctx.device.Address, name) + } +} + +func (c *poeCollector) valueForKey(name, value string) (float64, error) { + return strconv.ParseFloat(value, 64) +} + +func (c *poeCollector) descForKey(name string) *prometheus.Desc { + switch name { + case "poe-out-current": + return c.currentDesc + case "poe-out-voltage": + return c.voltageDesc + case "poe-out-power": + return c.powerDesc + } + + return nil +} diff --git a/config/config.go b/config/config.go index 63959477..7f8ccf71 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ type Config struct { DHCP bool `yaml:"dhcp,omitempty"` DHCPv6 bool `yaml:"dhcpv6,omitempty"` Routes bool `yaml:"routes,omitempty"` + POE bool `yaml:"poe,omitempty"` Pools bool `yaml:"pools,omitempty"` Optics bool `yaml:"optics,omitempty"` WlanSTA bool `yaml:"wlansta,omitempty"` diff --git a/main.go b/main.go index 787ada63..9868908d 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ var ( withRoutes = flag.Bool("with-routes", false, "retrieves routing table information") withDHCP = flag.Bool("with-dhcp", false, "retrieves DHCP server metrics") withDHCPv6 = flag.Bool("with-dhcpv6", false, "retrieves DHCPv6 server metrics") + withPOE = flag.Bool("with-poe", false, "retrieves PoE metrics") withPools = flag.Bool("with-pools", false, "retrieves IP(v6) pool metrics") withOptics = flag.Bool("with-optics", false, "retrieves optical diagnostic metrics") withWlanSTA = flag.Bool("with-wlansta", false, "retrieves connected wlan station metrics") @@ -187,6 +188,10 @@ func collectorOptions() []collector.Option { opts = append(opts, collector.WithDHCPv6()) } + if *withPOE || cfg.Features.POE { + opts = append(opts, collector.WithPOE()) + } + if *withPools || cfg.Features.Pools { opts = append(opts, collector.WithPools()) }