diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index c4e3fe7914..3bc6dce82b 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -297,6 +297,9 @@ func (c *flagConfig) setFeatureListOptions(logger *slog.Logger) error { case "use-uncached-io": c.tsdb.UseUncachedIO = true logger.Info("Experimental Uncached IO is enabled.") + case "otel-suffix": + c.scrape.EnableOtelSuffix = true + logger.Info("Experimental otel suffix is enabled.") default: logger.Warn("Unknown option for --enable-feature", "option", o) } diff --git a/model/textparse/benchmark_test.go b/model/textparse/benchmark_test.go index e15a6b3846..621e607823 100644 --- a/model/textparse/benchmark_test.go +++ b/model/textparse/benchmark_test.go @@ -149,7 +149,7 @@ func benchParse(b *testing.B, data []byte, parser string) { } case "promproto": newParserFn = func(b []byte, st *labels.SymbolTable) Parser { - return NewProtobufParser(b, true, false, st) + return NewProtobufParser(b, true, false, false, st) } case "omtext": newParserFn = func(b []byte, st *labels.SymbolTable) Parser { @@ -275,7 +275,7 @@ func BenchmarkCreatedTimestampPromProto(b *testing.B) { data := createTestProtoBuf(b).Bytes() st := labels.NewSymbolTable() - p := NewProtobufParser(data, true, false, st) + p := NewProtobufParser(data, true, false, false, st) found := false Inner: diff --git a/model/textparse/interface.go b/model/textparse/interface.go index c97e1f02ee..ce4316dac8 100644 --- a/model/textparse/interface.go +++ b/model/textparse/interface.go @@ -130,7 +130,7 @@ func extractMediaType(contentType, fallbackType string) (string, error) { // An error may also be returned if fallbackType had to be used or there was some // other error parsing the supplied Content-Type. // If the returned parser is nil then the scrape must fail. -func New(b []byte, contentType, fallbackType string, parseClassicHistograms, skipOMCTSeries, enableTypeAndUnitLabels bool, st *labels.SymbolTable) (Parser, error) { +func New(b []byte, contentType, fallbackType string, parseClassicHistograms, skipOMCTSeries, enableTypeAndUnitLabels bool, enableOtelSuffix bool, st *labels.SymbolTable) (Parser, error) { mediaType, err := extractMediaType(contentType, fallbackType) // err may be nil or something we want to warn about. @@ -139,9 +139,10 @@ func New(b []byte, contentType, fallbackType string, parseClassicHistograms, ski return NewOpenMetricsParser(b, st, func(o *openMetricsParserOptions) { o.skipCTSeries = skipOMCTSeries o.enableTypeAndUnitLabels = enableTypeAndUnitLabels + o.enableOtelSuffix = enableOtelSuffix }), err case "application/vnd.google.protobuf": - return NewProtobufParser(b, parseClassicHistograms, enableTypeAndUnitLabels, st), err + return NewProtobufParser(b, parseClassicHistograms, enableTypeAndUnitLabels, enableOtelSuffix, st), err case "text/plain": return NewPromParser(b, st, enableTypeAndUnitLabels), err default: diff --git a/model/textparse/interface_test.go b/model/textparse/interface_test.go index 3034fdade1..bafd3df793 100644 --- a/model/textparse/interface_test.go +++ b/model/textparse/interface_test.go @@ -168,7 +168,7 @@ func TestNewParser(t *testing.T) { fallbackProtoMediaType := tt.fallbackScrapeProtocol.HeaderMediaType() - p, err := New([]byte{}, tt.contentType, fallbackProtoMediaType, false, false, false, labels.NewSymbolTable()) + p, err := New([]byte{}, tt.contentType, fallbackProtoMediaType, false, false, false, false, labels.NewSymbolTable()) tt.validateParser(t, p) if tt.err == "" { require.NoError(t, err) diff --git a/model/textparse/nhcbparse_test.go b/model/textparse/nhcbparse_test.go index 61ef1df2b1..026ee8eed8 100644 --- a/model/textparse/nhcbparse_test.go +++ b/model/textparse/nhcbparse_test.go @@ -598,7 +598,7 @@ func TestNHCBParser_NoNHCBWhenExponential(t *testing.T) { func() (string, parserFactory, []int, parserOptions) { factory := func(keepClassic bool) Parser { inputBuf := createTestProtoBufHistogram(t) - return NewProtobufParser(inputBuf.Bytes(), keepClassic, false, labels.NewSymbolTable()) + return NewProtobufParser(inputBuf.Bytes(), keepClassic, false, false, labels.NewSymbolTable()) } return "ProtoBuf", factory, []int{1, 2, 3}, parserOptions{useUTF8sep: true, hasCreatedTimeStamp: true} }, @@ -1121,7 +1121,7 @@ metric: < }, } - p := NewProtobufParser(buf.Bytes(), false, false, labels.NewSymbolTable()) + p := NewProtobufParser(buf.Bytes(), false, false, false, labels.NewSymbolTable()) p = NewNHCBParser(p, labels.NewSymbolTable(), false) got := testParse(t, p) requireEntries(t, exp, got) diff --git a/model/textparse/openmetricsparse.go b/model/textparse/openmetricsparse.go index a0d259ce7c..cc2e48385d 100644 --- a/model/textparse/openmetricsparse.go +++ b/model/textparse/openmetricsparse.go @@ -112,11 +112,13 @@ type OpenMetricsParser struct { visitedMFName []byte skipCTSeries bool enableTypeAndUnitLabels bool + enableOtelSuffix bool } type openMetricsParserOptions struct { skipCTSeries bool enableTypeAndUnitLabels bool + enableOtelSuffix bool } type OpenMetricsOption func(*openMetricsParserOptions) @@ -155,6 +157,7 @@ func NewOpenMetricsParser(b []byte, st *labels.SymbolTable, opts ...OpenMetricsO builder: labels.NewScratchBuilderWithSymbolTable(st, 16), skipCTSeries: options.skipCTSeries, enableTypeAndUnitLabels: options.enableTypeAndUnitLabels, + enableOtelSuffix: options.enableOtelSuffix, } return parser @@ -524,7 +527,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) { case tUnit: p.unit = string(p.text) m := yoloString(p.l.b[p.offsets[0]:p.offsets[1]]) - if len(p.unit) > 0 { + if len(p.unit) > 0 && !p.enableOtelSuffix { if !strings.HasSuffix(m, p.unit) || len(m) < len(p.unit)+1 || p.l.b[p.offsets[1]-len(p.unit)-1] != '_' { return EntryInvalid, fmt.Errorf("unit %q not a suffix of metric %q", p.unit, m) } diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go index 2ca6c03af7..7ffcf6b2f3 100644 --- a/model/textparse/protobufparse.go +++ b/model/textparse/protobufparse.go @@ -80,10 +80,12 @@ type ProtobufParser struct { // native histogram. parseClassicHistograms bool enableTypeAndUnitLabels bool + + enableOtelSuffix bool } // NewProtobufParser returns a parser for the payload in the byte slice. -func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels bool, st *labels.SymbolTable) Parser { +func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels bool, enableOtelSuffix bool, st *labels.SymbolTable) Parser { return &ProtobufParser{ dec: dto.NewMetricStreamingDecoder(b), entryBytes: &bytes.Buffer{}, @@ -92,6 +94,7 @@ func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels state: EntryInvalid, parseClassicHistograms: parseClassicHistograms, enableTypeAndUnitLabels: enableTypeAndUnitLabels, + enableOtelSuffix: enableOtelSuffix, } } @@ -446,7 +449,7 @@ func (p *ProtobufParser) Next() (Entry, error) { return EntryInvalid, fmt.Errorf("unknown metric type for metric %q: %s", name, p.dec.GetType()) } unit := p.dec.GetUnit() - if len(unit) > 0 { + if len(unit) > 0 && !p.enableOtelSuffix { if p.dec.GetType() == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") { if !strings.HasSuffix(name[:len(name)-6], unit) || len(name)-6 < len(unit)+1 || name[len(name)-6-len(unit)-1] != '_' { return EntryInvalid, fmt.Errorf("unit %q not a suffix of counter %q", unit, name) @@ -609,12 +612,20 @@ func (p *ProtobufParser) getMagicName() string { return p.dec.GetName() } if p.fieldPos == -2 { - return p.dec.GetName() + "_count" + if p.enableOtelSuffix { + return p.dec.GetName() + ".count" + } else { + return p.dec.GetName() + "_count" + } } if p.fieldPos == -1 { - return p.dec.GetName() + "_sum" + if p.enableOtelSuffix { + return p.dec.GetName() + ".sum" + } else { + return p.dec.GetName() + "_sum" + } } - if t == dto.MetricType_HISTOGRAM || t == dto.MetricType_GAUGE_HISTOGRAM { + if !p.enableOtelSuffix && (t == dto.MetricType_HISTOGRAM || t == dto.MetricType_GAUGE_HISTOGRAM) { return p.dec.GetName() + "_bucket" } return p.dec.GetName() diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go index 35a4238fdb..09b2b29e8c 100644 --- a/model/textparse/protobufparse_test.go +++ b/model/textparse/protobufparse_test.go @@ -833,7 +833,7 @@ func TestProtobufParse(t *testing.T) { }{ { name: "parseClassicHistograms=false/enableTypeAndUnitLabels=false", - parser: NewProtobufParser(inputBuf.Bytes(), false, false, labels.NewSymbolTable()), + parser: NewProtobufParser(inputBuf.Bytes(), false, false, false, labels.NewSymbolTable()), expected: []parsedEntry{ { m: "go_build_info", @@ -1468,7 +1468,7 @@ func TestProtobufParse(t *testing.T) { }, { name: "parseClassicHistograms=false/enableTypeAndUnitLabels=true", - parser: NewProtobufParser(inputBuf.Bytes(), false, true, labels.NewSymbolTable()), + parser: NewProtobufParser(inputBuf.Bytes(), false, true, false, labels.NewSymbolTable()), expected: []parsedEntry{ { m: "go_build_info", @@ -2140,7 +2140,7 @@ func TestProtobufParse(t *testing.T) { }, { name: "parseClassicHistograms=true/enableTypeAndUnitLabels=false", - parser: NewProtobufParser(inputBuf.Bytes(), true, false, labels.NewSymbolTable()), + parser: NewProtobufParser(inputBuf.Bytes(), true, false, false, labels.NewSymbolTable()), expected: []parsedEntry{ { m: "go_build_info", diff --git a/promql/fuzz.go b/promql/fuzz.go index 362b33301d..73c37c8198 100644 --- a/promql/fuzz.go +++ b/promql/fuzz.go @@ -61,7 +61,7 @@ const ( var symbolTable = labels.NewSymbolTable() func fuzzParseMetricWithContentType(in []byte, contentType string) int { - p, warning := textparse.New(in, contentType, "", false, false, false, symbolTable) + p, warning := textparse.New(in, contentType, "", false, false, false, false, symbolTable) if p == nil || warning != nil { // An invalid content type is being passed, which should not happen // in this context. diff --git a/scrape/manager.go b/scrape/manager.go index c2da455858..50d81c01a4 100644 --- a/scrape/manager.go +++ b/scrape/manager.go @@ -92,6 +92,9 @@ type Options struct { // EnableTypeAndUnitLabels EnableTypeAndUnitLabels bool + // Allows metric names not to be suffixed with their unit and use dots for magic suffix (e.g. ".sum"). + EnableOtelSuffix bool + // Optional HTTP client options to use when scraping. HTTPClientOptions []config_util.HTTPClientOption diff --git a/scrape/scrape.go b/scrape/scrape.go index 84e00af600..7df6646994 100644 --- a/scrape/scrape.go +++ b/scrape/scrape.go @@ -211,6 +211,7 @@ func newScrapePool(cfg *config.ScrapeConfig, app storage.Appendable, offsetSeed options.EnableNativeHistogramsIngestion, options.EnableCreatedTimestampZeroIngestion, options.EnableTypeAndUnitLabels, + options.EnableOtelSuffix, options.ExtraMetrics, options.AppendMetadata, opts.target, @@ -933,6 +934,7 @@ type scrapeLoop struct { enableNativeHistogramIngestion bool enableCTZeroIngestion bool enableTypeAndUnitLabels bool + enableOtelSuffix bool appender func(ctx context.Context) storage.Appender symbolTable *labels.SymbolTable @@ -1241,6 +1243,7 @@ func newScrapeLoop(ctx context.Context, enableNativeHistogramIngestion bool, enableCTZeroIngestion bool, enableTypeAndUnitLabels bool, + enableOtelSuffix bool, reportExtraMetrics bool, appendMetadataToWAL bool, target *Target, @@ -1299,6 +1302,7 @@ func newScrapeLoop(ctx context.Context, enableNativeHistogramIngestion: enableNativeHistogramIngestion, enableCTZeroIngestion: enableCTZeroIngestion, enableTypeAndUnitLabels: enableTypeAndUnitLabels, + enableOtelSuffix: enableOtelSuffix, reportExtraMetrics: reportExtraMetrics, appendMetadataToWAL: appendMetadataToWAL, metrics: metrics, @@ -1625,7 +1629,7 @@ func (sl *scrapeLoop) append(app storage.Appender, b []byte, contentType string, return } - p, err := textparse.New(b, contentType, sl.fallbackScrapeProtocol, sl.alwaysScrapeClassicHist, sl.enableCTZeroIngestion, sl.enableTypeAndUnitLabels, sl.symbolTable) + p, err := textparse.New(b, contentType, sl.fallbackScrapeProtocol, sl.alwaysScrapeClassicHist, sl.enableCTZeroIngestion, sl.enableTypeAndUnitLabels, sl.enableOtelSuffix, sl.symbolTable) if p == nil { sl.l.Error( "Failed to determine correct type of scrape target.", diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go index c226be2d33..bb94caca31 100644 --- a/scrape/scrape_test.go +++ b/scrape/scrape_test.go @@ -986,6 +986,7 @@ func newBasicScrapeLoopWithFallback(t testing.TB, ctx context.Context, scraper s false, false, false, + false, true, nil, false, @@ -1135,6 +1136,7 @@ func TestScrapeLoopRun(t *testing.T) { false, false, false, + false, nil, false, scrapeMetrics, @@ -1284,6 +1286,7 @@ func TestScrapeLoopMetadata(t *testing.T) { false, false, false, + false, nil, false, scrapeMetrics, @@ -2011,7 +2014,7 @@ func TestScrapeLoopAppendCacheEntryButErrNotFound(t *testing.T) { fakeRef := storage.SeriesRef(1) expValue := float64(1) metric := []byte(`metric{n="1"} 1`) - p, warning := textparse.New(metric, "text/plain", "", false, false, false, labels.NewSymbolTable()) + p, warning := textparse.New(metric, "text/plain", "", false, false, false, false, labels.NewSymbolTable()) require.NotNil(t, p) require.NoError(t, warning) diff --git a/web/federate_test.go b/web/federate_test.go index 7bebf506de..44400b55a3 100644 --- a/web/federate_test.go +++ b/web/federate_test.go @@ -392,7 +392,7 @@ func TestFederationWithNativeHistograms(t *testing.T) { require.Equal(t, http.StatusOK, res.Code) body, err := io.ReadAll(res.Body) require.NoError(t, err) - p := textparse.NewProtobufParser(body, false, false, labels.NewSymbolTable()) + p := textparse.NewProtobufParser(body, false, false, false, labels.NewSymbolTable()) var actVec promql.Vector metricFamilies := 0 l := labels.Labels{}