From 9c3fe750dd50f0e00c795c7427e6cd03a6ba6321 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Wed, 3 Aug 2016 00:41:51 +0200 Subject: [PATCH] Expose the registry implementation and add two interfaces. Registry is now a struct, which implements two interfaces, Registrerer and Deliverer. The latter is particularly important as it is now the argument type for pushes and HTTP handler construction (i.e. it is easy to implement a custom Deliverer for testing or other purposes). The Registerer interface is not used as a parameter type but can (and should) be used by users of custom registries so that they can easily do things like mocking it out for testing purposes. With the broken up interfaces, adding MustRegister to the interface is not such a big deal anymore (interface is still small). And since setting the injection hook is such a rare thing to happen, it is acceptable to not have it in any of the interfaces. The renaming from `Collect` to `Deliver` was done to avoid confusion with Collectors. (The registry _collects_ from the Collectors, and then _delivers_ to the exposition mechanism.) --- prometheus/collector.go | 16 +-- prometheus/doc.go | 26 ++--- prometheus/example_clustermanager_test.go | 4 +- prometheus/examples_test.go | 4 +- prometheus/http.go | 6 +- prometheus/http_test.go | 6 +- prometheus/process_collector_test.go | 2 +- prometheus/push/push.go | 26 ++--- prometheus/push/push_test.go | 6 +- prometheus/registry.go | 115 ++++++++++++---------- 10 files changed, 113 insertions(+), 98 deletions(-) diff --git a/prometheus/collector.go b/prometheus/collector.go index adc07b1..5492ed3 100644 --- a/prometheus/collector.go +++ b/prometheus/collector.go @@ -15,15 +15,15 @@ package prometheus // Collector is the interface implemented by anything that can be used by // Prometheus to collect metrics. A Collector has to be registered for -// collection. See Register, MustRegister, RegisterOrGet, and MustRegisterOrGet. +// collection. See Registerer.Register. // -// The stock metrics provided by this package (like Gauge, Counter, Summary) are -// also Collectors (which only ever collect one metric, namely itself). An -// implementer of Collector may, however, collect multiple metrics in a -// coordinated fashion and/or create metrics on the fly. Examples for collectors -// already implemented in this library are the metric vectors (i.e. collection -// of multiple instances of the same Metric but with different label values) -// like GaugeVec or SummaryVec, and the ExpvarCollector. +// The stock metrics provided by this package (Gauge, Counter, Summary, +// Histogram, Untyped) are also Collectors (which only ever collect one metric, +// namely itself). An implementer of Collector may, however, collect multiple +// metrics in a coordinated fashion and/or create metrics on the fly. Examples +// for collectors already implemented in this library are the metric vectors +// (i.e. collection of multiple instances of the same Metric but with different +// label values) like GaugeVec or SummaryVec, and the ExpvarCollector. type Collector interface { // Describe sends the super-set of all possible descriptors of metrics // collected by this Collector to the provided channel and returns once diff --git a/prometheus/doc.go b/prometheus/doc.go index b0e384e..ce93443 100644 --- a/prometheus/doc.go +++ b/prometheus/doc.go @@ -141,10 +141,10 @@ // // So far, everything we did operated on the so-called default registry, as it // can be found in the global DefaultRegistry variable. With NewRegistry, you -// can create a custom registry, or you can even implement the Registry -// interface yourself. The methods Register and Unregister work in the same way -// on a custom registry as the global functions Register and Unregister on the -// default registry. +// can create a custom registry, or you can even implement the Registerer or +// Deliverer interfaces yourself. The methods Register and Unregister work in +// the same way on a custom registry as the global functions Register and +// Unregister on the default registry. // // There are a number of uses for custom registries: You can use registries // with special properties, see NewPedanticRegistry. You can avoid global state, @@ -160,15 +160,15 @@ // HTTP Exposition // // The Handler function used so far to get an http.Handler for serving the -// metrics is also acting on the DefaultRegistry. With HondlerFor, you can -// create a handler for a custom registry. It also allows to create handler that -// act differently on errors or allow to log errors. Also note that the handler -// returned by the Handler function is already instrumented with some HTTP -// metrics. You can call UninstrumentedHandler to get a handler for the -// DefaultRegistry that is not instrumented, or you can use InstrumentHandler to -// instrument any http.Handlers of your choice. (But note that the way the -// instrumentation happens is partially obsolete. Better ways are being worked -// on.) +// metrics is also acting on the DefaultRegistry. With HandlerFor, you can +// create a handler for a custom registry or anything the implements the +// Deliverer interface. It also allows to create handler that act differently on +// errors or allow to log errors. Also note that the handler returned by the +// Handler function is already instrumented with some HTTP metrics. You can call +// UninstrumentedHandler to get a handler for the DefaultRegistry that is not +// instrumented, or you can use InstrumentHandler to instrument any +// http.Handlers of your choice. (But note that the way the instrumentation +// happens is partially obsolete. Better ways are being worked on.) // // Pushing to the Pushgateway // diff --git a/prometheus/example_clustermanager_test.go b/prometheus/example_clustermanager_test.go index 3c263e6..581f922 100644 --- a/prometheus/example_clustermanager_test.go +++ b/prometheus/example_clustermanager_test.go @@ -113,6 +113,6 @@ func ExampleCollector_clustermanager() { // Since we are dealing with custom Collector implementations, it might // be a good idea to try it out with a pedantic registry. reg := prometheus.NewPedanticRegistry() - prometheus.MustRegisterWith(reg, workerDB) - prometheus.MustRegisterWith(reg, workerCA) + reg.MustRegister(workerDB) + reg.MustRegister(workerCA) } diff --git a/prometheus/examples_test.go b/prometheus/examples_test.go index 819c8e4..188e9fb 100644 --- a/prometheus/examples_test.go +++ b/prometheus/examples_test.go @@ -388,9 +388,9 @@ func ExampleSummaryVec() { // by registering it with a custom registry and then let it collect the // metrics. reg := prometheus.NewRegistry() - prometheus.MustRegisterWith(reg, temps) + reg.MustRegister(temps) - metricFamilies, err := reg.Collect() + metricFamilies, err := reg.Deliver() if err != nil || len(metricFamilies) != 1 { panic("unexpected behavior of custom test registry") } diff --git a/prometheus/http.go b/prometheus/http.go index f310925..41d1bd8 100644 --- a/prometheus/http.go +++ b/prometheus/http.go @@ -65,12 +65,12 @@ func UninstrumentedHandler() http.Handler { return HandlerFor(DefaultRegistry, HandlerOpts{}) } -// HandlerFor returns an http.Handler for the provided registry. The behavior ef +// HandlerFor returns an http.Handler for the provided Deliverer. The behavior ef // the Handler is defined by the provided HandlerOpts. The Handler is NOT // instrumented with InstrumentHandler. -func HandlerFor(r Registry, opts HandlerOpts) http.Handler { +func HandlerFor(reg Deliverer, opts HandlerOpts) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - mfs, err := r.Collect() + mfs, err := reg.Deliver() if err != nil { if opts.ErrorLog != nil { opts.ErrorLog.Println("error collecting metrics:", err) diff --git a/prometheus/http_test.go b/prometheus/http_test.go index 71a1379..d4bfe25 100644 --- a/prometheus/http_test.go +++ b/prometheus/http_test.go @@ -146,7 +146,7 @@ func TestHandlerErrorHandling(t *testing.T) { Name: "the_count", Help: "Ah-ah-ah! Thunder and lightning!", }) - MustRegisterWith(reg, cnt) + reg.MustRegister(cnt) cntVec := NewCounterVec( CounterOpts{ @@ -158,9 +158,9 @@ func TestHandlerErrorHandling(t *testing.T) { ) cntVec.WithLabelValues("val1").Inc() cntVec.WithLabelValues("val2").Inc() - MustRegisterWith(reg, cntVec) + reg.MustRegister(cntVec) - MustRegisterWith(reg, errorCollector{}) + reg.MustRegister(errorCollector{}) logBuf := &bytes.Buffer{} logger := log.New(logBuf, "", 0) diff --git a/prometheus/process_collector_test.go b/prometheus/process_collector_test.go index 6cae6e8..3bc08b1 100644 --- a/prometheus/process_collector_test.go +++ b/prometheus/process_collector_test.go @@ -25,7 +25,7 @@ func TestProcessCollector(t *testing.T) { t.Fatal(err) } - mfs, err := registry.Collect() + mfs, err := registry.Deliver() if err != nil { t.Fatal(err) } diff --git a/prometheus/push/push.go b/prometheus/push/push.go index 8b0351f..a65e792 100644 --- a/prometheus/push/push.go +++ b/prometheus/push/push.go @@ -44,8 +44,8 @@ import ( const contentTypeHeader = "Content-Type" -// Registry triggers a metric collection by the provided registry and pushes all -// collected metrics to the Pushgateway specified by url, using the provided job +// Registry triggers a metric collection by the provided Deliverer and pushes all +// delivered metrics to the Pushgateway specified by url, using the provided job // name and the (optional) further grouping labels (the grouping map may be // nil). See the Pushgateway documentation for detailed implications of the job // and other grouping labels. Neither the job name nor any grouping label value @@ -59,18 +59,18 @@ const contentTypeHeader = "Content-Type" // Note that all previously pushed metrics with the same job and other grouping // labels will be replaced with the metrics pushed by this call. (It uses HTTP // method 'PUT' to push to the Pushgateway.) -func Registry(r prometheus.Registry, job string, grouping map[string]string, url string) error { - return push(r, job, grouping, url, "PUT") +func Registry(reg prometheus.Deliverer, job string, grouping map[string]string, url string) error { + return push(reg, job, grouping, url, "PUT") } // RegistryAdd works like Registry, but only previously pushed metrics with the // same name (and the same job and other grouping labels) will be replaced. (It // uses HTTP method 'POST' to push to the Pushgateway.) -func RegistryAdd(r prometheus.Registry, job string, grouping map[string]string, url string) error { - return push(r, job, grouping, url, "POST") +func RegistryAdd(reg prometheus.Deliverer, job string, grouping map[string]string, url string) error { + return push(reg, job, grouping, url, "POST") } -func push(r prometheus.Registry, job string, grouping map[string]string, pushURL, method string) error { +func push(reg prometheus.Deliverer, job string, grouping map[string]string, pushURL, method string) error { if !strings.Contains(pushURL, "://") { pushURL = "http://" + pushURL } @@ -93,7 +93,7 @@ func push(r prometheus.Registry, job string, grouping map[string]string, pushURL } pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/")) - mfs, err := r.Collect() + mfs, err := reg.Deliver() if err != nil { return err } @@ -133,15 +133,15 @@ func push(r prometheus.Registry, job string, grouping map[string]string, pushURL return nil } -// Collectors works like Registry, but it does not collect via a -// registry. Instead, it collects from the provided collectors directly. It is a -// convenient way to push only a few metrics. +// Collectors works like Registry, but it does not use a Deliverer. Instead, it +// collects from the provided collectors directly. It is a convenient way to +// push only a few metrics. func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { return pushCollectors(job, grouping, url, "PUT", collectors...) } -// AddCollectors works like PushAdd, but it does not collect via a -// registry. Instead, it collects from the provided collectors directly. It is a +// AddCollectors works like RegistryAdd, but it does not use a Deliverer. +// Instead, it collects from the provided collectors directly. It is a // convenient way to push only a few metrics. func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { return pushCollectors(job, grouping, url, "POST", collectors...) diff --git a/prometheus/push/push_test.go b/prometheus/push/push_test.go index 86630ce..dde7b55 100644 --- a/prometheus/push/push_test.go +++ b/prometheus/push/push_test.go @@ -80,10 +80,10 @@ func TestPush(t *testing.T) { }) reg := prometheus.NewRegistry() - prometheus.MustRegisterWith(reg, metric1) - prometheus.MustRegisterWith(reg, metric2) + reg.MustRegister(metric1) + reg.MustRegister(metric2) - mfs, err := reg.Collect() + mfs, err := reg.Deliver() if err != nil { t.Fatal(err) } diff --git a/prometheus/registry.go b/prometheus/registry.go index dc0144f..1d1b57d 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -44,8 +44,8 @@ func init() { // NewRegistry creates a new vanilla Registry without any Collectors // pre-registered. -func NewRegistry() Registry { - return ®istry{ +func NewRegistry() *Registry { + return &Registry{ collectorsByID: map[uint64]Collector{}, descIDs: map[uint64]struct{}{}, dimHashesByName: map[string]uint64{}, @@ -62,14 +62,18 @@ func NewRegistry() Registry { // Collector. Well-behaved Collectors and Metrics will only provide consistent // Descs. This Registry is useful to test the implementation of Collectors and // Metrics. -func NewPedanticRegistry() Registry { - r := NewRegistry().(*registry) +func NewPedanticRegistry() *Registry { + r := NewRegistry() r.pedanticChecksEnabled = true return r } -// Registry is the interface for the metrics registry. -type Registry interface { +// Registerer is the interface for the part of a registry in charge of +// registering and unregistering. Users of custom registries should use +// Registerer as type for registration purposes (rather then Registry). In that +// way, they are free to exchange the Registerer implementation (e.g. for +// testing purposes). +type Registerer interface { // Register registers a new Collector to be included in metrics // collection. It returns an error if the descriptors provided by the // Collector are invalid or if they - in combination with descriptors of @@ -84,6 +88,10 @@ type Registry interface { // It is in general not safe to register the same Collector multiple // times concurrently. Register(Collector) error + // MustRegister works like Register but registers any number of + // Collectors and panics upon the first registration that causes an + // error. + MustRegister(...Collector) // Unregister unregisters the Collector that equals the Collector passed // in as an argument. (Two Collectors are considered equal if their // Describe method yields the same set of descriptors.) The function @@ -96,24 +104,14 @@ type Registry interface { // instance must only collect consistent metrics throughout its // lifetime. Unregister(Collector) bool - // SetInjectionHook sets the provided hook to inject MetricFamilies. The - // hook is a function that is called whenever metrics are collected. The - // MetricFamily protobufs returned by the hook function are merged with - // the metrics collected in the usual way. - // - // This is a way to directly inject MetricFamily protobufs managed and - // owned by the caller. The caller has full responsibility. As no - // registration of the injected metrics has happened, there was no check - // at registration time. If the injection results in inconsistent - // metrics, the Collect call will return an error. Some problems may - // even go undetected, like invalid label names in the injected - // protobufs. - // - // The hook function must be callable at any time and concurrently. - SetInjectionHook(hook func() []*dto.MetricFamily) - // Collect collects metrics from registered Collectors and returns them +} + +// Deliverer is the interface for the part of a registry in charge of delivering +// the collected metrics. +type Deliverer interface { + // Deliver collects metrics from registered Collectors and returns them // as lexicographically sorted MetricFamily protobufs. Even if an error - // occurs, Collect attempts to collect as many metrics as + // occurs, Deliver attempts to collect as many metrics as // possible. Hence, if a non-nil error is returned, the returned // MetricFamily slice could be nil (in case of a fatal error that // prevented any meaningful metric collection) or contain a number of @@ -124,19 +122,7 @@ type Registry interface { // duplicate metrics, no invalid identifiers). In scenarios where // complete collection is critical, the returned MetricFamily protobufs // should be disregarded if the returned error is non-nil. - Collect() ([]*dto.MetricFamily, error) -} - -// MustRegisterWith registers the provided Collectors with the provided Registry -// and panics upon the first registration that causes an error. -// -// See Registry.Register for more details of Collector registration. -func MustRegisterWith(r Registry, cs ...Collector) { - for _, c := range cs { - if err := r.Register(c); err != nil { - panic(err) - } - } + Deliver() ([]*dto.MetricFamily, error) } // Register registers the provided Collector with the DefaultRegistry. @@ -153,7 +139,7 @@ func Register(c Collector) error { // MustRegister is a shortcut for MustRegisterWith(DefaultRegistry, cs...). See // there for more details. func MustRegister(cs ...Collector) { - MustRegisterWith(DefaultRegistry, cs...) + DefaultRegistry.MustRegister(cs...) } // RegisterOrGet registers the provided Collector with the DefaultRegistry and @@ -208,13 +194,13 @@ func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) { DefaultRegistry.SetInjectionHook(hook) } -// AlreadyRegisteredError is returned by the Registry.Register if the Collector -// to be registered has already been registered before, or a different Collector -// that collects the same metrics has been registered before. Registration fails -// in that case, but you can detect from the kind of error what has -// happened. The error contains fields for the existing Collector and the -// (rejected) new Collector that equals the existing one. This can be used in -// the following way: +// AlreadyRegisteredError is returned by the Registerer.Register if the +// Collector to be registered has already been registered before, or a different +// Collector that collects the same metrics has been registered +// before. Registration fails in that case, but you can detect from the kind of +// error what has happened. The error contains fields for the existing Collector +// and the (rejected) new Collector that equals the existing one. This can be +// used in the following way: // // reqCounter := prometheus.NewCounter( /* ... */ ) // if err := registry.Register(reqCounter); err != nil { @@ -235,7 +221,11 @@ func (err AlreadyRegisteredError) Error() string { return "duplicate metrics collector registration attempted" } -type registry struct { +// Registry registers Prometheus collectors, collects their metrics, and +// delivers them for exposition. It implements Registerer and Deliverer. The +// zero value is not usable. Use NewRegistry or NewPedanticRegistry to create +// instances. +type Registry struct { mtx sync.RWMutex collectorsByID map[uint64]Collector // ID is a hash of the descIDs. descIDs map[uint64]struct{} @@ -244,7 +234,8 @@ type registry struct { pedanticChecksEnabled bool } -func (r *registry) Register(c Collector) error { +// Register implements Registerer. +func (r *Registry) Register(c Collector) error { var ( descChan = make(chan *Desc, capDescChan) newDescIDs = map[uint64]struct{}{} @@ -324,7 +315,8 @@ func (r *registry) Register(c Collector) error { return nil } -func (r *registry) Unregister(c Collector) bool { +// Unregister implements Registerer. +func (r *Registry) Unregister(c Collector) bool { var ( descChan = make(chan *Desc, capDescChan) descIDs = map[uint64]struct{}{} @@ -360,7 +352,17 @@ func (r *registry) Unregister(c Collector) bool { return true } -func (r *registry) Collect() ([]*dto.MetricFamily, error) { +// MustRegister implements Registerer. +func (r *Registry) MustRegister(cs ...Collector) { + for _, c := range cs { + if err := r.Register(c); err != nil { + panic(err) + } + } +} + +// Deliver implements Deliverer. +func (r *Registry) Deliver() ([]*dto.MetricFamily, error) { var ( metricChan = make(chan Metric, capMetricChan) metricHashes = map[uint64]struct{}{} @@ -495,7 +497,7 @@ func (r *registry) Collect() ([]*dto.MetricFamily, error) { return result, errs } -func (r *registry) checkConsistency( +func (r *Registry) checkConsistency( metricFamily *dto.MetricFamily, dtoMetric *dto.Metric, desc *Desc, @@ -583,7 +585,20 @@ func (r *registry) checkConsistency( return nil } -func (r *registry) SetInjectionHook(hook func() []*dto.MetricFamily) { +// SetInjectionHook sets the provided hook to inject MetricFamilies. The hook is +// a function that is called whenever metrics are collected. The MetricFamily +// protobufs returned by the hook function are merged with the metrics collected +// in the usual way. +// +// This is a way to directly inject MetricFamily protobufs managed and owned by +// the caller. The caller has full responsibility. As no registration of the +// injected metrics has happened, there was no check at registration time. If +// the injection results in inconsistent metrics, the Collect call will return +// an error. Some problems may even go undetected, like invalid label names in +// the injected protobufs. +// +// The hook function must be callable at any time and concurrently. +func (r *Registry) SetInjectionHook(hook func() []*dto.MetricFamily) { r.mtx.Lock() defer r.mtx.Unlock() r.metricFamilyInjectionHook = hook