mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-11 22:07:27 -08:00
Add option for metric names that don't follow Prometheus conventions
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
This commit is contained in:
parent
3e14a76716
commit
030cc13896
|
@ -78,7 +78,7 @@ var perUnitMap = map[string]string{
|
|||
"y": "year",
|
||||
}
|
||||
|
||||
// BuildCompliantName builds a Prometheus-compliant metric name for the specified metric.
|
||||
// BuildCompliantMetricName builds a Prometheus-compliant metric name for the specified metric.
|
||||
//
|
||||
// Metric name is prefixed with specified namespace and underscore (if any).
|
||||
// Namespace is not cleaned up. Make sure specified namespace follows Prometheus
|
||||
|
@ -87,7 +87,7 @@ var perUnitMap = map[string]string{
|
|||
// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels,
|
||||
// https://prometheus.io/docs/practices/naming/#metric-and-label-naming
|
||||
// and https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus.
|
||||
func BuildCompliantName(metric pmetric.Metric, namespace string, addMetricSuffixes bool) string {
|
||||
func BuildCompliantMetricName(metric pmetric.Metric, namespace string, addMetricSuffixes bool) string {
|
||||
// Full normalization following standard Prometheus naming conventions
|
||||
if addMetricSuffixes {
|
||||
return normalizeName(metric, namespace)
|
||||
|
@ -125,46 +125,12 @@ func normalizeName(metric pmetric.Metric, namespace string) string {
|
|||
func(r rune) bool { return nonMetricNameCharRE.MatchString(string(r)) },
|
||||
)
|
||||
|
||||
// Split unit at the '/' if any
|
||||
unitTokens := strings.SplitN(metric.Unit(), "/", 2)
|
||||
|
||||
// Main unit
|
||||
// Append if not blank, doesn't contain '{}', and is not present in metric name already
|
||||
if len(unitTokens) > 0 {
|
||||
var mainUnitProm, perUnitProm string
|
||||
mainUnitOTel := strings.TrimSpace(unitTokens[0])
|
||||
if mainUnitOTel != "" && !strings.ContainsAny(mainUnitOTel, "{}") {
|
||||
mainUnitProm = cleanUpUnit(unitMapGetOrDefault(mainUnitOTel))
|
||||
if slices.Contains(nameTokens, mainUnitProm) {
|
||||
mainUnitProm = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Per unit
|
||||
// Append if not blank, doesn't contain '{}', and is not present in metric name already
|
||||
if len(unitTokens) > 1 && unitTokens[1] != "" {
|
||||
perUnitOTel := strings.TrimSpace(unitTokens[1])
|
||||
if perUnitOTel != "" && !strings.ContainsAny(perUnitOTel, "{}") {
|
||||
perUnitProm = perUnitMapGetOrDefault(perUnitOTel)
|
||||
}
|
||||
if perUnitProm != "" {
|
||||
perUnitProm = "per_" + perUnitProm
|
||||
if slices.Contains(nameTokens, perUnitProm) {
|
||||
perUnitProm = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if perUnitProm != "" {
|
||||
mainUnitProm = strings.TrimSuffix(mainUnitProm, "_")
|
||||
}
|
||||
|
||||
if mainUnitProm != "" {
|
||||
nameTokens = append(nameTokens, mainUnitProm)
|
||||
}
|
||||
if perUnitProm != "" {
|
||||
nameTokens = append(nameTokens, perUnitProm)
|
||||
}
|
||||
mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(metric.Unit())
|
||||
if mainUnitSuffix != "" && !slices.Contains(nameTokens, mainUnitSuffix) {
|
||||
nameTokens = append(nameTokens, mainUnitSuffix)
|
||||
}
|
||||
if perUnitSuffix != "" && !slices.Contains(nameTokens, perUnitSuffix) {
|
||||
nameTokens = append(nameTokens, perUnitSuffix)
|
||||
}
|
||||
|
||||
// Append _total for Counters
|
||||
|
@ -235,3 +201,72 @@ func removeItem(slice []string, value string) []string {
|
|||
}
|
||||
return newSlice
|
||||
}
|
||||
|
||||
// BuildMetricName builds a valid metric name but without following Prometheus naming conventions.
|
||||
// It doesn't do any character transformation, it only prefixes the metric name with the namespace, if any,
|
||||
// and adds metric type suffixes, e.g. "_total" for counters and unit suffixes.
|
||||
//
|
||||
// Differently from BuildCompliantMetricName, it doesn't pre-check the presence of unit and type suffixes.
|
||||
// If "addMetricSuffixes" is true, it will add them anyway.
|
||||
//
|
||||
// Please use BuildCompliantMetricName for a metric name that follows Prometheus naming conventions.
|
||||
func BuildMetricName(metric pmetric.Metric, namespace string, addMetricSuffixes bool) string {
|
||||
metricName := metric.Name()
|
||||
|
||||
if namespace != "" {
|
||||
metricName = namespace + "_" + metricName
|
||||
}
|
||||
|
||||
if addMetricSuffixes {
|
||||
mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(metric.Unit())
|
||||
if mainUnitSuffix != "" {
|
||||
metricName = metricName + "_" + mainUnitSuffix
|
||||
}
|
||||
if perUnitSuffix != "" {
|
||||
metricName = metricName + "_" + perUnitSuffix
|
||||
}
|
||||
|
||||
// Append _total for Counters
|
||||
if metric.Type() == pmetric.MetricTypeSum && metric.Sum().IsMonotonic() {
|
||||
metricName = metricName + "_total"
|
||||
}
|
||||
|
||||
// Append _ratio for metrics with unit "1"
|
||||
// Some OTel receivers improperly use unit "1" for counters of objects
|
||||
// See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions
|
||||
// Until these issues have been fixed, we're appending `_ratio` for gauges ONLY
|
||||
// Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons)
|
||||
if metric.Unit() == "1" && metric.Type() == pmetric.MetricTypeGauge {
|
||||
metricName = metricName + "_ratio"
|
||||
}
|
||||
}
|
||||
return metricName
|
||||
}
|
||||
|
||||
func buildUnitSuffixes(unit string) (mainUnitSuffix, perUnitSuffix string) {
|
||||
// Split unit at the '/' if any
|
||||
unitTokens := strings.SplitN(unit, "/", 2)
|
||||
|
||||
if len(unitTokens) > 0 {
|
||||
mainUnitOTel := strings.TrimSpace(unitTokens[0])
|
||||
if mainUnitOTel != "" && !strings.ContainsAny(mainUnitOTel, "{}") {
|
||||
mainUnitSuffix = cleanUpUnit(unitMapGetOrDefault(mainUnitOTel))
|
||||
}
|
||||
|
||||
if len(unitTokens) > 1 && unitTokens[1] != "" {
|
||||
perUnitOTel := strings.TrimSpace(unitTokens[1])
|
||||
if perUnitOTel != "" && !strings.ContainsAny(perUnitOTel, "{}") {
|
||||
perUnitSuffix = perUnitMapGetOrDefault(perUnitOTel)
|
||||
}
|
||||
if perUnitSuffix != "" {
|
||||
perUnitSuffix = "per_" + perUnitSuffix
|
||||
}
|
||||
}
|
||||
|
||||
if perUnitSuffix != "" {
|
||||
mainUnitSuffix = strings.TrimSuffix(mainUnitSuffix, "_")
|
||||
}
|
||||
}
|
||||
|
||||
return mainUnitSuffix, perUnitSuffix
|
||||
}
|
|
@ -149,29 +149,66 @@ func TestRemoveItem(t *testing.T) {
|
|||
require.Equal(t, []string{"b", "c"}, removeItem([]string{"a", "b", "c"}, "a"))
|
||||
}
|
||||
|
||||
func TestBuildCompliantNameWithSuffixes(t *testing.T) {
|
||||
require.Equal(t, "system_io_bytes_total", BuildCompliantName(createCounter("system.io", "By"), "", true))
|
||||
require.Equal(t, "system_network_io_bytes_total", BuildCompliantName(createCounter("network.io", "By"), "system", true))
|
||||
require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", ""), "", true))
|
||||
require.Equal(t, "envoy_rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", true))
|
||||
require.Equal(t, ":foo::bar", BuildCompliantName(createGauge(":foo::bar", ""), "", true))
|
||||
require.Equal(t, ":foo::bar_total", BuildCompliantName(createCounter(":foo::bar", ""), "", true))
|
||||
func TestBuildCompliantMetricNameWithSuffixes(t *testing.T) {
|
||||
require.Equal(t, "system_io_bytes_total", BuildCompliantMetricName(createCounter("system.io", "By"), "", true))
|
||||
require.Equal(t, "system_network_io_bytes_total", BuildCompliantMetricName(createCounter("network.io", "By"), "system", true))
|
||||
require.Equal(t, "_3_14_digits", BuildCompliantMetricName(createGauge("3.14 digits", ""), "", true))
|
||||
require.Equal(t, "envoy_rule_engine_zlib_buf_error", BuildCompliantMetricName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", true))
|
||||
require.Equal(t, ":foo::bar", BuildCompliantMetricName(createGauge(":foo::bar", ""), "", true))
|
||||
require.Equal(t, ":foo::bar_total", BuildCompliantMetricName(createCounter(":foo::bar", ""), "", true))
|
||||
// Gauges with unit 1 are considered ratios.
|
||||
require.Equal(t, "foo_bar_ratio", BuildCompliantName(createGauge("foo.bar", "1"), "", true))
|
||||
require.Equal(t, "foo_bar_ratio", BuildCompliantMetricName(createGauge("foo.bar", "1"), "", true))
|
||||
// Slashes in units are converted.
|
||||
require.Equal(t, "system_io_foo_per_bar_total", BuildCompliantName(createCounter("system.io", "foo/bar"), "", true))
|
||||
require.Equal(t, "metric_with_foreign_characters_total", BuildCompliantName(createCounter("metric_with_字符_foreign_characters", ""), "", true))
|
||||
require.Equal(t, "system_io_foo_per_bar_total", BuildCompliantMetricName(createCounter("system.io", "foo/bar"), "", true))
|
||||
require.Equal(t, "metric_with_foreign_characters_total", BuildCompliantMetricName(createCounter("metric_with_字符_foreign_characters", ""), "", true))
|
||||
}
|
||||
|
||||
func TestBuildCompliantNameWithoutSuffixes(t *testing.T) {
|
||||
require.Equal(t, "system_io", BuildCompliantName(createCounter("system.io", "By"), "", false))
|
||||
require.Equal(t, "system_network_io", BuildCompliantName(createCounter("network.io", "By"), "system", false))
|
||||
require.Equal(t, "system_network_I_O", BuildCompliantName(createCounter("network (I/O)", "By"), "system", false))
|
||||
require.Equal(t, "_3_14_digits", BuildCompliantName(createGauge("3.14 digits", "By"), "", false))
|
||||
require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", false))
|
||||
require.Equal(t, ":foo::bar", BuildCompliantName(createGauge(":foo::bar", ""), "", false))
|
||||
require.Equal(t, ":foo::bar", BuildCompliantName(createCounter(":foo::bar", ""), "", false))
|
||||
require.Equal(t, "foo_bar", BuildCompliantName(createGauge("foo.bar", "1"), "", false))
|
||||
require.Equal(t, "system_io", BuildCompliantName(createCounter("system.io", "foo/bar"), "", false))
|
||||
require.Equal(t, "metric_with___foreign_characters", BuildCompliantName(createCounter("metric_with_字符_foreign_characters", ""), "", false))
|
||||
func TestBuildCompliantMetricNameWithoutSuffixes(t *testing.T) {
|
||||
require.Equal(t, "system_io", BuildCompliantMetricName(createCounter("system.io", "By"), "", false))
|
||||
require.Equal(t, "system_network_io", BuildCompliantMetricName(createCounter("network.io", "By"), "system", false))
|
||||
require.Equal(t, "system_network_I_O", BuildCompliantMetricName(createCounter("network (I/O)", "By"), "system", false))
|
||||
require.Equal(t, "_3_14_digits", BuildCompliantMetricName(createGauge("3.14 digits", "By"), "", false))
|
||||
require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildCompliantMetricName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", false))
|
||||
require.Equal(t, ":foo::bar", BuildCompliantMetricName(createGauge(":foo::bar", ""), "", false))
|
||||
require.Equal(t, ":foo::bar", BuildCompliantMetricName(createCounter(":foo::bar", ""), "", false))
|
||||
require.Equal(t, "foo_bar", BuildCompliantMetricName(createGauge("foo.bar", "1"), "", false))
|
||||
require.Equal(t, "system_io", BuildCompliantMetricName(createCounter("system.io", "foo/bar"), "", false))
|
||||
require.Equal(t, "metric_with___foreign_characters", BuildCompliantMetricName(createCounter("metric_with_字符_foreign_characters", ""), "", false))
|
||||
}
|
||||
|
||||
func TestBuildMetricNameWithSuffixes(t *testing.T) {
|
||||
require.Equal(t, "system.io_bytes_total", BuildMetricName(createCounter("system.io", "By"), "", true))
|
||||
require.Equal(t, "system_network.io_bytes_total", BuildMetricName(createCounter("network.io", "By"), "system", true))
|
||||
require.Equal(t, "3.14 digits", BuildMetricName(createGauge("3.14 digits", ""), "", true))
|
||||
require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildMetricName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", true))
|
||||
require.Equal(t, ":foo::bar", BuildMetricName(createGauge(":foo::bar", ""), "", true))
|
||||
require.Equal(t, ":foo::bar_total", BuildMetricName(createCounter(":foo::bar", ""), "", true))
|
||||
// Gauges with unit 1 are considered ratios.
|
||||
require.Equal(t, "foo.bar_ratio", BuildMetricName(createGauge("foo.bar", "1"), "", true))
|
||||
// Slashes in units are converted.
|
||||
require.Equal(t, "system.io_foo_per_bar_total", BuildMetricName(createCounter("system.io", "foo/bar"), "", true))
|
||||
require.Equal(t, "metric_with_字符_foreign_characters_total", BuildMetricName(createCounter("metric_with_字符_foreign_characters", ""), "", true))
|
||||
|
||||
// Tests below show weird interactions that users can have with the metric names.
|
||||
// With BuildMetricName we don't check if units/type suffixes are already present in the metric name, we always add them.
|
||||
require.Equal(t, "system_io_seconds_seconds", BuildMetricName(createGauge("system_io_seconds", "s"), "", true))
|
||||
require.Equal(t, "system_io_total_total", BuildMetricName(createCounter("system_io_total", ""), "", true))
|
||||
}
|
||||
|
||||
func TestBuildMetricNameWithoutSuffixes(t *testing.T) {
|
||||
require.Equal(t, "system.io", BuildMetricName(createCounter("system.io", "By"), "", false))
|
||||
require.Equal(t, "system_network.io", BuildMetricName(createCounter("network.io", "By"), "system", false))
|
||||
require.Equal(t, "3.14 digits", BuildMetricName(createGauge("3.14 digits", ""), "", false))
|
||||
require.Equal(t, "envoy__rule_engine_zlib_buf_error", BuildMetricName(createGauge("envoy__rule_engine_zlib_buf_error", ""), "", false))
|
||||
require.Equal(t, ":foo::bar", BuildMetricName(createGauge(":foo::bar", ""), "", false))
|
||||
require.Equal(t, ":foo::bar", BuildMetricName(createCounter(":foo::bar", ""), "", false))
|
||||
// Gauges with unit 1 are considered ratios.
|
||||
require.Equal(t, "foo.bar", BuildMetricName(createGauge("foo.bar", "1"), "", false))
|
||||
// Slashes in units are converted.
|
||||
require.Equal(t, "system.io", BuildMetricName(createCounter("system.io", "foo/bar"), "", false))
|
||||
require.Equal(t, "metric_with_字符_foreign_characters", BuildMetricName(createCounter("metric_with_字符_foreign_characters", ""), "", false))
|
||||
|
||||
|
||||
require.Equal(t, "system_io_seconds", BuildMetricName(createGauge("system_io_seconds", "s"), "", false))
|
||||
require.Equal(t, "system_io_total", BuildMetricName(createCounter("system_io_total", ""), "", false))
|
||||
}
|
|
@ -762,7 +762,7 @@ func TestPrometheusConverter_addExponentialHistogramDataPoints(t *testing.T) {
|
|||
Settings{
|
||||
ExportCreatedMetric: true,
|
||||
},
|
||||
prometheustranslator.BuildCompliantName(metric, "", true),
|
||||
prometheustranslator.BuildCompliantMetricName(metric, "", true),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, annots)
|
||||
|
|
|
@ -96,7 +96,12 @@ func (c *PrometheusConverter) FromMetrics(ctx context.Context, md pmetric.Metric
|
|||
continue
|
||||
}
|
||||
|
||||
promName := prometheustranslator.BuildCompliantName(metric, settings.Namespace, settings.AddMetricSuffixes)
|
||||
var promName string
|
||||
if settings.AllowUTF8 {
|
||||
promName = prometheustranslator.BuildMetricName(metric, settings.Namespace, settings.AddMetricSuffixes)
|
||||
} else {
|
||||
promName = prometheustranslator.BuildCompliantMetricName(metric, settings.Namespace, settings.AddMetricSuffixes)
|
||||
}
|
||||
c.metadata = append(c.metadata, prompb.MetricMetadata{
|
||||
Type: otelMetricTypeToPromMetricType(metric),
|
||||
MetricFamilyName: promName,
|
||||
|
|
|
@ -46,7 +46,7 @@ func TestFromMetrics(t *testing.T) {
|
|||
metricSlice := scopeMetricsSlice.At(j).Metrics()
|
||||
for k := 0; k < metricSlice.Len(); k++ {
|
||||
metric := metricSlice.At(k)
|
||||
promName := prometheustranslator.BuildCompliantName(metric, "", false)
|
||||
promName := prometheustranslator.BuildCompliantMetricName(metric, "", false)
|
||||
expMetadata = append(expMetadata, prompb.MetricMetadata{
|
||||
Type: otelMetricTypeToPromMetricType(metric),
|
||||
MetricFamilyName: promName,
|
||||
|
|
Loading…
Reference in a new issue