github.com/safing/portbase@v0.19.5/metrics/module.go (about)

     1  package metrics
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  
     9  	"github.com/safing/portbase/modules"
    10  )
    11  
    12  var (
    13  	module *modules.Module
    14  
    15  	registry     []Metric
    16  	registryLock sync.RWMutex
    17  
    18  	firstMetricRegistered bool
    19  	metricNamespace       string
    20  	globalLabels          = make(map[string]string)
    21  
    22  	// ErrAlreadyStarted is returned when an operation is only valid before the
    23  	// first metric is registered, and is called after.
    24  	ErrAlreadyStarted = errors.New("can only be changed before first metric is registered")
    25  
    26  	// ErrAlreadyRegistered is returned when a metric with the same ID is
    27  	// registered again.
    28  	ErrAlreadyRegistered = errors.New("metric already registered")
    29  
    30  	// ErrAlreadySet is returned when a value is already set and cannot be changed.
    31  	ErrAlreadySet = errors.New("already set")
    32  
    33  	// ErrInvalidOptions is returned when invalid options where provided.
    34  	ErrInvalidOptions = errors.New("invalid options")
    35  )
    36  
    37  func init() {
    38  	module = modules.Register("metrics", prep, start, stop, "config", "database", "api")
    39  }
    40  
    41  func prep() error {
    42  	return prepConfig()
    43  }
    44  
    45  func start() error {
    46  	// Add metric instance name as global variable if set.
    47  	if instanceOption() != "" {
    48  		if err := AddGlobalLabel("instance", instanceOption()); err != nil {
    49  			return err
    50  		}
    51  	}
    52  
    53  	if err := registerInfoMetric(); err != nil {
    54  		return err
    55  	}
    56  
    57  	if err := registerRuntimeMetric(); err != nil {
    58  		return err
    59  	}
    60  
    61  	if err := registerHostMetrics(); err != nil {
    62  		return err
    63  	}
    64  
    65  	if err := registerLogMetrics(); err != nil {
    66  		return err
    67  	}
    68  
    69  	if err := registerAPI(); err != nil {
    70  		return err
    71  	}
    72  
    73  	if pushOption() != "" {
    74  		module.StartServiceWorker("metric pusher", 0, metricsWriter)
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  func stop() error {
    81  	// Wait until the metrics pusher is done, as it may have started reporting
    82  	// and may report a higher number than we store to disk. For persistent
    83  	// metrics it can then happen that the first report is lower than the
    84  	// previous report, making prometheus think that all that happened since the
    85  	// last report, due to the automatic restart detection.
    86  
    87  	// The registry is read locked when writing metrics.
    88  	// Write lock the registry to make sure all writes are finished.
    89  	registryLock.Lock()
    90  	registryLock.Unlock() //nolint:staticcheck
    91  
    92  	storePersistentMetrics()
    93  
    94  	return nil
    95  }
    96  
    97  func register(m Metric) error {
    98  	registryLock.Lock()
    99  	defer registryLock.Unlock()
   100  
   101  	// Check if metric ID is already registered.
   102  	for _, registeredMetric := range registry {
   103  		if m.LabeledID() == registeredMetric.LabeledID() {
   104  			return ErrAlreadyRegistered
   105  		}
   106  		if m.Opts().InternalID != "" &&
   107  			m.Opts().InternalID == registeredMetric.Opts().InternalID {
   108  			return fmt.Errorf("%w with this internal ID", ErrAlreadyRegistered)
   109  		}
   110  	}
   111  
   112  	// Add new metric to registry and sort it.
   113  	registry = append(registry, m)
   114  	sort.Sort(byLabeledID(registry))
   115  
   116  	// Set flag that first metric is now registered.
   117  	firstMetricRegistered = true
   118  
   119  	if module.Status() < modules.StatusStarting {
   120  		return fmt.Errorf("registering metric %q too early", m.ID())
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  // SetNamespace sets the namespace for all metrics. It is prefixed to all
   127  // metric IDs.
   128  // It must be set before any metric is registered.
   129  // Does not affect golang runtime metrics.
   130  func SetNamespace(namespace string) error {
   131  	// Lock registry and check if a first metric is already registered.
   132  	registryLock.Lock()
   133  	defer registryLock.Unlock()
   134  	if firstMetricRegistered {
   135  		return ErrAlreadyStarted
   136  	}
   137  
   138  	// Check if the namespace is already set.
   139  	if metricNamespace != "" {
   140  		return ErrAlreadySet
   141  	}
   142  
   143  	metricNamespace = namespace
   144  	return nil
   145  }
   146  
   147  // AddGlobalLabel adds a global label to all metrics.
   148  // Global labels must be added before any metric is registered.
   149  // Does not affect golang runtime metrics.
   150  func AddGlobalLabel(name, value string) error {
   151  	// Lock registry and check if a first metric is already registered.
   152  	registryLock.Lock()
   153  	defer registryLock.Unlock()
   154  	if firstMetricRegistered {
   155  		return ErrAlreadyStarted
   156  	}
   157  
   158  	// Check format.
   159  	if !prometheusFormat.MatchString(name) {
   160  		return fmt.Errorf("metric label name %q must match %s", name, PrometheusFormatRequirement)
   161  	}
   162  
   163  	globalLabels[name] = value
   164  	return nil
   165  }
   166  
   167  type byLabeledID []Metric
   168  
   169  func (r byLabeledID) Len() int           { return len(r) }
   170  func (r byLabeledID) Less(i, j int) bool { return r[i].LabeledID() < r[j].LabeledID() }
   171  func (r byLabeledID) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }