Skip to content

Commit 73bd3d4

Browse files
committed
refactor histograms to support different buckets
1 parent 1097171 commit 73bd3d4

File tree

4 files changed

+104
-48
lines changed

4 files changed

+104
-48
lines changed

src/metrics/histogram.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use crate::metrics::macros::MetricFromOpts;
2+
use std::marker::PhantomData;
3+
use std::ops::{Deref, DerefMut};
4+
5+
/// Prometheus's histograms work by dividing datapoints in buckets, with each bucket containing the
6+
/// count of datapoints equal or greater to the bucket value.
7+
///
8+
/// Histogram buckets are not an exact science, so feel free to tweak the buckets or create new
9+
/// ones if you see that the histograms are not really accurate. Just avoid adding too many buckets
10+
/// for a single type as that increases the number of exported metric series.
11+
pub trait HistogramBuckets {
12+
const BUCKETS: &'static [f64];
13+
}
14+
15+
/// Buckets geared towards measuring timings, such as the response time of our requests, going from
16+
/// 0.5ms to 100ms with a higher resolution and from 100ms to 5 seconds with a slightly lower
17+
/// resolution. This allows us to properly measure download requests (which take around 1ms) and
18+
/// other requests (our 95h is around 10-20ms).
19+
pub struct TimingBuckets;
20+
21+
impl HistogramBuckets for TimingBuckets {
22+
const BUCKETS: &'static [f64] = &[
23+
0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.5, 1.0, 5.0,
24+
];
25+
}
26+
27+
/// Wrapper type over [`prometheus::Histogram`] to support defining buckets.
28+
pub struct Histogram<B: HistogramBuckets> {
29+
inner: prometheus::Histogram,
30+
_phantom: PhantomData<B>,
31+
}
32+
33+
impl<B: HistogramBuckets> MetricFromOpts for Histogram<B> {
34+
fn from_opts(opts: prometheus::Opts) -> Result<Self, prometheus::Error> {
35+
Ok(Histogram {
36+
inner: prometheus::Histogram::with_opts(prometheus::HistogramOpts {
37+
common_opts: opts,
38+
buckets: B::BUCKETS.to_vec(),
39+
})?,
40+
_phantom: PhantomData,
41+
})
42+
}
43+
}
44+
45+
impl<B: HistogramBuckets> Deref for Histogram<B> {
46+
type Target = prometheus::Histogram;
47+
48+
fn deref(&self) -> &Self::Target {
49+
&self.inner
50+
}
51+
}
52+
53+
impl<B: HistogramBuckets> DerefMut for Histogram<B> {
54+
fn deref_mut(&mut self) -> &mut Self::Target {
55+
&mut self.inner
56+
}
57+
}
58+
59+
/// Wrapper type over [`prometheus::HistogramVec`] to support defining buckets.
60+
pub struct HistogramVec<B: HistogramBuckets> {
61+
inner: prometheus::HistogramVec,
62+
_phantom: PhantomData<B>,
63+
}
64+
65+
impl<B: HistogramBuckets> MetricFromOpts for HistogramVec<B> {
66+
fn from_opts(opts: prometheus::Opts) -> Result<Self, prometheus::Error> {
67+
Ok(HistogramVec {
68+
inner: prometheus::HistogramVec::new(
69+
prometheus::HistogramOpts {
70+
common_opts: opts.clone(),
71+
buckets: B::BUCKETS.to_vec(),
72+
},
73+
opts.variable_labels
74+
.iter()
75+
.map(|s| s.as_str())
76+
.collect::<Vec<_>>()
77+
.as_slice(),
78+
)?,
79+
_phantom: PhantomData,
80+
})
81+
}
82+
}
83+
84+
impl<B: HistogramBuckets> Deref for HistogramVec<B> {
85+
type Target = prometheus::HistogramVec;
86+
87+
fn deref(&self) -> &Self::Target {
88+
&self.inner
89+
}
90+
}
91+
92+
impl<B: HistogramBuckets> DerefMut for HistogramVec<B> {
93+
fn deref_mut(&mut self) -> &mut Self::Target {
94+
&mut self.inner
95+
}
96+
}

src/metrics/instance.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
//! As a rule of thumb, if the metric requires a database query to be updated it's probably a
1818
//! service-level metric, and you should add it to `src/metrics/service.rs` instead.
1919
20+
use crate::metrics::histogram::{Histogram, HistogramVec, TimingBuckets};
2021
use crate::util::errors::AppResult;
2122
use crate::{app::App, db::DieselPool};
22-
use prometheus::{
23-
proto::MetricFamily, Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec,
24-
};
23+
use prometheus::{proto::MetricFamily, IntCounter, IntCounterVec, IntGauge, IntGaugeVec};
2524

2625
metrics! {
2726
pub struct InstanceMetrics {
@@ -30,15 +29,15 @@ metrics! {
3029
/// Number of used database connections in the pool
3130
database_used_conns: IntGaugeVec["pool"],
3231
/// Amount of time required to obtain a database connection
33-
pub database_time_to_obtain_connection: HistogramVec["pool"],
32+
pub database_time_to_obtain_connection: HistogramVec<TimingBuckets>["pool"],
3433

3534
/// Number of requests processed by this instance
3635
pub requests_total: IntCounter,
3736
/// Number of requests currently being processed
3837
pub requests_in_flight: IntGauge,
3938

4039
/// Response times of our endpoints
41-
pub response_times: HistogramVec["endpoint"],
40+
pub response_times: HistogramVec<TimingBuckets>["endpoint"],
4241
/// Nmber of responses per status code
4342
pub responses_by_status_code_total: IntCounterVec["status"],
4443

@@ -47,7 +46,7 @@ metrics! {
4746
/// Number of download requests with a non-canonical crate name.
4847
pub downloads_non_canonical_crate_name_total: IntCounter,
4948
/// How long it takes to execute the SELECT query in the download endpoint.
50-
pub downloads_select_query_execution_time: Histogram,
49+
pub downloads_select_query_execution_time: Histogram<TimingBuckets>,
5150
/// Number of download requests that are not counted yet.
5251
downloads_not_counted_total: IntGauge,
5352
}

src/metrics/macros.rs

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,4 @@
1-
use prometheus::{Histogram, HistogramOpts, HistogramVec, Opts};
2-
3-
/// Prometheus's histograms work by dividing datapoints in buckets, with each bucket containing
4-
/// the count of datapoints equal or greater to the bucket value.
5-
///
6-
/// The buckets used by crates.io are geared towards measuring the response time of our requests,
7-
/// going from 0.5ms to 100ms with a higher resolution and from 100ms to 5 seconds with a slightly
8-
/// lower resolution. This allows us to properly measure download requests (which take around 1ms)
9-
/// and other requests (our 95h is around 10-20ms).
10-
///
11-
/// Histogram buckets are not an exact science, so feel free to tweak the buckets if you see that
12-
/// the histograms are not really accurate. Just avoid adding too many buckets as that increases
13-
/// the number of exported metric series.
14-
const HISTOGRAM_BUCKETS: &[f64] = &[
15-
0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.5, 1.0, 5.0,
16-
];
1+
use prometheus::Opts;
172

183
pub(super) trait MetricFromOpts: Sized {
194
fn from_opts(opts: Opts) -> Result<Self, prometheus::Error>;
@@ -105,29 +90,4 @@ load_metric_type!(GaugeVec as vec);
10590
load_metric_type!(IntGauge as single);
10691
load_metric_type!(IntGaugeVec as vec);
10792

108-
// Use a custom implementation for histograms to customize the buckets.
109-
110-
impl MetricFromOpts for Histogram {
111-
fn from_opts(opts: Opts) -> Result<Self, prometheus::Error> {
112-
Histogram::with_opts(HistogramOpts {
113-
common_opts: opts,
114-
buckets: HISTOGRAM_BUCKETS.to_vec(),
115-
})
116-
}
117-
}
118-
119-
impl MetricFromOpts for HistogramVec {
120-
fn from_opts(opts: Opts) -> Result<Self, prometheus::Error> {
121-
HistogramVec::new(
122-
HistogramOpts {
123-
common_opts: opts.clone(),
124-
buckets: HISTOGRAM_BUCKETS.to_vec(),
125-
},
126-
opts.variable_labels
127-
.iter()
128-
.map(|s| s.as_str())
129-
.collect::<Vec<_>>()
130-
.as_slice(),
131-
)
132-
}
133-
}
93+
// Histograms are defined in histogram.rs

src/metrics/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub use self::service::ServiceMetrics;
55
#[macro_use]
66
mod macros;
77

8+
mod histogram;
89
mod instance;
910
mod log_encoder;
1011
mod service;

0 commit comments

Comments
 (0)