Skip to content

Commit

Permalink
Extend prometheus.Registry to implement Collector (#1103)
Browse files Browse the repository at this point in the history
* 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 <robertfratto@gmail.com>

* fix grammar in doc comment

Signed-off-by: Robert Fratto <robertfratto@gmail.com>

* document why Registry implements Collector with example

Signed-off-by: Robert Fratto <robertfratto@gmail.com>

Signed-off-by: Robert Fratto <robertfratto@gmail.com>
  • Loading branch information
rfratto committed Aug 23, 2022
1 parent 4c41dfb commit 83d56b1
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
34 changes: 31 additions & 3 deletions prometheus/registry.go
Expand Up @@ -252,9 +252,12 @@ 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 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.
Expand Down Expand Up @@ -556,6 +559,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.
Expand Down
34 changes: 34 additions & 0 deletions prometheus/registry_test.go
Expand Up @@ -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)
}
}

0 comments on commit 83d56b1

Please sign in to comment.