From b0c1f3d819401841cebc06f560d09607cf5ece93 Mon Sep 17 00:00:00 2001 From: Roman Khavronenko <roman@victoriametrics.com> Date: Tue, 14 May 2024 14:43:39 +0200 Subject: [PATCH] app/vmalert/rule: reduce number of allocations for getStaleSeries fn (#6269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allocations are reduced by re-using the byte buffer when converting labels to string keys. ``` name old allocs/op new allocs/op delta GetStaleSeries-10 703 ± 0% 203 ± 0% ~ (p=1.000 n=1+1) ``` Signed-off-by: hagen1778 <roman@victoriametrics.com> --- app/vmalert/rule/group.go | 34 ++++++++++++++----------- app/vmalert/rule/group_timing_test.go | 36 +++++++++++++++++++++++++++ app/vmalert/rule/test_helpers.go | 2 +- 3 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 app/vmalert/rule/group_timing_test.go diff --git a/app/vmalert/rule/group.go b/app/vmalert/rule/group.go index f4e6cd0ddb..520039d71f 100644 --- a/app/vmalert/rule/group.go +++ b/app/vmalert/rule/group.go @@ -9,10 +9,11 @@ import ( "hash/fnv" "net/url" "strconv" - "strings" "sync" "time" + "github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil" + "github.com/cheggaaa/pb/v3" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config" @@ -724,13 +725,19 @@ func (e *executor) exec(ctx context.Context, r Rule, ts time.Time, resolveDurati return errGr.Err() } +var bbPool bytesutil.ByteBufferPool + // getStaleSeries checks whether there are stale series from previously sent ones. func (e *executor) getStaleSeries(r Rule, tss []prompbmarshal.TimeSeries, timestamp time.Time) []prompbmarshal.TimeSeries { + bb := bbPool.Get() + defer bbPool.Put(bb) + ruleLabels := make(map[string][]prompbmarshal.Label, len(tss)) for _, ts := range tss { - // convert labels to strings so we can compare with previously sent series - key := labelsToString(ts.Labels) - ruleLabels[key] = ts.Labels + // convert labels to strings, so we can compare with previously sent series + bb.B = labelsToString(bb.B, ts.Labels) + ruleLabels[string(bb.B)] = ts.Labels + bb.Reset() } rID := r.ID() @@ -776,21 +783,20 @@ func (e *executor) purgeStaleSeries(activeRules []Rule) { e.previouslySentSeriesToRWMu.Unlock() } -func labelsToString(labels []prompbmarshal.Label) string { - var b strings.Builder - b.WriteRune('{') +func labelsToString(dst []byte, labels []prompbmarshal.Label) []byte { + dst = append(dst, '{') for i, label := range labels { if len(label.Name) == 0 { - b.WriteString("__name__") + dst = append(dst, "__name__"...) } else { - b.WriteString(label.Name) + dst = append(dst, label.Name...) } - b.WriteRune('=') - b.WriteString(strconv.Quote(label.Value)) + dst = append(dst, '=') + dst = strconv.AppendQuote(dst, label.Value) if i < len(labels)-1 { - b.WriteRune(',') + dst = append(dst, ',') } } - b.WriteRune('}') - return b.String() + dst = append(dst, '}') + return dst } diff --git a/app/vmalert/rule/group_timing_test.go b/app/vmalert/rule/group_timing_test.go new file mode 100644 index 0000000000..c233ed34da --- /dev/null +++ b/app/vmalert/rule/group_timing_test.go @@ -0,0 +1,36 @@ +package rule + +import ( + "fmt" + "testing" + "time" + + "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal" +) + +func BenchmarkGetStaleSeries(b *testing.B) { + ts := time.Now() + n := 100 + payload := make([]prompbmarshal.TimeSeries, n) + for i := 0; i < n; i++ { + s := fmt.Sprintf("%d", i) + labels := toPromLabels(b, + "__name__", "foo", ""+ + "instance", s, + "job", s, + "state", s, + ) + payload = append(payload, newTimeSeriesPB([]float64{1}, []int64{ts.Unix()}, labels)) + } + + e := &executor{ + previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label), + } + ar := &AlertingRule{RuleID: 1} + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + e.getStaleSeries(ar, payload, ts) + } +} diff --git a/app/vmalert/rule/test_helpers.go b/app/vmalert/rule/test_helpers.go index dcf23dc768..9373e2b814 100644 --- a/app/vmalert/rule/test_helpers.go +++ b/app/vmalert/rule/test_helpers.go @@ -95,7 +95,7 @@ func metricWithLabels(t *testing.T, labels ...string) datasource.Metric { return m } -func toPromLabels(t *testing.T, labels ...string) []prompbmarshal.Label { +func toPromLabels(t testing.TB, labels ...string) []prompbmarshal.Label { t.Helper() if len(labels) == 0 || len(labels)%2 != 0 { t.Fatalf("expected to get even number of labels")