This commit is contained in:
Grégoire 2025-08-05 18:11:56 -03:00 committed by GitHub
commit 3945b525e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 48 additions and 20 deletions

View File

@ -297,6 +297,9 @@ func (c *flagConfig) setFeatureListOptions(logger *slog.Logger) error {
case "use-uncached-io": case "use-uncached-io":
c.tsdb.UseUncachedIO = true c.tsdb.UseUncachedIO = true
logger.Info("Experimental Uncached IO is enabled.") logger.Info("Experimental Uncached IO is enabled.")
case "otel-suffix":
c.scrape.EnableOtelSuffix = true
logger.Info("Experimental otel suffix is enabled.")
default: default:
logger.Warn("Unknown option for --enable-feature", "option", o) logger.Warn("Unknown option for --enable-feature", "option", o)
} }

View File

@ -149,7 +149,7 @@ func benchParse(b *testing.B, data []byte, parser string) {
} }
case "promproto": case "promproto":
newParserFn = func(b []byte, st *labels.SymbolTable) Parser { newParserFn = func(b []byte, st *labels.SymbolTable) Parser {
return NewProtobufParser(b, true, false, st) return NewProtobufParser(b, true, false, false, st)
} }
case "omtext": case "omtext":
newParserFn = func(b []byte, st *labels.SymbolTable) Parser { newParserFn = func(b []byte, st *labels.SymbolTable) Parser {
@ -275,7 +275,7 @@ func BenchmarkCreatedTimestampPromProto(b *testing.B) {
data := createTestProtoBuf(b).Bytes() data := createTestProtoBuf(b).Bytes()
st := labels.NewSymbolTable() st := labels.NewSymbolTable()
p := NewProtobufParser(data, true, false, st) p := NewProtobufParser(data, true, false, false, st)
found := false found := false
Inner: Inner:

View File

@ -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 // An error may also be returned if fallbackType had to be used or there was some
// other error parsing the supplied Content-Type. // other error parsing the supplied Content-Type.
// If the returned parser is nil then the scrape must fail. // 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) mediaType, err := extractMediaType(contentType, fallbackType)
// err may be nil or something we want to warn about. // 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) { return NewOpenMetricsParser(b, st, func(o *openMetricsParserOptions) {
o.skipCTSeries = skipOMCTSeries o.skipCTSeries = skipOMCTSeries
o.enableTypeAndUnitLabels = enableTypeAndUnitLabels o.enableTypeAndUnitLabels = enableTypeAndUnitLabels
o.enableOtelSuffix = enableOtelSuffix
}), err }), err
case "application/vnd.google.protobuf": case "application/vnd.google.protobuf":
return NewProtobufParser(b, parseClassicHistograms, enableTypeAndUnitLabels, st), err return NewProtobufParser(b, parseClassicHistograms, enableTypeAndUnitLabels, enableOtelSuffix, st), err
case "text/plain": case "text/plain":
return NewPromParser(b, st, enableTypeAndUnitLabels), err return NewPromParser(b, st, enableTypeAndUnitLabels), err
default: default:

View File

@ -168,7 +168,7 @@ func TestNewParser(t *testing.T) {
fallbackProtoMediaType := tt.fallbackScrapeProtocol.HeaderMediaType() 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) tt.validateParser(t, p)
if tt.err == "" { if tt.err == "" {
require.NoError(t, err) require.NoError(t, err)

View File

@ -598,7 +598,7 @@ func TestNHCBParser_NoNHCBWhenExponential(t *testing.T) {
func() (string, parserFactory, []int, parserOptions) { func() (string, parserFactory, []int, parserOptions) {
factory := func(keepClassic bool) Parser { factory := func(keepClassic bool) Parser {
inputBuf := createTestProtoBufHistogram(t) 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} 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) p = NewNHCBParser(p, labels.NewSymbolTable(), false)
got := testParse(t, p) got := testParse(t, p)
requireEntries(t, exp, got) requireEntries(t, exp, got)

View File

@ -112,11 +112,13 @@ type OpenMetricsParser struct {
visitedMFName []byte visitedMFName []byte
skipCTSeries bool skipCTSeries bool
enableTypeAndUnitLabels bool enableTypeAndUnitLabels bool
enableOtelSuffix bool
} }
type openMetricsParserOptions struct { type openMetricsParserOptions struct {
skipCTSeries bool skipCTSeries bool
enableTypeAndUnitLabels bool enableTypeAndUnitLabels bool
enableOtelSuffix bool
} }
type OpenMetricsOption func(*openMetricsParserOptions) type OpenMetricsOption func(*openMetricsParserOptions)
@ -155,6 +157,7 @@ func NewOpenMetricsParser(b []byte, st *labels.SymbolTable, opts ...OpenMetricsO
builder: labels.NewScratchBuilderWithSymbolTable(st, 16), builder: labels.NewScratchBuilderWithSymbolTable(st, 16),
skipCTSeries: options.skipCTSeries, skipCTSeries: options.skipCTSeries,
enableTypeAndUnitLabels: options.enableTypeAndUnitLabels, enableTypeAndUnitLabels: options.enableTypeAndUnitLabels,
enableOtelSuffix: options.enableOtelSuffix,
} }
return parser return parser
@ -524,7 +527,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
case tUnit: case tUnit:
p.unit = string(p.text) p.unit = string(p.text)
m := yoloString(p.l.b[p.offsets[0]:p.offsets[1]]) 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] != '_' { 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) return EntryInvalid, fmt.Errorf("unit %q not a suffix of metric %q", p.unit, m)
} }

View File

@ -80,10 +80,12 @@ type ProtobufParser struct {
// native histogram. // native histogram.
parseClassicHistograms bool parseClassicHistograms bool
enableTypeAndUnitLabels bool enableTypeAndUnitLabels bool
enableOtelSuffix bool
} }
// NewProtobufParser returns a parser for the payload in the byte slice. // 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{ return &ProtobufParser{
dec: dto.NewMetricStreamingDecoder(b), dec: dto.NewMetricStreamingDecoder(b),
entryBytes: &bytes.Buffer{}, entryBytes: &bytes.Buffer{},
@ -92,6 +94,7 @@ func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels
state: EntryInvalid, state: EntryInvalid,
parseClassicHistograms: parseClassicHistograms, parseClassicHistograms: parseClassicHistograms,
enableTypeAndUnitLabels: enableTypeAndUnitLabels, 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()) return EntryInvalid, fmt.Errorf("unknown metric type for metric %q: %s", name, p.dec.GetType())
} }
unit := p.dec.GetUnit() 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 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] != '_' { 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) 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() return p.dec.GetName()
} }
if p.fieldPos == -2 { 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 { 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() + "_bucket"
} }
return p.dec.GetName() return p.dec.GetName()

View File

@ -833,7 +833,7 @@ func TestProtobufParse(t *testing.T) {
}{ }{
{ {
name: "parseClassicHistograms=false/enableTypeAndUnitLabels=false", name: "parseClassicHistograms=false/enableTypeAndUnitLabels=false",
parser: NewProtobufParser(inputBuf.Bytes(), false, false, labels.NewSymbolTable()), parser: NewProtobufParser(inputBuf.Bytes(), false, false, false, labels.NewSymbolTable()),
expected: []parsedEntry{ expected: []parsedEntry{
{ {
m: "go_build_info", m: "go_build_info",
@ -1468,7 +1468,7 @@ func TestProtobufParse(t *testing.T) {
}, },
{ {
name: "parseClassicHistograms=false/enableTypeAndUnitLabels=true", name: "parseClassicHistograms=false/enableTypeAndUnitLabels=true",
parser: NewProtobufParser(inputBuf.Bytes(), false, true, labels.NewSymbolTable()), parser: NewProtobufParser(inputBuf.Bytes(), false, true, false, labels.NewSymbolTable()),
expected: []parsedEntry{ expected: []parsedEntry{
{ {
m: "go_build_info", m: "go_build_info",
@ -2140,7 +2140,7 @@ func TestProtobufParse(t *testing.T) {
}, },
{ {
name: "parseClassicHistograms=true/enableTypeAndUnitLabels=false", name: "parseClassicHistograms=true/enableTypeAndUnitLabels=false",
parser: NewProtobufParser(inputBuf.Bytes(), true, false, labels.NewSymbolTable()), parser: NewProtobufParser(inputBuf.Bytes(), true, false, false, labels.NewSymbolTable()),
expected: []parsedEntry{ expected: []parsedEntry{
{ {
m: "go_build_info", m: "go_build_info",

View File

@ -61,7 +61,7 @@ const (
var symbolTable = labels.NewSymbolTable() var symbolTable = labels.NewSymbolTable()
func fuzzParseMetricWithContentType(in []byte, contentType string) int { 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 { if p == nil || warning != nil {
// An invalid content type is being passed, which should not happen // An invalid content type is being passed, which should not happen
// in this context. // in this context.

View File

@ -92,6 +92,9 @@ type Options struct {
// EnableTypeAndUnitLabels // EnableTypeAndUnitLabels
EnableTypeAndUnitLabels bool 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. // Optional HTTP client options to use when scraping.
HTTPClientOptions []config_util.HTTPClientOption HTTPClientOptions []config_util.HTTPClientOption

View File

@ -211,6 +211,7 @@ func newScrapePool(cfg *config.ScrapeConfig, app storage.Appendable, offsetSeed
options.EnableNativeHistogramsIngestion, options.EnableNativeHistogramsIngestion,
options.EnableCreatedTimestampZeroIngestion, options.EnableCreatedTimestampZeroIngestion,
options.EnableTypeAndUnitLabels, options.EnableTypeAndUnitLabels,
options.EnableOtelSuffix,
options.ExtraMetrics, options.ExtraMetrics,
options.AppendMetadata, options.AppendMetadata,
opts.target, opts.target,
@ -933,6 +934,7 @@ type scrapeLoop struct {
enableNativeHistogramIngestion bool enableNativeHistogramIngestion bool
enableCTZeroIngestion bool enableCTZeroIngestion bool
enableTypeAndUnitLabels bool enableTypeAndUnitLabels bool
enableOtelSuffix bool
appender func(ctx context.Context) storage.Appender appender func(ctx context.Context) storage.Appender
symbolTable *labels.SymbolTable symbolTable *labels.SymbolTable
@ -1241,6 +1243,7 @@ func newScrapeLoop(ctx context.Context,
enableNativeHistogramIngestion bool, enableNativeHistogramIngestion bool,
enableCTZeroIngestion bool, enableCTZeroIngestion bool,
enableTypeAndUnitLabels bool, enableTypeAndUnitLabels bool,
enableOtelSuffix bool,
reportExtraMetrics bool, reportExtraMetrics bool,
appendMetadataToWAL bool, appendMetadataToWAL bool,
target *Target, target *Target,
@ -1299,6 +1302,7 @@ func newScrapeLoop(ctx context.Context,
enableNativeHistogramIngestion: enableNativeHistogramIngestion, enableNativeHistogramIngestion: enableNativeHistogramIngestion,
enableCTZeroIngestion: enableCTZeroIngestion, enableCTZeroIngestion: enableCTZeroIngestion,
enableTypeAndUnitLabels: enableTypeAndUnitLabels, enableTypeAndUnitLabels: enableTypeAndUnitLabels,
enableOtelSuffix: enableOtelSuffix,
reportExtraMetrics: reportExtraMetrics, reportExtraMetrics: reportExtraMetrics,
appendMetadataToWAL: appendMetadataToWAL, appendMetadataToWAL: appendMetadataToWAL,
metrics: metrics, metrics: metrics,
@ -1625,7 +1629,7 @@ func (sl *scrapeLoop) append(app storage.Appender, b []byte, contentType string,
return 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 { if p == nil {
sl.l.Error( sl.l.Error(
"Failed to determine correct type of scrape target.", "Failed to determine correct type of scrape target.",

View File

@ -986,6 +986,7 @@ func newBasicScrapeLoopWithFallback(t testing.TB, ctx context.Context, scraper s
false, false,
false, false,
false, false,
false,
true, true,
nil, nil,
false, false,
@ -1135,6 +1136,7 @@ func TestScrapeLoopRun(t *testing.T) {
false, false,
false, false,
false, false,
false,
nil, nil,
false, false,
scrapeMetrics, scrapeMetrics,
@ -1284,6 +1286,7 @@ func TestScrapeLoopMetadata(t *testing.T) {
false, false,
false, false,
false, false,
false,
nil, nil,
false, false,
scrapeMetrics, scrapeMetrics,
@ -2011,7 +2014,7 @@ func TestScrapeLoopAppendCacheEntryButErrNotFound(t *testing.T) {
fakeRef := storage.SeriesRef(1) fakeRef := storage.SeriesRef(1)
expValue := float64(1) expValue := float64(1)
metric := []byte(`metric{n="1"} 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.NotNil(t, p)
require.NoError(t, warning) require.NoError(t, warning)

View File

@ -392,7 +392,7 @@ func TestFederationWithNativeHistograms(t *testing.T) {
require.Equal(t, http.StatusOK, res.Code) require.Equal(t, http.StatusOK, res.Code)
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
require.NoError(t, err) 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 var actVec promql.Vector
metricFamilies := 0 metricFamilies := 0
l := labels.Labels{} l := labels.Labels{}