Skip to content

Commit b1488cd

Browse files
authored
[EXPORTER] Add config options to prometheus exporter (open-telemetry#3104)
1 parent 1185405 commit b1488cd

File tree

5 files changed

+169
-30
lines changed

5 files changed

+169
-30
lines changed

exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_options.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ struct PrometheusExporterOptions
2828

2929
// Populating otel_scope_name/otel_scope_labels attributes
3030
bool without_otel_scope = false;
31+
32+
// Option to export metrics without the unit suffix
33+
bool without_units = false;
34+
35+
// Option to export metrics without the type suffix
36+
bool without_type_suffix = false;
3137
};
3238

3339
} // namespace metrics

exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@ class PrometheusExporterUtils
3030
* @param populate_target_info whether to populate target_info
3131
* @param without_otel_scope whether to populate otel_scope_name and otel_scope_version
3232
* attributes
33+
* @param without_units exporter configuration controlling whether to append unit suffix in
34+
* the exported metrics.
35+
* @param without_type_suffix exporter configuration controlling whether to append type suffix in
36+
* the exported metrics.
3337
* @return a collection of translated metrics that is acceptable by Prometheus
3438
*/
3539
static std::vector<::prometheus::MetricFamily> TranslateToPrometheus(
3640
const sdk::metrics::ResourceMetrics &data,
3741
bool populate_target_info = true,
38-
bool without_otel_scope = false);
42+
bool without_otel_scope = false,
43+
bool without_units = false,
44+
bool without_type_suffix = false);
3945

4046
private:
4147
/**
@@ -61,7 +67,9 @@ class PrometheusExporterUtils
6167

6268
static std::string MapToPrometheusName(const std::string &name,
6369
const std::string &unit,
64-
::prometheus::MetricType prometheus_type);
70+
::prometheus::MetricType prometheus_type,
71+
bool without_units,
72+
bool without_type_suffix);
6573

6674
/**
6775
* A utility function that returns the equivalent Prometheus name for the provided OTLP metric

exporters/prometheus/src/exporter_options.cc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,33 @@ inline bool GetPrometheusPopulateTargetInfo()
4848
return exists ? setting : true;
4949
}
5050

51+
inline bool GetPrometheusWithoutUnits()
52+
{
53+
constexpr char kPrometheusWithoutUnits[] = "OTEL_CPP_PROMETHEUS_EXPORTER_WITHOUT_UNITS";
54+
bool setting;
55+
const auto exists =
56+
opentelemetry::sdk::common::GetBoolEnvironmentVariable(kPrometheusWithoutUnits, setting);
57+
58+
return exists ? setting : false;
59+
}
60+
61+
inline bool GetPrometheusWithoutTypeSuffix()
62+
{
63+
constexpr char kPrometheusWithoutTypeSuffix[] =
64+
"OTEL_CPP_PROMETHEUS_EXPORTER_WITHOUT_TYPE_SUFFIX";
65+
bool setting;
66+
const auto exists =
67+
opentelemetry::sdk::common::GetBoolEnvironmentVariable(kPrometheusWithoutTypeSuffix, setting);
68+
69+
return exists ? setting : false;
70+
}
71+
5172
PrometheusExporterOptions::PrometheusExporterOptions()
5273
: url(GetPrometheusDefaultHttpEndpoint()),
5374
populate_target_info(GetPrometheusPopulateTargetInfo()),
54-
without_otel_scope(GetPrometheusWithoutOtelScope())
75+
without_otel_scope(GetPrometheusWithoutOtelScope()),
76+
without_units(GetPrometheusWithoutUnits()),
77+
without_type_suffix(GetPrometheusWithoutTypeSuffix())
5578
{}
5679

5780
} // namespace metrics

exporters/prometheus/src/exporter_utils.cc

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,15 @@ std::string SanitizeLabel(std::string label_key)
102102
* Helper function to convert OpenTelemetry metrics data collection
103103
* to Prometheus metrics data collection
104104
*
105-
* @param records a collection of metrics in OpenTelemetry
105+
* @param data a collection of metrics in OpenTelemetry
106106
* @return a collection of translated metrics that is acceptable by Prometheus
107107
*/
108108
std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateToPrometheus(
109109
const sdk::metrics::ResourceMetrics &data,
110110
bool populate_target_info,
111-
bool without_otel_scope)
111+
bool without_otel_scope,
112+
bool without_units,
113+
bool without_type_suffix)
112114
{
113115

114116
// initialize output vector
@@ -150,7 +152,8 @@ std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateT
150152
}
151153
const prometheus_client::MetricType type = TranslateType(kind, is_monotonic);
152154
metric_family.name = MapToPrometheusName(metric_data.instrument_descriptor.name_,
153-
metric_data.instrument_descriptor.unit_, type);
155+
metric_data.instrument_descriptor.unit_, type,
156+
without_units, without_type_suffix);
154157
metric_family.type = type;
155158
const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope =
156159
without_otel_scope ? nullptr : instrumentation_info.scope_;
@@ -492,34 +495,43 @@ std::string PrometheusExporterUtils::CleanUpString(const std::string &str)
492495
std::string PrometheusExporterUtils::MapToPrometheusName(
493496
const std::string &name,
494497
const std::string &unit,
495-
prometheus_client::MetricType prometheus_type)
498+
prometheus_client::MetricType prometheus_type,
499+
bool without_units,
500+
bool without_type_suffix)
496501
{
497-
auto sanitized_name = SanitizeNames(name);
498-
std::string prometheus_equivalent_unit = GetEquivalentPrometheusUnit(unit);
499-
500-
// Append prometheus unit if not null or empty.
501-
if (!prometheus_equivalent_unit.empty() &&
502-
sanitized_name.find(prometheus_equivalent_unit) == std::string::npos)
503-
{
504-
sanitized_name += "_" + prometheus_equivalent_unit;
505-
}
506-
507-
// Special case - counter
508-
if (prometheus_type == prometheus_client::MetricType::Counter)
509-
{
510-
auto t_pos = sanitized_name.rfind("_total");
511-
bool ends_with_total = t_pos == sanitized_name.size() - 6;
512-
if (!ends_with_total)
502+
auto sanitized_name = SanitizeNames(name);
503+
// append unit suffixes
504+
if (!without_units)
505+
{
506+
std::string prometheus_equivalent_unit = GetEquivalentPrometheusUnit(unit);
507+
// Append prometheus unit if not null or empty.
508+
if (!prometheus_equivalent_unit.empty() &&
509+
sanitized_name.find(prometheus_equivalent_unit) == std::string::npos)
513510
{
514-
sanitized_name += "_total";
511+
sanitized_name += "_" + prometheus_equivalent_unit;
512+
}
513+
// Special case - gauge
514+
if (unit == "1" && prometheus_type == prometheus_client::MetricType::Gauge &&
515+
sanitized_name.find("ratio") == std::string::npos)
516+
{
517+
// this is replacing the unit name
518+
sanitized_name += "_ratio";
515519
}
516520
}
517521

518-
// Special case - gauge
519-
if (unit == "1" && prometheus_type == prometheus_client::MetricType::Gauge &&
520-
sanitized_name.find("ratio") == std::string::npos)
522+
// append type suffixes
523+
if (!without_type_suffix)
521524
{
522-
sanitized_name += "_ratio";
525+
// Special case - counter
526+
if (prometheus_type == prometheus_client::MetricType::Counter)
527+
{
528+
auto t_pos = sanitized_name.rfind("_total");
529+
bool ends_with_total = t_pos == sanitized_name.size() - 6;
530+
if (!ends_with_total)
531+
{
532+
sanitized_name += "_total";
533+
}
534+
}
523535
}
524536

525537
return CleanUpString(SanitizeNames(sanitized_name));

exporters/prometheus/test/exporter_utils_test.cc

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ class SanitizeNameTester
5454
}
5555
static std::string mapToPrometheusName(const std::string &name,
5656
const std::string &unit,
57-
prometheus_client::MetricType prometheus_type)
57+
prometheus_client::MetricType prometheus_type,
58+
bool without_units = false,
59+
bool without_type_suffix = false)
5860
{
59-
return PrometheusExporterUtils::MapToPrometheusName(name, unit, prometheus_type);
61+
return PrometheusExporterUtils::MapToPrometheusName(name, unit, prometheus_type, without_units,
62+
without_type_suffix);
6063
}
6164
};
6265
} // namespace metrics
@@ -419,6 +422,93 @@ TEST(PrometheusExporterUtils, ConvertRateExpressedToPrometheusUnit)
419422
"_per_minute");
420423
}
421424

425+
TEST(PromentheusExporterUtils, PrometheusNameMapping)
426+
{
427+
// General test cases on unit expansions and name sanitization
428+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
429+
"sample_metric___name", "g", prometheus::MetricType::Counter),
430+
"sample_metric_name_grams_total");
431+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
432+
"sample_metric_name", "s", prometheus::MetricType::Counter),
433+
"sample_metric_name_seconds_total");
434+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
435+
"sample_metric_name", "s", prometheus::MetricType::Gauge),
436+
"sample_metric_name_seconds");
437+
// Test without_units & without_type_suffix with Counters and unit = 1
438+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
439+
"sample_metric_name", "1", prometheus::MetricType::Counter),
440+
"sample_metric_name_total");
441+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
442+
"sample_metric_name", "1", prometheus::MetricType::Counter, true, false),
443+
"sample_metric_name_total");
444+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
445+
"sample_metric_name", "1", prometheus::MetricType::Counter, false, true),
446+
"sample_metric_name");
447+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
448+
"sample_metric_name", "1", prometheus::MetricType::Counter, true, true),
449+
"sample_metric_name");
450+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
451+
"sample_metric_name", "1", prometheus::MetricType::Counter, true, true),
452+
"sample_metric_name");
453+
// Test without_units & without_type_suffix with Counters and non-special units
454+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
455+
"sample_metric_name", "%", prometheus::MetricType::Counter),
456+
"sample_metric_name_percent_total");
457+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
458+
"sample_metric_name", "m", prometheus::MetricType::Counter, true, false),
459+
"sample_metric_name_total");
460+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
461+
"sample_metric_name", "By", prometheus::MetricType::Counter, false, true),
462+
"sample_metric_name_bytes");
463+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
464+
"sample_metric_name", "s", prometheus::MetricType::Counter, true, true),
465+
"sample_metric_name");
466+
// Special case Gauges & ratio
467+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
468+
"sample_metric_name", "1", prometheus::MetricType::Gauge),
469+
"sample_metric_name_ratio");
470+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
471+
"sample_metric_name", "1", prometheus::MetricType::Gauge, false, true),
472+
"sample_metric_name_ratio");
473+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
474+
"sample_metric_name", "1", prometheus::MetricType::Gauge, true, false),
475+
"sample_metric_name");
476+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
477+
"sample_metric_name", "1", prometheus::MetricType::Gauge, true, true),
478+
"sample_metric_name");
479+
// Test without_type_suffix affects only counters
480+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
481+
"sample_metric_name", "Hz", prometheus::MetricType::Counter),
482+
"sample_metric_name_hertz_total");
483+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
484+
"sample_metric_name", "Hz", prometheus::MetricType::Counter, false, true),
485+
"sample_metric_name_hertz");
486+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
487+
"sample_metric_name", "Hz", prometheus::MetricType::Gauge),
488+
"sample_metric_name_hertz");
489+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
490+
"sample_metric_name", "Hz", prometheus::MetricType::Gauge, false, true),
491+
"sample_metric_name_hertz");
492+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
493+
"sample_metric_name", "Hz", prometheus::MetricType::Histogram),
494+
"sample_metric_name_hertz");
495+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
496+
"sample_metric_name", "Hz", prometheus::MetricType::Histogram, false, true),
497+
"sample_metric_name_hertz");
498+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
499+
"sample_metric_name", "Hz", prometheus::MetricType::Summary),
500+
"sample_metric_name_hertz");
501+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
502+
"sample_metric_name", "Hz", prometheus::MetricType::Summary, false, true),
503+
"sample_metric_name_hertz");
504+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
505+
"sample_metric_name", "Hz", prometheus::MetricType::Info),
506+
"sample_metric_name_hertz");
507+
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
508+
"sample_metric_name", "Hz", prometheus::MetricType::Info, false, true),
509+
"sample_metric_name_hertz");
510+
}
511+
422512
TEST_F(AttributeCollisionTest, JoinsCollidingKeys)
423513
{
424514
CheckTranslation({{"foo.a", "value1"}, {"foo_a", "value2"}}, {{"foo_a", "value1;value2"},

0 commit comments

Comments
 (0)