Skip to content

Commit 83d56b1

Browse files
authored
Extend prometheus.Registry to implement Collector (#1103)
* 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 <[email protected]> * fix grammar in doc comment Signed-off-by: Robert Fratto <[email protected]> * document why Registry implements Collector with example Signed-off-by: Robert Fratto <[email protected]> Signed-off-by: Robert Fratto <[email protected]>
1 parent 4c41dfb commit 83d56b1

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

prometheus/registry.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,12 @@ func (errs MultiError) MaybeUnwrap() error {
252252
}
253253

254254
// Registry registers Prometheus collectors, collects their metrics, and gathers
255-
// them into MetricFamilies for exposition. It implements both Registerer and
256-
// Gatherer. The zero value is not usable. Create instances with NewRegistry or
257-
// NewPedanticRegistry.
255+
// them into MetricFamilies for exposition. It implements Registerer, Gatherer,
256+
// and Collector. The zero value is not usable. Create instances with
257+
// NewRegistry or NewPedanticRegistry.
258+
//
259+
// Registry implements Collector to allow it to be used for creating groups of
260+
// metrics. See the Grouping example for how this can be done.
258261
type Registry struct {
259262
mtx sync.RWMutex
260263
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
@@ -556,6 +559,31 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
556559
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
557560
}
558561

562+
// Describe implements Collector.
563+
func (r *Registry) Describe(ch chan<- *Desc) {
564+
r.mtx.RLock()
565+
defer r.mtx.RUnlock()
566+
567+
// Only report the checked Collectors; unchecked collectors don't report any
568+
// Desc.
569+
for _, c := range r.collectorsByID {
570+
c.Describe(ch)
571+
}
572+
}
573+
574+
// Collect implements Collector.
575+
func (r *Registry) Collect(ch chan<- Metric) {
576+
r.mtx.RLock()
577+
defer r.mtx.RUnlock()
578+
579+
for _, c := range r.collectorsByID {
580+
c.Collect(ch)
581+
}
582+
for _, c := range r.uncheckedCollectors {
583+
c.Collect(ch)
584+
}
585+
}
586+
559587
// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
560588
// Prometheus text format, and writes it to a temporary file. Upon success, the
561589
// temporary file is renamed to the provided filename.

prometheus/registry_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,3 +1254,37 @@ func TestNewMultiTRegistry(t *testing.T) {
12541254
}
12551255
})
12561256
}
1257+
1258+
// This example shows how to use multiple registries for registering and
1259+
// unregistering groups of metrics.
1260+
func ExampleRegistry_grouping() {
1261+
// Create a global registry.
1262+
globalReg := prometheus.NewRegistry()
1263+
1264+
// Spawn 10 workers, each of which will have their own group of metrics.
1265+
for i := 0; i < 10; i++ {
1266+
// Create a new registry for each worker, which acts as a group of
1267+
// worker-specific metrics.
1268+
workerReg := prometheus.NewRegistry()
1269+
globalReg.Register(workerReg)
1270+
1271+
go func(workerID int) {
1272+
// Once the worker is done, it can unregister itself.
1273+
defer globalReg.Unregister(workerReg)
1274+
1275+
workTime := prometheus.NewCounter(prometheus.CounterOpts{
1276+
Name: "worker_total_work_time_milliseconds",
1277+
ConstLabels: prometheus.Labels{
1278+
// Generate a label unique to this worker so its metric doesn't
1279+
// collide with the metrics from other workers.
1280+
"worker_id": fmt.Sprintf("%d", workerID),
1281+
},
1282+
})
1283+
workerReg.MustRegister(workTime)
1284+
1285+
start := time.Now()
1286+
time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
1287+
workTime.Add(float64(time.Since(start).Milliseconds()))
1288+
}(i)
1289+
}
1290+
}

0 commit comments

Comments
 (0)