prometheus
433 строки · 10.9 Кб
1// Copyright 2015 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package promql_test
15
16import (
17"context"
18"fmt"
19"strconv"
20"strings"
21"testing"
22"time"
23
24"github.com/prometheus/prometheus/model/histogram"
25"github.com/prometheus/prometheus/model/labels"
26"github.com/prometheus/prometheus/promql"
27"github.com/prometheus/prometheus/promql/parser"
28"github.com/prometheus/prometheus/storage"
29"github.com/prometheus/prometheus/tsdb/tsdbutil"
30"github.com/prometheus/prometheus/util/teststorage"
31)
32
33func setupRangeQueryTestData(stor *teststorage.TestStorage, _ *promql.Engine, interval, numIntervals int) error {
34ctx := context.Background()
35
36metrics := []labels.Labels{}
37// Generating test series: a_X, b_X, and h_X, where X can take values of one, ten, or hundred,
38// representing the number of series each metric name contains.
39// Metric a_X and b_X are simple metrics where h_X is a histogram.
40// These metrics will have data for all test time range
41metrics = append(metrics, labels.FromStrings("__name__", "a_one"))
42metrics = append(metrics, labels.FromStrings("__name__", "b_one"))
43for j := 0; j < 10; j++ {
44metrics = append(metrics, labels.FromStrings("__name__", "h_one", "le", strconv.Itoa(j)))
45}
46metrics = append(metrics, labels.FromStrings("__name__", "h_one", "le", "+Inf"))
47
48for i := 0; i < 10; i++ {
49metrics = append(metrics, labels.FromStrings("__name__", "a_ten", "l", strconv.Itoa(i)))
50metrics = append(metrics, labels.FromStrings("__name__", "b_ten", "l", strconv.Itoa(i)))
51for j := 0; j < 10; j++ {
52metrics = append(metrics, labels.FromStrings("__name__", "h_ten", "l", strconv.Itoa(i), "le", strconv.Itoa(j)))
53}
54metrics = append(metrics, labels.FromStrings("__name__", "h_ten", "l", strconv.Itoa(i), "le", "+Inf"))
55}
56
57for i := 0; i < 100; i++ {
58metrics = append(metrics, labels.FromStrings("__name__", "a_hundred", "l", strconv.Itoa(i)))
59metrics = append(metrics, labels.FromStrings("__name__", "b_hundred", "l", strconv.Itoa(i)))
60for j := 0; j < 10; j++ {
61metrics = append(metrics, labels.FromStrings("__name__", "h_hundred", "l", strconv.Itoa(i), "le", strconv.Itoa(j)))
62}
63metrics = append(metrics, labels.FromStrings("__name__", "h_hundred", "l", strconv.Itoa(i), "le", "+Inf"))
64}
65refs := make([]storage.SeriesRef, len(metrics))
66
67// Number points for each different label value of "l" for the sparse series
68pointsPerSparseSeries := numIntervals / 50
69
70for s := 0; s < numIntervals; s++ {
71a := stor.Appender(context.Background())
72ts := int64(s * interval)
73for i, metric := range metrics {
74ref, _ := a.Append(refs[i], metric, ts, float64(s)+float64(i)/float64(len(metrics)))
75refs[i] = ref
76}
77// Generating a sparse time series: each label value of "l" will contain data only for
78// pointsPerSparseSeries points
79metric := labels.FromStrings("__name__", "sparse", "l", strconv.Itoa(s/pointsPerSparseSeries))
80_, err := a.Append(0, metric, ts, float64(s)/float64(len(metrics)))
81if err != nil {
82return err
83}
84if err := a.Commit(); err != nil {
85return err
86}
87}
88
89stor.DB.ForceHeadMMap() // Ensure we have at most one head chunk for every series.
90stor.DB.Compact(ctx)
91return nil
92}
93
94type benchCase struct {
95expr string
96steps int
97}
98
99func rangeQueryCases() []benchCase {
100cases := []benchCase{
101// Plain retrieval.
102{
103expr: "a_X",
104},
105// Simple rate.
106{
107expr: "rate(a_X[1m])",
108},
109{
110expr: "rate(a_X[1m])",
111steps: 10000,
112},
113{
114expr: "rate(sparse[1m])",
115steps: 10000,
116},
117// Holt-Winters and long ranges.
118{
119expr: "holt_winters(a_X[1d], 0.3, 0.3)",
120},
121{
122expr: "changes(a_X[1d])",
123},
124{
125expr: "rate(a_X[1d])",
126},
127{
128expr: "absent_over_time(a_X[1d])",
129},
130// Unary operators.
131{
132expr: "-a_X",
133},
134// Binary operators.
135{
136expr: "a_X - b_X",
137},
138{
139expr: "a_X - b_X",
140steps: 10000,
141},
142{
143expr: "a_X and b_X{l=~'.*[0-4]$'}",
144},
145{
146expr: "a_X or b_X{l=~'.*[0-4]$'}",
147},
148{
149expr: "a_X unless b_X{l=~'.*[0-4]$'}",
150},
151{
152expr: "a_X and b_X{l='notfound'}",
153},
154// Simple functions.
155{
156expr: "abs(a_X)",
157},
158{
159expr: "label_replace(a_X, 'l2', '$1', 'l', '(.*)')",
160},
161{
162expr: "label_join(a_X, 'l2', '-', 'l', 'l')",
163},
164// Simple aggregations.
165{
166expr: "sum(a_X)",
167},
168{
169expr: "sum without (l)(h_X)",
170},
171{
172expr: "sum without (le)(h_X)",
173},
174{
175expr: "sum by (l)(h_X)",
176},
177{
178expr: "sum by (le)(h_X)",
179},
180{
181expr: "count_values('value', h_X)",
182steps: 100,
183},
184{
185expr: "topk(1, a_X)",
186},
187{
188expr: "topk(5, a_X)",
189},
190// Combinations.
191{
192expr: "rate(a_X[1m]) + rate(b_X[1m])",
193},
194{
195expr: "sum without (l)(rate(a_X[1m]))",
196},
197{
198expr: "sum without (l)(rate(a_X[1m])) / sum without (l)(rate(b_X[1m]))",
199},
200{
201expr: "histogram_quantile(0.9, rate(h_X[5m]))",
202},
203// Many-to-one join.
204{
205expr: "a_X + on(l) group_right a_one",
206},
207// Label compared to blank string.
208{
209expr: "count({__name__!=\"\"})",
210steps: 1,
211},
212{
213expr: "count({__name__!=\"\",l=\"\"})",
214steps: 1,
215},
216// Functions which have special handling inside eval()
217{
218expr: "timestamp(a_X)",
219},
220}
221
222// X in an expr will be replaced by different metric sizes.
223tmp := []benchCase{}
224for _, c := range cases {
225if !strings.Contains(c.expr, "X") {
226tmp = append(tmp, c)
227} else {
228tmp = append(tmp, benchCase{expr: strings.ReplaceAll(c.expr, "X", "one"), steps: c.steps})
229tmp = append(tmp, benchCase{expr: strings.ReplaceAll(c.expr, "X", "ten"), steps: c.steps})
230tmp = append(tmp, benchCase{expr: strings.ReplaceAll(c.expr, "X", "hundred"), steps: c.steps})
231}
232}
233cases = tmp
234
235// No step will be replaced by cases with the standard step.
236tmp = []benchCase{}
237for _, c := range cases {
238if c.steps != 0 {
239tmp = append(tmp, c)
240} else {
241tmp = append(tmp, benchCase{expr: c.expr, steps: 1})
242tmp = append(tmp, benchCase{expr: c.expr, steps: 100})
243tmp = append(tmp, benchCase{expr: c.expr, steps: 1000})
244}
245}
246return tmp
247}
248
249func BenchmarkRangeQuery(b *testing.B) {
250stor := teststorage.New(b)
251stor.DB.DisableCompactions() // Don't want auto-compaction disrupting timings.
252defer stor.Close()
253opts := promql.EngineOpts{
254Logger: nil,
255Reg: nil,
256MaxSamples: 50000000,
257Timeout: 100 * time.Second,
258}
259engine := promql.NewEngine(opts)
260
261const interval = 10000 // 10s interval.
262// A day of data plus 10k steps.
263numIntervals := 8640 + 10000
264
265err := setupRangeQueryTestData(stor, engine, interval, numIntervals)
266if err != nil {
267b.Fatal(err)
268}
269cases := rangeQueryCases()
270
271for _, c := range cases {
272name := fmt.Sprintf("expr=%s,steps=%d", c.expr, c.steps)
273b.Run(name, func(b *testing.B) {
274ctx := context.Background()
275b.ReportAllocs()
276for i := 0; i < b.N; i++ {
277qry, err := engine.NewRangeQuery(
278ctx, stor, nil, c.expr,
279time.Unix(int64((numIntervals-c.steps)*10), 0),
280time.Unix(int64(numIntervals*10), 0), time.Second*10)
281if err != nil {
282b.Fatal(err)
283}
284res := qry.Exec(ctx)
285if res.Err != nil {
286b.Fatal(res.Err)
287}
288qry.Close()
289}
290})
291}
292}
293
294func BenchmarkNativeHistograms(b *testing.B) {
295testStorage := teststorage.New(b)
296defer testStorage.Close()
297
298app := testStorage.Appender(context.TODO())
299if err := generateNativeHistogramSeries(app, 3000); err != nil {
300b.Fatal(err)
301}
302if err := app.Commit(); err != nil {
303b.Fatal(err)
304}
305
306start := time.Unix(0, 0)
307end := start.Add(2 * time.Hour)
308step := time.Second * 30
309
310cases := []struct {
311name string
312query string
313}{
314{
315name: "sum",
316query: "sum(native_histogram_series)",
317},
318{
319name: "sum rate with short rate interval",
320query: "sum(rate(native_histogram_series[2m]))",
321},
322{
323name: "sum rate with long rate interval",
324query: "sum(rate(native_histogram_series[20m]))",
325},
326}
327
328opts := promql.EngineOpts{
329Logger: nil,
330Reg: nil,
331MaxSamples: 50000000,
332Timeout: 100 * time.Second,
333EnableAtModifier: true,
334EnableNegativeOffset: true,
335}
336
337b.ResetTimer()
338b.ReportAllocs()
339
340for _, tc := range cases {
341b.Run(tc.name, func(b *testing.B) {
342ng := promql.NewEngine(opts)
343for i := 0; i < b.N; i++ {
344qry, err := ng.NewRangeQuery(context.Background(), testStorage, nil, tc.query, start, end, step)
345if err != nil {
346b.Fatal(err)
347}
348if result := qry.Exec(context.Background()); result.Err != nil {
349b.Fatal(result.Err)
350}
351}
352})
353}
354}
355
356func generateNativeHistogramSeries(app storage.Appender, numSeries int) error {
357commonLabels := []string{labels.MetricName, "native_histogram_series", "foo", "bar"}
358series := make([][]*histogram.Histogram, numSeries)
359for i := range series {
360series[i] = tsdbutil.GenerateTestHistograms(2000)
361}
362higherSchemaHist := &histogram.Histogram{
363Schema: 3,
364PositiveSpans: []histogram.Span{
365{Offset: -5, Length: 2}, // -5 -4
366{Offset: 2, Length: 3}, // -1 0 1
367{Offset: 2, Length: 2}, // 4 5
368},
369PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 3},
370Count: 13,
371}
372for sid, histograms := range series {
373seriesLabels := labels.FromStrings(append(commonLabels, "h", strconv.Itoa(sid))...)
374for i := range histograms {
375ts := time.Unix(int64(i*15), 0).UnixMilli()
376if i == 0 {
377// Inject a histogram with a higher schema.
378if _, err := app.AppendHistogram(0, seriesLabels, ts, higherSchemaHist, nil); err != nil {
379return err
380}
381}
382if _, err := app.AppendHistogram(0, seriesLabels, ts, histograms[i], nil); err != nil {
383return err
384}
385}
386}
387
388return nil
389}
390
391func BenchmarkParser(b *testing.B) {
392cases := []string{
393"a",
394"metric",
395"1",
396"1 >= bool 1",
397"1 + 2/(3*1)",
398"foo or bar",
399"foo and bar unless baz or qux",
400"bar + on(foo) bla / on(baz, buz) group_right(test) blub",
401"foo / ignoring(test,blub) group_left(blub) bar",
402"foo - ignoring(test,blub) group_right(bar,foo) bar",
403`foo{a="b", foo!="bar", test=~"test", bar!~"baz"}`,
404`min_over_time(rate(foo{bar="baz"}[2s])[5m:])[4m:3s]`,
405"sum without(and, by, avg, count, alert, annotations)(some_metric) [30m:10s]",
406}
407errCases := []string{
408"(",
409"}",
410"1 or 1",
411"1 or on(bar) foo",
412"foo unless on(bar) group_left(baz) bar",
413"test[5d] OFFSET 10s [10m:5s]",
414}
415
416for _, c := range cases {
417b.Run(c, func(b *testing.B) {
418b.ReportAllocs()
419for i := 0; i < b.N; i++ {
420parser.ParseExpr(c)
421}
422})
423}
424for _, c := range errCases {
425name := fmt.Sprintf("%s (should fail)", c)
426b.Run(name, func(b *testing.B) {
427b.ReportAllocs()
428for i := 0; i < b.N; i++ {
429parser.ParseExpr(c)
430}
431})
432}
433}
434