Go Best Practices for Registering Collector

1,183 views
Skip to first unread message

Keegan Carruthers-Smith

unread,
Jun 9, 2016, 8:30:01 AM6/9/16
to prometheus...@googlegroups.com
Hi all,

I was taking a look at the prometheus codebase and noticed two main styles of registering collectors/metrics. The first is the one I usually see in the documentation, which is in `init()` you call `prometheus.MustRegister()`. An example of this is in Prometheus is at https://github.com/prometheus/prometheus/blob/a08943e6d347119bfb1b50e738fca577e5733290/retrieval/scrape.go#L91-L97

The second style I saw was each component implementing the collector interface, then in `main.go` calling `MustRegister` on each component. An example is https://github.com/prometheus/prometheus/blob/a08943e6d347119bfb1b50e738fca577e5733290/cmd/prometheus/main.go#L159-L162 with https://github.com/prometheus/prometheus/blob/a08943e6d347119bfb1b50e738fca577e5733290/notifier/notifier.go#L313-L335

The second style seems cleaner, but I haven't seen that pattern mentioned in the documentation. It would be useful for a project I work on since we generate several service binaries from the same codebase, but currently we have can get services reporting metrics that never change (actually a sign we should split up some packages to prevent the init).

I'm interested why the prometheus project uses both styles?

Cheers,
Keegan

Brian Brazil

unread,
Jun 9, 2016, 8:42:26 AM6/9/16
to Keegan Carruthers-Smith, Prometheus Developers
On 9 June 2016 at 13:29, Keegan Carruthers-Smith <keegan...@gmail.com> wrote:
Hi all,

I was taking a look at the prometheus codebase and noticed two main styles of registering collectors/metrics. The first is the one I usually see in the documentation, which is in `init()` you call `prometheus.MustRegister()`. An example of this is in Prometheus is at https://github.com/prometheus/prometheus/blob/a08943e6d347119bfb1b50e738fca577e5733290/retrieval/scrape.go#L91-L97

The second style I saw was each component implementing the collector interface, then in `main.go` calling `MustRegister` on each component. An example is https://github.com/prometheus/prometheus/blob/a08943e6d347119bfb1b50e738fca577e5733290/cmd/prometheus/main.go#L159-L162 with https://github.com/prometheus/prometheus/blob/a08943e6d347119bfb1b50e738fca577e5733290/notifier/notifier.go#L313-L335

The second style seems cleaner, but I haven't seen that pattern mentioned in the documentation.

The former is better. With the latter you have the disadvantage that instrumentation leaks throughout your codebase, which would be a significant impediment to widespread instrumentation use. A given pieve of instrumentation should generally exist only inside a single file.

Brian
 
It would be useful for a project I work on since we generate several service binaries from the same codebase, but currently we have can get services reporting metrics that never change (actually a sign we should split up some packages to prevent the init).

I'm interested why the prometheus project uses both styles?

Cheers,
Keegan

--
You received this message because you are subscribed to the Google Groups "Prometheus Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to prometheus-devel...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Björn Rabenstein

unread,
Jun 9, 2016, 12:35:31 PM6/9/16
to Keegan Carruthers-Smith, prometheus-developers
On 9 June 2016 at 14:29, Keegan Carruthers-Smith
The first style is most straight forward but it is most suitable for
self-contained programs or within a limited, controlled code-base.

The second approach is more flexible. It defers the use of global
state (the global registration) and avoids code execution in `init()`
and is thus more suitable for modularized approaches where the user of
a package decides what to register. It also allows to create metrics
on the fly during collection time.

You can also strike a balance between the two styles: Nothing forces
you to register metrics in `init()`, so you can call `Register()`
later, e.g. upon instantiation of an instrumented object, which gives
you the opportunity to parametrize the metrics with fixed labels and
such. And your metrics don't have to be global vars, although it often
makes sense.

In principle, you only need to consider implementing `Collector` if
you create metrics on the fly during collection. Within the Prometheus
codebase, we do this occasionally, and while we are on it, it's often
easier to stuff in all the normal metrics, too, and then have just one
`Register()` call (but more LOCs in `Collect()` and `Describe()`).

The Go client library will soon export a Registry interface so that
you can register with different registries (for separate exposure or
test purposes) and avoid global state altogether. In which case you
either have to parametrize the registry to register with in your
constructor, or go the other way and implement Collector so that the
user of your package can register the whole thing at will.

Some people just hate global state. Others embrace it and happily have
loads of metrics as global vars.

https://peter.bourgon.org/go-best-practices-2016/#logging-and-instrumentation
is an interesting read in this context.

Hope this is not too convoluted and makes at least a bit of sense.
Brian's answer is simpler, and depending on your concrete scenario, it
might be sufficient.

I'll try to add more textbook-style documentation after I'm done with
the current set of (pretty fundamental) changes of the Go client (cf.
exported Registry above).
--
Björn Rabenstein, Engineer
http://soundcloud.com/brabenstein

SoundCloud Ltd. | Rheinsberger Str. 76/77, 10115 Berlin, Germany
Managing Director: Alexander Ljung | Incorporated in England & Wales
with Company No. 6343600 | Local Branch Office | AG Charlottenburg |
HRB 110657B
Reply all
Reply to author
Forward
0 new messages