mirror of
https://github.com/prometheus/prometheus.git
synced 2025-02-21 03:16:00 -08:00
* model/textparse: Change parser interface Metric(...) string to Labels(...) Simplified the interface given no one is using the return argument. Renamed for clarity too. Found and discussed https://github.com/prometheus/prometheus/pull/15731#discussion_r1950916842 Signed-off-by: bwplotka <bwplotka@gmail.com> * Fixed comments; optimized not needed copy for om and text. Signed-off-by: bwplotka <bwplotka@gmail.com> --------- Signed-off-by: bwplotka <bwplotka@gmail.com>
265 lines
7.2 KiB
Go
265 lines
7.2 KiB
Go
// Copyright 2024 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package textparse
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/prometheus/prometheus/model/exemplar"
|
|
"github.com/prometheus/prometheus/model/labels"
|
|
|
|
"github.com/prometheus/common/expfmt"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// BenchmarkParse... set of benchmarks analyze efficiency of parsing various
|
|
// datasets with different parsers. It mimics how scrape/scrape.go#append use parsers
|
|
// and allows comparison with expfmt decoders if applicable.
|
|
//
|
|
// NOTE(bwplotka): Previous iterations of this benchmark had different cases for isolated
|
|
// Series, Series+Metrics with and without reuse, Series+CT. Those cases are sometimes
|
|
// good to know if you are working on a certain optimization, but it does not
|
|
// make sense to persist such cases for everybody (e.g. for CI one day).
|
|
// For local iteration, feel free to adjust cases/comment out code etc.
|
|
//
|
|
// NOTE(bwplotka): Those benchmarks are purposefully categorized per data-sets,
|
|
// to avoid temptation to assess "what parser (OM, proto, prom) is the fastest,
|
|
// in general" here due to not every parser supporting every data set type.
|
|
// Use scrape.BenchmarkScrapeLoopAppend if you want one benchmark comparing parsers fairly.
|
|
|
|
/*
|
|
export bench=v1 && go test ./model/textparse/... \
|
|
-run '^$' -bench '^BenchmarkParsePromText' \
|
|
-benchtime 2s -count 6 -cpu 2 -benchmem -timeout 999m \
|
|
| tee ${bench}.txt
|
|
*/
|
|
func BenchmarkParsePromText(b *testing.B) {
|
|
data := readTestdataFile(b, "alltypes.237mfs.prom.txt")
|
|
|
|
for _, parser := range []string{
|
|
"promtext",
|
|
"omtext", // Compare how omtext parser deals with Prometheus text format.
|
|
"expfmt-promtext",
|
|
} {
|
|
b.Run(fmt.Sprintf("parser=%v", parser), func(b *testing.B) {
|
|
if strings.HasPrefix(parser, "expfmt-") {
|
|
benchExpFmt(b, data, parser)
|
|
} else {
|
|
benchParse(b, data, parser)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/*
|
|
export bench=v1 && go test ./model/textparse/... \
|
|
-run '^$' -bench '^BenchmarkParsePromText_NoMeta' \
|
|
-benchtime 2s -count 6 -cpu 2 -benchmem -timeout 999m \
|
|
| tee ${bench}.txt
|
|
*/
|
|
func BenchmarkParsePromText_NoMeta(b *testing.B) {
|
|
data := readTestdataFile(b, "alltypes.237mfs.nometa.prom.txt")
|
|
|
|
for _, parser := range []string{
|
|
"promtext",
|
|
"expfmt-promtext",
|
|
} {
|
|
b.Run(fmt.Sprintf("parser=%v", parser), func(b *testing.B) {
|
|
if strings.HasPrefix(parser, "expfmt-") {
|
|
benchExpFmt(b, data, parser)
|
|
} else {
|
|
benchParse(b, data, parser)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/*
|
|
export bench=v1 && go test ./model/textparse/... \
|
|
-run '^$' -bench '^BenchmarkParseOMText' \
|
|
-benchtime 2s -count 6 -cpu 2 -benchmem -timeout 999m \
|
|
| tee ${bench}.txt
|
|
*/
|
|
func BenchmarkParseOMText(b *testing.B) {
|
|
data := readTestdataFile(b, "alltypes.5mfs.om.txt")
|
|
// TODO(bwplotka): Add comparison with expfmt.TypeOpenMetrics once expfmt
|
|
// support OM exemplars, see https://github.com/prometheus/common/issues/703.
|
|
benchParse(b, data, "omtext")
|
|
}
|
|
|
|
/*
|
|
export bench=v1 && go test ./model/textparse/... \
|
|
-run '^$' -bench '^BenchmarkParsePromProto' \
|
|
-benchtime 2s -count 6 -cpu 2 -benchmem -timeout 999m \
|
|
| tee ${bench}.txt
|
|
*/
|
|
func BenchmarkParsePromProto(b *testing.B) {
|
|
data := createTestProtoBuf(b).Bytes()
|
|
// TODO(bwplotka): Add comparison with expfmt.TypeProtoDelim once expfmt
|
|
// support GAUGE_HISTOGRAM, see https://github.com/prometheus/common/issues/430.
|
|
benchParse(b, data, "promproto")
|
|
}
|
|
|
|
/*
|
|
export bench=v1 && go test ./model/textparse/... \
|
|
-run '^$' -bench '^BenchmarkParseOpenMetricsNHCB' \
|
|
-benchtime 2s -count 6 -cpu 2 -benchmem -timeout 999m \
|
|
| tee ${bench}.txt
|
|
*/
|
|
func BenchmarkParseOpenMetricsNHCB(b *testing.B) {
|
|
data := readTestdataFile(b, "1histogram.om.txt")
|
|
|
|
for _, parser := range []string{
|
|
"omtext", // Measure OM parser baseline for histograms.
|
|
"omtext_with_nhcb", // Measure NHCB over OM parser.
|
|
} {
|
|
b.Run(fmt.Sprintf("parser=%v", parser), func(b *testing.B) {
|
|
benchParse(b, data, parser)
|
|
})
|
|
}
|
|
}
|
|
|
|
func benchParse(b *testing.B, data []byte, parser string) {
|
|
type newParser func([]byte, *labels.SymbolTable) Parser
|
|
|
|
var newParserFn newParser
|
|
switch parser {
|
|
case "promtext":
|
|
newParserFn = NewPromParser
|
|
case "promproto":
|
|
newParserFn = func(b []byte, st *labels.SymbolTable) Parser {
|
|
return NewProtobufParser(b, true, st)
|
|
}
|
|
case "omtext":
|
|
newParserFn = func(b []byte, st *labels.SymbolTable) Parser {
|
|
return NewOpenMetricsParser(b, st, WithOMParserCTSeriesSkipped())
|
|
}
|
|
case "omtext_with_nhcb":
|
|
newParserFn = func(b []byte, st *labels.SymbolTable) Parser {
|
|
p := NewOpenMetricsParser(b, st, WithOMParserCTSeriesSkipped())
|
|
return NewNHCBParser(p, st, false)
|
|
}
|
|
default:
|
|
b.Fatal("unknown parser", parser)
|
|
}
|
|
|
|
var (
|
|
res labels.Labels
|
|
e exemplar.Exemplar
|
|
)
|
|
|
|
b.SetBytes(int64(len(data)))
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
st := labels.NewSymbolTable()
|
|
for i := 0; i < b.N; i++ {
|
|
p := newParserFn(data, st)
|
|
|
|
Inner:
|
|
for {
|
|
t, err := p.Next()
|
|
switch t {
|
|
case EntryInvalid:
|
|
if errors.Is(err, io.EOF) {
|
|
break Inner
|
|
}
|
|
b.Fatal(err)
|
|
case EntryType:
|
|
_, _ = p.Type()
|
|
continue
|
|
case EntryHelp:
|
|
_, _ = p.Help()
|
|
continue
|
|
case EntryUnit:
|
|
_, _ = p.Unit()
|
|
continue
|
|
case EntryComment:
|
|
continue
|
|
case EntryHistogram:
|
|
_, _, _, _ = p.Histogram()
|
|
case EntrySeries:
|
|
_, _, _ = p.Series()
|
|
default:
|
|
b.Fatal("not implemented entry", t)
|
|
}
|
|
|
|
p.Labels(&res)
|
|
_ = p.CreatedTimestamp()
|
|
for hasExemplar := p.Exemplar(&e); hasExemplar; hasExemplar = p.Exemplar(&e) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchExpFmt(b *testing.B, data []byte, expFormatTypeStr string) {
|
|
expfmtFormatType := expfmt.TypeUnknown
|
|
switch expFormatTypeStr {
|
|
case "expfmt-promtext":
|
|
expfmtFormatType = expfmt.TypeProtoText
|
|
case "expfmt-promproto":
|
|
expfmtFormatType = expfmt.TypeProtoDelim
|
|
case "expfmt-omtext":
|
|
expfmtFormatType = expfmt.TypeOpenMetrics
|
|
default:
|
|
b.Fatal("unknown expfmt format type", expFormatTypeStr)
|
|
}
|
|
|
|
b.SetBytes(int64(len(data)))
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
decSamples := make(model.Vector, 0, 50)
|
|
sdec := expfmt.SampleDecoder{
|
|
Dec: expfmt.NewDecoder(bytes.NewReader(data), expfmt.NewFormat(expfmtFormatType)),
|
|
Opts: &expfmt.DecodeOptions{
|
|
Timestamp: model.TimeFromUnixNano(0),
|
|
},
|
|
}
|
|
|
|
for {
|
|
if err := sdec.Decode(&decSamples); err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
b.Fatal(err)
|
|
}
|
|
decSamples = decSamples[:0]
|
|
}
|
|
}
|
|
}
|
|
|
|
func readTestdataFile(tb testing.TB, file string) []byte {
|
|
tb.Helper()
|
|
|
|
f, err := os.Open(filepath.Join("testdata", file))
|
|
require.NoError(tb, err)
|
|
|
|
tb.Cleanup(func() {
|
|
_ = f.Close()
|
|
})
|
|
buf, err := io.ReadAll(f)
|
|
require.NoError(tb, err)
|
|
return buf
|
|
}
|