refactor(textparse): Introduce Variadic options in textParse.New (#17155)

* refactor(textparse): introduce ParserOptions struct for cleaner parser initialization

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(fuzz): update fuzzParseMetricWithContentType to use ParserOptions

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parser): simplify ParserOptions usage in tests and implementations

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parse): using variadic options

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parser): add fallbackType & SymbolTable to variadic options

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parser): private fields

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(scrape): compose parser options

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parser): add comments

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parser): update to use ParserOptions struct for configuration

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(scrape): remove unused parserOptions field from scrapeLoop

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

* refactor(parser): update ParserOptions field names and add comments for clarity

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>

---------

Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>
This commit is contained in:
NamanParlecha 2025-09-11 15:19:42 +05:30 committed by GitHub
parent d7e9a2ffb0
commit 594f9d63a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 60 additions and 27 deletions

View File

@ -157,7 +157,7 @@ func benchParse(b *testing.B, data []byte, parser string) {
} }
case "omtext_with_nhcb": case "omtext_with_nhcb":
newParserFn = func(buf []byte, st *labels.SymbolTable) Parser { newParserFn = func(buf []byte, st *labels.SymbolTable) Parser {
p, err := New(buf, "application/openmetrics-text", "", false, true, false, false, st) p, err := New(buf, "application/openmetrics-text", st, ParserOptions{ConvertClassicHistogramsToNHCB: true})
require.NoError(b, err) require.NoError(b, err)
return p return p
} }

View File

@ -122,6 +122,28 @@ func extractMediaType(contentType, fallbackType string) (string, error) {
return fallbackType, fmt.Errorf("received unsupported Content-Type %q, using fallback_scrape_protocol %q", contentType, fallbackType) return fallbackType, fmt.Errorf("received unsupported Content-Type %q, using fallback_scrape_protocol %q", contentType, fallbackType)
} }
type ParserOptions struct {
// EnableTypeAndUnitLabels enables parsing and inclusion of type and unit labels
// in the parsed metrics.
EnableTypeAndUnitLabels bool
// ConvertClassicHistogramsToNHCB enables conversion of classic histograms
// to native histogram custom buckets (NHCB) format.
ConvertClassicHistogramsToNHCB bool
// KeepClassicOnClassicAndNativeHistograms causes parser to output classic histogram
// that is also present as a native histogram. (Proto parsing only).
KeepClassicOnClassicAndNativeHistograms bool
// OpenMetricsSkipCTSeries determines whether to skip `_created` timestamp series
// during (OpenMetrics parsing only).
OpenMetricsSkipCTSeries bool
// FallbackContentType specifies the fallback content type to use when the provided
// Content-Type header cannot be parsed or is not supported.
FallbackContentType string
}
// New returns a new parser of the byte slice. // New returns a new parser of the byte slice.
// //
// This function no longer guarantees to return a valid parser. // This function no longer guarantees to return a valid parser.
@ -130,27 +152,31 @@ 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, convertClassicHistogramsToNHCB, skipOMCTSeries, enableTypeAndUnitLabels bool, st *labels.SymbolTable) (Parser, error) { func New(b []byte, contentType string, st *labels.SymbolTable, opts ParserOptions) (Parser, error) {
mediaType, err := extractMediaType(contentType, fallbackType) if st == nil {
st = labels.NewSymbolTable()
}
mediaType, err := extractMediaType(contentType, opts.FallbackContentType)
// err may be nil or something we want to warn about. // err may be nil or something we want to warn about.
var baseParser Parser var baseParser Parser
switch mediaType { switch mediaType {
case "application/openmetrics-text": case "application/openmetrics-text":
baseParser = NewOpenMetricsParser(b, st, func(o *openMetricsParserOptions) { baseParser = NewOpenMetricsParser(b, st, func(o *openMetricsParserOptions) {
o.skipCTSeries = skipOMCTSeries o.skipCTSeries = opts.OpenMetricsSkipCTSeries
o.enableTypeAndUnitLabels = enableTypeAndUnitLabels o.enableTypeAndUnitLabels = opts.EnableTypeAndUnitLabels
}) })
case "application/vnd.google.protobuf": case "application/vnd.google.protobuf":
return NewProtobufParser(b, parseClassicHistograms, convertClassicHistogramsToNHCB, enableTypeAndUnitLabels, st), err return NewProtobufParser(b, opts.KeepClassicOnClassicAndNativeHistograms, opts.ConvertClassicHistogramsToNHCB, opts.EnableTypeAndUnitLabels, st), err
case "text/plain": case "text/plain":
baseParser = NewPromParser(b, st, enableTypeAndUnitLabels) baseParser = NewPromParser(b, st, opts.EnableTypeAndUnitLabels)
default: default:
return nil, err return nil, err
} }
if baseParser != nil && convertClassicHistogramsToNHCB { if baseParser != nil && opts.ConvertClassicHistogramsToNHCB {
baseParser = NewNHCBParser(baseParser, st, parseClassicHistograms) baseParser = NewNHCBParser(baseParser, st, opts.KeepClassicOnClassicAndNativeHistograms)
} }
return baseParser, err return baseParser, err

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, false, labels.NewSymbolTable()) p, err := New([]byte{}, tt.contentType, labels.NewSymbolTable(), ParserOptions{FallbackContentType: fallbackProtoMediaType})
tt.validateParser(t, p) tt.validateParser(t, p)
if tt.err == "" { if tt.err == "" {
require.NoError(t, err) require.NoError(t, err)

View File

@ -443,7 +443,7 @@ foobar{quantile="0.99"} 150.1`
}, },
} }
p, err := New([]byte(input), "application/openmetrics-text", "", false, true, false, false, labels.NewSymbolTable()) p, err := New([]byte(input), "application/openmetrics-text", labels.NewSymbolTable(), ParserOptions{ConvertClassicHistogramsToNHCB: true})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, p) require.NotNil(t, p)
got := testParse(t, p) got := testParse(t, p)
@ -509,7 +509,7 @@ something_bucket{a="b",le="+Inf"} 9 # {id="something-test"} 2e100 123.000
}, },
} }
p, err := New([]byte(input), "application/openmetrics-text", "", false, true, false, false, labels.NewSymbolTable()) p, err := New([]byte(input), "application/openmetrics-text", labels.NewSymbolTable(), ParserOptions{ConvertClassicHistogramsToNHCB: true})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, p) require.NotNil(t, p)
got := testParse(t, p) got := testParse(t, p)
@ -596,21 +596,21 @@ func TestNHCBParser_NoNHCBWhenExponential(t *testing.T) {
func() (string, parserFactory, []int, parserOptions) { func() (string, parserFactory, []int, parserOptions) {
factory := func(keepClassic, nhcb bool) (Parser, error) { factory := func(keepClassic, nhcb bool) (Parser, error) {
inputBuf := createTestProtoBufHistogram(t) inputBuf := createTestProtoBufHistogram(t)
return New(inputBuf.Bytes(), "application/vnd.google.protobuf", "", keepClassic, nhcb, false, false, labels.NewSymbolTable()) return New(inputBuf.Bytes(), "application/vnd.google.protobuf", labels.NewSymbolTable(), ParserOptions{KeepClassicOnClassicAndNativeHistograms: keepClassic, ConvertClassicHistogramsToNHCB: nhcb})
} }
return "ProtoBuf", factory, []int{1, 2, 3}, parserOptions{useUTF8sep: true, hasCreatedTimeStamp: true} return "ProtoBuf", factory, []int{1, 2, 3}, parserOptions{useUTF8sep: true, hasCreatedTimeStamp: true}
}, },
func() (string, parserFactory, []int, parserOptions) { func() (string, parserFactory, []int, parserOptions) {
factory := func(keepClassic, nhcb bool) (Parser, error) { factory := func(keepClassic, nhcb bool) (Parser, error) {
input := createTestOpenMetricsHistogram() input := createTestOpenMetricsHistogram()
return New([]byte(input), "application/openmetrics-text", "", keepClassic, nhcb, false, false, labels.NewSymbolTable()) return New([]byte(input), "application/openmetrics-text", labels.NewSymbolTable(), ParserOptions{KeepClassicOnClassicAndNativeHistograms: keepClassic, ConvertClassicHistogramsToNHCB: nhcb})
} }
return "OpenMetrics", factory, []int{1}, parserOptions{hasCreatedTimeStamp: true} return "OpenMetrics", factory, []int{1}, parserOptions{hasCreatedTimeStamp: true}
}, },
func() (string, parserFactory, []int, parserOptions) { func() (string, parserFactory, []int, parserOptions) {
factory := func(keepClassic, nhcb bool) (Parser, error) { factory := func(keepClassic, nhcb bool) (Parser, error) {
input := createTestPromHistogram() input := createTestPromHistogram()
return New([]byte(input), "text/plain", "", keepClassic, nhcb, false, false, labels.NewSymbolTable()) return New([]byte(input), "text/plain", labels.NewSymbolTable(), ParserOptions{KeepClassicOnClassicAndNativeHistograms: keepClassic, ConvertClassicHistogramsToNHCB: nhcb})
} }
return "Prometheus", factory, []int{1}, parserOptions{} return "Prometheus", factory, []int{1}, parserOptions{}
}, },
@ -956,7 +956,7 @@ something_bucket{a="b",le="+Inf"} 9
}, },
} }
p, err := New([]byte(input), "application/openmetrics-text", "", false, true, false, false, labels.NewSymbolTable()) p, err := New([]byte(input), "application/openmetrics-text", labels.NewSymbolTable(), ParserOptions{ConvertClassicHistogramsToNHCB: true})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, p) require.NotNil(t, p)
got := testParse(t, p) got := testParse(t, p)
@ -1087,7 +1087,7 @@ metric: <
}, },
} }
p, err := New(buf.Bytes(), "application/vnd.google.protobuf", "", false, true, false, false, labels.NewSymbolTable()) p, err := New(buf.Bytes(), "application/vnd.google.protobuf", labels.NewSymbolTable(), ParserOptions{ConvertClassicHistogramsToNHCB: true})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, p) require.NotNil(t, p)
got := testParse(t, p) got := testParse(t, p)
@ -1149,7 +1149,7 @@ metric: <
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.typ, func(t *testing.T) { t.Run(tc.typ, func(t *testing.T) {
p, err := New(tc.input, tc.typ, "", false, true, false, false, labels.NewSymbolTable()) p, err := New(tc.input, tc.typ, labels.NewSymbolTable(), ParserOptions{ConvertClassicHistogramsToNHCB: true})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, p) require.NotNil(t, p)

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, false, symbolTable) p, warning := textparse.New(in, contentType, symbolTable, textparse.ParserOptions{})
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

@ -930,16 +930,17 @@ type scrapeLoop struct {
labelLimits *labelLimits labelLimits *labelLimits
interval time.Duration interval time.Duration
timeout time.Duration timeout time.Duration
alwaysScrapeClassicHist bool
convertClassicHistToNHCB bool
validationScheme model.ValidationScheme validationScheme model.ValidationScheme
escapingScheme model.EscapingScheme escapingScheme model.EscapingScheme
alwaysScrapeClassicHist bool
convertClassicHistToNHCB bool
enableCTZeroIngestion bool
enableTypeAndUnitLabels bool
fallbackScrapeProtocol string fallbackScrapeProtocol string
// Feature flagged options. // Feature flagged options.
enableNativeHistogramIngestion bool enableNativeHistogramIngestion bool
enableCTZeroIngestion bool
enableTypeAndUnitLabels bool
appender func(ctx context.Context) storage.Appender appender func(ctx context.Context) storage.Appender
symbolTable *labels.SymbolTable symbolTable *labels.SymbolTable
@ -1305,16 +1306,16 @@ func newScrapeLoop(ctx context.Context,
timeout: timeout, timeout: timeout,
alwaysScrapeClassicHist: alwaysScrapeClassicHist, alwaysScrapeClassicHist: alwaysScrapeClassicHist,
convertClassicHistToNHCB: convertClassicHistToNHCB, convertClassicHistToNHCB: convertClassicHistToNHCB,
enableNativeHistogramIngestion: enableNativeHistogramIngestion,
enableCTZeroIngestion: enableCTZeroIngestion, enableCTZeroIngestion: enableCTZeroIngestion,
enableTypeAndUnitLabels: enableTypeAndUnitLabels, enableTypeAndUnitLabels: enableTypeAndUnitLabels,
fallbackScrapeProtocol: fallbackScrapeProtocol,
enableNativeHistogramIngestion: enableNativeHistogramIngestion,
reportExtraMetrics: reportExtraMetrics, reportExtraMetrics: reportExtraMetrics,
appendMetadataToWAL: appendMetadataToWAL, appendMetadataToWAL: appendMetadataToWAL,
metrics: metrics, metrics: metrics,
skipOffsetting: skipOffsetting, skipOffsetting: skipOffsetting,
validationScheme: validationScheme, validationScheme: validationScheme,
escapingScheme: escapingScheme, escapingScheme: escapingScheme,
fallbackScrapeProtocol: fallbackScrapeProtocol,
} }
sl.ctx, sl.cancel = context.WithCancel(ctx) sl.ctx, sl.cancel = context.WithCancel(ctx)
@ -1634,7 +1635,13 @@ func (sl *scrapeLoop) append(app storage.Appender, b []byte, contentType string,
return return
} }
p, err := textparse.New(b, contentType, sl.fallbackScrapeProtocol, sl.alwaysScrapeClassicHist, sl.convertClassicHistToNHCB, sl.enableCTZeroIngestion, sl.enableTypeAndUnitLabels, sl.symbolTable) p, err := textparse.New(b, contentType, sl.symbolTable, textparse.ParserOptions{
EnableTypeAndUnitLabels: sl.enableTypeAndUnitLabels,
ConvertClassicHistogramsToNHCB: sl.convertClassicHistToNHCB,
KeepClassicOnClassicAndNativeHistograms: sl.alwaysScrapeClassicHist,
OpenMetricsSkipCTSeries: sl.enableCTZeroIngestion,
FallbackContentType: sl.fallbackScrapeProtocol,
})
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

@ -2097,7 +2097,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, false, labels.NewSymbolTable()) p, warning := textparse.New(metric, "text/plain", labels.NewSymbolTable(), textparse.ParserOptions{})
require.NotNil(t, p) require.NotNil(t, p)
require.NoError(t, warning) require.NoError(t, warning)