diff --git a/collector/collector.go b/collector/collector.go index 6a409ce..a6c384a 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -90,6 +90,13 @@ func WithDHCPv6() Option { } } +// WithHealth enables board Health metrics +func WithHealth() Option { + return func(c *collector) { + c.collectors = append(c.collectors, newhealthCollector()) + } +} + // WithPOE enables PoE metrics func WithPOE() Option { return func(c *collector) { @@ -207,7 +214,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { log.WithFields(log.Fields{ "SRV": dev.Srv.Record, }).Info("SRV configuration detected") - conf, _ := dns.ClientConfigFromFile("/etc/resolv.conf") + conf, _ := dns.ClientConfigFromFile("/etc/resolv.conf") dnsServer := net.JoinHostPort(conf.Servers[0], strconv.Itoa(dnsPort)) if (config.DnsServer{}) != dev.Srv.Dns { dnsServer = net.JoinHostPort(dev.Srv.Dns.Address, strconv.Itoa(dev.Srv.Dns.Port)) @@ -325,7 +332,7 @@ func (c *collector) connect(d *config.Device) (*routeros.Client, error) { log.WithField("device", d.Name).Debug("trying to Dial") if !c.enableTLS { - if(d.Port) == "" { + if (d.Port) == "" { d.Port = apiPort } conn, err = net.DialTimeout("tcp", d.Address+":"+d.Port, c.timeout) @@ -337,7 +344,7 @@ func (c *collector) connect(d *config.Device) (*routeros.Client, error) { tlsCfg := &tls.Config{ InsecureSkipVerify: c.insecureTLS, } - if(d.Port) == "" { + if (d.Port) == "" { d.Port = apiPortTLS } conn, err = tls.DialWithDialer(&net.Dialer{ diff --git a/collector/dhcp_lease_collector.go b/collector/dhcp_lease_collector.go index 1a671ae..d8e8035 100644 --- a/collector/dhcp_lease_collector.go +++ b/collector/dhcp_lease_collector.go @@ -1,10 +1,10 @@ package collector import ( - "strings" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" "gopkg.in/routeros.v2/proto" + "strings" ) type dhcpLeaseCollector struct { diff --git a/collector/health_collector.go b/collector/health_collector.go new file mode 100644 index 0000000..359bb60 --- /dev/null +++ b/collector/health_collector.go @@ -0,0 +1,93 @@ +package collector + +import ( + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "gopkg.in/routeros.v2/proto" +) + +type healthCollector struct { + props []string + descriptions map[string]*prometheus.Desc +} + +func newhealthCollector() routerOSCollector { + c := &healthCollector{} + c.init() + return c +} + +func (c *healthCollector) init() { + c.props = []string{"voltage", "temperature"} + + labelNames := []string{"name", "address"} + helpText := []string{"Input voltage to the RouterOS board, in volts", "Temperature of RouterOS board, in degrees Celsius"} + c.descriptions = make(map[string]*prometheus.Desc) + for i, p := range c.props { + c.descriptions[p] = descriptionForPropertyNameHelpText("health", p, labelNames, helpText[i]) + } +} + +func (c *healthCollector) describe(ch chan<- *prometheus.Desc) { + for _, d := range c.descriptions { + ch <- d + } +} + +func (c *healthCollector) 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 *healthCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) { + reply, err := ctx.client.Run("/system/health/print", "=.proplist="+strings.Join(c.props, ",")) + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "error": err, + }).Error("error fetching system health metrics") + return nil, err + } + + return reply.Re, nil +} + +func (c *healthCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) { + for _, p := range c.props[:2] { + c.collectMetricForProperty(p, re, ctx) + } +} + +func (c *healthCollector) collectMetricForProperty(property string, re *proto.Sentence, ctx *collectorContext) { + var v float64 + var err error + + if re.Map[property] == "" { + return + } + v, err = strconv.ParseFloat(re.Map[property], 64) + + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "property": property, + "value": re.Map[property], + "error": err, + }).Error("error parsing system health metric value") + return + } + + desc := c.descriptions[property] + ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address) +} diff --git a/collector/helper.go b/collector/helper.go index 8f5bd6a..623875a 100644 --- a/collector/helper.go +++ b/collector/helper.go @@ -13,9 +13,13 @@ func metricStringCleanup(in string) string { } func descriptionForPropertyName(prefix, property string, labelNames []string) *prometheus.Desc { + return descriptionForPropertyNameHelpText(prefix, property, labelNames, property) +} + +func descriptionForPropertyNameHelpText(prefix, property string, labelNames []string, helpText string) *prometheus.Desc { return prometheus.NewDesc( prometheus.BuildFQName(namespace, prefix, metricStringCleanup(property)), - property, + helpText, labelNames, nil, ) diff --git a/config/config.go b/config/config.go index 7d10027..69195b0 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ type Config struct { DHCP bool `yaml:"dhcp,omitempty"` DHCPL bool `yaml:"dhcpl,omitempty"` DHCPv6 bool `yaml:"dhcpv6,omitempty"` + Health bool `yaml:"health,omitempty"` Routes bool `yaml:"routes,omitempty"` POE bool `yaml:"poe,omitempty"` Pools bool `yaml:"pools,omitempty"` diff --git a/main.go b/main.go index 08748dd..4c143d1 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,7 @@ var ( withDHCP = flag.Bool("with-dhcp", false, "retrieves DHCP server metrics") withDHCPL = flag.Bool("with-dhcpl", false, "retrieves DHCP server lease metrics") withDHCPv6 = flag.Bool("with-dhcpv6", false, "retrieves DHCPv6 server metrics") + withHealth = flag.Bool("with-health", false, "retrieves board Health 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") @@ -124,7 +125,7 @@ func loadConfigFromFlags() (*config.Config, error) { Address: *address, User: *user, Password: *password, - Port: *deviceport, + Port: *deviceport, }, }, }, nil @@ -198,6 +199,10 @@ func collectorOptions() []collector.Option { opts = append(opts, collector.WithDHCPv6()) } + if *withHealth || cfg.Features.Health { + opts = append(opts, collector.WithHealth()) + } + if *withPOE || cfg.Features.POE { opts = append(opts, collector.WithPOE()) }