Skip to content

Commit

Permalink
prometheus: add without_counter_suffixes option (#1158)
Browse files Browse the repository at this point in the history
  • Loading branch information
jtescher authored Jul 19, 2023
1 parent 2c35c55 commit db1fedb
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 53 deletions.
1 change: 1 addition & 0 deletions opentelemetry-prometheus/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

- Add `with_namespace` option to exporter config.
- Add `without_counter_suffixes` option to exporter config.

## v0.12.0

Expand Down
14 changes: 14 additions & 0 deletions opentelemetry-prometheus/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct ExporterBuilder {
registry: Option<prometheus::Registry>,
disable_target_info: bool,
without_units: bool,
without_counter_suffixes: bool,
namespace: Option<String>,
aggregation: Option<Box<dyn AggregationSelector>>,
disable_scope_info: bool,
Expand All @@ -24,6 +25,7 @@ impl fmt::Debug for ExporterBuilder {
.field("registry", &self.registry)
.field("disable_target_info", &self.disable_target_info)
.field("without_units", &self.without_units)
.field("without_counter_suffixes", &self.without_counter_suffixes)
.field("namespace", &self.namespace)
.field("aggregation", &self.aggregation.is_some())
.field("disable_scope_info", &self.disable_scope_info)
Expand All @@ -44,6 +46,17 @@ impl ExporterBuilder {
self
}

/// Disables exporter's addition `_total` suffixes on counters.
///
/// By default, metric names include a `_total` suffix to follow Prometheus
/// naming conventions. For example, the counter metric `happy.people` would
/// become `happy_people_total`. With this option set, the name would instead be
/// `happy_people`.
pub fn without_counter_suffixes(mut self) -> Self {
self.without_counter_suffixes = true;
self
}

/// Configures the exporter to not export the resource `target_info` metric.
///
/// If not specified, the exporter will create a `target_info` metric containing
Expand Down Expand Up @@ -112,6 +125,7 @@ impl ExporterBuilder {
reader: Arc::clone(&reader),
disable_target_info: self.disable_target_info,
without_units: self.without_units,
without_counter_suffixes: self.without_counter_suffixes,
disable_scope_info: self.disable_scope_info,
create_target_info_once: OnceCell::new(),
namespace: self.namespace,
Expand Down
148 changes: 95 additions & 53 deletions opentelemetry-prometheus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
)]
#![cfg_attr(test, deny(warnings))]

use once_cell::sync::OnceCell;
use once_cell::sync::{Lazy, OnceCell};
use opentelemetry_api::{
global,
metrics::{MetricsError, Result, Unit},
Expand All @@ -112,6 +112,7 @@ use prometheus::{
proto::{LabelPair, MetricFamily, MetricType},
};
use std::{
any::TypeId,
borrow::Cow,
collections::{BTreeMap, HashMap},
sync::{Arc, Mutex},
Expand All @@ -126,8 +127,8 @@ const SCOPE_INFO_DESCRIPTION: &str = "Instrumentation Scope metadata";

const SCOPE_INFO_KEYS: [&str; 2] = ["otel_scope_name", "otel_scope_version"];

// prometheus counters MUST have a _total suffix:
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.19.0/specification/compatibility/prometheus_and_openmetrics.md?plain=1#L282
// prometheus counters MUST have a _total suffix by default:
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/compatibility/prometheus_and_openmetrics.md
const COUNTER_SUFFIX: &str = "_total";

mod config;
Expand Down Expand Up @@ -185,6 +186,7 @@ struct Collector {
reader: Arc<ManualReader>,
disable_target_info: bool,
without_units: bool,
without_counter_suffixes: bool,
disable_scope_info: bool,
create_target_info_once: OnceCell<MetricFamily>,
namespace: Option<String>,
Expand All @@ -197,7 +199,65 @@ struct CollectorInner {
metric_families: HashMap<String, MetricFamily>,
}

// TODO: Remove lazy and switch to pattern matching once `TypeId` is stable in
// const context: https://github.com/rust-lang/rust/issues/77125
static HISTOGRAM_TYPES: Lazy<[TypeId; 3]> = Lazy::new(|| {
[
TypeId::of::<data::Histogram<i64>>(),
TypeId::of::<data::Histogram<u64>>(),
TypeId::of::<data::Histogram<f64>>(),
]
});
static SUM_TYPES: Lazy<[TypeId; 3]> = Lazy::new(|| {
[
TypeId::of::<data::Sum<i64>>(),
TypeId::of::<data::Sum<u64>>(),
TypeId::of::<data::Sum<f64>>(),
]
});
static GAUGE_TYPES: Lazy<[TypeId; 3]> = Lazy::new(|| {
[
TypeId::of::<data::Gauge<i64>>(),
TypeId::of::<data::Gauge<u64>>(),
TypeId::of::<data::Gauge<f64>>(),
]
});

impl Collector {
fn metric_type_and_name(&self, m: &data::Metric) -> Option<(MetricType, Cow<'static, str>)> {
let mut name = self.get_name(m);

let data = m.data.as_any();
let type_id = data.type_id();

if HISTOGRAM_TYPES.contains(&type_id) {
Some((MetricType::HISTOGRAM, name))
} else if GAUGE_TYPES.contains(&type_id) {
Some((MetricType::GAUGE, name))
} else if SUM_TYPES.contains(&type_id) {
let is_monotonic = if let Some(v) = data.downcast_ref::<data::Sum<i64>>() {
v.is_monotonic
} else if let Some(v) = data.downcast_ref::<data::Sum<u64>>() {
v.is_monotonic
} else if let Some(v) = data.downcast_ref::<data::Sum<f64>>() {
v.is_monotonic
} else {
false
};

if is_monotonic {
if !self.without_counter_suffixes {
name = format!("{name}{COUNTER_SUFFIX}").into();
}
Some((MetricType::COUNTER, name))
} else {
Some((MetricType::GAUGE, name))
}
} else {
None
}
}

fn get_name(&self, m: &data::Metric) -> Cow<'static, str> {
let name = sanitize_name(&m.name);
match (
Expand Down Expand Up @@ -271,28 +331,38 @@ impl prometheus::core::Collector for Collector {
};

for metrics in scope_metrics.metrics {
let name = self.get_name(&metrics);
let description = metrics.description;
let data = metrics.data.as_any();
let (metric_type, name) = match self.metric_type_and_name(&metrics) {
Some((metric_type, name)) => (metric_type, name),
_ => continue,
};

let mfs = &mut inner.metric_families;
let (drop, help) = validate_metrics(&name, &metrics.description, metric_type, mfs);
if drop {
continue;
}

let description = help.unwrap_or_else(|| metrics.description.into());
let data = metrics.data.as_any();

if let Some(hist) = data.downcast_ref::<data::Histogram<i64>>() {
add_histogram_metric(&mut res, hist, description, &scope_labels, name, mfs);
add_histogram_metric(&mut res, hist, description, &scope_labels, name);
} else if let Some(hist) = data.downcast_ref::<data::Histogram<u64>>() {
add_histogram_metric(&mut res, hist, description, &scope_labels, name, mfs);
add_histogram_metric(&mut res, hist, description, &scope_labels, name);
} else if let Some(hist) = data.downcast_ref::<data::Histogram<f64>>() {
add_histogram_metric(&mut res, hist, description, &scope_labels, name, mfs);
add_histogram_metric(&mut res, hist, description, &scope_labels, name);
} else if let Some(sum) = data.downcast_ref::<data::Sum<u64>>() {
add_sum_metric(&mut res, sum, description, &scope_labels, name, mfs);
add_sum_metric(&mut res, sum, description, &scope_labels, name);
} else if let Some(sum) = data.downcast_ref::<data::Sum<i64>>() {
add_sum_metric(&mut res, sum, description, &scope_labels, name, mfs);
add_sum_metric(&mut res, sum, description, &scope_labels, name);
} else if let Some(sum) = data.downcast_ref::<data::Sum<f64>>() {
add_sum_metric(&mut res, sum, description, &scope_labels, name, mfs);
add_sum_metric(&mut res, sum, description, &scope_labels, name);
} else if let Some(g) = data.downcast_ref::<data::Gauge<u64>>() {
add_gauge_metric(&mut res, g, description, &scope_labels, name, mfs);
add_gauge_metric(&mut res, g, description, &scope_labels, name);
} else if let Some(g) = data.downcast_ref::<data::Gauge<i64>>() {
add_gauge_metric(&mut res, g, description, &scope_labels, name, mfs);
add_gauge_metric(&mut res, g, description, &scope_labels, name);
} else if let Some(g) = data.downcast_ref::<data::Gauge<f64>>() {
add_gauge_metric(&mut res, g, description, &scope_labels, name, mfs);
add_gauge_metric(&mut res, g, description, &scope_labels, name);
}
}
}
Expand Down Expand Up @@ -363,20 +433,12 @@ fn validate_metrics(
fn add_histogram_metric<T: Numeric>(
res: &mut Vec<MetricFamily>,
histogram: &data::Histogram<T>,
mut description: Cow<'static, str>,
description: String,
extra: &[LabelPair],
name: Cow<'static, str>,
mfs: &mut HashMap<String, MetricFamily>,
) {
// Consider supporting exemplars when `prometheus` crate has the feature
// See: https://github.com/tikv/rust-prometheus/issues/393
let (drop, help) = validate_metrics(&name, &description, MetricType::HISTOGRAM, mfs);
if drop {
return;
}
if let Some(help) = help {
description = help.into();
}

for dp in &histogram.data_points {
let kvs = get_attrs(&mut dp.attributes.iter(), extra);
Expand Down Expand Up @@ -404,7 +466,7 @@ fn add_histogram_metric<T: Numeric>(

let mut mf = prometheus::proto::MetricFamily::default();
mf.set_name(name.to_string());
mf.set_help(description.to_string());
mf.set_help(description.clone());
mf.set_field_type(prometheus::proto::MetricType::HISTOGRAM);
mf.set_metric(protobuf::RepeatedField::from_vec(vec![pm]));
res.push(mf);
Expand All @@ -414,26 +476,15 @@ fn add_histogram_metric<T: Numeric>(
fn add_sum_metric<T: Numeric>(
res: &mut Vec<MetricFamily>,
sum: &data::Sum<T>,
mut description: Cow<'static, str>,
description: String,
extra: &[LabelPair],
mut name: Cow<'static, str>,
mfs: &mut HashMap<String, MetricFamily>,
name: Cow<'static, str>,
) {
let metric_type;
if sum.is_monotonic {
name = format!("{name}{COUNTER_SUFFIX}").into();
metric_type = MetricType::COUNTER;
let metric_type = if sum.is_monotonic {
MetricType::COUNTER
} else {
metric_type = MetricType::GAUGE;
}

let (drop, help) = validate_metrics(&name, &description, metric_type, mfs);
if drop {
return;
}
if let Some(help) = help {
description = help.into();
}
MetricType::GAUGE
};

for dp in &sum.data_points {
let kvs = get_attrs(&mut dp.attributes.iter(), extra);
Expand All @@ -453,7 +504,7 @@ fn add_sum_metric<T: Numeric>(

let mut mf = prometheus::proto::MetricFamily::default();
mf.set_name(name.to_string());
mf.set_help(description.to_string());
mf.set_help(description.clone());
mf.set_field_type(metric_type);
mf.set_metric(protobuf::RepeatedField::from_vec(vec![pm]));
res.push(mf);
Expand All @@ -463,19 +514,10 @@ fn add_sum_metric<T: Numeric>(
fn add_gauge_metric<T: Numeric>(
res: &mut Vec<MetricFamily>,
gauge: &data::Gauge<T>,
mut description: Cow<'static, str>,
description: String,
extra: &[LabelPair],
name: Cow<'static, str>,
mfs: &mut HashMap<String, MetricFamily>,
) {
let (drop, help) = validate_metrics(&name, &description, MetricType::GAUGE, mfs);
if drop {
return;
}
if let Some(help) = help {
description = help.into();
}

for dp in &gauge.data_points {
let kvs = get_attrs(&mut dp.attributes.iter(), extra);

Expand Down
10 changes: 10 additions & 0 deletions opentelemetry-prometheus/tests/data/counter_disabled_suffix.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# HELP foo_milliseconds a simple counter without a total suffix
# TYPE foo_milliseconds counter
foo_milliseconds{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3
foo_milliseconds{A="D",C="B",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 5
# HELP otel_scope_info Instrumentation Scope metadata
# TYPE otel_scope_info gauge
otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1
# HELP target_info Target metadata
# TYPE target_info gauge
target_info{service_name="prometheus_test",telemetry_sdk_language="rust",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
29 changes: 29 additions & 0 deletions opentelemetry-prometheus/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,35 @@ fn prometheus_exporter_integration() {
}),
..Default::default()
},
TestCase {
name: "counter with suffixes disabled",
expected_file: "counter_disabled_suffix.txt",
builder: ExporterBuilder::default().without_counter_suffixes(),
record_metrics: Box::new(|meter| {
let attrs = vec![
Key::new("A").string("B"),
Key::new("C").string("D"),
Key::new("E").bool(true),
Key::new("F").i64(42),
];
let counter = meter
.f64_counter("foo")
.with_description("a simple counter without a total suffix")
.with_unit(Unit::new("ms"))
.init();
counter.add(5.0, &attrs);
counter.add(10.3, &attrs);
counter.add(9.0, &attrs);
let attrs2 = vec![
Key::new("A").string("D"),
Key::new("C").string("B"),
Key::new("E").bool(true),
Key::new("F").i64(42),
];
counter.add(5.0, &attrs2);
}),
..Default::default()
},
TestCase {
name: "gauge",
expected_file: "gauge.txt",
Expand Down

0 comments on commit db1fedb

Please sign in to comment.