mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 08:36:55 +03:00
Add `-opentelemetry.labelNameUnderscoreSanitization` command-line flag to control whether to enable prepending of `key` to labels starting with `_` when `-opentelemetry.usePrometheusNaming` is enabled. The labels starting with `__` are not modified. Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9663 Signed-off-by: andriibeee <154226341+andriibeee@users.noreply.github.com> Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
205 lines
6.4 KiB
Go
205 lines
6.4 KiB
Go
package stream
|
|
|
|
import (
|
|
"flag"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/pb"
|
|
)
|
|
|
|
var (
|
|
usePrometheusNaming = flag.Bool("opentelemetry.usePrometheusNaming", false, "Whether to convert metric names and labels into Prometheus-compatible format for the metrics ingested "+
|
|
"via OpenTelemetry protocol; see https://docs.victoriametrics.com/victoriametrics/integrations/opentelemetry/")
|
|
convertMetricNamesToPrometheus = flag.Bool("opentelemetry.convertMetricNamesToPrometheus", false, "Whether to convert only metric names into Prometheus-compatible format for the metrics ingested "+
|
|
"via OpenTelemetry protocol; see https://docs.victoriametrics.com/victoriametrics/integrations/opentelemetry/")
|
|
labelNameUnderscoreSanitization = flag.Bool("opentelemetry.labelNameUnderscoreSanitization", true, "Whether to enable prepending of 'key' to labels starting with '_' "+
|
|
"when -opentelemetry.usePrometheusNaming is enabled. Reserved labels starting with '__' are not modified. "+
|
|
"See https://docs.victoriametrics.com/victoriametrics/integrations/opentelemetry/")
|
|
)
|
|
|
|
// unitMap is obtained from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b8655058501bed61a06bb660869051491f46840b/pkg/translator/prometheus/normalize_name.go#L19
|
|
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",
|
|
}
|
|
|
|
// perUnitMap is copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b8655058501bed61a06bb660869051491f46840b/pkg/translator/prometheus/normalize_name.go#L58
|
|
var perUnitMap = map[string]string{
|
|
"s": "second",
|
|
"m": "minute",
|
|
"h": "hour",
|
|
"d": "day",
|
|
"w": "week",
|
|
"mo": "month",
|
|
"y": "year",
|
|
}
|
|
|
|
type sanitizerContext struct {
|
|
metricNameTokens []string
|
|
metricNameBuf []byte
|
|
labelBuf []byte
|
|
}
|
|
|
|
func (sctx *sanitizerContext) reset() {
|
|
clear(sctx.metricNameTokens)
|
|
sctx.metricNameTokens = sctx.metricNameTokens[:0]
|
|
|
|
sctx.metricNameBuf = sctx.metricNameBuf[:0]
|
|
sctx.labelBuf = sctx.labelBuf[:0]
|
|
}
|
|
|
|
// The returned string is valid until the next call to sanitizeLabelName.
|
|
func (sctx *sanitizerContext) sanitizeLabelName(labelName string) string {
|
|
if !*usePrometheusNaming {
|
|
return labelName
|
|
}
|
|
return sctx.sanitizePrometheusLabelName(labelName)
|
|
}
|
|
|
|
// sanitizePrometheusLabelName performs conversion and normalization of OpenTelemetry attributes to Prometheus labels.
|
|
// It follows the Prometheus guidelines: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/translator/prometheus#labels
|
|
func (sctx *sanitizerContext) sanitizePrometheusLabelName(labelName string) string {
|
|
if len(labelName) == 0 {
|
|
return ""
|
|
}
|
|
labelName = promrelabel.SanitizeLabelName(labelName)
|
|
if labelName[0] >= '0' && labelName[0] <= '9' {
|
|
return sctx.concatLabel("key_", labelName)
|
|
} else if *labelNameUnderscoreSanitization && strings.HasPrefix(labelName, "_") && !strings.HasPrefix(labelName, "__") {
|
|
return sctx.concatLabel("key", labelName)
|
|
}
|
|
return labelName
|
|
}
|
|
|
|
func (sctx *sanitizerContext) concatLabel(a, b string) string {
|
|
sctx.labelBuf = append(sctx.labelBuf[:0], a...)
|
|
sctx.labelBuf = append(sctx.labelBuf, b...)
|
|
return bytesutil.ToUnsafeString(sctx.labelBuf)
|
|
}
|
|
|
|
// See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b8655058501bed61a06bb660869051491f46840b/pkg/translator/prometheus/normalize_name.go#L83
|
|
//
|
|
// The returned string is valid until the next call to sanitizeMetricName.
|
|
func (sctx *sanitizerContext) sanitizeMetricName(mm *pb.MetricMetadata) string {
|
|
if !*usePrometheusNaming && !*convertMetricNamesToPrometheus {
|
|
return mm.Name
|
|
}
|
|
return sctx.sanitizePrometheusMetricName(mm)
|
|
}
|
|
|
|
func (sctx *sanitizerContext) sanitizePrometheusMetricName(mm *pb.MetricMetadata) string {
|
|
sctx.splitMetricNameToTokens(mm.Name)
|
|
|
|
n := strings.IndexByte(mm.Unit, '/')
|
|
|
|
mainUnit := mm.Unit
|
|
perUnit := ""
|
|
if n >= 0 {
|
|
mainUnit = mm.Unit[:n]
|
|
perUnit = mm.Unit[n+1:]
|
|
}
|
|
mainUnit = strings.TrimSpace(mainUnit)
|
|
perUnit = strings.TrimSpace(perUnit)
|
|
|
|
if mainUnit != "" && !strings.Contains(mainUnit, "{") {
|
|
if u, ok := unitMap[mainUnit]; ok {
|
|
mainUnit = u
|
|
}
|
|
if mainUnit != "" && !slices.Contains(sctx.metricNameTokens, mainUnit) {
|
|
sctx.metricNameTokens = append(sctx.metricNameTokens, mainUnit)
|
|
}
|
|
}
|
|
|
|
if perUnit != "" && !strings.Contains(perUnit, "{") {
|
|
if u, ok := perUnitMap[perUnit]; ok {
|
|
perUnit = u
|
|
}
|
|
if perUnit != "" && !slices.Contains(sctx.metricNameTokens, perUnit) {
|
|
sctx.metricNameTokens = append(sctx.metricNameTokens, "per", perUnit)
|
|
}
|
|
}
|
|
|
|
if mm.Type == prompb.MetricTypeCounter {
|
|
sctx.metricNameTokens = moveOrAppend(sctx.metricNameTokens, "total")
|
|
} else if mm.Unit == "1" && mm.Type == prompb.MetricTypeGauge {
|
|
sctx.metricNameTokens = moveOrAppend(sctx.metricNameTokens, "ratio")
|
|
}
|
|
|
|
sctx.metricNameBuf = joinMetricNameTokens(sctx.metricNameBuf[:0], sctx.metricNameTokens)
|
|
return bytesutil.ToUnsafeString(sctx.metricNameBuf)
|
|
}
|
|
|
|
func (sctx *sanitizerContext) splitMetricNameToTokens(metricName string) {
|
|
clear(sctx.metricNameTokens)
|
|
sctx.metricNameTokens = sctx.metricNameTokens[:0]
|
|
|
|
s := metricName
|
|
for len(s) > 0 {
|
|
n := strings.IndexAny(s, "/_.-: ")
|
|
if n < 0 {
|
|
sctx.metricNameTokens = append(sctx.metricNameTokens, s)
|
|
return
|
|
}
|
|
if n > 0 {
|
|
sctx.metricNameTokens = append(sctx.metricNameTokens, s[:n])
|
|
}
|
|
s = s[n+1:]
|
|
}
|
|
}
|
|
|
|
func joinMetricNameTokens(dst []byte, metricNameTokens []string) []byte {
|
|
if len(metricNameTokens) == 0 {
|
|
return dst
|
|
}
|
|
for _, token := range metricNameTokens {
|
|
dst = append(dst, token...)
|
|
dst = append(dst, '_')
|
|
}
|
|
return dst[:len(dst)-1]
|
|
}
|
|
|
|
func moveOrAppend(tokens []string, value string) []string {
|
|
for i := range tokens {
|
|
if tokens[i] == value {
|
|
tokens = append(tokens[:i], tokens[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
return append(tokens, value)
|
|
}
|