Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EXPORTER] Add config options to prometheus exporter #3104

Merged
merged 7 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ struct PrometheusExporterOptions

// Populating otel_scope_name/otel_scope_labels attributes
bool without_otel_scope = false;

// Option to export metrics without the unit suffix
bool without_units = false;

// Option to export metrics without the type suffix
bool without_type_suffix = false;
};

} // namespace metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ class PrometheusExporterUtils
* @param populate_target_info whether to populate target_info
* @param without_otel_scope whether to populate otel_scope_name and otel_scope_version
* attributes
* @param without_units exporter configuration controlling whether to append unit suffix in
* the exported metrics.
* @param without_type_suffix exporter configuration controlling whether to append type suffix in
* the exported metrics.
* @return a collection of translated metrics that is acceptable by Prometheus
*/
static std::vector<::prometheus::MetricFamily> TranslateToPrometheus(
const sdk::metrics::ResourceMetrics &data,
bool populate_target_info = true,
bool without_otel_scope = false);
bool without_otel_scope = false,
bool without_units = false,
bool without_type_suffix = false);

private:
/**
Expand All @@ -61,7 +67,9 @@ class PrometheusExporterUtils

static std::string MapToPrometheusName(const std::string &name,
const std::string &unit,
::prometheus::MetricType prometheus_type);
::prometheus::MetricType prometheus_type,
bool without_units,
bool without_type_suffix);

/**
* A utility function that returns the equivalent Prometheus name for the provided OTLP metric
Expand Down
25 changes: 24 additions & 1 deletion exporters/prometheus/src/exporter_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,33 @@ inline bool GetPrometheusPopulateTargetInfo()
return exists ? setting : true;
}

inline bool GetPrometheusWithoutUnits()
{
constexpr char kPrometheusWithoutUnits[] = "OTEL_CPP_PROMETHEUS_EXPORTER_WITHOUT_UNITS";
bool setting;
const auto exists =
opentelemetry::sdk::common::GetBoolEnvironmentVariable(kPrometheusWithoutUnits, setting);

return exists ? setting : false;
}

inline bool GetPrometheusWithoutTypeSuffix()
{
constexpr char kPrometheusWithoutTypeSuffix[] =
"OTEL_CPP_PROMETHEUS_EXPORTER_WITHOUT_TYPE_SUFFIX";
bool setting;
const auto exists =
opentelemetry::sdk::common::GetBoolEnvironmentVariable(kPrometheusWithoutTypeSuffix, setting);

return exists ? setting : false;
}

PrometheusExporterOptions::PrometheusExporterOptions()
: url(GetPrometheusDefaultHttpEndpoint()),
populate_target_info(GetPrometheusPopulateTargetInfo()),
without_otel_scope(GetPrometheusWithoutOtelScope())
without_otel_scope(GetPrometheusWithoutOtelScope()),
without_units(GetPrometheusWithoutUnits()),
without_type_suffix(GetPrometheusWithoutTypeSuffix())
{}

} // namespace metrics
Expand Down
62 changes: 37 additions & 25 deletions exporters/prometheus/src/exporter_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,15 @@ std::string SanitizeLabel(std::string label_key)
* Helper function to convert OpenTelemetry metrics data collection
* to Prometheus metrics data collection
*
* @param records a collection of metrics in OpenTelemetry
* @param data a collection of metrics in OpenTelemetry
* @return a collection of translated metrics that is acceptable by Prometheus
*/
std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateToPrometheus(
const sdk::metrics::ResourceMetrics &data,
bool populate_target_info,
bool without_otel_scope)
bool without_otel_scope,
bool without_units,
bool without_type_suffix)
{

// initialize output vector
Expand Down Expand Up @@ -150,7 +152,8 @@ std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateT
}
const prometheus_client::MetricType type = TranslateType(kind, is_monotonic);
metric_family.name = MapToPrometheusName(metric_data.instrument_descriptor.name_,
metric_data.instrument_descriptor.unit_, type);
metric_data.instrument_descriptor.unit_, type,
without_units, without_type_suffix);
metric_family.type = type;
const opentelemetry::sdk::instrumentationscope::InstrumentationScope *scope =
without_otel_scope ? nullptr : instrumentation_info.scope_;
Expand Down Expand Up @@ -492,34 +495,43 @@ std::string PrometheusExporterUtils::CleanUpString(const std::string &str)
std::string PrometheusExporterUtils::MapToPrometheusName(
const std::string &name,
const std::string &unit,
prometheus_client::MetricType prometheus_type)
prometheus_client::MetricType prometheus_type,
bool without_units,
bool without_type_suffix)
{
auto sanitized_name = SanitizeNames(name);
std::string prometheus_equivalent_unit = GetEquivalentPrometheusUnit(unit);

// Append prometheus unit if not null or empty.
if (!prometheus_equivalent_unit.empty() &&
sanitized_name.find(prometheus_equivalent_unit) == std::string::npos)
{
sanitized_name += "_" + prometheus_equivalent_unit;
}

// Special case - counter
if (prometheus_type == prometheus_client::MetricType::Counter)
{
auto t_pos = sanitized_name.rfind("_total");
bool ends_with_total = t_pos == sanitized_name.size() - 6;
if (!ends_with_total)
auto sanitized_name = SanitizeNames(name);
// append unit suffixes
if (!without_units)
{
std::string prometheus_equivalent_unit = GetEquivalentPrometheusUnit(unit);
// Append prometheus unit if not null or empty.
if (!prometheus_equivalent_unit.empty() &&
sanitized_name.find(prometheus_equivalent_unit) == std::string::npos)
{
sanitized_name += "_total";
sanitized_name += "_" + prometheus_equivalent_unit;
}
// Special case - gauge
if (unit == "1" && prometheus_type == prometheus_client::MetricType::Gauge &&
sanitized_name.find("ratio") == std::string::npos)
{
// this is replacing the unit name
sanitized_name += "_ratio";
}
}

// Special case - gauge
if (unit == "1" && prometheus_type == prometheus_client::MetricType::Gauge &&
sanitized_name.find("ratio") == std::string::npos)
// append type suffixes
if (!without_type_suffix)
{
sanitized_name += "_ratio";
// Special case - counter
if (prometheus_type == prometheus_client::MetricType::Counter)
{
auto t_pos = sanitized_name.rfind("_total");
bool ends_with_total = t_pos == sanitized_name.size() - 6;
if (!ends_with_total)
{
sanitized_name += "_total";
}
}
}

return CleanUpString(SanitizeNames(sanitized_name));
Expand Down
94 changes: 92 additions & 2 deletions exporters/prometheus/test/exporter_utils_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ class SanitizeNameTester
}
static std::string mapToPrometheusName(const std::string &name,
const std::string &unit,
prometheus_client::MetricType prometheus_type)
prometheus_client::MetricType prometheus_type,
bool without_units = false,
bool without_type_suffix = false)
{
return PrometheusExporterUtils::MapToPrometheusName(name, unit, prometheus_type);
return PrometheusExporterUtils::MapToPrometheusName(name, unit, prometheus_type, without_units,
without_type_suffix);
}
};
} // namespace metrics
Expand Down Expand Up @@ -419,6 +422,93 @@ TEST(PrometheusExporterUtils, ConvertRateExpressedToPrometheusUnit)
"_per_minute");
}

TEST(PromentheusExporterUtils, PrometheusNameMapping)
{
// General test cases on unit expansions and name sanitization
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric___name", "g", prometheus::MetricType::Counter),
"sample_metric_name_grams_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "s", prometheus::MetricType::Counter),
"sample_metric_name_seconds_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "s", prometheus::MetricType::Gauge),
"sample_metric_name_seconds");
// Test without_units & without_type_suffix with Counters and unit = 1
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Counter),
"sample_metric_name_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Counter, true, false),
"sample_metric_name_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Counter, false, true),
"sample_metric_name");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Counter, true, true),
"sample_metric_name");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Counter, true, true),
"sample_metric_name");
// Test without_units & without_type_suffix with Counters and non-special units
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "%", prometheus::MetricType::Counter),
"sample_metric_name_percent_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "m", prometheus::MetricType::Counter, true, false),
"sample_metric_name_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "By", prometheus::MetricType::Counter, false, true),
"sample_metric_name_bytes");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "s", prometheus::MetricType::Counter, true, true),
"sample_metric_name");
// Special case Gauges & ratio
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Gauge),
"sample_metric_name_ratio");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Gauge, false, true),
"sample_metric_name_ratio");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Gauge, true, false),
"sample_metric_name");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "1", prometheus::MetricType::Gauge, true, true),
"sample_metric_name");
// Test without_type_suffix affects only counters
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Counter),
"sample_metric_name_hertz_total");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Counter, false, true),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Gauge),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Gauge, false, true),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Histogram),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Histogram, false, true),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Summary),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Summary, false, true),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Info),
"sample_metric_name_hertz");
ASSERT_EQ(exporter::metrics::SanitizeNameTester::mapToPrometheusName(
"sample_metric_name", "Hz", prometheus::MetricType::Info, false, true),
"sample_metric_name_hertz");
}

TEST_F(AttributeCollisionTest, JoinsCollidingKeys)
{
CheckTranslation({{"foo.a", "value1"}, {"foo_a", "value2"}}, {{"foo_a", "value1;value2"},
Expand Down