Skip to content

Commit ed64fb6

Browse files
authored
Add units to prometheus metric lines (#535)
1 parent d97f801 commit ed64fb6

File tree

3 files changed

+85
-24
lines changed

3 files changed

+85
-24
lines changed

metrics-exporter-prometheus/src/exporter/builder.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub struct PrometheusBuilder {
4848
upkeep_timeout: Duration,
4949
recency_mask: MetricKindMask,
5050
global_labels: Option<IndexMap<String, String>>,
51+
enable_unit_suffix: bool,
5152
}
5253

5354
impl PrometheusBuilder {
@@ -80,6 +81,7 @@ impl PrometheusBuilder {
8081
upkeep_timeout,
8182
recency_mask: MetricKindMask::NONE,
8283
global_labels: None,
84+
enable_unit_suffix: false,
8385
}
8486
}
8587

@@ -279,6 +281,19 @@ impl PrometheusBuilder {
279281
Ok(self)
280282
}
281283

284+
/// Sets whether a unit suffix is appended to metric names.
285+
///
286+
/// When this is enabled and the [`Unit`][metrics::Unit] of metric is
287+
/// given, then the exported metric name will be appended to according to
288+
/// the [Prometheus Best Practices](https://prometheus.io/docs/practices/naming/).
289+
///
290+
/// Defaults to false.
291+
#[must_use]
292+
pub fn set_enable_unit_suffix(mut self, enabled: bool) -> Self {
293+
self.enable_unit_suffix = enabled;
294+
self
295+
}
296+
282297
/// Sets the bucket for a specific pattern.
283298
///
284299
/// The match pattern can be a full match (equality), prefix match, or suffix match. The matchers are applied in
@@ -512,6 +527,7 @@ impl PrometheusBuilder {
512527
),
513528
descriptions: RwLock::new(HashMap::new()),
514529
global_labels: self.global_labels.unwrap_or_default(),
530+
enable_unit_suffix: self.enable_unit_suffix,
515531
};
516532

517533
PrometheusRecorder::from(inner)

metrics-exporter-prometheus/src/formatting.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Helpers for rendering metrics in the Prometheus exposition format.
22
33
use indexmap::IndexMap;
4-
use metrics::Key;
4+
use metrics::{Key, Unit};
55

66
/// Breaks a key into the name and label components, with optional default labels.
77
///
@@ -64,6 +64,7 @@ pub fn write_metric_line<T, T2>(
6464
labels: &[String],
6565
additional_label: Option<(&'static str, T)>,
6666
value: T2,
67+
unit: Option<Unit>,
6768
) where
6869
T: std::fmt::Display,
6970
T2: std::fmt::Display,
@@ -74,6 +75,18 @@ pub fn write_metric_line<T, T2>(
7475
buffer.push_str(suffix);
7576
}
7677

78+
match unit {
79+
Some(Unit::Count) | None => {}
80+
Some(Unit::Percent) => {
81+
buffer.push('_');
82+
buffer.push_str("ratio");
83+
}
84+
Some(unit) => {
85+
buffer.push('_');
86+
buffer.push_str(unit.as_str());
87+
}
88+
}
89+
7790
if !labels.is_empty() || additional_label.is_some() {
7891
buffer.push('{');
7992

metrics-exporter-prometheus/src/recorder.rs

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ pub(crate) struct Inner {
2121
pub recency: Recency<Key>,
2222
pub distributions: RwLock<HashMap<String, IndexMap<Vec<String>, Distribution>>>,
2323
pub distribution_builder: DistributionBuilder,
24-
pub descriptions: RwLock<HashMap<String, SharedString>>,
24+
pub descriptions: RwLock<HashMap<String, (SharedString, Option<Unit>)>>,
2525
pub global_labels: IndexMap<String, String>,
26+
pub enable_unit_suffix: bool,
2627
}
2728

2829
impl Inner {
@@ -116,33 +117,52 @@ impl Inner {
116117
let descriptions = self.descriptions.read().unwrap_or_else(PoisonError::into_inner);
117118

118119
for (name, mut by_labels) in counters.drain() {
119-
if let Some(desc) = descriptions.get(name.as_str()) {
120+
let unit = descriptions.get(name.as_str()).and_then(|(desc, unit)| {
120121
write_help_line(&mut output, name.as_str(), desc);
121-
}
122+
*unit
123+
});
122124

123125
write_type_line(&mut output, name.as_str(), "counter");
124126
for (labels, value) in by_labels.drain() {
125-
write_metric_line::<&str, u64>(&mut output, &name, None, &labels, None, value);
127+
write_metric_line::<&str, u64>(
128+
&mut output,
129+
&name,
130+
None,
131+
&labels,
132+
None,
133+
value,
134+
unit.filter(|_| self.enable_unit_suffix),
135+
);
126136
}
127137
output.push('\n');
128138
}
129139

130140
for (name, mut by_labels) in gauges.drain() {
131-
if let Some(desc) = descriptions.get(name.as_str()) {
141+
let unit = descriptions.get(name.as_str()).and_then(|(desc, unit)| {
132142
write_help_line(&mut output, name.as_str(), desc);
133-
}
143+
*unit
144+
});
134145

135146
write_type_line(&mut output, name.as_str(), "gauge");
136147
for (labels, value) in by_labels.drain() {
137-
write_metric_line::<&str, f64>(&mut output, &name, None, &labels, None, value);
148+
write_metric_line::<&str, f64>(
149+
&mut output,
150+
&name,
151+
None,
152+
&labels,
153+
None,
154+
value,
155+
unit.filter(|_| self.enable_unit_suffix),
156+
);
138157
}
139158
output.push('\n');
140159
}
141160

142161
for (name, mut by_labels) in distributions.drain() {
143-
if let Some(desc) = descriptions.get(name.as_str()) {
162+
let unit = descriptions.get(name.as_str()).and_then(|(desc, unit)| {
144163
write_help_line(&mut output, name.as_str(), desc);
145-
}
164+
*unit
165+
});
146166

147167
let distribution_type = self.distribution_builder.get_distribution_type(name.as_str());
148168
write_type_line(&mut output, name.as_str(), distribution_type);
@@ -159,6 +179,7 @@ impl Inner {
159179
&labels,
160180
Some(("quantile", quantile.value())),
161181
value,
182+
unit.filter(|_| self.enable_unit_suffix),
162183
);
163184
}
164185

@@ -173,6 +194,7 @@ impl Inner {
173194
&labels,
174195
Some(("le", le)),
175196
count,
197+
unit.filter(|_| self.enable_unit_suffix),
176198
);
177199
}
178200
write_metric_line(
@@ -182,20 +204,30 @@ impl Inner {
182204
&labels,
183205
Some(("le", "+Inf")),
184206
histogram.count(),
207+
unit.filter(|_| self.enable_unit_suffix),
185208
);
186209

187210
(histogram.sum(), histogram.count())
188211
}
189212
};
190213

191-
write_metric_line::<&str, f64>(&mut output, &name, Some("sum"), &labels, None, sum);
214+
write_metric_line::<&str, f64>(
215+
&mut output,
216+
&name,
217+
Some("sum"),
218+
&labels,
219+
None,
220+
sum,
221+
unit,
222+
);
192223
write_metric_line::<&str, u64>(
193224
&mut output,
194225
&name,
195226
Some("count"),
196227
&labels,
197228
None,
198229
count,
230+
unit,
199231
);
200232
}
201233

@@ -226,11 +258,16 @@ impl PrometheusRecorder {
226258
PrometheusHandle { inner: self.inner.clone() }
227259
}
228260

229-
fn add_description_if_missing(&self, key_name: &KeyName, description: SharedString) {
261+
fn add_description_if_missing(
262+
&self,
263+
key_name: &KeyName,
264+
description: SharedString,
265+
unit: Option<Unit>,
266+
) {
230267
let sanitized = sanitize_metric_name(key_name.as_str());
231268
let mut descriptions =
232269
self.inner.descriptions.write().unwrap_or_else(PoisonError::into_inner);
233-
descriptions.entry(sanitized).or_insert(description);
270+
descriptions.entry(sanitized).or_insert((description, unit));
234271
}
235272
}
236273

@@ -241,21 +278,16 @@ impl From<Inner> for PrometheusRecorder {
241278
}
242279

243280
impl Recorder for PrometheusRecorder {
244-
fn describe_counter(&self, key_name: KeyName, _unit: Option<Unit>, description: SharedString) {
245-
self.add_description_if_missing(&key_name, description);
281+
fn describe_counter(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
282+
self.add_description_if_missing(&key_name, description, unit);
246283
}
247284

248-
fn describe_gauge(&self, key_name: KeyName, _unit: Option<Unit>, description: SharedString) {
249-
self.add_description_if_missing(&key_name, description);
285+
fn describe_gauge(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
286+
self.add_description_if_missing(&key_name, description, unit);
250287
}
251288

252-
fn describe_histogram(
253-
&self,
254-
key_name: KeyName,
255-
_unit: Option<Unit>,
256-
description: SharedString,
257-
) {
258-
self.add_description_if_missing(&key_name, description);
289+
fn describe_histogram(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
290+
self.add_description_if_missing(&key_name, description, unit);
259291
}
260292

261293
fn register_counter(&self, key: &Key, _metadata: &Metadata<'_>) -> Counter {

0 commit comments

Comments
 (0)