Description
💬 background
Counter
and Gauge
provide a get()
method, which are helpful for various use cases. currently, Histogram
does not provide any equivalent way for callers to observe its count, sum, or buckets.
for my particular use case, i am interested in test coverage that confirms that Prometheus metrics are properly installed and measuring behavior in my application. because there is not any way to inspect histograms however, i am left in an unfortunate position where i can only write test coverage for my application if i use counters and gauges.
so, i'd like to propose the addition of a few new methods to Histogram
.
⚖️ proposal (part 1)
currently the histogram structure is defined as such:
pub struct Histogram {
inner: Arc<RwLock<Inner>>,
}
#[derive(Debug)]
pub(crate) struct Inner {
sum: f64,
count: u64,
buckets: Vec<(f64, u64)>,
}
first, i'd like to propose the following, which seem least controversial and most straightforward:
impl Histogram {
/// Returns the current sum of all observations.
pub fn sum(&self) -> f64 {
self.inner.read().sum
}
/// Returns the current number of observations.
pub fn count(&self) -> u64 {
self.inner.read().count
}
}
these two methods would address most of my particular concerns with respect to introspection in tests, and would help bring Histogram
in line with the interfaces provided by Gauge
and Counter
metrics.
⚖️ proposal (part 2)
it would also be very helpful if there was a way to access the buckets.
most simply, we could provide a way to acquire an iterator over the bucket tuples. i'd imagine this would look approximately like:
impl Histogram {
/// Returns a view of the histogram's buckets.
pub fn buckets(&self) -> MappedRwLockReadGuard<[(f64, u64)]> {
let inner = self.inner.read();
let buckets = RwLockReadGuard::map(inner, |inner| inner.buckets.as_slice());
buckets
}
}
we could provide a way to get the bucket for a given observed value. the nice part about this is that a caller wouldn't need to know how a histogram has been configured, and spares them the trouble/boilerplate of iterating through all of the buckets to find the proper bucket.
impl Histogram {
/// Returns the bucket given an observed value.
pub fn lookup(&self, v: f64) -> Option<(f64, u64)> {
let inner = self.inner.read();
inner
.buckets
.iter()
.find(|(upper_bound, _value)| upper_bound >= &v)
.copied()
}
}
note that this isn't mutually exclusive with the buckets()
accessor, we could provide either/both of these.
that said, i'm less attached to these bucket interfaces. i'll also note that the MappedRwLockReadGuard
would be subject to the same deadlock concerns mentioned in #231, but this signature seems like it follows the spirit of other existing interfaces in this library.
i am especially interested in adding sum()
and count()
, though i am also happy to help with adding the other methods if these are also welcome.