Skip to content

Commit fa45c69

Browse files
authored
Prometheus: Add setting for recommended metric names (#596)
1 parent 5bf2aee commit fa45c69

File tree

3 files changed

+95
-25
lines changed

3 files changed

+95
-25
lines changed

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

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub struct PrometheusBuilder {
4848
upkeep_timeout: Duration,
4949
recency_mask: MetricKindMask,
5050
global_labels: Option<IndexMap<String, String>>,
51+
enable_recommended_naming: bool,
52+
/// TODO Remove this field in next version and merge with `enable_recommended_naming`
5153
enable_unit_suffix: bool,
5254
}
5355

@@ -81,6 +83,7 @@ impl PrometheusBuilder {
8183
upkeep_timeout,
8284
recency_mask: MetricKindMask::NONE,
8385
global_labels: None,
86+
enable_recommended_naming: false,
8487
enable_unit_suffix: false,
8588
}
8689
}
@@ -291,11 +294,27 @@ impl PrometheusBuilder {
291294
///
292295
/// Defaults to false.
293296
#[must_use]
297+
#[deprecated(
298+
since = "0.18.0",
299+
note = "users should prefer `with_recommended_naming` which automatically enables unit suffixes"
300+
)]
294301
pub fn set_enable_unit_suffix(mut self, enabled: bool) -> Self {
295302
self.enable_unit_suffix = enabled;
296303
self
297304
}
298305

306+
/// Enables Prometheus naming best practices for metrics.
307+
///
308+
/// When set to `true`, counter names are suffixed with `_total` and unit suffixes are appended to metric names,
309+
/// following [Prometheus Best Practices](https://prometheus.io/docs/practices/naming/).
310+
///
311+
/// Defaults to `false`.
312+
#[must_use]
313+
pub fn with_recommended_naming(mut self, enabled: bool) -> Self {
314+
self.enable_recommended_naming = enabled;
315+
self
316+
}
317+
299318
/// Sets the bucket for a specific pattern.
300319
///
301320
/// The match pattern can be a full match (equality), prefix match, or suffix match. The matchers are applied in
@@ -538,7 +557,8 @@ impl PrometheusBuilder {
538557
),
539558
descriptions: RwLock::new(HashMap::new()),
540559
global_labels: self.global_labels.unwrap_or_default(),
541-
enable_unit_suffix: self.enable_unit_suffix,
560+
enable_unit_suffix: self.enable_recommended_naming || self.enable_unit_suffix,
561+
counter_suffix: self.enable_recommended_naming.then_some("total"),
542562
};
543563

544564
PrometheusRecorder::from(inner)
@@ -558,7 +578,7 @@ mod tests {
558578

559579
use quanta::Clock;
560580

561-
use metrics::{Key, KeyName, Label, Recorder};
581+
use metrics::{Key, KeyName, Label, Recorder, Unit};
562582
use metrics_util::MetricKindMask;
563583

564584
use super::{Matcher, PrometheusBuilder};
@@ -610,6 +630,43 @@ mod tests {
610630
assert_eq!(rendered, expected_histogram);
611631
}
612632

633+
#[test]
634+
fn test_render_with_recommended_naming() {
635+
// test 1 - no unit or description
636+
let recorder = PrometheusBuilder::new().with_recommended_naming(true).build_recorder();
637+
638+
let key = Key::from_name("basic_counter");
639+
let counter1 = recorder.register_counter(&key, &METADATA);
640+
counter1.increment(42);
641+
642+
let handle = recorder.handle();
643+
let rendered = handle.render();
644+
let expected_counter = "# TYPE basic_counter counter\nbasic_counter_total 42\n\n";
645+
646+
assert_eq!(rendered, expected_counter);
647+
648+
// test 2 - with unit and description
649+
// Note: we need to create a new recorder, as the render order is not deterministic
650+
let recorder = PrometheusBuilder::new().with_recommended_naming(true).build_recorder();
651+
652+
let key_name = KeyName::from_const_str("counter_with_unit");
653+
let key = Key::from_name(key_name.clone());
654+
recorder.describe_counter(key_name, Some(Unit::Bytes), "A counter with a unit".into());
655+
let counter2 = recorder.register_counter(&key, &METADATA);
656+
counter2.increment(42);
657+
658+
let handle = recorder.handle();
659+
let rendered = handle.render();
660+
let expected_counter = concat!(
661+
"# HELP counter_with_unit_bytes A counter with a unit\n",
662+
"# TYPE counter_with_unit_bytes counter\n",
663+
"counter_with_unit_bytes_total 42\n",
664+
"\n",
665+
);
666+
667+
assert_eq!(rendered, expected_counter);
668+
}
669+
613670
#[test]
614671
fn test_buckets() {
615672
const DEFAULT_VALUES: [f64; 3] = [10.0, 100.0, 1000.0];

metrics-exporter-prometheus/src/formatting.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ pub fn key_to_parts(
2929
/// Writes a help (description) line in the Prometheus [exposition format].
3030
///
3131
/// [exposition format]: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-format-details
32-
pub fn write_help_line(buffer: &mut String, name: &str, desc: &str) {
32+
pub fn write_help_line(buffer: &mut String, name: &str, unit: Option<Unit>, desc: &str) {
3333
buffer.push_str("# HELP ");
3434
buffer.push_str(name);
35+
if let Some(unit) = unit {
36+
buffer.push('_');
37+
buffer.push_str(unit.as_str());
38+
}
3539
buffer.push(' ');
3640
let desc = sanitize_description(desc);
3741
buffer.push_str(&desc);
@@ -41,9 +45,13 @@ pub fn write_help_line(buffer: &mut String, name: &str, desc: &str) {
4145
/// Writes a metric type line in the Prometheus [exposition format].
4246
///
4347
/// [exposition format]: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-format-details
44-
pub fn write_type_line(buffer: &mut String, name: &str, metric_type: &str) {
48+
pub fn write_type_line(buffer: &mut String, name: &str, unit: Option<Unit>, metric_type: &str) {
4549
buffer.push_str("# TYPE ");
4650
buffer.push_str(name);
51+
if let Some(unit) = unit {
52+
buffer.push('_');
53+
buffer.push_str(unit.as_str());
54+
}
4755
buffer.push(' ');
4856
buffer.push_str(metric_type);
4957
buffer.push('\n');
@@ -70,17 +78,18 @@ pub fn write_metric_line<T, T2>(
7078
T2: std::fmt::Display,
7179
{
7280
buffer.push_str(name);
73-
if let Some(suffix) = suffix {
74-
buffer.push('_');
75-
buffer.push_str(suffix);
76-
}
7781

7882
match unit {
7983
Some(Unit::Count) | None => {}
8084
Some(Unit::Percent) => add_unit(buffer, "ratio"),
8185
Some(unit) => add_unit(buffer, unit.as_str()),
8286
}
8387

88+
if let Some(suffix) = suffix {
89+
buffer.push('_');
90+
buffer.push_str(suffix);
91+
}
92+
8493
if !labels.is_empty() || additional_label.is_some() {
8594
buffer.push('{');
8695

metrics-exporter-prometheus/src/recorder.rs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub(crate) struct Inner {
2424
pub descriptions: RwLock<HashMap<String, (SharedString, Option<Unit>)>>,
2525
pub global_labels: IndexMap<String, String>,
2626
pub enable_unit_suffix: bool,
27+
pub counter_suffix: Option<&'static str>,
2728
}
2829

2930
impl Inner {
@@ -118,32 +119,34 @@ impl Inner {
118119

119120
for (name, mut by_labels) in counters.drain() {
120121
let unit = descriptions.get(name.as_str()).and_then(|(desc, unit)| {
121-
write_help_line(&mut output, name.as_str(), desc);
122-
*unit
122+
let unit = unit.filter(|_| self.enable_unit_suffix);
123+
write_help_line(&mut output, name.as_str(), unit, desc);
124+
unit
123125
});
124126

125-
write_type_line(&mut output, name.as_str(), "counter");
127+
write_type_line(&mut output, name.as_str(), unit, "counter");
126128
for (labels, value) in by_labels.drain() {
127129
write_metric_line::<&str, u64>(
128130
&mut output,
129131
&name,
130-
None,
132+
self.counter_suffix,
131133
&labels,
132134
None,
133135
value,
134-
unit.filter(|_| self.enable_unit_suffix),
136+
unit,
135137
);
136138
}
137139
output.push('\n');
138140
}
139141

140142
for (name, mut by_labels) in gauges.drain() {
141143
let unit = descriptions.get(name.as_str()).and_then(|(desc, unit)| {
142-
write_help_line(&mut output, name.as_str(), desc);
143-
*unit
144+
let unit = unit.filter(|_| self.enable_unit_suffix);
145+
write_help_line(&mut output, name.as_str(), unit, desc);
146+
unit
144147
});
145148

146-
write_type_line(&mut output, name.as_str(), "gauge");
149+
write_type_line(&mut output, name.as_str(), unit, "gauge");
147150
for (labels, value) in by_labels.drain() {
148151
write_metric_line::<&str, f64>(
149152
&mut output,
@@ -152,20 +155,21 @@ impl Inner {
152155
&labels,
153156
None,
154157
value,
155-
unit.filter(|_| self.enable_unit_suffix),
158+
unit,
156159
);
157160
}
158161
output.push('\n');
159162
}
160163

161164
for (name, mut by_labels) in distributions.drain() {
162165
let unit = descriptions.get(name.as_str()).and_then(|(desc, unit)| {
163-
write_help_line(&mut output, name.as_str(), desc);
164-
*unit
166+
let unit = unit.filter(|_| self.enable_unit_suffix);
167+
write_help_line(&mut output, name.as_str(), unit, desc);
168+
unit
165169
});
166170

167171
let distribution_type = self.distribution_builder.get_distribution_type(name.as_str());
168-
write_type_line(&mut output, name.as_str(), distribution_type);
172+
write_type_line(&mut output, name.as_str(), unit, distribution_type);
169173
for (labels, distribution) in by_labels.drain(..) {
170174
let (sum, count) = match distribution {
171175
Distribution::Summary(summary, quantiles, sum) => {
@@ -179,7 +183,7 @@ impl Inner {
179183
&labels,
180184
Some(("quantile", quantile.value())),
181185
value,
182-
unit.filter(|_| self.enable_unit_suffix),
186+
unit,
183187
);
184188
}
185189

@@ -194,7 +198,7 @@ impl Inner {
194198
&labels,
195199
Some(("le", le)),
196200
count,
197-
unit.filter(|_| self.enable_unit_suffix),
201+
unit,
198202
);
199203
}
200204
write_metric_line(
@@ -204,7 +208,7 @@ impl Inner {
204208
&labels,
205209
Some(("le", "+Inf")),
206210
histogram.count(),
207-
unit.filter(|_| self.enable_unit_suffix),
211+
unit,
208212
);
209213

210214
(histogram.sum(), histogram.count())
@@ -218,7 +222,7 @@ impl Inner {
218222
&labels,
219223
None,
220224
sum,
221-
unit.filter(|_| self.enable_unit_suffix),
225+
unit,
222226
);
223227
write_metric_line::<&str, u64>(
224228
&mut output,
@@ -227,7 +231,7 @@ impl Inner {
227231
&labels,
228232
None,
229233
count,
230-
unit.filter(|_| self.enable_unit_suffix),
234+
unit,
231235
);
232236
}
233237

0 commit comments

Comments
 (0)