From 97a08dd7640866f466127481e9787685ac9a5cb2 Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Wed, 3 Aug 2022 11:00:14 -0400 Subject: [PATCH 1/3] prometheus: implement Collector interface for Registry This change allows Registries to be used as Collectors. This enables new instances of Registry to be passed to ephemeral subroutines for collecting metrics from subroutines which are still running: ```go package main import ( "fmt" "github.com/prometheus/client_golang/prometheus" ) func main() { globalReg := prometheus.NewRegistry() for i := 0; i < 100; i++ { workerReg := prometheus.WrapRegistererWith(prometheus.Labels{ // Add an ID label so registered metrics from workers don't // collide. "worker_id": fmt.Sprintf("%d", i), }, prometheus.NewRegistry() globalReg.MustRegister(workerReg) go func(i int) { runWorker(workerReg) // Unregister any metrics the worker may have created. globalReg.Unregister(workerReg) }(i) } } // runWorker runs a worker, registering worker-specific metrics. func runWorker(reg *prometheus.Registry) { // ... register metrics ... // ... do work ... } ``` This change makes it easier to avoid leaking metrics from subroutines which do not consistently properly unregister metrics. Signed-off-by: Robert Fratto --- prometheus/registry.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index 325f665ff..ee61ebbe4 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -252,9 +252,9 @@ func (errs MultiError) MaybeUnwrap() error { } // Registry registers Prometheus collectors, collects their metrics, and gathers -// them into MetricFamilies for exposition. It implements both Registerer and -// Gatherer. The zero value is not usable. Create instances with NewRegistry or -// NewPedanticRegistry. +// them into MetricFamilies for exposition. It implements both Registerer, +// Gatherer, and Collector. The zero value is not usable. Create instances with +// NewRegistry or NewPedanticRegistry. type Registry struct { mtx sync.RWMutex collectorsByID map[uint64]Collector // ID is a hash of the descIDs. @@ -556,6 +556,31 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() } +// Describe implements Collector. +func (r *Registry) Describe(ch chan<- *Desc) { + r.mtx.RLock() + defer r.mtx.RUnlock() + + // Only report the checked Collectors; unchecked collectors don't report any + // Desc. + for _, c := range r.collectorsByID { + c.Describe(ch) + } +} + +// Collect implements Collector. +func (r *Registry) Collect(ch chan<- Metric) { + r.mtx.RLock() + defer r.mtx.RUnlock() + + for _, c := range r.collectorsByID { + c.Collect(ch) + } + for _, c := range r.uncheckedCollectors { + c.Collect(ch) + } +} + // WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the // Prometheus text format, and writes it to a temporary file. Upon success, the // temporary file is renamed to the provided filename. From 688d41e838f64b1e5d961aba4c73e382e525e25c Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Wed, 3 Aug 2022 11:16:08 -0400 Subject: [PATCH 2/3] fix grammar in doc comment Signed-off-by: Robert Fratto --- prometheus/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index ee61ebbe4..259cf9182 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -252,8 +252,8 @@ func (errs MultiError) MaybeUnwrap() error { } // Registry registers Prometheus collectors, collects their metrics, and gathers -// them into MetricFamilies for exposition. It implements both Registerer, -// Gatherer, and Collector. The zero value is not usable. Create instances with +// them into MetricFamilies for exposition. It implements Registerer, Gatherer, +// and Collector. The zero value is not usable. Create instances with // NewRegistry or NewPedanticRegistry. type Registry struct { mtx sync.RWMutex From 74ea536a5587b07b040f7973d417999a6816f567 Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Mon, 8 Aug 2022 11:49:26 -0400 Subject: [PATCH 3/3] document why Registry implements Collector with example Signed-off-by: Robert Fratto --- prometheus/registry.go | 3 +++ prometheus/registry_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/prometheus/registry.go b/prometheus/registry.go index 259cf9182..09e34d307 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -255,6 +255,9 @@ func (errs MultiError) MaybeUnwrap() error { // them into MetricFamilies for exposition. It implements Registerer, Gatherer, // and Collector. The zero value is not usable. Create instances with // NewRegistry or NewPedanticRegistry. +// +// Registry implements Collector to allow it to be used for creating groups of +// metrics. See the Grouping example for how this can be done. type Registry struct { mtx sync.RWMutex collectorsByID map[uint64]Collector // ID is a hash of the descIDs. diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index f0871ba87..3faf67aa5 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -1254,3 +1254,37 @@ func TestNewMultiTRegistry(t *testing.T) { } }) } + +// This example shows how to use multiple registries for registering and +// unregistering groups of metrics. +func ExampleRegistry_grouping() { + // Create a global registry. + globalReg := prometheus.NewRegistry() + + // Spawn 10 workers, each of which will have their own group of metrics. + for i := 0; i < 10; i++ { + // Create a new registry for each worker, which acts as a group of + // worker-specific metrics. + workerReg := prometheus.NewRegistry() + globalReg.Register(workerReg) + + go func(workerID int) { + // Once the worker is done, it can unregister itself. + defer globalReg.Unregister(workerReg) + + workTime := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "worker_total_work_time_milliseconds", + ConstLabels: prometheus.Labels{ + // Generate a label unique to this worker so its metric doesn't + // collide with the metrics from other workers. + "worker_id": fmt.Sprintf("%d", workerID), + }, + }) + workerReg.MustRegister(workTime) + + start := time.Now() + time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) + workTime.Add(float64(time.Since(start).Milliseconds())) + }(i) + } +}