Skip to content

Commit 06b2288

Browse files
committed
Prometheus: Add unit to names, convert to word
1 parent 049ab63 commit 06b2288

File tree

4 files changed

+404
-22
lines changed

4 files changed

+404
-22
lines changed

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

+73
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,79 @@ class PrometheusExporterUtils
4141
*/
4242
static std::string SanitizeNames(std::string name);
4343

44+
static std::string MapToPrometheusName(const std::string &name,
45+
const std::string &unit,
46+
::prometheus::MetricType prometheus_type);
47+
48+
/**
49+
* A utility function that returns the equivalent Prometheus name for the provided OTLP metric
50+
* unit.
51+
*
52+
* @param raw_metric_unitName The raw metric unit for which Prometheus metric unit needs to be
53+
* computed.
54+
* @return the computed Prometheus metric unit equivalent of the OTLP metric un
55+
*/
56+
static std::string GetEquivalentPrometheusUnit(const std::string &raw_metric_unitName);
57+
58+
/**
59+
* This method retrieves the expanded Prometheus unit name for known abbreviations. OTLP metrics
60+
* use the c/s notation as specified at <a href="https://ucum.org/ucum.html">UCUM</a>. The list of
61+
* mappings is adopted from <a
62+
* href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/9a9d4778bbbf242dba233db28e2fbcfda3416959/pkg/translator/prometheus/normalize_name.go#L30">OpenTelemetry
63+
* Collector Contrib</a>.
64+
*
65+
* @param unit_abbreviation The unit that name that needs to be expanded/converted to Prometheus
66+
* units.
67+
* @return The expanded/converted unit name if known, otherwise returns the input unit name as-is.
68+
*/
69+
static std::string GetPrometheusUnit(const std::string &unit_abbreviation);
70+
71+
/**
72+
* This method retrieves the expanded Prometheus unit name to be used with "per" units for known
73+
* units. For example: s => per second (singular)
74+
*
75+
* @param per_unit_abbreviation The unit abbreviation used in a 'per' unit.
76+
* @return The expanded unit equivalent to be used in 'per' unit if the input is a known unit,
77+
* otherwise returns the input as-is.
78+
*/
79+
static std::string GetPrometheusPerUnit(const std::string &per_unit_abbreviation);
80+
81+
/**
82+
* Replaces all characters that are not a letter or a digit with '_' to make the resulting string
83+
* Prometheus compliant. This method also removes leading and trailing underscores - this is done
84+
* to keep the resulting unit similar to what is produced from the collector's implementation.
85+
*
86+
* @param str The string input that needs to be made Prometheus compliant.
87+
* @return the cleaned-up Prometheus compliant string.
88+
*/
89+
static std::string CleanUpString(const std::string &str);
90+
91+
/**
92+
* This method is used to convert the units expressed as a rate via '/' symbol in their name to
93+
* their expanded text equivalent. For instance, km/h => km_per_hour. The method operates on the
94+
* input by splitting it in 2 parts - before and after '/' symbol and will attempt to expand any
95+
* known unit abbreviation in both parts. Unknown abbreviations & unsupported characters will
96+
* remain unchanged in the final output of this function.
97+
*
98+
* @param rate_expressed_unit The rate unit input that needs to be converted to its text
99+
* equivalent.
100+
* @return The text equivalent of unit expressed as rate. If the input does not contain '/', the
101+
* function returns it as-is.
102+
*/
103+
static std::string ConvertRateExpressedToPrometheusUnit(const std::string &rate_expressed_unit);
104+
105+
/**
106+
* This method drops all characters enclosed within '{}' (including the curly braces) by replacing
107+
* them with an empty string. Note that this method will not produce the intended effect if there
108+
* are nested curly braces within the outer enclosure of '{}'.
109+
*
110+
* <p>For instance, {packet{s}s} => s}.
111+
*
112+
* @param unit The input unit from which text within curly braces needs to be removed.
113+
* @return The resulting unit after removing the text within '{}'.
114+
*/
115+
static std::string RemoveUnitPortionInBraces(const std::string &unit);
116+
44117
static opentelemetry::sdk::metrics::AggregationType getAggregationType(
45118
const opentelemetry::sdk::metrics::PointType &point_type);
46119

exporters/prometheus/src/exporter_utils.cc

+159-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
#include <regex>
45
#include <sstream>
6+
#include <string>
57
#include <utility>
68
#include <vector>
79
#include "prometheus/metric_family.h"
@@ -38,13 +40,7 @@ std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateT
3840
{
3941
for (const auto &metric_data : instrumentation_info.metric_data_)
4042
{
41-
auto origin_name = metric_data.instrument_descriptor.name_;
42-
auto unit = metric_data.instrument_descriptor.unit_;
43-
auto sanitized = SanitizeNames(origin_name);
44-
prometheus_client::MetricFamily metric_family;
45-
metric_family.name = sanitized + "_" + unit;
46-
metric_family.help = metric_data.instrument_descriptor.description_;
47-
auto time = metric_data.end_ts.time_since_epoch();
43+
auto time = metric_data.end_ts.time_since_epoch();
4844
for (const auto &point_data_attr : metric_data.point_data_attr_)
4945
{
5046
auto kind = getAggregationType(point_data_attr.point_data);
@@ -55,7 +51,11 @@ std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateT
5551
nostd::get<sdk::metrics::SumPointData>(point_data_attr.point_data).is_monotonic_;
5652
}
5753
const prometheus_client::MetricType type = TranslateType(kind, is_monotonic);
58-
metric_family.type = type;
54+
prometheus_client::MetricFamily metric_family;
55+
metric_family.type = type;
56+
metric_family.name = MapToPrometheusName(metric_data.instrument_descriptor.name_,
57+
metric_data.instrument_descriptor.unit_, type);
58+
metric_family.help = metric_data.instrument_descriptor.description_;
5959
if (type == prometheus_client::MetricType::Histogram) // Histogram
6060
{
6161
auto histogram_point_data =
@@ -114,8 +114,8 @@ std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateT
114114
"invalid SumPointData type");
115115
}
116116
}
117+
output.emplace_back(metric_family);
117118
}
118-
output.emplace_back(metric_family);
119119
}
120120
}
121121
return output;
@@ -167,6 +167,156 @@ std::string PrometheusExporterUtils::SanitizeNames(std::string name)
167167
return name;
168168
}
169169

170+
std::regex INVALID_CHARACTERS_PATTERN("[^a-zA-Z0-9]");
171+
std::regex CHARACTERS_BETWEEN_BRACES_PATTERN("\\{(.*?)\\}");
172+
std::regex SANITIZE_LEADING_UNDERSCORES("^_+");
173+
std::regex SANITIZE_TRAILING_UNDERSCORES("_+$");
174+
std::regex SANITIZE_CONSECUTIVE_UNDERSCORES("[_]{2,}");
175+
176+
std::string PrometheusExporterUtils::GetEquivalentPrometheusUnit(
177+
const std::string &raw_metric_unit_name)
178+
{
179+
if (raw_metric_unit_name.empty())
180+
{
181+
return raw_metric_unit_name;
182+
}
183+
184+
std::string converted_metric_unit_name = RemoveUnitPortionInBraces(raw_metric_unit_name);
185+
converted_metric_unit_name = ConvertRateExpressedToPrometheusUnit(converted_metric_unit_name);
186+
187+
return CleanUpString(GetPrometheusUnit(converted_metric_unit_name));
188+
}
189+
190+
std::string PrometheusExporterUtils::GetPrometheusUnit(const std::string &unit_abbreviation)
191+
{
192+
static std::map<std::string, std::string> units{// Time
193+
{"d", "days"},
194+
{"h", "hours"},
195+
{"min", "minutes"},
196+
{"s", "seconds"},
197+
{"ms", "milliseconds"},
198+
{"us", "microseconds"},
199+
{"ns", "nanoseconds"},
200+
// Bytes
201+
{"By", "bytes"},
202+
{"KiBy", "kibibytes"},
203+
{"MiBy", "mebibytes"},
204+
{"GiBy", "gibibytes"},
205+
{"TiBy", "tibibytes"},
206+
{"KBy", "kilobytes"},
207+
{"MBy", "megabytes"},
208+
{"GBy", "gigabytes"},
209+
{"TBy", "terabytes"},
210+
{"B", "bytes"},
211+
{"KB", "kilobytes"},
212+
{"MB", "megabytes"},
213+
{"GB", "gigabytes"},
214+
{"TB", "terabytes"},
215+
// SI
216+
{"m", "meters"},
217+
{"V", "volts"},
218+
{"A", "amperes"},
219+
{"J", "joules"},
220+
{"W", "watts"},
221+
{"g", "grams"},
222+
// Misc
223+
{"Cel", "celsius"},
224+
{"Hz", "hertz"},
225+
{"1", ""},
226+
{"%", "percent"},
227+
{"$", "dollars"}};
228+
auto res_it = units.find(unit_abbreviation);
229+
if (res_it == units.end())
230+
{
231+
return unit_abbreviation;
232+
}
233+
return res_it->second;
234+
}
235+
236+
std::string PrometheusExporterUtils::GetPrometheusPerUnit(const std::string &per_unit_abbreviation)
237+
{
238+
static std::map<std::string, std::string> per_units{
239+
{"s", "second"}, {"m", "minute"}, {"h", "hour"}, {"d", "day"},
240+
{"w", "week"}, {"mo", "month"}, {"y", "year"}};
241+
auto res_it = per_units.find(per_unit_abbreviation);
242+
if (res_it == per_units.end())
243+
{
244+
return per_unit_abbreviation;
245+
}
246+
return res_it->second;
247+
}
248+
249+
std::string PrometheusExporterUtils::RemoveUnitPortionInBraces(const std::string &unit)
250+
{
251+
return std::regex_replace(unit, CHARACTERS_BETWEEN_BRACES_PATTERN, "");
252+
}
253+
254+
std::string PrometheusExporterUtils::ConvertRateExpressedToPrometheusUnit(
255+
const std::string &rate_expressed_unit)
256+
{
257+
if (rate_expressed_unit.find("/") == std::string::npos)
258+
{
259+
return rate_expressed_unit;
260+
}
261+
262+
std::vector<std::string> rate_entities;
263+
size_t pos = rate_expressed_unit.find("/");
264+
rate_entities.push_back(rate_expressed_unit.substr(0, pos));
265+
rate_entities.push_back(rate_expressed_unit.substr(pos + 1));
266+
267+
if (rate_entities[1].empty())
268+
{
269+
return rate_expressed_unit;
270+
}
271+
272+
std::string prometheus_unit = GetPrometheusUnit(rate_entities[0]);
273+
std::string prometheus_per_unit = GetPrometheusPerUnit(rate_entities[1]);
274+
275+
return prometheus_unit + "_per_" + prometheus_per_unit;
276+
}
277+
278+
std::string PrometheusExporterUtils::CleanUpString(const std::string &str)
279+
{
280+
std::string cleaned_string = std::regex_replace(str, INVALID_CHARACTERS_PATTERN, "_");
281+
cleaned_string = std::regex_replace(cleaned_string, SANITIZE_CONSECUTIVE_UNDERSCORES, "_");
282+
cleaned_string = std::regex_replace(cleaned_string, SANITIZE_TRAILING_UNDERSCORES, "");
283+
cleaned_string = std::regex_replace(cleaned_string, SANITIZE_LEADING_UNDERSCORES, "");
284+
285+
return cleaned_string;
286+
}
287+
288+
std::string PrometheusExporterUtils::MapToPrometheusName(
289+
const std::string &name,
290+
const std::string &unit,
291+
prometheus_client::MetricType prometheus_type)
292+
{
293+
auto sanitized_name = SanitizeNames(name);
294+
std::string prometheus_equivalent_unit = GetEquivalentPrometheusUnit(unit);
295+
296+
// Append prometheus unit if not null or empty.
297+
if (!prometheus_equivalent_unit.empty() &&
298+
sanitized_name.find(prometheus_equivalent_unit) == std::string::npos)
299+
{
300+
sanitized_name += "_" + prometheus_equivalent_unit;
301+
}
302+
303+
// Special case - counter
304+
if (prometheus_type == prometheus_client::MetricType::Counter &&
305+
sanitized_name.find("total") == std::string::npos)
306+
{
307+
sanitized_name += "_total";
308+
}
309+
310+
// Special case - gauge
311+
if (unit == "1" && prometheus_type == prometheus_client::MetricType::Gauge &&
312+
sanitized_name.find("ratio") == std::string::npos)
313+
{
314+
sanitized_name += "_ratio";
315+
}
316+
317+
return CleanUpString(SanitizeNames(sanitized_name));
318+
}
319+
170320
metric_sdk::AggregationType PrometheusExporterUtils::getAggregationType(
171321
const metric_sdk::PointType &point_type)
172322
{

exporters/prometheus/test/collector_test.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ TEST(PrometheusCollector, BasicTests)
7676

7777
// Collection size should be the same as the size
7878
// of the records collection produced by MetricProducer.
79-
ASSERT_EQ(data.size(), 1);
79+
ASSERT_EQ(data.size(), 2);
8080
delete reader;
8181
delete producer;
8282
}

0 commit comments

Comments
 (0)