Avoid unnecessary allocations in recording rule evaluation (#11812)

Re-use the Builder each time round the loop.
This commit is contained in:
Charles Korn 2023-03-08 23:57:19 +11:00 committed by GitHub
parent 80cf90b080
commit 3db98d7dde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 121 additions and 52 deletions

View file

@ -79,19 +79,21 @@ func (rule *RecordingRule) Eval(ctx context.Context, ts time.Time, query QueryFu
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Override the metric name and labels. // Override the metric name and labels.
lb := labels.NewBuilder(labels.EmptyLabels())
for i := range vector { for i := range vector {
sample := &vector[i] sample := &vector[i]
lb := labels.NewBuilder(sample.Metric) lb.Reset(sample.Metric)
lb.Set(labels.MetricName, rule.name) lb.Set(labels.MetricName, rule.name)
rule.labels.Range(func(l labels.Label) { rule.labels.Range(func(l labels.Label) {
lb.Set(l.Name, l.Value) lb.Set(l.Name, l.Value)
}) })
sample.Metric = lb.Labels(labels.EmptyLabels()) sample.Metric = lb.Labels(sample.Metric)
} }
// Check that the rule does not produce identical metrics after applying // Check that the rule does not produce identical metrics after applying

View file

@ -27,59 +27,126 @@ import (
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
) )
func TestRuleEval(t *testing.T) { var (
storage := teststorage.New(t) ruleEvaluationTime = time.Unix(0, 0).UTC()
defer storage.Close() exprWithMetricName, _ = parser.ParseExpr(`sort(metric)`)
exprWithoutMetricName, _ = parser.ParseExpr(`sort(metric + metric)`)
)
opts := promql.EngineOpts{ var ruleEvalTestScenarios = []struct {
Logger: nil,
Reg: nil,
MaxSamples: 10,
Timeout: 10 * time.Second,
}
engine := promql.NewEngine(opts)
ctx, cancelCtx := context.WithCancel(context.Background())
defer cancelCtx()
now := time.Now()
suite := []struct {
name string name string
ruleLabels labels.Labels
expr parser.Expr expr parser.Expr
labels labels.Labels expected promql.Vector
result promql.Vector
err string
}{ }{
{ {
name: "nolabels", name: "no labels in recording rule, metric name in query result",
expr: &parser.NumberLiteral{Val: 1}, ruleLabels: labels.EmptyLabels(),
labels: labels.Labels{}, expr: exprWithMetricName,
result: promql.Vector{promql.Sample{ expected: promql.Vector{
Metric: labels.FromStrings("__name__", "nolabels"), promql.Sample{
Point: promql.Point{V: 1, T: timestamp.FromTime(now)}, Metric: labels.FromStrings("__name__", "test_rule", "label_a", "1", "label_b", "3"),
}}, Point: promql.Point{V: 1, T: timestamp.FromTime(ruleEvaluationTime)},
},
promql.Sample{
Metric: labels.FromStrings("__name__", "test_rule", "label_a", "2", "label_b", "4"),
Point: promql.Point{V: 10, T: timestamp.FromTime(ruleEvaluationTime)},
},
},
}, },
{ {
name: "labels", name: "only new labels in recording rule, metric name in query result",
expr: &parser.NumberLiteral{Val: 1}, ruleLabels: labels.FromStrings("extra_from_rule", "foo"),
labels: labels.FromStrings("foo", "bar"), expr: exprWithMetricName,
result: promql.Vector{promql.Sample{ expected: promql.Vector{
Metric: labels.FromStrings("__name__", "labels", "foo", "bar"), promql.Sample{
Point: promql.Point{V: 1, T: timestamp.FromTime(now)}, Metric: labels.FromStrings("__name__", "test_rule", "label_a", "1", "label_b", "3", "extra_from_rule", "foo"),
}}, Point: promql.Point{V: 1, T: timestamp.FromTime(ruleEvaluationTime)},
},
promql.Sample{
Metric: labels.FromStrings("__name__", "test_rule", "label_a", "2", "label_b", "4", "extra_from_rule", "foo"),
Point: promql.Point{V: 10, T: timestamp.FromTime(ruleEvaluationTime)},
},
},
},
{
name: "some replacement labels in recording rule, metric name in query result",
ruleLabels: labels.FromStrings("label_a", "from_rule"),
expr: exprWithMetricName,
expected: promql.Vector{
promql.Sample{
Metric: labels.FromStrings("__name__", "test_rule", "label_a", "from_rule", "label_b", "3"),
Point: promql.Point{V: 1, T: timestamp.FromTime(ruleEvaluationTime)},
},
promql.Sample{
Metric: labels.FromStrings("__name__", "test_rule", "label_a", "from_rule", "label_b", "4"),
Point: promql.Point{V: 10, T: timestamp.FromTime(ruleEvaluationTime)},
},
},
},
{
name: "no labels in recording rule, no metric name in query result",
ruleLabels: labels.EmptyLabels(),
expr: exprWithoutMetricName,
expected: promql.Vector{
promql.Sample{
Metric: labels.FromStrings("__name__", "test_rule", "label_a", "1", "label_b", "3"),
Point: promql.Point{V: 2, T: timestamp.FromTime(ruleEvaluationTime)},
},
promql.Sample{
Metric: labels.FromStrings("__name__", "test_rule", "label_a", "2", "label_b", "4"),
Point: promql.Point{V: 20, T: timestamp.FromTime(ruleEvaluationTime)},
},
},
}, },
} }
for _, test := range suite { func setUpRuleEvalTest(t require.TestingT) *promql.Test {
rule := NewRecordingRule(test.name, test.expr, test.labels) suite, err := promql.NewTest(t, `
result, err := rule.Eval(ctx, now, EngineQueryFunc(engine, storage), nil, 0) load 1m
if test.err == "" { metric{label_a="1",label_b="3"} 1
metric{label_a="2",label_b="4"} 10
`)
require.NoError(t, err) require.NoError(t, err)
} else {
require.Equal(t, test.err, err.Error()) return suite
} }
require.Equal(t, test.result, result)
func TestRuleEval(t *testing.T) {
suite := setUpRuleEvalTest(t)
defer suite.Close()
require.NoError(t, suite.Run())
for _, scenario := range ruleEvalTestScenarios {
t.Run(scenario.name, func(t *testing.T) {
rule := NewRecordingRule("test_rule", scenario.expr, scenario.ruleLabels)
result, err := rule.Eval(suite.Context(), ruleEvaluationTime, EngineQueryFunc(suite.QueryEngine(), suite.Storage()), nil, 0)
require.NoError(t, err)
require.Equal(t, scenario.expected, result)
})
}
}
func BenchmarkRuleEval(b *testing.B) {
suite := setUpRuleEvalTest(b)
defer suite.Close()
require.NoError(b, suite.Run())
for _, scenario := range ruleEvalTestScenarios {
b.Run(scenario.name, func(b *testing.B) {
rule := NewRecordingRule("test_rule", scenario.expr, scenario.ruleLabels)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := rule.Eval(suite.Context(), ruleEvaluationTime, EngineQueryFunc(suite.QueryEngine(), suite.Storage()), nil, 0)
if err != nil {
require.NoError(b, err)
}
}
})
} }
} }