opnsense-exporter/internal/collector/collector.go

149 lines
4 KiB
Go
Raw Permalink Normal View History

package collector
import (
"errors"
"fmt"
"sync"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/st3ga/opnsense-exporter/opnsense"
)
const namespace = "opnsense"
// CollectorInstance is the interface a service specific collectors must implement.
type CollectorInstance interface {
Register(namespace, isntance string, log log.Logger)
Name() string
Describe(ch chan<- *prometheus.Desc)
Update(client *opnsense.Client, ch chan<- prometheus.Metric) *opnsense.APICallError
}
// collectorInstances is a list of collectorInstances that will be registered
// from the init() function in each collector file
var collectorInstances []CollectorInstance
type Collector struct {
instanceLabel string
mutex sync.RWMutex
Client *opnsense.Client
log log.Logger
collectors []CollectorInstance
scrapes prometheus.CounterVec
endpointErrors prometheus.CounterVec
}
type Option func(*Collector) error
// withoutCollectorInstance removes a collector by given name from the list of collectors
// that are registered from their init functions.
func withoutCollectorInstance(name string) Option {
return func(o *Collector) error {
for i, collector := range o.collectors {
if collector.Name() == name {
o.collectors = append(o.collectors[:i], o.collectors[i+1:]...)
return nil
}
}
return fmt.Errorf("collector %s not found", name)
}
}
// WithoutArpTableCollector Option
// removes the arp_table collector from the list of collectors
func WithoutArpTableCollector() Option {
return withoutCollectorInstance("arp_table")
}
// WithoutCronCollector Option
// removes the cron collector from the list of collectors
func WithoutCronCollector() Option {
return withoutCollectorInstance("cron")
}
// New creates a new Collector instance.
func New(client *opnsense.Client, log log.Logger, instanceName string, options ...Option) (*Collector, error) {
c := Collector{
Client: client,
log: log,
instanceLabel: instanceName,
collectors: collectorInstances,
}
for _, option := range options {
if err := option(&c); err != nil {
return nil, errors.Join(err, fmt.Errorf("failed to apply option"))
}
}
for _, collector := range c.collectors {
collector.Register(namespace, instanceName, c.log)
}
c.scrapes = *prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Name: "exporter_scrapes_total",
Help: "Total number of times OPNsense was scraped for metrics.",
}, []string{"opnsense_instance"})
c.endpointErrors = *prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Name: "exporter_endpoint_errors_total",
Help: "Total number of errors by endpoint returned by the OPNsense API during data fetching",
}, []string{"endpoint", "opnsense_instance"})
prometheus.MustRegister(c.scrapes)
prometheus.MustRegister(c.endpointErrors)
c.scrapes.WithLabelValues(c.instanceLabel).Add(0)
for _, path := range c.Client.Endpoints() {
c.endpointErrors.WithLabelValues(string(path), c.instanceLabel).Add(0)
}
return &c, nil
}
// Describe implements the prometheus.Collector interface.
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
c.scrapes.Describe(ch)
c.endpointErrors.Describe(ch)
for _, collector := range c.collectors {
collector.Describe(ch)
}
}
// Collect implements the prometheus.Collector interface.
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
c.mutex.Lock()
defer c.mutex.Unlock()
var wg sync.WaitGroup
wg.Add(len(c.collectors))
for _, collector := range c.collectors {
go func(coll CollectorInstance) {
if err := coll.Update(c.Client, ch); err != nil {
level.Error(c.log).Log(
"msg", "failed to update",
"component", "collector",
"collector_name", coll.Name(),
"err", err,
)
c.endpointErrors.WithLabelValues(err.Endpoint, c.instanceLabel).Inc()
}
wg.Done()
}(collector)
}
wg.Wait()
c.scrapes.WithLabelValues(c.instanceLabel).Inc()
c.scrapes.Collect(ch)
c.endpointErrors.Collect(ch)
}