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:
committed by
Steve Brunton
parent
f2866a3a2f
commit
d170b0a4d2
@@ -42,7 +42,7 @@ created for the exporter to use to access the API.
|
|||||||
where `config-file` is the path to a config file in YAML format.
|
where `config-file` is the path to a config file in YAML format.
|
||||||
|
|
||||||
###### example config
|
###### example config
|
||||||
```
|
```yaml
|
||||||
devices:
|
devices:
|
||||||
- name: my_router
|
- name: my_router
|
||||||
address: 10.10.0.1
|
address: 10.10.0.1
|
||||||
@@ -52,6 +52,13 @@ devices:
|
|||||||
address: 10.10.0.2
|
address: 10.10.0.2
|
||||||
user: prometheus2
|
user: prometheus2
|
||||||
password: password_to_second_router
|
password: password_to_second_router
|
||||||
|
|
||||||
|
features:
|
||||||
|
bgp: true
|
||||||
|
dhcp: true
|
||||||
|
dhcpv6: true
|
||||||
|
routes: true
|
||||||
|
pools: true
|
||||||
```
|
```
|
||||||
|
|
||||||
###### example output
|
###### example output
|
||||||
|
|||||||
@@ -4,59 +4,60 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nshttpd/mikrotik-exporter/config"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
routeros "gopkg.in/routeros.v2"
|
|
||||||
"gopkg.in/routeros.v2/proto"
|
"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
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type bgpCollector struct {
|
type bgpCollector struct {
|
||||||
|
props []string
|
||||||
|
descriptions map[string]*prometheus.Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *bgpCollector) describe(ch chan<- *prometheus.Desc) {
|
func (c *bgpCollector) describe(ch chan<- *prometheus.Desc) {
|
||||||
for _, d := range bgpDescriptions {
|
for _, d := range c.descriptions {
|
||||||
ch <- d
|
ch <- d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *bgpCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
|
func (c *bgpCollector) collect(ctx *collectorContext) error {
|
||||||
stats, err := c.fetch(client, device)
|
stats, err := c.fetch(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, re := range stats {
|
for _, re := range stats {
|
||||||
c.collectForStat(re, device, ch)
|
c.collectForStat(re, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *bgpCollector) fetch(client *routeros.Client, device *config.Device) ([]*proto.Sentence, error) {
|
func (c *bgpCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
|
||||||
reply, err := client.Run("/routing/bgp/peer/print", "=.proplist="+strings.Join(bgpProps, ","))
|
reply, err := ctx.client.Run("/routing/bgp/peer/print", "=.proplist="+strings.Join(c.props, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error fetching bgp metrics")
|
}).Error("error fetching bgp metrics")
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -65,25 +66,25 @@ func (c *bgpCollector) fetch(client *routeros.Client, device *config.Device) ([]
|
|||||||
return reply.Re, nil
|
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
|
var session, asn string
|
||||||
for _, p := range bgpProps {
|
for _, p := range c.props {
|
||||||
if p == "name" {
|
if p == "name" {
|
||||||
session = re.Map[p]
|
session = re.Map[p]
|
||||||
} else if p == "remote-as" {
|
} else if p == "remote-as" {
|
||||||
asn = re.Map[p]
|
asn = re.Map[p]
|
||||||
} else {
|
} 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) {
|
func (c *bgpCollector) collectMetricForProperty(property, session, asn string, re *proto.Sentence, ctx *collectorContext) {
|
||||||
desc := bgpDescriptions[property]
|
desc := c.descriptions[property]
|
||||||
v, err := c.parseValueForProperty(property, re.Map[property])
|
v, err := c.parseValueForProperty(property, re.Map[property])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"session": session,
|
"session": session,
|
||||||
"property": property,
|
"property": property,
|
||||||
"value": re.Map[property],
|
"value": re.Map[property],
|
||||||
@@ -92,7 +93,7 @@ func (c *bgpCollector) collectMetricForProperty(property, session, asn string, d
|
|||||||
return
|
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) {
|
func (c *bgpCollector) parseValueForProperty(property, value string) (float64, error) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ var (
|
|||||||
|
|
||||||
type collector struct {
|
type collector struct {
|
||||||
devices []config.Device
|
devices []config.Device
|
||||||
collectors []metricCollector
|
collectors []routerOSCollector
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
enableTLS bool
|
enableTLS bool
|
||||||
insecureTLS bool
|
insecureTLS bool
|
||||||
@@ -53,7 +53,28 @@ func WithBGP() Option {
|
|||||||
// WithRoutes enables routing table metrics
|
// WithRoutes enables routing table metrics
|
||||||
func WithRoutes() Option {
|
func WithRoutes() Option {
|
||||||
return func(c *collector) {
|
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{
|
c := &collector{
|
||||||
devices: cfg.Devices,
|
devices: cfg.Devices,
|
||||||
timeout: DefaultTimeout,
|
timeout: DefaultTimeout,
|
||||||
collectors: []metricCollector{
|
collectors: []routerOSCollector{
|
||||||
&interfaceCollector{},
|
newInterfaceCollector(),
|
||||||
&resourceCollector{},
|
newResourceCollector(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +174,8 @@ func (c *collector) connectAndCollect(d *config.Device, ch chan<- prometheus.Met
|
|||||||
defer cl.Close()
|
defer cl.Close()
|
||||||
|
|
||||||
for _, co := range c.collectors {
|
for _, co := range c.collectors {
|
||||||
err = co.collect(ch, d, cl)
|
ctx := &collectorContext{ch, d, cl}
|
||||||
|
err = co.collect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import (
|
|||||||
routeros "gopkg.in/routeros.v2"
|
routeros "gopkg.in/routeros.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type metricCollector interface {
|
type collectorContext struct {
|
||||||
describe(ch chan<- *prometheus.Desc)
|
ch chan<- prometheus.Metric
|
||||||
collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error
|
device *config.Device
|
||||||
|
client *routeros.Client
|
||||||
}
|
}
|
||||||
89
collector/dhcp_collector.go
Normal file
89
collector/dhcp_collector.go
Normal 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
|
||||||
|
}
|
||||||
89
collector/dhcpv6_collector.go
Normal file
89
collector/dhcpv6_collector.go
Normal 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
|
||||||
|
}
|
||||||
@@ -18,3 +18,12 @@ func descriptionForPropertyName(prefix, property string, labelNames []string) *p
|
|||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func description(prefix, name, helpText string, labelNames []string) *prometheus.Desc {
|
||||||
|
return prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, prefix, name),
|
||||||
|
helpText,
|
||||||
|
labelNames,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,54 +4,56 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nshttpd/mikrotik-exporter/config"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/routeros.v2"
|
|
||||||
"gopkg.in/routeros.v2/proto"
|
"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
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
interfaceDescriptions = make(map[string]*prometheus.Desc)
|
|
||||||
for _, p := range interfaceProps[1:] {
|
|
||||||
interfaceDescriptions[p] = descriptionForPropertyName("interface", p, interfaceLabelNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type interfaceCollector struct {
|
type interfaceCollector struct {
|
||||||
|
props []string
|
||||||
|
descriptions map[string]*prometheus.Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *interfaceCollector) describe(ch chan<- *prometheus.Desc) {
|
func (c *interfaceCollector) describe(ch chan<- *prometheus.Desc) {
|
||||||
for _, d := range interfaceDescriptions {
|
for _, d := range c.descriptions {
|
||||||
ch <- d
|
ch <- d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *interfaceCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
|
func (c *interfaceCollector) collect(ctx *collectorContext) error {
|
||||||
stats, err := c.fetch(client, device)
|
stats, err := c.fetch(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, re := range stats {
|
for _, re := range stats {
|
||||||
c.collectForStat(re, device, ch)
|
c.collectForStat(re, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *interfaceCollector) fetch(client *routeros.Client, device *config.Device) ([]*proto.Sentence, error) {
|
func (c *interfaceCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
|
||||||
reply, err := client.Run("/interface/print", "?disabled=false",
|
reply, err := ctx.client.Run("/interface/print", "?disabled=false", "?running=true", "=.proplist="+strings.Join(c.props, ","))
|
||||||
"?running=true", "=.proplist="+strings.Join(interfaceProps, ","))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error fetching interface metrics")
|
}).Error("error fetching interface metrics")
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -60,23 +62,23 @@ func (c *interfaceCollector) fetch(client *routeros.Client, device *config.Devic
|
|||||||
return reply.Re, nil
|
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
|
var iface string
|
||||||
for _, p := range interfaceProps {
|
for _, p := range c.props {
|
||||||
if p == "name" {
|
if p == "name" {
|
||||||
iface = re.Map[p]
|
iface = re.Map[p]
|
||||||
} else {
|
} 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) {
|
func (c *interfaceCollector) collectMetricForProperty(property, iface string, re *proto.Sentence, ctx *collectorContext) {
|
||||||
desc := interfaceDescriptions[property]
|
desc := c.descriptions[property]
|
||||||
v, err := strconv.ParseFloat(re.Map[property], 64)
|
v, err := strconv.ParseFloat(re.Map[property], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"interface": iface,
|
"interface": iface,
|
||||||
"property": property,
|
"property": property,
|
||||||
"value": re.Map[property],
|
"value": re.Map[property],
|
||||||
@@ -85,5 +87,5 @@ func (c *interfaceCollector) collectMetricForProperty(property, iface string, de
|
|||||||
return
|
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
105
collector/pool_collector.go
Normal 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
|
||||||
|
}
|
||||||
@@ -4,53 +4,56 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nshttpd/mikrotik-exporter/config"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/routeros.v2"
|
|
||||||
"gopkg.in/routeros.v2/proto"
|
"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
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
resourceDescriptions = make(map[string]*prometheus.Desc)
|
|
||||||
for _, p := range resourceProps {
|
|
||||||
resourceDescriptions[p] = descriptionForPropertyName("system", p, resourceLabelNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type resourceCollector struct {
|
type resourceCollector struct {
|
||||||
|
props []string
|
||||||
|
descriptions map[string]*prometheus.Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *resourceCollector) describe(ch chan<- *prometheus.Desc) {
|
func (c *resourceCollector) describe(ch chan<- *prometheus.Desc) {
|
||||||
for _, d := range resourceDescriptions {
|
for _, d := range c.descriptions {
|
||||||
ch <- d
|
ch <- d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *resourceCollector) collect(ch chan<- prometheus.Metric, device *config.Device, client *routeros.Client) error {
|
func (c *resourceCollector) collect(ctx *collectorContext) error {
|
||||||
stats, err := c.fetch(client, device)
|
stats, err := c.fetch(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, re := range stats {
|
for _, re := range stats {
|
||||||
c.collectForStat(re, device, ch)
|
c.collectForStat(re, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *resourceCollector) fetch(client *routeros.Client, device *config.Device) ([]*proto.Sentence, error) {
|
func (c *resourceCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
|
||||||
reply, err := client.Run("/system/resource/print", "=.proplist="+strings.Join(resourceProps, ","))
|
reply, err := ctx.client.Run("/system/resource/print", "=.proplist="+strings.Join(c.props, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error fetching system resource metrics")
|
}).Error("error fetching system resource metrics")
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -59,17 +62,17 @@ func (c *resourceCollector) fetch(client *routeros.Client, device *config.Device
|
|||||||
return reply.Re, nil
|
return reply.Re, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *resourceCollector) collectForStat(re *proto.Sentence, device *config.Device, ch chan<- prometheus.Metric) {
|
func (c *resourceCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) {
|
||||||
for _, p := range resourceProps {
|
for _, p := range c.props {
|
||||||
c.collectMetricForProperty(p, device, re, ch)
|
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)
|
v, err := strconv.ParseFloat(re.Map[property], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"property": property,
|
"property": property,
|
||||||
"value": re.Map[property],
|
"value": re.Map[property],
|
||||||
"error": err,
|
"error": err,
|
||||||
@@ -77,6 +80,6 @@ func (c *resourceCollector) collectMetricForProperty(property string, device *co
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
desc := resourceDescriptions[property]
|
desc := c.descriptions[property]
|
||||||
ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, device.Name, device.Address)
|
ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, ctx.device.Name, ctx.device.Address)
|
||||||
}
|
}
|
||||||
|
|||||||
10
collector/routeros_collector.go
Normal file
10
collector/routeros_collector.go
Normal 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
|
||||||
|
}
|
||||||
@@ -4,95 +4,101 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/nshttpd/mikrotik-exporter/config"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
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 {
|
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) {
|
func (c *routesCollector) describe(ch chan<- *prometheus.Desc) {
|
||||||
ch <- routesTotalDesc
|
ch <- c.countDesc
|
||||||
ch <- routesProtocolDesc
|
ch <- c.countProtocolDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *routesCollector) colllectForIPVersion(client *routeros.Client, device *config.Device, ch chan<- prometheus.Metric, ipVersion, topic string) {
|
func (c *routesCollector) colllectForIPVersion(ipVersion, topic string, ctx *collectorContext) error {
|
||||||
c.colllectCount(client, device, ch, ipVersion, topic)
|
err := c.colllectCount(ipVersion, topic, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range routesProtocols {
|
for _, p := range c.protocols {
|
||||||
c.colllectCountProtcol(client, device, ch, ipVersion, topic, p)
|
err := c.colllectCountProtcol(ipVersion, topic, p, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *routesCollector) colllectCount(client *routeros.Client, device *config.Device, ch chan<- prometheus.Metric, ipVersion, topic string) {
|
return nil
|
||||||
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 {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"ip_version": ipVersion,
|
"ip_version": ipVersion,
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error fetching routes metrics")
|
}).Error("error fetching routes metrics")
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
|
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"ip_version": ipVersion,
|
"ip_version": ipVersion,
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error parsing routes metrics")
|
}).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) {
|
func (c *routesCollector) colllectCountProtcol(ipVersion, topic, protocol string, ctx *collectorContext) error {
|
||||||
reply, err := client.Run(fmt.Sprintf("/%s/route/print", topic), "?disabled=false", fmt.Sprintf("?%s", protocol), "=count-only=")
|
reply, err := ctx.client.Run(fmt.Sprintf("/%s/route/print", topic), "?disabled=false", fmt.Sprintf("?%s", protocol), "=count-only=")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"ip_version": ipVersion,
|
"ip_version": ipVersion,
|
||||||
"protocol": protocol,
|
"protocol": protocol,
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error fetching routes metrics")
|
}).Error("error fetching routes metrics")
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := strconv.ParseFloat(reply.Done.Map["ret"], 32)
|
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{
|
log.WithFields(log.Fields{
|
||||||
"ip_version": ipVersion,
|
"ip_version": ipVersion,
|
||||||
"protocol": protocol,
|
"protocol": protocol,
|
||||||
"device": device.Name,
|
"device": ctx.device.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("error parsing routes metrics")
|
}).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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,19 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Config represents the configuration for the exporter
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Devices []Device `yaml:"devices"`
|
Devices []Device `yaml:"devices"`
|
||||||
|
Features struct {
|
||||||
|
BGP bool `yaml:"bgp,omitempty"`
|
||||||
|
DHCP bool `yaml:"dhcp,omitempty"`
|
||||||
|
DHCPv6 bool `yaml:"dhcpv6,omitempty"`
|
||||||
|
Routes bool `yaml:"routes,omitempty"`
|
||||||
|
Pools bool `yaml:"pools,omitempty"`
|
||||||
|
} `yaml:"features,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device represents a target device
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
@@ -18,6 +27,7 @@ type Device struct {
|
|||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load reads YAML from reader and unmashals in Config
|
||||||
func Load(r io.Reader) (*Config, error) {
|
func Load(r io.Reader) (*Config, error) {
|
||||||
b, err := ioutil.ReadAll(r)
|
b, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ func TestShouldParse(t *testing.T) {
|
|||||||
t.Fatalf("expected 2 devices, got %v", len(c.Devices))
|
t.Fatalf("expected 2 devices, got %v", len(c.Devices))
|
||||||
}
|
}
|
||||||
|
|
||||||
assertConfig("test1", "192.168.1.1", "foo", "bar", c.Devices[0], t)
|
assertDevice("test1", "192.168.1.1", "foo", "bar", c.Devices[0], t)
|
||||||
assertConfig("test2", "192.168.2.1", "test", "123", c.Devices[1], t)
|
assertDevice("test2", "192.168.2.1", "test", "123", c.Devices[1], t)
|
||||||
|
assertFeature("BGP", c.Features.BGP, t)
|
||||||
|
assertFeature("DHCP", c.Features.DHCP, t)
|
||||||
|
assertFeature("DHCPv6", c.Features.DHCPv6, t)
|
||||||
|
assertFeature("Pools", c.Features.Pools, t)
|
||||||
|
assertFeature("Routes", c.Features.Routes, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTestFile(t *testing.T) []byte {
|
func loadTestFile(t *testing.T) []byte {
|
||||||
@@ -30,7 +35,7 @@ func loadTestFile(t *testing.T) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertConfig(name, address, user, password string, c Device, t *testing.T) {
|
func assertDevice(name, address, user, password string, c Device, t *testing.T) {
|
||||||
if c.Name != name {
|
if c.Name != name {
|
||||||
t.Fatalf("expected name %s, got %s", name, c.Name)
|
t.Fatalf("expected name %s, got %s", name, c.Name)
|
||||||
}
|
}
|
||||||
@@ -47,3 +52,9 @@ func assertConfig(name, address, user, password string, c Device, t *testing.T)
|
|||||||
t.Fatalf("expected password %s, got %s", password, c.Password)
|
t.Fatalf("expected password %s, got %s", password, c.Password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertFeature(name string, v bool, t *testing.T) {
|
||||||
|
if !v {
|
||||||
|
t.Fatalf("exprected feature %s to be enabled", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,3 +9,10 @@ devices:
|
|||||||
address: 192.168.2.1
|
address: 192.168.2.1
|
||||||
user: test
|
user: test
|
||||||
password: 123
|
password: 123
|
||||||
|
|
||||||
|
features:
|
||||||
|
bgp: true
|
||||||
|
dhcp: true
|
||||||
|
dhcpv6: true
|
||||||
|
routes: true
|
||||||
|
pools: true
|
||||||
43
main.go
43
main.go
@@ -31,6 +31,9 @@ var (
|
|||||||
configFile = flag.String("config-file", "", "config file to load")
|
configFile = flag.String("config-file", "", "config file to load")
|
||||||
withBgp = flag.Bool("with-bgp", false, "retrieves BGP routing infrormation")
|
withBgp = flag.Bool("with-bgp", false, "retrieves BGP routing infrormation")
|
||||||
withRoutes = flag.Bool("with-routes", false, "retrieves routing table information")
|
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")
|
||||||
|
withPools = flag.Bool("with-pools", false, "retrieves IP(v6) pool metrics")
|
||||||
timeout = flag.Duration("timeout", collector.DefaultTimeout*time.Second, "timeout when connecting to routers")
|
timeout = flag.Duration("timeout", collector.DefaultTimeout*time.Second, "timeout when connecting to routers")
|
||||||
tls = flag.Bool("tls", false, "use tls to connect to routers")
|
tls = flag.Bool("tls", false, "use tls to connect to routers")
|
||||||
insecure = flag.Bool("insecure", false, "skips verification of server certificate when using TLS (not recommended)")
|
insecure = flag.Bool("insecure", false, "skips verification of server certificate when using TLS (not recommended)")
|
||||||
@@ -100,7 +103,11 @@ func loadConfigFromFlags() (*config.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startServer() {
|
func startServer() {
|
||||||
http.HandleFunc(*metricsPath, prometheus.InstrumentHandlerFunc("prometheus", handler))
|
h, err := createMetricsHandler()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
http.Handle(*metricsPath, h)
|
||||||
|
|
||||||
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
@@ -120,45 +127,49 @@ func startServer() {
|
|||||||
log.Fatal(http.ListenAndServe(*port, nil))
|
log.Fatal(http.ListenAndServe(*port, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
func createMetricsHandler() (http.Handler, error) {
|
||||||
opts := collectorOptions()
|
opts := collectorOptions()
|
||||||
nc, err := collector.NewCollector(cfg, opts...)
|
nc, err := collector.NewCollector(cfg, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Couldn't create", err)
|
return nil, err
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(fmt.Sprintf("Couldn't create %s", err)))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
err = registry.Register(nc)
|
err = registry.Register(nc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("Couldn't register collector:", err)
|
return nil, err
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(fmt.Sprintf("Couldn't register collector: %s", err)))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delegate http serving to Prometheus client library, which will call collector.Collect.
|
return promhttp.HandlerFor(registry,
|
||||||
h := promhttp.HandlerFor(registry,
|
|
||||||
promhttp.HandlerOpts{
|
promhttp.HandlerOpts{
|
||||||
ErrorLog: log.New(),
|
ErrorLog: log.New(),
|
||||||
ErrorHandling: promhttp.ContinueOnError,
|
ErrorHandling: promhttp.ContinueOnError,
|
||||||
})
|
}), nil
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectorOptions() []collector.Option {
|
func collectorOptions() []collector.Option {
|
||||||
opts := []collector.Option{}
|
opts := []collector.Option{}
|
||||||
|
|
||||||
if *withBgp {
|
if *withBgp || cfg.Features.BGP {
|
||||||
opts = append(opts, collector.WithBGP())
|
opts = append(opts, collector.WithBGP())
|
||||||
}
|
}
|
||||||
|
|
||||||
if *withRoutes {
|
if *withRoutes || cfg.Features.Routes {
|
||||||
opts = append(opts, collector.WithRoutes())
|
opts = append(opts, collector.WithRoutes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *withDHCP || cfg.Features.DHCP {
|
||||||
|
opts = append(opts, collector.WithDHCP())
|
||||||
|
}
|
||||||
|
|
||||||
|
if *withDHCPv6 || cfg.Features.DHCPv6 {
|
||||||
|
opts = append(opts, collector.WithDHCPv6())
|
||||||
|
}
|
||||||
|
|
||||||
|
if *withPools || cfg.Features.Pools {
|
||||||
|
opts = append(opts, collector.WithPools())
|
||||||
|
}
|
||||||
|
|
||||||
if *timeout != collector.DefaultTimeout {
|
if *timeout != collector.DefaultTimeout {
|
||||||
opts = append(opts, collector.WithTimeout(*timeout))
|
opts = append(opts, collector.WithTimeout(*timeout))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user