| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package prometheus |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | // pkg/prometheus parses an exposition on every scrape across ~19 importing |
| 11 | // collectors, so it is a hot path. Keep these results updated before/after parser |
| 12 | // changes so regressions stay visible. |
| 13 | // |
| 14 | // The payload is the real exposition fixtures the parser tests use: |
| 15 | // testdata/testdata.txt (a go-process scrape of gauges/counters/summaries) plus |
| 16 | // testdata/histogram-meta.txt (the histogram path), so the benchmark exercises |
| 17 | // every assembler branch on representative data. |
| 18 | // |
| 19 | // Command: |
| 20 | // |
| 21 | // go test ./pkg/prometheus -run '^$' -bench parseTo -benchmem |
| 22 | // |
| 23 | // Measured on a developer laptop (Apple M-series, 14 logical CPUs), not CI, so the |
| 24 | // absolute numbers are machine-specific — compare relative before/after deltas. |
| 25 | // |
| 26 | // Legacy fused parser (master, pre-rewrite — captured 2026-06-05): |
| 27 | // |
| 28 | // parseToMetricFamilies 127868 ns/op 73404 B/op 1314 allocs/op |
| 29 | // parseToSeries 111382 ns/op 100444 B/op 1584 allocs/op |
| 30 | // |
| 31 | // Unified driver+assembler parser (this rewrite — captured 2026-06-05): |
| 32 | // |
| 33 | // parseToMetricFamilies 133911 ns/op 73408 B/op 1314 allocs/op |
| 34 | // parseToSeries 112803 ns/op 100448 B/op 1584 allocs/op |
| 35 | // |
| 36 | // Scrape (parseToMetricFamilies) and ScrapeSeries keep the legacy allocation profile |
| 37 | // (flat allocs); ScrapeSeries is the raw-label path (identical to legacy, essentially |
| 38 | // free), Scrape a few percent slower on CPU (per-sample Sample + iterate indirection + |
| 39 | // assembler dispatch — makeSample/applySample, inherent to the single-driver model). |
| 40 | // |
| 41 | // A transform runs with ownLabels=true, so each kept sample owns a copy of its labels |
| 42 | // — the cost metric relabeling pays to mutate safely (captured 2026-06-08): |
| 43 | // |
| 44 | // parseToMetricFamilies 142135 ns/op 73412 B/op 1314 allocs/op |
| 45 | // parseToMetricFamiliesWithTransform 147460 ns/op 93940 B/op 1709 allocs/op |
| 46 | |
| 47 | func readBenchData(tb testing.TB) []byte { |
| 48 | tb.Helper() |
| 49 | var data []byte |
| 50 | for _, f := range []string{"testdata/testdata.txt", "testdata/histogram-meta.txt"} { |
| 51 | b, err := os.ReadFile(f) |
| 52 | if err != nil { |
| 53 | tb.Fatal(err) |
| 54 | } |
| 55 | data = append(data, b...) |
| 56 | data = append(data, '\n') // keep files separated when concatenated |
| 57 | } |
| 58 | return data |
| 59 | } |
| 60 | |
| 61 | func BenchmarkPromTextParser_parseToMetricFamilies(b *testing.B) { |
| 62 | data := readBenchData(b) |
| 63 | var p promTextParser |
| 64 | b.ReportAllocs() |
| 65 | b.SetBytes(int64(len(data))) |
| 66 | b.ResetTimer() |
| 67 | for range b.N { |
| 68 | if _, err := p.parseToMetricFamilies(data, nil); err != nil { |
| 69 | b.Fatal(err) |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | func BenchmarkPromTextParser_parseToSeries(b *testing.B) { |
| 75 | data := readBenchData(b) |
| 76 | var p promTextParser |
| 77 | b.ReportAllocs() |
| 78 | b.SetBytes(int64(len(data))) |
| 79 | b.ResetTimer() |
| 80 | for range b.N { |
| 81 | if _, err := p.parseToSeries(data); err != nil { |
| 82 | b.Fatal(err) |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | func BenchmarkPromTextParser_parseToMetricFamiliesWithTransform(b *testing.B) { |
| 88 | data := readBenchData(b) |
| 89 | var p promTextParser |
| 90 | keep := func(s Sample) (Sample, bool, error) { return s, true, nil } |
| 91 | b.ReportAllocs() |
| 92 | b.SetBytes(int64(len(data))) |
| 93 | b.ResetTimer() |
| 94 | for range b.N { |
| 95 | if _, err := p.parseToMetricFamilies(data, keep); err != nil { |
| 96 | b.Fatal(err) |
| 97 | } |
| 98 | } |
| 99 | } |