package collector import ( "crypto/tls" "sync" "time" "github.com/nshttpd/mikrotik-exporter/config" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" routeros "gopkg.in/routeros.v2" ) const ( namespace = "mikrotik" apiPort = ":8728" apiPortTLS = ":8729" // DefaultTimeout defines the default timeout when connecting to a router DefaultTimeout = 5 * time.Second ) var ( scrapeDurationDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "scrape", "collector_duration_seconds"), "mikrotik_exporter: duration of a collector scrape", []string{"device"}, nil, ) scrapeSuccessDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "scrape", "collector_success"), "mikrotik_exporter: whether a collector succeeded", []string{"device"}, nil, ) ) type collector struct { devices []config.Device collectors []routerOSCollector timeout time.Duration enableTLS bool insecureTLS bool } // WithBGP enables BGP routing metrics func WithBGP() Option { return func(c *collector) { c.collectors = append(c.collectors, newBGPCollector()) } } // WithRoutes enables routing table metrics func WithRoutes() Option { return func(c *collector) { 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()) } } // WithOptics enables optical diagnstocs func WithOptics() Option { return func(c *collector) { c.collectors = append(c.collectors, newOpticsCollector()) } } // WithTimeout sets timeout for connecting to router func WithTimeout(d time.Duration) Option { return func(c *collector) { c.timeout = d } } // WithTLS enables TLS func WithTLS(insecure bool) Option { return func(c *collector) { c.enableTLS = true c.insecureTLS = true } } // Option applies options to collector type Option func(*collector) // NewCollector creates a collector instance func NewCollector(cfg *config.Config, opts ...Option) (prometheus.Collector, error) { log.WithFields(log.Fields{ "numDevices": len(cfg.Devices), }).Info("setting up collector for devices") c := &collector{ devices: cfg.Devices, timeout: DefaultTimeout, collectors: []routerOSCollector{ newInterfaceCollector(), newResourceCollector(), }, } for _, o := range opts { o(c) } return c, nil } // Describe implements the prometheus.Collector interface. func (c *collector) Describe(ch chan<- *prometheus.Desc) { ch <- scrapeDurationDesc ch <- scrapeSuccessDesc for _, co := range c.collectors { co.describe(ch) } } // Collect implements the prometheus.Collector interface. func (c *collector) Collect(ch chan<- prometheus.Metric) { wg := sync.WaitGroup{} wg.Add(len(c.devices)) for _, dev := range c.devices { go func(d config.Device) { c.collectForDevice(d, ch) wg.Done() }(dev) } wg.Wait() } func (c *collector) collectForDevice(d config.Device, ch chan<- prometheus.Metric) { begin := time.Now() err := c.connectAndCollect(&d, ch) duration := time.Since(begin) var success float64 if err != nil { log.Errorf("ERROR: %s collector failed after %fs: %s", d.Name, duration.Seconds(), err) success = 0 } else { log.Debugf("OK: %s collector succeeded after %fs.", d.Name, duration.Seconds()) success = 1 } ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration.Seconds(), d.Name) ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, success, d.Name) } func (c *collector) connectAndCollect(d *config.Device, ch chan<- prometheus.Metric) error { cl, err := c.connect(d) if err != nil { log.WithFields(log.Fields{ "device": d.Name, "error": err, }).Error("error dialing device") return err } defer cl.Close() for _, co := range c.collectors { ctx := &collectorContext{ch, d, cl} err = co.collect(ctx) if err != nil { return err } } return nil } func (c *collector) connect(d *config.Device) (*routeros.Client, error) { if !c.enableTLS { return routeros.DialTimeout(d.Address+apiPort, d.User, d.Password, c.timeout) } tls := &tls.Config{ InsecureSkipVerify: c.insecureTLS, } return routeros.DialTLSTimeout(d.Address+apiPortTLS, d.User, d.Password, tls, c.timeout) }