prometheus-cpp
prometheus-cpp copied to clipboard
default registry support present or not?
How do we let a metric family be added to a default registry? If we do not call Register(registry) on an object of say GaugeBuilder (if that helps to add it to default registry), then how will we able to call Add() fn later to be able to add metrics to the family as Add function can be called only on a Family<Gauge> object, which is returned by Register(registry) fn?
I'm assuming that by default registry you mean a static global object? We don't provide one because we think that global variables are a bad idea. That being said, nothing prevents you from creating one and using it in the same way as the local one created in the usage example.
Was wondering this as well, this is from the prometheus docs: There MUST be a default CollectorRegistry, the standard metrics MUST by default implicitly register into it with no special work required by the user.
What's best practice for sharing an exposer and registry across different parts of a project? Or is it expected that every class would create it's own registry? Was also wondering how I should keep track of families. Should I just re-register the same metric name, track the metric names globally, or do it at the class level? Is it just as bad as having a global to have public methods in a Prometheus singleton? Was looking at some other libraries, and they'll panic if you register the same metric name twice with a registry, but as far as I can tell that works fine with cpp.
All excellent questions.
There MUST be a default CollectorRegistry
Due to the nature of C++, it's not a good way for this library to provide a global variable for this that fits everybodys use case. Think DLL loading, dependencies between static variables, etc.
What's best practice for sharing an exposer and registry across different parts of a project?
Create an Exposer close to main(). Inject it into your subcomponents, they create a registry and register it with the exposer.
Was also wondering how I should keep track of families. Should I just re-register the same metric name, track the metric names globally, or do it at the class level?
If two pieces of code should share the same family, I usually inject a reference families into their constructors instead of/in addition to the registry.
Was looking at some other libraries, and they'll panic if you register the same metric name twice with a registry, but as far as I can tell that works fine with cpp.
You are right, PR #286 is hopefully fixing this soon. With multiple registries, you have the additional problem that they might have conflicts with each other when metrics are combined on collection.
In general, I'm curious if colliding metrics is an actual issue in somebodys codebase. You should be able to avoid this by namespacing in your metrics names (ie network_ for everything related to the network component of your server). Phrased differently, how can you even know what part of your code a specific metrics belongs to if they are disorganized enough to cause conflicts?
Thanks, that's very helpful!
Due to the nature of C++, it's not a good way for this library to provide a global variable for this that fits everybodys use case. Think DLL loading, dependencies between static variables, etc.
Create an
Exposerclose tomain(). Inject it into your subcomponents, they create a registry and register it with the exposer.
For a basic use case, is there any reason it's helpful to have each subcomponent create it's own registry? I think I'll just end up doing this every single time:
auto registry = std::make_shared<Registry>();
exposer.RegisterCollectable(registry);
Or of course I could just pass a registry instead of passing the exposer.
Instead of that boilerplate, I'd rather have a per exposer (not global) default CollectorRegistry and just call Register(exposer). Don't know if that's incompatible with use cases where you need multiple registries, but seems like that it would somewhat provide what the docs talk about without requiring globals.
For a basic use case, is there any reason it's helpful to have each subcomponent create it's own registry?
You can also inject the registry. The benefit of injecting the exposer is that - for complex use cases -components are little more flexible in terms of the number of registries they want to create, pass on to subcomponents, etc.
Instead of that boilerplate, I'd rather have a per exposer (not global) default CollectorRegistry and just call Register(exposer). Don't know if that's incompatible with use cases where you need multiple registries, but seems like that it would somewhat provide what the docs talk about without requiring globals.
That's an interesting idea I haven't thought about before. I'm open to a PR for this, especially as it wouldn't break the current API.
Cool if I get a chance I'll take a look at it. Thanks!
If two pieces of code should share the same family, I usually inject a reference families into their constructors instead of/in addition to the registry.
It should be very very rare that two pieces of code share a metric name. Almost always that means that the metric should be moved up the call stack. A metric name is fundamentally a pointer to a single specific bit of code, so should generally live and be scoped at file level.
Inject it into your subcomponents, they create a registry and register it with the exposer.
What if a subcomponent is via a transitive dependency, which you have no knowledge of? The default registry exists partially for that reason, globals aren't always bad - just often bad.
It should be very very rare that two pieces of code share a metric name. Almost always that means that the metric should be moved up the call stack. A metric name is fundamentally a pointer to a single specific bit of code, so should generally live and be scoped at file level.
I agree, injecting references to the actual metrics is the most common case.
What if a subcomponent is via a transitive dependency, which you have no knowledge of? The default registry exists partially for that reason, globals aren't always bad - just often bad.
Because I have no knowledge of it, I cannot easily provide lifetime guarantees around a default registry for it. Is the subcomponent loaded at runtime via dlopen()? Does it outlive main()? I don't think it's a good idea for a library to prescribe a model for this in C++, it should be the applications concern.
It doesn't matter how long the subcomponent lives, its metrics will remain for the lifetime of the process. The only place you'd need to be careful here is with custom collectors, as they might reference things that no longer exist.
It doesn't matter how long the subcomponent lives
It does, it can live longer than the registry singleton and then crash on shutdown when accessing metrics. Static initialization/destruction order in C++ is non-deterministic. It's surprisingly hard to implement global variables, especially if you are a library and know nothing about the executable.
True, I'd try to arrange things so it's never destructed. The process is about to die anyway.
I'd try to arrange things so it's never destructed
That triggers tools like LeakSanitizer, which would make unit/integration test suites fail.