diff options
Diffstat (limited to 'vendor/github.com/prometheus/otlptranslator/metric_namer.go')
| -rw-r--r-- | vendor/github.com/prometheus/otlptranslator/metric_namer.go | 365 |
1 files changed, 0 insertions, 365 deletions
diff --git a/vendor/github.com/prometheus/otlptranslator/metric_namer.go b/vendor/github.com/prometheus/otlptranslator/metric_namer.go deleted file mode 100644 index 79e005f68..000000000 --- a/vendor/github.com/prometheus/otlptranslator/metric_namer.go +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// Provenance-includes-location: https://github.com/prometheus/prometheus/blob/93e991ef7ed19cc997a9360c8016cac3767b8057/storage/remote/otlptranslator/prometheus/metric_name_builder.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: Copyright The Prometheus Authors -// Provenance-includes-location: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/95e8f8fdc2a9dc87230406c9a3cf02be4fd68bea/pkg/translator/prometheus/normalize_name.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: Copyright The OpenTelemetry Authors. - -package otlptranslator - -import ( - "fmt" - "slices" - "strings" - "unicode" - - "github.com/grafana/regexp" -) - -// The map to translate OTLP units to Prometheus units -// OTLP metrics use the c/s notation as specified at https://ucum.org/ucum.html -// (See also https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/README.md#instrument-units) -// Prometheus best practices for units: https://prometheus.io/docs/practices/naming/#base-units -// OpenMetrics specification for units: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#units-and-base-units -var unitMap = map[string]string{ - // Time - "d": "days", - "h": "hours", - "min": "minutes", - "s": "seconds", - "ms": "milliseconds", - "us": "microseconds", - "ns": "nanoseconds", - - // Bytes - "By": "bytes", - "KiBy": "kibibytes", - "MiBy": "mebibytes", - "GiBy": "gibibytes", - "TiBy": "tibibytes", - "KBy": "kilobytes", - "MBy": "megabytes", - "GBy": "gigabytes", - "TBy": "terabytes", - - // SI - "m": "meters", - "V": "volts", - "A": "amperes", - "J": "joules", - "W": "watts", - "g": "grams", - - // Misc - "Cel": "celsius", - "Hz": "hertz", - "1": "", - "%": "percent", -} - -// The map that translates the "per" unit. -// Example: s => per second (singular). -var perUnitMap = map[string]string{ - "s": "second", - "m": "minute", - "h": "hour", - "d": "day", - "w": "week", - "mo": "month", - "y": "year", -} - -// MetricNamer is a helper struct to build metric names. -// It converts OpenTelemetry Protocol (OTLP) metric names to Prometheus-compliant metric names. -// -// Example usage: -// -// namer := MetricNamer{ -// WithMetricSuffixes: true, -// UTF8Allowed: false, -// } -// -// metric := Metric{ -// Name: "http.server.duration", -// Unit: "s", -// Type: MetricTypeHistogram, -// } -// -// result := namer.Build(metric) // "http_server_duration_seconds" -type MetricNamer struct { - Namespace string - WithMetricSuffixes bool - UTF8Allowed bool -} - -// NewMetricNamer creates a MetricNamer with the specified namespace (can be -// blank) and the requested Translation Strategy. -func NewMetricNamer(namespace string, strategy TranslationStrategyOption) MetricNamer { - return MetricNamer{ - Namespace: namespace, - WithMetricSuffixes: strategy.ShouldAddSuffixes(), - UTF8Allowed: !strategy.ShouldEscape(), - } -} - -// Metric is a helper struct that holds information about a metric. -// It represents an OpenTelemetry metric with its name, unit, and type. -// -// Example: -// -// metric := Metric{ -// Name: "http.server.request.duration", -// Unit: "s", -// Type: MetricTypeHistogram, -// } -type Metric struct { - Name string - Unit string - Type MetricType -} - -// Build builds a metric name for the specified metric. -// -// The method applies different transformations based on the MetricNamer configuration: -// - If UTF8Allowed is true, doesn't translate names - all characters must be valid UTF-8, however. -// - If UTF8Allowed is false, translates metric names to comply with legacy Prometheus name scheme by escaping invalid characters to `_`. -// - If WithMetricSuffixes is true, adds appropriate suffixes based on type and unit. -// -// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels -// -// Examples: -// -// namer := MetricNamer{WithMetricSuffixes: true, UTF8Allowed: false} -// -// // Counter gets _total suffix -// counter := Metric{Name: "requests.count", Unit: "1", Type: MetricTypeMonotonicCounter} -// result := namer.Build(counter) // "requests_count_total" -// -// // Gauge with unit suffix -// gauge := Metric{Name: "memory.usage", Unit: "By", Type: MetricTypeGauge} -// result = namer.Build(gauge) // "memory_usage_bytes" -func (mn *MetricNamer) Build(metric Metric) (string, error) { - if mn.UTF8Allowed { - return mn.buildMetricName(metric.Name, metric.Unit, metric.Type) - } - return mn.buildCompliantMetricName(metric.Name, metric.Unit, metric.Type) -} - -func (mn *MetricNamer) buildCompliantMetricName(name, unit string, metricType MetricType) (normalizedName string, err error) { - defer func() { - if len(normalizedName) == 0 { - err = fmt.Errorf("normalization for metric %q resulted in empty name", name) - return - } - - if normalizedName == name { - return - } - - // Check that the resulting normalized name contains at least one non-underscore character - for _, c := range normalizedName { - if c != '_' { - return - } - } - err = fmt.Errorf("normalization for metric %q resulted in invalid name %q", name, normalizedName) - normalizedName = "" - }() - - // Full normalization following standard Prometheus naming conventions - if mn.WithMetricSuffixes { - normalizedName = normalizeName(name, unit, metricType, mn.Namespace) - return - } - - // Simple case (no full normalization, no units, etc.). - metricName := strings.Join(strings.FieldsFunc(name, func(r rune) bool { - return !isValidCompliantMetricChar(r) && r != '_' - }), "_") - - // Namespace? - if mn.Namespace != "" { - namespace := strings.Join(strings.FieldsFunc(mn.Namespace, func(r rune) bool { - return !isValidCompliantMetricChar(r) && r != '_' - }), "_") - normalizedName = namespace + "_" + metricName - return - } - - // Metric name starts with a digit? Prefix it with an underscore. - if metricName != "" && unicode.IsDigit(rune(metricName[0])) { - metricName = "_" + metricName - } - - normalizedName = metricName - return -} - -var multipleUnderscoresRE = regexp.MustCompile(`__+`) - -// isValidCompliantMetricChar checks if a rune is a valid metric name character (a-z, A-Z, 0-9, :). -func isValidCompliantMetricChar(r rune) bool { - return (r >= 'a' && r <= 'z') || - (r >= 'A' && r <= 'Z') || - (r >= '0' && r <= '9') || - r == ':' -} - -// replaceInvalidMetricChar replaces invalid metric name characters with underscore. -func replaceInvalidMetricChar(r rune) rune { - if isValidCompliantMetricChar(r) { - return r - } - return '_' -} - -// Build a normalized name for the specified metric. -func normalizeName(name, unit string, metricType MetricType, namespace string) string { - // Split metric name into "tokens" (of supported metric name runes). - // Note that this has the side effect of replacing multiple consecutive underscores with a single underscore. - // This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. - nameTokens := strings.FieldsFunc( - name, - func(r rune) bool { return !isValidCompliantMetricChar(r) }, - ) - - mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit) - nameTokens = addUnitTokens(nameTokens, cleanUpUnit(mainUnitSuffix), cleanUpUnit(perUnitSuffix)) - - // Append _total for Counters - if metricType == MetricTypeMonotonicCounter { - nameTokens = append(removeItem(nameTokens, "total"), "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 unit == "1" && metricType == MetricTypeGauge { - nameTokens = append(removeItem(nameTokens, "ratio"), "ratio") - } - - // Namespace? - if namespace != "" { - nameTokens = append([]string{namespace}, nameTokens...) - } - - // Build the string from the tokens, separated with underscores - normalizedName := strings.Join(nameTokens, "_") - - // Metric name cannot start with a digit, so prefix it with "_" in this case - if normalizedName != "" && unicode.IsDigit(rune(normalizedName[0])) { - normalizedName = "_" + normalizedName - } - - return normalizedName -} - -// addUnitTokens will add the suffixes to the nameTokens if they are not already present. -// It will also remove trailing underscores from the main suffix to avoid double underscores -// when joining the tokens. -// -// If the 'per' unit ends with underscore, the underscore will be removed. If the per unit is just -// 'per_', it will be entirely removed. -func addUnitTokens(nameTokens []string, mainUnitSuffix, perUnitSuffix string) []string { - if slices.Contains(nameTokens, mainUnitSuffix) { - mainUnitSuffix = "" - } - - if perUnitSuffix == "per_" { - perUnitSuffix = "" - } else { - perUnitSuffix = strings.TrimSuffix(perUnitSuffix, "_") - if slices.Contains(nameTokens, perUnitSuffix) { - perUnitSuffix = "" - } - } - - if perUnitSuffix != "" { - mainUnitSuffix = strings.TrimSuffix(mainUnitSuffix, "_") - } - - if mainUnitSuffix != "" { - nameTokens = append(nameTokens, mainUnitSuffix) - } - if perUnitSuffix != "" { - nameTokens = append(nameTokens, perUnitSuffix) - } - return nameTokens -} - -// Remove the specified value from the slice. -func removeItem(slice []string, value string) []string { - newSlice := make([]string, 0, len(slice)) - for _, sliceEntry := range slice { - if sliceEntry != value { - newSlice = append(newSlice, sliceEntry) - } - } - return newSlice -} - -func (mn *MetricNamer) buildMetricName(inputName, unit string, metricType MetricType) (name string, err error) { - name = inputName - if mn.Namespace != "" { - name = mn.Namespace + "_" + name - } - - if mn.WithMetricSuffixes { - // 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 unit == "1" && metricType == MetricTypeGauge { - name = trimSuffixAndDelimiter(name, "ratio") - defer func() { - name += "_ratio" - }() - } - - // Append _total for Counters. - if metricType == MetricTypeMonotonicCounter { - name = trimSuffixAndDelimiter(name, "total") - defer func() { - name += "_total" - }() - } - - mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit) - if perUnitSuffix != "" { - name = trimSuffixAndDelimiter(name, perUnitSuffix) - defer func() { - name = name + "_" + perUnitSuffix - }() - } - // We don't need to trim and re-append the suffix here because this is - // the inner-most suffix. - if mainUnitSuffix != "" && !strings.HasSuffix(name, mainUnitSuffix) { - name = name + "_" + mainUnitSuffix - } - } - return -} - -// trimSuffixAndDelimiter trims a suffix, plus one extra character which is -// assumed to be a delimiter. -func trimSuffixAndDelimiter(name, suffix string) string { - if strings.HasSuffix(name, suffix) && len(name) > len(suffix)+1 { - return name[:len(name)-(len(suffix)+1)] - } - return name -} |
