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
// 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

View File

@ -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
//

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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...)

View File

@ -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)
}

View File

@ -44,8 +44,8 @@ func init() {
// NewRegistry creates a new vanilla Registry without any Collectors
// pre-registered.
func NewRegistry() Registry {
return &registry{
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