More features (#9)

* added config file implementation, refactoring
* add gitignore
* improved test
* preperations for more metrics
* added resource metrics
* added first bgp metrics
* added asn as label for bgp metrics
* added prefix and message counts to bgp metrics
* simplified
* Update README.md
* added yaml dependency
* fixed go routine call
* added timeout
* clean up
* added TLS support
* set default api port for TLS
* added routes metric
* added missing log information
* added type collectorContext to reduce the count of parameters for better readability
* added DHCP and DHCPv6 metrics
* filter for active dhcp leases only
* added pool metrics
* enable/disable feature in config file
* refactoring

* clean up

* comment fix
This commit is contained in:
Daniel Czerwonk
2018-04-11 15:21:38 +02:00
committed by Steve Brunton
parent f2866a3a2f
commit d170b0a4d2
16 changed files with 559 additions and 175 deletions

View File

@@ -4,59 +4,60 @@ import (
"strconv"
"strings"
"github.com/nshttpd/mikrotik-exporter/config"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
routeros "gopkg.in/routeros.v2"
"gopkg.in/routeros.v2/proto"
)
var (
bgpabelNames = []string{"name", "address", "session", "asn"}
bgpProps = []string{"name", "remote-as", "state", "prefix-count", "updates-sent", "updates-received", "withdrawn-sent", "withdrawn-received"}
bgpDescriptions map[string]*prometheus.Desc
)
type bgpCollector struct {
props []string
descriptions map[string]*prometheus.Desc
}
func init() {
bgpDescriptions = make(map[string]*prometheus.Desc)
bgpDescriptions["state"] = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "bgp", "up"),
"BGP session is established (up = 1)",
bgpabelNames,
nil,
)
for _, p := range bgpProps[3:] {
bgpDescriptions[p] = descriptionForPropertyName("bgp", p, bgpabelNames)
func newBGPCollector() routerOSCollector {
c := &bgpCollector{}
c.init()
return c
}
func (c *bgpCollector) init() {
c.props = []string{"name", "remote-as", "state", "prefix-count", "updates-sent", "updates-received", "withdrawn-sent", "withdrawn-received"}
const prefix = "bgp"
labelNames := []string{"name", "address", "session", "asn"}
c.descriptions = make(map[string]*prometheus.Desc)
c.descriptions["state"] = description(prefix, "up", "BGP session is established (up = 1)", labelNames)
for _, p := range c.props[3:] {
c.descriptions[p] = descriptionForPropertyName(prefix, p, labelNames)
}
}
type bgpCollector struct {
}
func (c *bgpCollector) describe(ch chan<- *prometheus.Desc) {
for _, d := range bgpDescriptions {
for _, d := range c.descriptions {
ch <- d
}
}
func (c *bgpCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
stats, err := c.fetch(client, device)
func (c *bgpCollector) collect(ctx *collectorContext) error {
stats, err := c.fetch(ctx)
if err != nil {
return err
}
for _, re := range stats {
c.collectForStat(re, device, ch)
c.collectForStat(re, ctx)
}
return nil
}
func (c *bgpCollector) fetch(client *routeros.Client, device *config.Device) ([]*proto.Sentence, error) {
reply, err := client.Run("/routing/bgp/peer/print", "=.proplist="+strings.Join(bgpProps, ","))
func (c *bgpCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
reply, err := ctx.client.Run("/routing/bgp/peer/print", "=.proplist="+strings.Join(c.props, ","))
if err != nil {
log.WithFields(log.Fields{
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching bgp metrics")
return nil, err
@@ -65,25 +66,25 @@ func (c *bgpCollector) fetch(client *routeros.Client, device *config.Device) ([]
return reply.Re, nil
}
func (c *bgpCollector) collectForStat(re *proto.Sentence, device *config.Device, ch chan<- prometheus.Metric) {
func (c *bgpCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) {
var session, asn string
for _, p := range bgpProps {
for _, p := range c.props {
if p == "name" {
session = re.Map[p]
} else if p == "remote-as" {
asn = re.Map[p]
} else {
c.collectMetricForProperty(p, session, asn, device, re, ch)
c.collectMetricForProperty(p, session, asn, re, ctx)
}
}
}
func (c *bgpCollector) collectMetricForProperty(property, session, asn string, device *config.Device, re *proto.Sentence, ch chan<- prometheus.Metric) {
desc := bgpDescriptions[property]
func (c *bgpCollector) collectMetricForProperty(property, session, asn string, re *proto.Sentence, ctx *collectorContext) {
desc := c.descriptions[property]
v, err := c.parseValueForProperty(property, re.Map[property])
if err != nil {
log.WithFields(log.Fields{
"device": device.Name,
"device": ctx.device.Name,
"session": session,
"property": property,
"value": re.Map[property],
@@ -92,7 +93,7 @@ func (c *bgpCollector) collectMetricForProperty(property, session, asn string, d
return
}
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, device.Name, device.Address, session, asn)
ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, session, asn)
}
func (c *bgpCollector) parseValueForProperty(property, value string) (float64, error) {

View File

@@ -37,7 +37,7 @@ var (
type collector struct {
devices []config.Device
collectors []metricCollector
collectors []routerOSCollector
timeout time.Duration
enableTLS bool
insecureTLS bool
@@ -53,7 +53,28 @@ func WithBGP() Option {
// WithRoutes enables routing table metrics
func WithRoutes() Option {
return func(c *collector) {
c.collectors = append(c.collectors, &routesCollector{})
c.collectors = append(c.collectors, newRoutesCollector())
}
}
// WithDHCP enables DHCP serrver metrics
func WithDHCP() Option {
return func(c *collector) {
c.collectors = append(c.collectors, newDHCPCollector())
}
}
// WithDHCPv6 enables DHCPv6 serrver metrics
func WithDHCPv6() Option {
return func(c *collector) {
c.collectors = append(c.collectors, newDHCPv6Collector())
}
}
// WithPools enables IP(v6) pool metrics
func WithPools() Option {
return func(c *collector) {
c.collectors = append(c.collectors, newPoolCollector())
}
}
@@ -84,9 +105,9 @@ func NewCollector(cfg *config.Config, opts ...Option) (prometheus.Collector, err
c := &collector{
devices: cfg.Devices,
timeout: DefaultTimeout,
collectors: []metricCollector{
&interfaceCollector{},
&resourceCollector{},
collectors: []routerOSCollector{
newInterfaceCollector(),
newResourceCollector(),
},
}
@@ -153,7 +174,8 @@ func (c *collector) connectAndCollect(d *config.Device, ch chan<- prometheus.Met
defer cl.Close()
for _, co := range c.collectors {
err = co.collect(ch, d, cl)
ctx := &collectorContext{ch, d, cl}
err = co.collect(ctx)
if err != nil {
return err
}

View File

@@ -6,7 +6,8 @@ import (
routeros "gopkg.in/routeros.v2"
)
type metricCollector interface {
describe(ch chan<- *prometheus.Desc)
collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error
type collectorContext struct {
ch chan<- prometheus.Metric
device *config.Device
client *routeros.Client
}

View File

@@ -0,0 +1,89 @@
package collector
import (
"fmt"
"strconv"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)
type dhcpCollector struct {
leasesActiveCountDesc *prometheus.Desc
}
func (c *dhcpCollector) init() {
const prefix = "dhcp"
labelNames := []string{"name", "address", "server"}
c.leasesActiveCountDesc = description(prefix, "leases_active_count", "number of active leases per DHCP server", labelNames)
}
func newDHCPCollector() routerOSCollector {
c := &dhcpCollector{}
c.init()
return c
}
func (c *dhcpCollector) describe(ch chan<- *prometheus.Desc) {
ch <- c.leasesActiveCountDesc
}
func (c *dhcpCollector) collect(ctx *collectorContext) error {
names, err := c.fetchDHCPServerNames(ctx)
if err != nil {
return err
}
for _, n := range names {
err := c.colllectForDHCPServer(ctx, n)
if err != nil {
return err
}
}
return nil
}
func (c *dhcpCollector) fetchDHCPServerNames(ctx *collectorContext) ([]string, error) {
reply, err := ctx.client.Run("/ip/dhcp-server/print", "=.proplist=name")
if err != nil {
log.WithFields(log.Fields{
"device": ctx.device.Name,
"error": err,
}).Error("error fetching DHCP server names")
return nil, err
}
names := []string{}
for _, re := range reply.Re {
names = append(names, re.Map["name"])
}
return names, nil
}
func (c *dhcpCollector) colllectForDHCPServer(ctx *collectorContext, dhcpServer string) error {
reply, err := ctx.client.Run("/ip/dhcp-server/lease/print", fmt.Sprintf("?server=%s", dhcpServer), "=active=", "=count-only=")
if err != nil {
log.WithFields(log.Fields{
"dhcp_server": dhcpServer,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching DHCP lease counts")
return err
}
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
if err != nil {
log.WithFields(log.Fields{
"dhcp_server": dhcpServer,
"device": ctx.device.Name,
"error": err,
}).Error("error parsing DHCP lease counts")
return err
}
ctx.ch <- prometheus.MustNewConstMetric(c.leasesActiveCountDesc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, dhcpServer)
return nil
}

View File

@@ -0,0 +1,89 @@
package collector
import (
"fmt"
"strconv"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)
type dhcpv6Collector struct {
bindingCountDesc *prometheus.Desc
}
func newDHCPv6Collector() routerOSCollector {
c := &dhcpv6Collector{}
c.init()
return c
}
func (c *dhcpv6Collector) init() {
const prefix = "dhcpv6"
labelNames := []string{"name", "address", "server"}
c.bindingCountDesc = description(prefix, "binding_count", "number of active bindings per DHCPv6 server", labelNames)
}
func (c *dhcpv6Collector) describe(ch chan<- *prometheus.Desc) {
ch <- c.bindingCountDesc
}
func (c *dhcpv6Collector) collect(ctx *collectorContext) error {
names, err := c.fetchDHCPServerNames(ctx)
if err != nil {
return err
}
for _, n := range names {
err := c.colllectForDHCPServer(ctx, n)
if err != nil {
return err
}
}
return nil
}
func (c *dhcpv6Collector) fetchDHCPServerNames(ctx *collectorContext) ([]string, error) {
reply, err := ctx.client.Run("/ipv6/dhcp-server/print", "=.proplist=name")
if err != nil {
log.WithFields(log.Fields{
"device": ctx.device.Name,
"error": err,
}).Error("error fetching DHCPv6 server names")
return nil, err
}
names := []string{}
for _, re := range reply.Re {
names = append(names, re.Map["name"])
}
return names, nil
}
func (c *dhcpv6Collector) colllectForDHCPServer(ctx *collectorContext, dhcpServer string) error {
reply, err := ctx.client.Run("/ipv6/dhcp-server/binding/print", fmt.Sprintf("?server=%s", dhcpServer), "=count-only=")
if err != nil {
log.WithFields(log.Fields{
"dhcpv6_server": dhcpServer,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching DHCPv6 binding counts")
return err
}
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
if err != nil {
log.WithFields(log.Fields{
"dhcpv6_server": dhcpServer,
"device": ctx.device.Name,
"error": err,
}).Error("error parsing DHCPv6 binding counts")
return err
}
ctx.ch <- prometheus.MustNewConstMetric(c.bindingCountDesc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, dhcpServer)
return nil
}

View File

@@ -18,3 +18,12 @@ func descriptionForPropertyName(prefix, property string, labelNames []string) *p
nil,
)
}
func description(prefix, name, helpText string, labelNames []string) *prometheus.Desc {
return prometheus.NewDesc(
prometheus.BuildFQName(namespace, prefix, name),
helpText,
labelNames,
nil,
)
}

View File

@@ -4,54 +4,56 @@ import (
"strconv"
"strings"
"github.com/nshttpd/mikrotik-exporter/config"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"gopkg.in/routeros.v2"
"gopkg.in/routeros.v2/proto"
)
var (
interfaceLabelNames = []string{"name", "address", "interface"}
interfaceProps = []string{"name", "rx-byte", "tx-byte", "rx-packet", "tx-packet", "rx-error", "tx-error", "rx-drop", "tx-drop"}
interfaceDescriptions map[string]*prometheus.Desc
)
type interfaceCollector struct {
props []string
descriptions map[string]*prometheus.Desc
}
func init() {
interfaceDescriptions = make(map[string]*prometheus.Desc)
for _, p := range interfaceProps[1:] {
interfaceDescriptions[p] = descriptionForPropertyName("interface", p, interfaceLabelNames)
func newInterfaceCollector() routerOSCollector {
c := &interfaceCollector{}
c.init()
return c
}
func (c *interfaceCollector) init() {
c.props = []string{"name", "rx-byte", "tx-byte", "rx-packet", "tx-packet", "rx-error", "tx-error", "rx-drop", "tx-drop"}
labelNames := []string{"name", "address", "interface"}
c.descriptions = make(map[string]*prometheus.Desc)
for _, p := range c.props[1:] {
c.descriptions[p] = descriptionForPropertyName("interface", p, labelNames)
}
}
type interfaceCollector struct {
}
func (c *interfaceCollector) describe(ch chan<- *prometheus.Desc) {
for _, d := range interfaceDescriptions {
for _, d := range c.descriptions {
ch <- d
}
}
func (c *interfaceCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
stats, err := c.fetch(client, device)
func (c *interfaceCollector) collect(ctx *collectorContext) error {
stats, err := c.fetch(ctx)
if err != nil {
return err
}
for _, re := range stats {
c.collectForStat(re, device, ch)
c.collectForStat(re, ctx)
}
return nil
}
func (c *interfaceCollector) fetch(client *routeros.Client, device *config.Device) ([]*proto.Sentence, error) {
reply, err := client.Run("/interface/print", "?disabled=false",
"?running=true", "=.proplist="+strings.Join(interfaceProps, ","))
func (c *interfaceCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
reply, err := ctx.client.Run("/interface/print", "?disabled=false", "?running=true", "=.proplist="+strings.Join(c.props, ","))
if err != nil {
log.WithFields(log.Fields{
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching interface metrics")
return nil, err
@@ -60,23 +62,23 @@ func (c *interfaceCollector) fetch(client *routeros.Client, device *config.Devic
return reply.Re, nil
}
func (c *interfaceCollector) collectForStat(re *proto.Sentence, device *config.Device, ch chan<- prometheus.Metric) {
func (c *interfaceCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) {
var iface string
for _, p := range interfaceProps {
for _, p := range c.props {
if p == "name" {
iface = re.Map[p]
} else {
c.collectMetricForProperty(p, iface, device, re, ch)
c.collectMetricForProperty(p, iface, re, ctx)
}
}
}
func (c *interfaceCollector) collectMetricForProperty(property, iface string, device *config.Device, re *proto.Sentence, ch chan<- prometheus.Metric) {
desc := interfaceDescriptions[property]
func (c *interfaceCollector) collectMetricForProperty(property, iface string, re *proto.Sentence, ctx *collectorContext) {
desc := c.descriptions[property]
v, err := strconv.ParseFloat(re.Map[property], 64)
if err != nil {
log.WithFields(log.Fields{
"device": device.Name,
"device": ctx.device.Name,
"interface": iface,
"property": property,
"value": re.Map[property],
@@ -85,5 +87,5 @@ func (c *interfaceCollector) collectMetricForProperty(property, iface string, de
return
}
ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, device.Name, device.Address, iface)
ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, ctx.device.Name, ctx.device.Address, iface)
}

105
collector/pool_collector.go Normal file
View File

@@ -0,0 +1,105 @@
package collector
import (
"fmt"
"strconv"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)
type poolCollector struct {
usedCountDesc *prometheus.Desc
}
func (c *poolCollector) init() {
const prefix = "ip_pool"
labelNames := []string{"name", "address", "ip_version", "pool"}
c.usedCountDesc = description(prefix, "pool_used_count", "number of used IP/prefixes in a pool", labelNames)
}
func newPoolCollector() routerOSCollector {
c := &poolCollector{}
c.init()
return c
}
func (c *poolCollector) describe(ch chan<- *prometheus.Desc) {
ch <- c.usedCountDesc
}
func (c *poolCollector) collect(ctx *collectorContext) error {
err := c.collectForIPVersion("4", "ip", ctx)
if err != nil {
return err
}
err = c.collectForIPVersion("6", "ipv6", ctx)
if err != nil {
return err
}
return nil
}
func (c *poolCollector) collectForIPVersion(ipVersion, topic string, ctx *collectorContext) error {
names, err := c.fetchPoolNames(ipVersion, topic, ctx)
if err != nil {
return err
}
for _, n := range names {
err := c.collectForPool(ipVersion, topic, n, ctx)
if err != nil {
return err
}
}
return nil
}
func (c *poolCollector) fetchPoolNames(ipVersion, topic string, ctx *collectorContext) ([]string, error) {
reply, err := ctx.client.Run(fmt.Sprintf("/%s/pool/print", topic), "=.proplist=name")
if err != nil {
log.WithFields(log.Fields{
"device": ctx.device.Name,
"error": err,
}).Error("error fetching pool names")
return nil, err
}
names := []string{}
for _, re := range reply.Re {
names = append(names, re.Map["name"])
}
return names, nil
}
func (c *poolCollector) collectForPool(ipVersion, topic, pool string, ctx *collectorContext) error {
reply, err := ctx.client.Run(fmt.Sprintf("/%s/pool/used/print", topic), fmt.Sprintf("?pool=%s", pool), "=count-only=")
if err != nil {
log.WithFields(log.Fields{
"pool": pool,
"ip_version": ipVersion,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching pool counts")
return err
}
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
if err != nil {
log.WithFields(log.Fields{
"pool": pool,
"ip_version": ipVersion,
"device": ctx.device.Name,
"error": err,
}).Error("error parsing pool counts")
return err
}
ctx.ch <- prometheus.MustNewConstMetric(c.usedCountDesc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, ipVersion, pool)
return nil
}

View File

@@ -4,53 +4,56 @@ import (
"strconv"
"strings"
"github.com/nshttpd/mikrotik-exporter/config"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"gopkg.in/routeros.v2"
"gopkg.in/routeros.v2/proto"
)
var (
resourceLabelNames = []string{"name", "address"}
resourceProps = []string{"free-memory", "total-memory", "cpu-load", "free-hdd-space", "total-hdd-space"}
resourceDescriptions map[string]*prometheus.Desc
)
type resourceCollector struct {
props []string
descriptions map[string]*prometheus.Desc
}
func init() {
resourceDescriptions = make(map[string]*prometheus.Desc)
for _, p := range resourceProps {
resourceDescriptions[p] = descriptionForPropertyName("system", p, resourceLabelNames)
func newResourceCollector() routerOSCollector {
c := &resourceCollector{}
c.init()
return c
}
func (c *resourceCollector) init() {
c.props = []string{"free-memory", "total-memory", "cpu-load", "free-hdd-space", "total-hdd-space"}
labelNames := []string{"name", "address"}
c.descriptions = make(map[string]*prometheus.Desc)
for _, p := range c.props {
c.descriptions[p] = descriptionForPropertyName("system", p, labelNames)
}
}
type resourceCollector struct {
}
func (c *resourceCollector) describe(ch chan<- *prometheus.Desc) {
for _, d := range resourceDescriptions {
for _, d := range c.descriptions {
ch <- d
}
}
func (c *resourceCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
stats, err := c.fetch(client, device)
func (c *resourceCollector) collect(ctx *collectorContext) error {
stats, err := c.fetch(ctx)
if err != nil {
return err
}
for _, re := range stats {
c.collectForStat(re, device, ch)
c.collectForStat(re, ctx)
}
return nil
}
func (c *resourceCollector) fetch(client *routeros.Client, device *config.Device) ([]*proto.Sentence, error) {
reply, err := client.Run("/system/resource/print", "=.proplist="+strings.Join(resourceProps, ","))
func (c *resourceCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
reply, err := ctx.client.Run("/system/resource/print", "=.proplist="+strings.Join(c.props, ","))
if err != nil {
log.WithFields(log.Fields{
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching system resource metrics")
return nil, err
@@ -59,17 +62,17 @@ func (c *resourceCollector) fetch(client *routeros.Client, device *config.Device
return reply.Re, nil
}
func (c *resourceCollector) collectForStat(re *proto.Sentence, device *config.Device, ch chan<- prometheus.Metric) {
for _, p := range resourceProps {
c.collectMetricForProperty(p, device, re, ch)
func (c *resourceCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) {
for _, p := range c.props {
c.collectMetricForProperty(p, re, ctx)
}
}
func (c *resourceCollector) collectMetricForProperty(property string, device *config.Device, re *proto.Sentence, ch chan<- prometheus.Metric) {
func (c *resourceCollector) collectMetricForProperty(property string, re *proto.Sentence, ctx *collectorContext) {
v, err := strconv.ParseFloat(re.Map[property], 64)
if err != nil {
log.WithFields(log.Fields{
"device": device.Name,
"device": ctx.device.Name,
"property": property,
"value": re.Map[property],
"error": err,
@@ -77,6 +80,6 @@ func (c *resourceCollector) collectMetricForProperty(property string, device *co
return
}
desc := resourceDescriptions[property]
ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, device.Name, device.Address)
desc := c.descriptions[property]
ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, ctx.device.Name, ctx.device.Address)
}

View File

@@ -0,0 +1,10 @@
package collector
import (
"github.com/prometheus/client_golang/prometheus"
)
type routerOSCollector interface {
describe(ch chan<- *prometheus.Desc)
collect(ctx *collectorContext) error
}

View File

@@ -4,95 +4,101 @@ import (
"fmt"
"strconv"
"github.com/nshttpd/mikrotik-exporter/config"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"gopkg.in/routeros.v2"
)
const routesPrefiix = "routes"
var (
routesProtocols = []string{"bgp", "static", "ospf", "dynamic", "connect"}
)
var (
routesTotalDesc *prometheus.Desc
routesProtocolDesc *prometheus.Desc
)
func init() {
l := []string{"name", "address", "ip_version"}
routesTotalDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, routesPrefiix, "total_count"),
"number of routes in RIB",
l,
nil,
)
routesProtocolDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, routesPrefiix, "protocol_count"),
"number of routes per protocol in RIB",
append(l, "protocol"),
nil,
)
}
type routesCollector struct {
protocols []string
countDesc *prometheus.Desc
countProtocolDesc *prometheus.Desc
}
func newRoutesCollector() routerOSCollector {
c := &routesCollector{}
c.init()
return c
}
func (c *routesCollector) init() {
const prefix = "routes"
labelNames := []string{"name", "address", "ip_version"}
c.countDesc = description(prefix, "total_count", "number of routes in RIB", labelNames)
c.countProtocolDesc = description(prefix, "protocol_count", "number of routes per protocol in RIB", append(labelNames, "protocol"))
c.protocols = []string{"bgp", "static", "ospf", "dynamic", "connect"}
}
func (c *routesCollector) describe(ch chan<- *prometheus.Desc) {
ch <- routesTotalDesc
ch <- routesProtocolDesc
ch <- c.countDesc
ch <- c.countProtocolDesc
}
func (c *routesCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
c.colllectForIPVersion(client, device, ch, "4", "ip")
c.colllectForIPVersion(client, device, ch, "6", "ipv6")
func (c *routesCollector) collect(ctx *collectorContext) error {
err := c.colllectForIPVersion("4", "ip", ctx)
if err != nil {
return err
}
err = c.colllectForIPVersion("6", "ipv6", ctx)
if err != nil {
return err
}
return nil
}
func (c *routesCollector) colllectForIPVersion(client *routeros.Client, device *config.Device, ch chan<- prometheus.Metric, ipVersion, topic string) {
c.colllectCount(client, device, ch, ipVersion, topic)
for _, p := range routesProtocols {
c.colllectCountProtcol(client, device, ch, ipVersion, topic, p)
func (c *routesCollector) colllectForIPVersion(ipVersion, topic string, ctx *collectorContext) error {
err := c.colllectCount(ipVersion, topic, ctx)
if err != nil {
return err
}
for _, p := range c.protocols {
err := c.colllectCountProtcol(ipVersion, topic, p, ctx)
if err != nil {
return err
}
}
return nil
}
func (c *routesCollector) colllectCount(client *routeros.Client, device *config.Device, ch chan<- prometheus.Metric, ipVersion, topic string) {
reply, err := client.Run(fmt.Sprintf("/%s/route/print", topic), "?disabled=false", "=count-only=")
func (c *routesCollector) colllectCount(ipVersion, topic string, ctx *collectorContext) error {
reply, err := ctx.client.Run(fmt.Sprintf("/%s/route/print", topic), "?disabled=false", "=count-only=")
if err != nil {
log.WithFields(log.Fields{
"ip_version": ipVersion,
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching routes metrics")
return
return err
}
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
if err != nil {
log.WithFields(log.Fields{
"ip_version": ipVersion,
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error parsing routes metrics")
return
return err
}
ch <- prometheus.MustNewConstMetric(routesTotalDesc, prometheus.GaugeValue, v, device.Name, device.Address, ipVersion)
ctx.ch <- prometheus.MustNewConstMetric(c.countDesc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, ipVersion)
return nil
}
func (c *routesCollector) colllectCountProtcol(client *routeros.Client, device *config.Device, ch chan<- prometheus.Metric, ipVersion, topic, protocol string) {
reply, err := client.Run(fmt.Sprintf("/%s/route/print", topic), "?disabled=false", fmt.Sprintf("?%s", protocol), "=count-only=")
func (c *routesCollector) colllectCountProtcol(ipVersion, topic, protocol string, ctx *collectorContext) error {
reply, err := ctx.client.Run(fmt.Sprintf("/%s/route/print", topic), "?disabled=false", fmt.Sprintf("?%s", protocol), "=count-only=")
if err != nil {
log.WithFields(log.Fields{
"ip_version": ipVersion,
"protocol": protocol,
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error fetching routes metrics")
return
return err
}
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
@@ -100,11 +106,12 @@ func (c *routesCollector) colllectCountProtcol(client *routeros.Client, device *
log.WithFields(log.Fields{
"ip_version": ipVersion,
"protocol": protocol,
"device": device.Name,
"device": ctx.device.Name,
"error": err,
}).Error("error parsing routes metrics")
return
return err
}
ch <- prometheus.MustNewConstMetric(routesProtocolDesc, prometheus.GaugeValue, v, device.Name, device.Address, ipVersion, protocol)
ctx.ch <- prometheus.MustNewConstMetric(c.countProtocolDesc, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, ipVersion, protocol)
return nil
}