Skip to content

Commit

Permalink
Create a public registry interface and separate out HTTP exposition
Browse files Browse the repository at this point in the history
General context and approch
---------------------------

This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.

What's happening in this commit?
--------------------------------

This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ to
fat. This commit introduces a very lean `registry.Registry`
interface. Most of the existing functionality that is not part of that
interface is provided by utility functions (in
`registry/util.go`). The top-level function that act on the default
registry are mostly retained and kept in the `prometheus` package, to
minimize breakage and for convenience. The general idea is here that
the basic use case isn't involved with the `registry` package at
all. Only if you are going for one of the advanced use cases, you need
to import the `registry` package.

The default registry is kept in the public variable
`registry.Default`. This follows the example of the http package in
the standard library (cf. `http.DefaultServeMux`,
`http.DefaultClient`).

Another important part in making the registry lean is the extraction
of the HTTP exposition into its own package `promhttp`. Note that the
package is not simply called `http` because that would collide with
the `http` package from the standard library, which will usually be
used in the same source file as the `promhttp` package.

The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):

#46
#100
#170
#205

What future changes does this commit enable?
--------------------------------------------

- The separation of the HTTP exposition allows the implementation of
  other exposition methods as known from other Prometheus client
  libraries, e.g. sending the metrics to Graphite.
  Cf. #197

- The public `Registry` interface allows to implement convenience
  tools for testing metric collection. Those tools can inspect the
  collected MetricFamily protobufs and compare them to
  expectation. Also, tests can use their own testing instance of a
  registry.
  Cf. #58

Notable non-goals of this commit
--------------------------------

The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:

- `InstrumentHandler` has many known problems. The plan is to create a
  saner way to conveniently intrument HTTP handlers in the new
  `promhttp` package and remove the old `InstrumentHandler`
  altogether. To keep breakage low for now, even the default handler
  to expose metrics is still using the old `InstrumentHandler`.
  Cf. #200

- There is work underway to make the whole handling of metric
  descriptors (`Desc`) more intuitive and transparent for the user
  (including an ability for less strict checking,
  cf. #47). That's
  quite invasive from the perspective of the internal code, namely the
  registry. I deliberately kept those changes out of this commit. An
  unfortunate side effect is that the fields of the current `Desc` had
  to be exported for the time being. This will be solved in a cleaner
  way with the upcoming `Desc` changes.

Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection, and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming
fashion.

What are the breaking changes?
------------------------------

- Signature of functions pushing to Pushgateway has changed to allow
  arbitrary grouping (long planned anyway, and now that I worked on it
  anyway, I did it,
  cf. #100). Also,
  the PushCollectors and PushAddCollectors has been moved into the
  promhttp package, where the new Push and PushAdd functions for
  custom registries live, too.

- `SetMetricFamilyInjectionHook` is gone. A registry with a
  MetricFamily injection hook has to be created now with
  `registry.NewWithInjectionHook`.

- `PanicOnCollectError` is gone. This behavior can now be configured
  when creating a `promhttp.Handler`.

- `EnableCollectChecks` is gone. A registry with those checks can now
  be created with `registry.NewTestRegistry` (it is only ever used to
  test custom Collectors).

- `NewProcessCollector`, `NewGoCollector`, and `NewExpvarCollector`
  have been moved into their own package `collectors`.
  • Loading branch information
beorn7 committed Jul 27, 2016
1 parent 28be158 commit 97de01c
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 236 deletions.
5 changes: 0 additions & 5 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ SoundCloud Ltd. (http://soundcloud.com/).

The following components are included in this product:

goautoneg
http://bitbucket.org/ww/goautoneg
Copyright 2011, Open Knowledge Foundation Ltd.
See README.txt for license details.

perks - a fork of https://github.com/bmizerany/perks
https://github.com/beorn7/perks
Copyright 2013-2015 Blake Mizerany, Björn Rabenstein
Expand Down
20 changes: 10 additions & 10 deletions prometheus/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ type Collector interface {
// executing this method, it must send an invalid descriptor (created
// with NewInvalidDesc) to signal the error to the registry.
Describe(chan<- *Desc)
// Collect is called by Prometheus when collecting metrics. The
// implementation sends each collected metric via the provided channel
// and returns once the last metric has been sent. The descriptor of
// each sent metric is one of those returned by Describe. Returned
// metrics that share the same descriptor must differ in their variable
// label values. This method may be called concurrently and must
// therefore be implemented in a concurrency safe way. Blocking occurs
// at the expense of total performance of rendering all registered
// metrics. Ideally, Collector implementations support concurrent
// readers.
// Collect is called by the Prometheus registry when collecting
// metrics. The implementation sends each collected metric via the
// provided channel and returns once the last metric has been sent. The
// descriptor of each sent metric is one of those returned by
// Describe. Returned metrics that share the same descriptor must differ
// in their variable label values. This method may be called
// concurrently and must therefore be implemented in a concurrency safe
// way. Blocking occurs at the expense of total performance of rendering
// all registered metrics. Ideally, Collector implementations support
// concurrent readers.
Collect(chan<- Metric)
}

Expand Down
34 changes: 21 additions & 13 deletions prometheus/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package prometheus provides embeddable metric primitives for servers and
// standardized exposition of telemetry through a web services interface.
// Package prometheus provides metrics primitives to instrument code for
// monitoring. It also offers a standardized way to register metrics and then
// expose them via an HTTP endpoint or push them to a Pushgateway. See the
// sub-package registry for advanced metrics registration and the sub-package
// promhttp for customized HTTP exposition and instrumentation.
//
// All exported functions and methods are safe to be used concurrently unless
// specified otherwise.
//
// To expose metrics registered with the Prometheus registry, an HTTP server
// needs to know about the Prometheus handler. The usual endpoint is "/metrics".
//
// http.Handle("/metrics", prometheus.Handler())
//
// As a starting point a very basic usage example:
// As a starting point, a very basic usage example:
//
// package main
//
Expand All @@ -44,6 +42,7 @@
// )
//
// func init() {
// // Metrics have to be registered to be exposed:
// prometheus.MustRegister(cpuTemp)
// prometheus.MustRegister(hdFailures)
// }
Expand All @@ -52,6 +51,8 @@
// cpuTemp.Set(65.3)
// hdFailures.Inc()
//
// // The Handler function provides a default handler to expose metrics
// // via an HTTP server. "/metrics" is the usual endpoint for that.
// http.Handle("/metrics", prometheus.Handler())
// http.ListenAndServe(":8080", nil)
// }
Expand All @@ -74,8 +75,8 @@
// Those are all the parts needed for basic usage. Detailed documentation and
// examples are provided below.
//
// Everything else this package offers is essentially for "power users" only. A
// few pointers to "power user features":
// Everything else this package and its sub-packages offer is essentially for
// "power users" only. A few pointers to "power user features":
//
// All the various ...Opts structs have a ConstLabels field for labels that
// never change their value (which is only useful under special circumstances,
Expand All @@ -84,9 +85,6 @@
// The Untyped metric behaves like a Gauge, but signals the Prometheus server
// not to assume anything about its type.
//
// Functions to fine-tune how the metric registry works: EnableCollectChecks,
// PanicOnCollectError, Register, Unregister, SetMetricFamilyInjectionHook.
//
// For custom metric collection, there are two entry points: Custom Metric
// implementations and custom Collector implementations. A Metric is the
// fundamental unit in the Prometheus data model: a sample at a point in time
Expand All @@ -108,4 +106,14 @@
// A good example for a custom Collector is the ExpVarCollector included in this
// package, which exports variables exported via the "expvar" package as
// Prometheus metrics.
//
// The functions Register, Unregister, MustRegister, RegisterOrGet, and
// MustRegisterOrGet all act on the default registry. See the registry
// sub-package for working with custom registries.
//
// The functions Handler and UninstrumentedHandler create an HTTP handler to
// serve metrics from the default registry in the default way. The functions
// Push and PushAll push the metrics from the default registry via HTTP to a
// Pushgateway. See the promhttp sub-package to serve or push metrics in custom
// ways from custom registries.
package prometheus
32 changes: 16 additions & 16 deletions prometheus/expvar.go → prometheus/expvar_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ import (
"expvar"
)

// ExpvarCollector collects metrics from the expvar interface. It provides a
// quick way to expose numeric values that are already exported via expvar as
// Prometheus metrics. Note that the data models of expvar and Prometheus are
// fundamentally different, and that the ExpvarCollector is inherently
// slow. Thus, the ExpvarCollector is probably great for experiments and
// prototying, but you should seriously consider a more direct implementation of
// Prometheus metrics for monitoring production systems.
//
// Use NewExpvarCollector to create new instances.
type ExpvarCollector struct {
type expvarCollector struct {
exports map[string]*Desc
}

// NewExpvarCollector returns a newly allocated ExpvarCollector that still has
// to be registered with the Prometheus registry.
// NewExpvarCollector returns a newly allocated expvar Collector that still has
// to be registered with a Prometheus registry.
//
// An expvar Collector collects metrics from the expvar interface. It provides a
// quick way to expose numeric values that are already exported via expvar as
// Prometheus metrics. Note that the data models of expvar and Prometheus are
// fundamentally different, and that the expvar Collector is inherently slower
// than native Prometheus metrics. Thus, the expvar Collector is probably great
// for experiments and prototying, but you should seriously consider a more
// direct implementation of Prometheus metrics for monitoring production
// systems.
//
// The exports map has the following meaning:
//
Expand All @@ -59,21 +59,21 @@ type ExpvarCollector struct {
// sample values.
//
// Anything that does not fit into the scheme above is silently ignored.
func NewExpvarCollector(exports map[string]*Desc) *ExpvarCollector {
return &ExpvarCollector{
func NewExpvarCollector(exports map[string]*Desc) Collector {
return &expvarCollector{
exports: exports,
}
}

// Describe implements Collector.
func (e *ExpvarCollector) Describe(ch chan<- *Desc) {
func (e *expvarCollector) Describe(ch chan<- *Desc) {
for _, desc := range e.exports {
ch <- desc
}
}

// Collect implements Collector.
func (e *ExpvarCollector) Collect(ch chan<- Metric) {
func (e *expvarCollector) Collect(ch chan<- Metric) {
for name, desc := range e.exports {
var m Metric
expVar := expvar.Get(name)
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion prometheus/go_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type goCollector struct {

// NewGoCollector returns a collector which exports metrics about the current
// go process.
func NewGoCollector() *goCollector {
func NewGoCollector() Collector {
return &goCollector{
goroutines: NewGauge(GaugeOpts{
Namespace: "go",
Expand Down
21 changes: 21 additions & 0 deletions prometheus/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ import (
"time"
)

// Handler returns the HTTP handler for the global Prometheus registry. It is
// already instrumented with InstrumentHandler (using "prometheus" as handler
// name). Usually the handler is used to handle the "/metrics" endpoint.
//
// Please note the issues described in the doc comment of InstrumentHandler. You
// might want to consider using UninstrumentedHandler instead.
func Handler() http.Handler {
// TODO
return InstrumentHandler("prometheus", defRegistry)
}

// UninstrumentedHandler works in the same way as Handler, but the returned HTTP
// handler is not instrumented. This is useful if no instrumentation is desired
// (for whatever reason) or if the instrumentation has to happen with a
// different handler name (or with a different instrumentation approach
// altogether). See the InstrumentHandler example.
func UninstrumentedHandler() http.Handler {
// TODO
return defRegistry
}

var instLabels = []string{"method", "code"}

type nower interface {
Expand Down
4 changes: 2 additions & 2 deletions prometheus/process_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type processCollector struct {
// NewProcessCollector returns a collector which exports the current state of
// process metrics including cpu, memory and file descriptor usage as well as
// the process start time for the given process id under the given namespace.
func NewProcessCollector(pid int, namespace string) *processCollector {
func NewProcessCollector(pid int, namespace string) Collector {
return NewProcessCollectorPIDFn(
func() (int, error) { return pid, nil },
namespace,
Expand All @@ -43,7 +43,7 @@ func NewProcessCollector(pid int, namespace string) *processCollector {
func NewProcessCollectorPIDFn(
pidFn func() (int, error),
namespace string,
) *processCollector {
) Collector {
c := processCollector{
pidFn: pidFn,
collectFn: func(chan<- Metric) {},
Expand Down
2 changes: 2 additions & 0 deletions prometheus/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package prometheus

// TODO: Move partially to promhttp.

// Push triggers a metric collection by the default registry and pushes all
// collected metrics to the Pushgateway specified by url. See the Pushgateway
// documentation for detailed implications of the job and instance
Expand Down
Loading

0 comments on commit 97de01c

Please sign in to comment.