mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
This increases the parser performance by 4x-6x. This commit uses the technique similar to https://github.com/VictoriaMetrics/VictoriaLogs/pull/720 goos: linux goarch: amd64 pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/stream cpu: AMD Ryzen 7 PRO 5850U with Radeon Graphics │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ ParseStream/default-metrics-labels-formatting-16 15.565µ ± 1% 2.150µ ± 3% -86.19% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 24.228µ ± 2% 4.355µ ± 1% -82.02% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 23.028µ ± 2% 3.395µ ± 1% -85.26% (p=0.000 n=10) geomean 20.55µ 3.168µ -84.59% │ old.txt │ new.txt │ │ B/s │ B/s vs base │ ParseStream/default-metrics-labels-formatting-16 127.9Mi ± 1% 918.3Mi ± 3% +617.82% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 82.19Mi ± 2% 453.32Mi ± 1% +451.57% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 86.47Mi ± 2% 581.56Mi ± 1% +572.52% (p=0.000 n=10) geomean 96.88Mi 623.3Mi +543.34% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ ParseStream/default-metrics-labels-formatting-16 12.53Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 21.15Ki ± 1% 0.00Ki ± ? -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 20.74Ki ± 1% 0.00Ki ± ? -100.00% (p=0.000 n=10) geomean 17.65Ki ? ¹ ² ¹ summaries must be >0 to compute geomean ² ratios must be >0 to compute geomean │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ ParseStream/default-metrics-labels-formatting-16 426.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 514.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 514.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) geomean 482.8 ? ¹ ²
202 lines
6.0 KiB
Go
202 lines
6.0 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/single-server-victoriametrics/#sending-data-via-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/single-server-victoriametrics/#sending-data-via-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]
|
|
}
|
|
|
|
// See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b8655058501bed61a06bb660869051491f46840b/pkg/translator/prometheus/normalize_label.go#L26
|
|
//
|
|
// 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)
|
|
}
|
|
|
|
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 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)
|
|
}
|