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.)
This commit is contained in:
beorn7 2016-08-03 00:41:51 +02:00
parent cf7e1caf17
commit 9c3fe750dd
10 changed files with 113 additions and 98 deletions

View File

@ -15,15 +15,15 @@ package prometheus
// Collector is the interface implemented by anything that can be used by // Collector is the interface implemented by anything that can be used by
// Prometheus to collect metrics. A Collector has to be registered for // 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 // The stock metrics provided by this package (Gauge, Counter, Summary,
// also Collectors (which only ever collect one metric, namely itself). An // Histogram, Untyped) are also Collectors (which only ever collect one metric,
// implementer of Collector may, however, collect multiple metrics in a // namely itself). An implementer of Collector may, however, collect multiple
// coordinated fashion and/or create metrics on the fly. Examples for collectors // metrics in a coordinated fashion and/or create metrics on the fly. Examples
// already implemented in this library are the metric vectors (i.e. collection // for collectors already implemented in this library are the metric vectors
// of multiple instances of the same Metric but with different label values) // (i.e. collection of multiple instances of the same Metric but with different
// like GaugeVec or SummaryVec, and the ExpvarCollector. // label values) like GaugeVec or SummaryVec, and the ExpvarCollector.
type Collector interface { type Collector interface {
// Describe sends the super-set of all possible descriptors of metrics // Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector to the provided channel and returns once // collected by this Collector to the provided channel and returns once

View File

@ -141,10 +141,10 @@
// //
// So far, everything we did operated on the so-called default registry, as it // 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 be found in the global DefaultRegistry variable. With NewRegistry, you
// can create a custom registry, or you can even implement the Registry // can create a custom registry, or you can even implement the Registerer or
// interface yourself. The methods Register and Unregister work in the same way // Deliverer interfaces yourself. The methods Register and Unregister work in
// on a custom registry as the global functions Register and Unregister on the // the same way on a custom registry as the global functions Register and
// default registry. // Unregister on the default registry.
// //
// There are a number of uses for custom registries: You can use registries // There are a number of uses for custom registries: You can use registries
// with special properties, see NewPedanticRegistry. You can avoid global state, // with special properties, see NewPedanticRegistry. You can avoid global state,
@ -160,15 +160,15 @@
// HTTP Exposition // HTTP Exposition
// //
// The Handler function used so far to get an http.Handler for serving the // 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 // metrics is also acting on the DefaultRegistry. With HandlerFor, you can
// create a handler for a custom registry. It also allows to create handler that // create a handler for a custom registry or anything the implements the
// act differently on errors or allow to log errors. Also note that the handler // Deliverer interface. It also allows to create handler that act differently on
// returned by the Handler function is already instrumented with some HTTP // errors or allow to log errors. Also note that the handler returned by the
// metrics. You can call UninstrumentedHandler to get a handler for the // Handler function is already instrumented with some HTTP metrics. You can call
// DefaultRegistry that is not instrumented, or you can use InstrumentHandler to // UninstrumentedHandler to get a handler for the DefaultRegistry that is not
// instrument any http.Handlers of your choice. (But note that the way the // instrumented, or you can use InstrumentHandler to instrument any
// instrumentation happens is partially obsolete. Better ways are being worked // http.Handlers of your choice. (But note that the way the instrumentation
// on.) // happens is partially obsolete. Better ways are being worked on.)
// //
// Pushing to the Pushgateway // Pushing to the Pushgateway
// //

View File

@ -113,6 +113,6 @@ func ExampleCollector_clustermanager() {
// Since we are dealing with custom Collector implementations, it might // Since we are dealing with custom Collector implementations, it might
// be a good idea to try it out with a pedantic registry. // be a good idea to try it out with a pedantic registry.
reg := prometheus.NewPedanticRegistry() reg := prometheus.NewPedanticRegistry()
prometheus.MustRegisterWith(reg, workerDB) reg.MustRegister(workerDB)
prometheus.MustRegisterWith(reg, workerCA) reg.MustRegister(workerCA)
} }

View File

@ -388,9 +388,9 @@ func ExampleSummaryVec() {
// by registering it with a custom registry and then let it collect the // by registering it with a custom registry and then let it collect the
// metrics. // metrics.
reg := prometheus.NewRegistry() reg := prometheus.NewRegistry()
prometheus.MustRegisterWith(reg, temps) reg.MustRegister(temps)
metricFamilies, err := reg.Collect() metricFamilies, err := reg.Deliver()
if err != nil || len(metricFamilies) != 1 { if err != nil || len(metricFamilies) != 1 {
panic("unexpected behavior of custom test registry") panic("unexpected behavior of custom test registry")
} }

View File

@ -65,12 +65,12 @@ func UninstrumentedHandler() http.Handler {
return HandlerFor(DefaultRegistry, HandlerOpts{}) 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 // the Handler is defined by the provided HandlerOpts. The Handler is NOT
// instrumented with InstrumentHandler. // 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) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
mfs, err := r.Collect() mfs, err := reg.Deliver()
if err != nil { if err != nil {
if opts.ErrorLog != nil { if opts.ErrorLog != nil {
opts.ErrorLog.Println("error collecting metrics:", err) opts.ErrorLog.Println("error collecting metrics:", err)

View File

@ -146,7 +146,7 @@ func TestHandlerErrorHandling(t *testing.T) {
Name: "the_count", Name: "the_count",
Help: "Ah-ah-ah! Thunder and lightning!", Help: "Ah-ah-ah! Thunder and lightning!",
}) })
MustRegisterWith(reg, cnt) reg.MustRegister(cnt)
cntVec := NewCounterVec( cntVec := NewCounterVec(
CounterOpts{ CounterOpts{
@ -158,9 +158,9 @@ func TestHandlerErrorHandling(t *testing.T) {
) )
cntVec.WithLabelValues("val1").Inc() cntVec.WithLabelValues("val1").Inc()
cntVec.WithLabelValues("val2").Inc() cntVec.WithLabelValues("val2").Inc()
MustRegisterWith(reg, cntVec) reg.MustRegister(cntVec)
MustRegisterWith(reg, errorCollector{}) reg.MustRegister(errorCollector{})
logBuf := &bytes.Buffer{} logBuf := &bytes.Buffer{}
logger := log.New(logBuf, "", 0) logger := log.New(logBuf, "", 0)

View File

@ -25,7 +25,7 @@ func TestProcessCollector(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
mfs, err := registry.Collect() mfs, err := registry.Deliver()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -44,8 +44,8 @@ import (
const contentTypeHeader = "Content-Type" const contentTypeHeader = "Content-Type"
// Registry triggers a metric collection by the provided registry and pushes all // Registry triggers a metric collection by the provided Deliverer and pushes all
// collected metrics to the Pushgateway specified by url, using the provided job // delivered metrics to the Pushgateway specified by url, using the provided job
// name and the (optional) further grouping labels (the grouping map may be // name and the (optional) further grouping labels (the grouping map may be
// nil). See the Pushgateway documentation for detailed implications of the job // nil). See the Pushgateway documentation for detailed implications of the job
// and other grouping labels. Neither the job name nor any grouping label value // 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 // 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 // labels will be replaced with the metrics pushed by this call. (It uses HTTP
// method 'PUT' to push to the Pushgateway.) // method 'PUT' to push to the Pushgateway.)
func Registry(r prometheus.Registry, job string, grouping map[string]string, url string) error { func Registry(reg prometheus.Deliverer, job string, grouping map[string]string, url string) error {
return push(r, job, grouping, url, "PUT") return push(reg, job, grouping, url, "PUT")
} }
// RegistryAdd works like Registry, but only previously pushed metrics with the // 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 // same name (and the same job and other grouping labels) will be replaced. (It
// uses HTTP method 'POST' to push to the Pushgateway.) // uses HTTP method 'POST' to push to the Pushgateway.)
func RegistryAdd(r prometheus.Registry, job string, grouping map[string]string, url string) error { func RegistryAdd(reg prometheus.Deliverer, job string, grouping map[string]string, url string) error {
return push(r, job, grouping, url, "POST") 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, "://") { if !strings.Contains(pushURL, "://") {
pushURL = "http://" + 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, "/")) pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
mfs, err := r.Collect() mfs, err := reg.Deliver()
if err != nil { if err != nil {
return err return err
} }
@ -133,15 +133,15 @@ func push(r prometheus.Registry, job string, grouping map[string]string, pushURL
return nil return nil
} }
// Collectors works like Registry, but it does not collect via a // Collectors works like Registry, but it does not use a Deliverer. Instead, it
// registry. Instead, it collects from the provided collectors directly. It is a // collects from the provided collectors directly. It is a convenient way to
// convenient way to push only a few metrics. // push only a few metrics.
func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
return pushCollectors(job, grouping, url, "PUT", collectors...) return pushCollectors(job, grouping, url, "PUT", collectors...)
} }
// AddCollectors works like PushAdd, but it does not collect via a // AddCollectors works like RegistryAdd, but it does not use a Deliverer.
// registry. Instead, it collects from the provided collectors directly. It is a // Instead, it collects from the provided collectors directly. It is a
// convenient way to push only a few metrics. // convenient way to push only a few metrics.
func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error { func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
return pushCollectors(job, grouping, url, "POST", collectors...) return pushCollectors(job, grouping, url, "POST", collectors...)

View File

@ -80,10 +80,10 @@ func TestPush(t *testing.T) {
}) })
reg := prometheus.NewRegistry() reg := prometheus.NewRegistry()
prometheus.MustRegisterWith(reg, metric1) reg.MustRegister(metric1)
prometheus.MustRegisterWith(reg, metric2) reg.MustRegister(metric2)
mfs, err := reg.Collect() mfs, err := reg.Deliver()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -44,8 +44,8 @@ func init() {
// NewRegistry creates a new vanilla Registry without any Collectors // NewRegistry creates a new vanilla Registry without any Collectors
// pre-registered. // pre-registered.
func NewRegistry() Registry { func NewRegistry() *Registry {
return &registry{ return &Registry{
collectorsByID: map[uint64]Collector{}, collectorsByID: map[uint64]Collector{},
descIDs: map[uint64]struct{}{}, descIDs: map[uint64]struct{}{},
dimHashesByName: map[string]uint64{}, dimHashesByName: map[string]uint64{},
@ -62,14 +62,18 @@ func NewRegistry() Registry {
// Collector. Well-behaved Collectors and Metrics will only provide consistent // Collector. Well-behaved Collectors and Metrics will only provide consistent
// Descs. This Registry is useful to test the implementation of Collectors and // Descs. This Registry is useful to test the implementation of Collectors and
// Metrics. // Metrics.
func NewPedanticRegistry() Registry { func NewPedanticRegistry() *Registry {
r := NewRegistry().(*registry) r := NewRegistry()
r.pedanticChecksEnabled = true r.pedanticChecksEnabled = true
return r return r
} }
// Registry is the interface for the metrics registry. // Registerer is the interface for the part of a registry in charge of
type Registry interface { // 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 // Register registers a new Collector to be included in metrics
// collection. It returns an error if the descriptors provided by the // collection. It returns an error if the descriptors provided by the
// Collector are invalid or if they - in combination with descriptors of // 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 // It is in general not safe to register the same Collector multiple
// times concurrently. // times concurrently.
Register(Collector) error 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 // Unregister unregisters the Collector that equals the Collector passed
// in as an argument. (Two Collectors are considered equal if their // in as an argument. (Two Collectors are considered equal if their
// Describe method yields the same set of descriptors.) The function // 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 // instance must only collect consistent metrics throughout its
// lifetime. // lifetime.
Unregister(Collector) bool 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 // Deliverer is the interface for the part of a registry in charge of delivering
// the metrics collected in the usual way. // the collected metrics.
// type Deliverer interface {
// This is a way to directly inject MetricFamily protobufs managed and // Deliver collects metrics from registered Collectors and returns them
// 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
// as lexicographically sorted MetricFamily protobufs. Even if an error // 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 // possible. Hence, if a non-nil error is returned, the returned
// MetricFamily slice could be nil (in case of a fatal error that // MetricFamily slice could be nil (in case of a fatal error that
// prevented any meaningful metric collection) or contain a number of // 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 // duplicate metrics, no invalid identifiers). In scenarios where
// complete collection is critical, the returned MetricFamily protobufs // complete collection is critical, the returned MetricFamily protobufs
// should be disregarded if the returned error is non-nil. // should be disregarded if the returned error is non-nil.
Collect() ([]*dto.MetricFamily, error) Deliver() ([]*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)
}
}
} }
// Register registers the provided Collector with the DefaultRegistry. // 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 // MustRegister is a shortcut for MustRegisterWith(DefaultRegistry, cs...). See
// there for more details. // there for more details.
func MustRegister(cs ...Collector) { func MustRegister(cs ...Collector) {
MustRegisterWith(DefaultRegistry, cs...) DefaultRegistry.MustRegister(cs...)
} }
// RegisterOrGet registers the provided Collector with the DefaultRegistry and // RegisterOrGet registers the provided Collector with the DefaultRegistry and
@ -208,13 +194,13 @@ func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
DefaultRegistry.SetInjectionHook(hook) DefaultRegistry.SetInjectionHook(hook)
} }
// AlreadyRegisteredError is returned by the Registry.Register if the Collector // AlreadyRegisteredError is returned by the Registerer.Register if the
// to be registered has already been registered before, or a different Collector // Collector to be registered has already been registered before, or a different
// that collects the same metrics has been registered before. Registration fails // Collector that collects the same metrics has been registered
// in that case, but you can detect from the kind of error what has // before. Registration fails in that case, but you can detect from the kind of
// happened. The error contains fields for the existing Collector and the // error what has happened. The error contains fields for the existing Collector
// (rejected) new Collector that equals the existing one. This can be used in // and the (rejected) new Collector that equals the existing one. This can be
// the following way: // used in the following way:
// //
// reqCounter := prometheus.NewCounter( /* ... */ ) // reqCounter := prometheus.NewCounter( /* ... */ )
// if err := registry.Register(reqCounter); err != nil { // if err := registry.Register(reqCounter); err != nil {
@ -235,7 +221,11 @@ func (err AlreadyRegisteredError) Error() string {
return "duplicate metrics collector registration attempted" 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 mtx sync.RWMutex
collectorsByID map[uint64]Collector // ID is a hash of the descIDs. collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
descIDs map[uint64]struct{} descIDs map[uint64]struct{}
@ -244,7 +234,8 @@ type registry struct {
pedanticChecksEnabled bool pedanticChecksEnabled bool
} }
func (r *registry) Register(c Collector) error { // Register implements Registerer.
func (r *Registry) Register(c Collector) error {
var ( var (
descChan = make(chan *Desc, capDescChan) descChan = make(chan *Desc, capDescChan)
newDescIDs = map[uint64]struct{}{} newDescIDs = map[uint64]struct{}{}
@ -324,7 +315,8 @@ func (r *registry) Register(c Collector) error {
return nil return nil
} }
func (r *registry) Unregister(c Collector) bool { // Unregister implements Registerer.
func (r *Registry) Unregister(c Collector) bool {
var ( var (
descChan = make(chan *Desc, capDescChan) descChan = make(chan *Desc, capDescChan)
descIDs = map[uint64]struct{}{} descIDs = map[uint64]struct{}{}
@ -360,7 +352,17 @@ func (r *registry) Unregister(c Collector) bool {
return true 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 ( var (
metricChan = make(chan Metric, capMetricChan) metricChan = make(chan Metric, capMetricChan)
metricHashes = map[uint64]struct{}{} metricHashes = map[uint64]struct{}{}
@ -495,7 +497,7 @@ func (r *registry) Collect() ([]*dto.MetricFamily, error) {
return result, errs return result, errs
} }
func (r *registry) checkConsistency( func (r *Registry) checkConsistency(
metricFamily *dto.MetricFamily, metricFamily *dto.MetricFamily,
dtoMetric *dto.Metric, dtoMetric *dto.Metric,
desc *Desc, desc *Desc,
@ -583,7 +585,20 @@ func (r *registry) checkConsistency(
return nil 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() r.mtx.Lock()
defer r.mtx.Unlock() defer r.mtx.Unlock()
r.metricFamilyInjectionHook = hook r.metricFamilyInjectionHook = hook