opnsense-exporter/internal/collector/collector.go
ihatemodels 24e8161262 Add initial project structure
- add base structure
 - unify the proto metrics creation and propagation
 - implement arp and openvpn
 - refactor to meet the prom exporter standart
 - add instance label to the metrics
 - refactor the call chain
 - add gateway, unbound_dns and openvpn implementations
 - add gateway stuff
 - structure refactor; mod clean; cron implementation
 - implement cron in the collector; refactor utils in the opnsense package

refactor names and implement option functions to disable collectorInstances

add GH action workflows

Create codeql.yml

- clean

fix stuff
2023-11-26 16:06:03 +02:00

148 lines
4 KiB
Go

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)
}