Add Set.RegisterMetricsWriter() function for registering user-defined callbacks for arbitrary metrics generation in Prometheus text exposition format

This commit is contained in:
Aliaksandr Valialkin 2024-01-16 00:06:40 +02:00
parent 64b88f0e8f
commit fdfd428a62
No known key found for this signature in database
GPG Key ID: 52C003EE2BCDB9EB
3 changed files with 68 additions and 1 deletions

View File

@ -62,9 +62,21 @@ func UnregisterSet(s *Set) {
registeredSetsLock.Unlock() registeredSetsLock.Unlock()
} }
// WritePrometheus writes all the metrics from default set and all the registered sets in Prometheus format to w. // RegisterMetricsWriter registers writeMetrics callback for including metrics in the output generated by WritePrometheus.
//
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
// The last line generated by writeMetrics must end with \n.
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
//
// It is OK to register multiple writeMetrics callbacks - all of them will be called sequentially for gererating the output at WritePrometheus.
func RegisterMetricsWriter(writeMetrics func(w io.Writer)) {
defaultSet.RegisterMetricsWriter(writeMetrics)
}
// WritePrometheus writes all the metrics in Prometheus format from the default set, all the added sets and metrics writers to w.
// //
// Additional sets can be registered via RegisterSet() call. // Additional sets can be registered via RegisterSet() call.
// Additional metric writers can be registered via RegisterMetricsWriter() call.
// //
// If exposeProcessMetrics is true, then various `go_*` and `process_*` metrics // If exposeProcessMetrics is true, then various `go_*` and `process_*` metrics
// are exposed for the current process. // are exposed for the current process.
@ -232,6 +244,8 @@ func UnregisterMetric(name string) bool {
} }
// UnregisterAllMetrics unregisters all the metrics from default set. // UnregisterAllMetrics unregisters all the metrics from default set.
//
// It also unregisters writeMetrics callbacks passed to RegisterMetricsWriter.
func UnregisterAllMetrics() { func UnregisterAllMetrics() {
defaultSet.UnregisterAllMetrics() defaultSet.UnregisterAllMetrics()
} }

View File

@ -3,6 +3,7 @@ package metrics
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -108,6 +109,29 @@ func TestUnregisterAllMetrics(t *testing.T) {
} }
} }
func TestRegisterMetricsWriter(t *testing.T) {
RegisterMetricsWriter(func(w io.Writer) {
WriteCounterUint64(w, `counter{label="abc"}`, 1234)
WriteGaugeFloat64(w, `gauge{a="b",c="d"}`, -34.43)
})
var bb bytes.Buffer
WritePrometheus(&bb, false)
data := bb.String()
UnregisterAllMetrics()
expectedLine := fmt.Sprintf(`counter{label="abc"} 1234` + "\n")
if !strings.Contains(data, expectedLine) {
t.Fatalf("missing %q in\n%s", expectedLine, data)
}
expectedLine = fmt.Sprintf(`gauge{a="b",c="d"} -34.43` + "\n")
if !strings.Contains(data, expectedLine) {
t.Fatalf("missing %q in\n%s", expectedLine, data)
}
}
func TestRegisterUnregisterSet(t *testing.T) { func TestRegisterUnregisterSet(t *testing.T) {
const metricName = "metric_from_set" const metricName = "metric_from_set"
const metricValue = 123 const metricValue = 123

29
set.go
View File

@ -19,6 +19,8 @@ type Set struct {
a []*namedMetric a []*namedMetric
m map[string]*namedMetric m map[string]*namedMetric
summaries []*Summary summaries []*Summary
metricsWriters []func(w io.Writer)
} }
// NewSet creates new set of metrics. // NewSet creates new set of metrics.
@ -45,6 +47,7 @@ func (s *Set) WritePrometheus(w io.Writer) {
sort.Slice(s.a, lessFunc) sort.Slice(s.a, lessFunc)
} }
sa := append([]*namedMetric(nil), s.a...) sa := append([]*namedMetric(nil), s.a...)
metricsWriters := s.metricsWriters
s.mu.Unlock() s.mu.Unlock()
prevMetricFamily := "" prevMetricFamily := ""
@ -61,6 +64,10 @@ func (s *Set) WritePrometheus(w io.Writer) {
nm.metric.marshalTo(nm.name, &bb) nm.metric.marshalTo(nm.name, &bb)
} }
w.Write(bb.Bytes()) w.Write(bb.Bytes())
for _, writeMetrics := range metricsWriters {
writeMetrics(w)
}
} }
// NewHistogram creates and returns new histogram in s with the given name. // NewHistogram creates and returns new histogram in s with the given name.
@ -523,14 +530,22 @@ func (s *Set) unregisterMetricLocked(nm *namedMetric) bool {
} }
// UnregisterAllMetrics de-registers all metrics registered in s. // UnregisterAllMetrics de-registers all metrics registered in s.
//
// It also de-registers writeMetrics callbacks passed to RegisterMetricsWriter.
func (s *Set) UnregisterAllMetrics() { func (s *Set) UnregisterAllMetrics() {
metricNames := s.ListMetricNames() metricNames := s.ListMetricNames()
for _, name := range metricNames { for _, name := range metricNames {
s.UnregisterMetric(name) s.UnregisterMetric(name)
} }
s.mu.Lock()
s.metricsWriters = nil
s.mu.Unlock()
} }
// ListMetricNames returns sorted list of all the metrics in s. // ListMetricNames returns sorted list of all the metrics in s.
//
// The returned list doesn't include metrics generated by metricsWriter passed to RegisterMetricsWriter.
func (s *Set) ListMetricNames() []string { func (s *Set) ListMetricNames() []string {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -544,3 +559,17 @@ func (s *Set) ListMetricNames() []string {
sort.Strings(metricNames) sort.Strings(metricNames)
return metricNames return metricNames
} }
// RegisterMetricsWriter registers writeMetrics callback for including metrics in the output generated by s.WritePrometheus.
//
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
// The last line generated by writeMetrics must end with \n.
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
//
// It is OK to reguster multiple writeMetrics callbacks - all of them will be called sequentially for gererating the output at s.WritePrometheus.
func (s *Set) RegisterMetricsWriter(writeMetrics func(w io.Writer)) {
s.mu.Lock()
defer s.mu.Unlock()
s.metricsWriters = append(s.metricsWriters, writeMetrics)
}