client_golang
client_golang copied to clipboard
Please expose `wrappingCollector` publicly
We have a number of custom collectors that wrap other collectors - e.g. some cached value is periodically refreshed, where we want to collect metrics about the last refresh time, and also metrics about the value itself.
Currently for the outer collector to add a label or metric to the inner collector, we either need to reimplement wrappingCollector
ourselves, or we need to also apply the labels to the outer collector when it's registered using e.g. WrapRegistererWith
, in turn leading to some ugly patterns like
{
r := prometheus.WrapRegistererWith(labels, prometheus.DefaultRegisterer)
r.MustRegister(c)
defer r.Unregister(c)
}
repeated for every collector with different labels.
With a public wrappingCollector
we could avoid this by either wrapping it at the layer between the outer and inner collector.
Hi, thanks for proposing this.
It sounds what you try to achieve is similar to our attempt here: https://github.com/prometheus/client_golang/blob/cache/prometheus/cache/cache.go
Do you mind providing us with more details? It might be useful to upstream that special collector if this will be useful for others!
Currently for the outer collector to add a label or metric to the inner collector, we either need to reimplement wrappingCollector ourselves, or we need to also apply the labels to the outer collector when it's registered using e.g. WrapRegistererWith, in turn leading to some ugly patterns like. With a public wrappingCollector we could avoid this by either wrapping it at the layer between the outer and inner collector.
Does it mean you want to change the labels
passed to WrapRegistererWith
dynamically?
Sorry, I think I wasn't clear about presenting my use case and it got confused with what I'm asking for. I don't want to cache metrics, and I don't want to change labels over time. I'm just using a cache (of some application-specific objects, not metrics) as an example of where we have nested prometheus.Collector
s and I would like to use wrapped metrics.
tl;dr: If wrappingCollector
(or wrapDesc
/ wrappingMetric
) was exposed publicly we could write our custom collectors in a clearer and more flexible way.
Detailed use case information
We have a general-purpose "cache some value and refresh it every X interval" type, Cache[T]
. This is a prometheus.Collector
, it reports metrics like update_timestamp_seconds
and errors_total
. But it's also caching something, like an Image
or a GitRepository
. These are themselves Collector
s, e.g. Image
might report height
and width
, GitRepository
repo_size_bytes
, repo_branches
, etc. Cache
's Collect
method checks whether the cached value is a collector, and if so, also reports its metrics. The cached value can't be registered directly because nothing has a direct reference to it other than the Cache
, and because it's changing in parallel with other work.
The problem arises when a program has multiple Cache
values; the update_timestamp_seconds
metric will conflict. If it's both a Cache[Image]
and a Cache[GitRepository]
at the same time we can work around that by providing some Labels
to NewCache
. But we still can't have two Cache[Image]
s at the same time because the image metrics will conflict. Without something like wrappingCollector
or wrapDesc
and wrappedMetric
, Cache
has no way to apply its labels to the subcollection.
As Desc
is entirely private, implementing our own wrappingCollector
equivalent quickly explodes how much of the client we need to reimplement. Otherwise the prometheus
package only exposes wrapping at the registry level, so then we either have to push all wrapping parameters down the stack all the way to the scope of registration lifetime, or we need to invert control and have Cache
self-register/unregister which leads to a more complex Cache
implementation (e.g. a manual finalizer).
If we did have a public WrappingCollector
, we could instead compose c
completely ahead of time, even in a completely different scope, and the registration process doesn't have to care whether it's wrapped or not:
c := prometheus.Wrap(cache, labels)
// ...
prometheus.MustRegister(c)
defer prometheus.Unregister(c)
Or, again independently of registration details, Cache
's Collect
method could wrap Desc
s and Metric
s individually when forwarding from the subcollector. (Probably we would have Cache
use the first approach but we have some other subcollectors which might use the second.)
I have another possible use case for this.
We have a process worker pool controller. We'd like to use collectors.NewProcessCollector()
to create a collector for each worker.
But that function needs to be wrapped with additional
worker_pooland
worker_id` labels.
So we'd end up with something like this:
for w := range workers {
c := prometheus.Wrap(
collectors.NewProcessCollector(...),
prometheus.Labels{"worker_id", w.ID},
)
prometheus.MustRegister(c)
}
Not sure if this is valid, but that's what we're in need of.
Nevermind, this turned out to be easier to do than I thought.
prometheus.WrapRegistererWith(
prometheus.Labels{"worker_id": workerID},
prometheus.DefaultRegisterer,
).MustRegister(collectors.NewProcessCollector(...))
Hello 👋 Looks like there was no activity on this issue for the last 3 months. Do you mind updating us on the status? Is this still reproducible or needed? If yes, just comment on this PR or push a commit. Thanks! 🤗 If there will be no activity in the next 4 weeks, this issue will be closed (we can always reopen an issue if we need!).
I'm still interested in this.
We could probably improve our user-facing API a little after #1103, which would make it easier for the cache to maintain its own Registry
doing all the wrapping. But implementation-wise wrapping the Collector
directly still seems like the better option to me - it's less intrusive in the structs in question and lighter than having lots of full Registry
s around.
Closing for now as promised, let us know if you need this to be reopened! 🤗