Optimise relabeling by re-using memory (#11147)

* model/relabel: Add benchmark

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

* model/relabel: re-use Builder across relabels

Saves memory allocations.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

* labels.Builder: allow re-use of result slice

This reduces memory allocations where the caller has a suitable slice available.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

* model/relabel: re-use source values slice

To reduce memory allocations.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

* Unwind one change causing test failures

Restore original behaviour in PopulateLabels, where we must not overwrite the input set.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

* relabel: simplify values optimisation

Use a stack-based array for up to 16 source labels, which will be the
vast majority of cases.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

* lint

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
This commit is contained in:
Bryan Boreham 2022-08-19 10:57:52 +01:00 committed by GitHub
parent 2d9b3f6e96
commit 8b863c42dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 206 additions and 35 deletions

View file

@ -165,7 +165,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
lb.Set(labels.MetricName, ruleName) lb.Set(labels.MetricName, ruleName)
for _, value := range sample.Values { for _, value := range sample.Values {
if err := app.add(ctx, lb.Labels(), timestamp.FromTime(value.Timestamp.Time()), float64(value.Value)); err != nil { if err := app.add(ctx, lb.Labels(nil), timestamp.FromTime(value.Timestamp.Time()), float64(value.Value)); err != nil {
return fmt.Errorf("add: %w", err) return fmt.Errorf("add: %w", err)
} }
} }

View file

@ -467,17 +467,25 @@ func (b *Builder) Set(n, v string) *Builder {
return b return b
} }
// Labels returns the labels from the builder. If no modifications // Labels returns the labels from the builder, adding them to res if non-nil.
// were made, the original labels are returned. // Argument res can be the same as b.base, if caller wants to overwrite that slice.
func (b *Builder) Labels() Labels { // If no modifications were made, the original labels are returned.
func (b *Builder) Labels(res Labels) Labels {
if len(b.del) == 0 && len(b.add) == 0 { if len(b.del) == 0 && len(b.add) == 0 {
return b.base return b.base
} }
// In the general case, labels are removed, modified or moved if res == nil {
// rather than added. // In the general case, labels are removed, modified or moved
res := make(Labels, 0, len(b.base)) // rather than added.
res = make(Labels, 0, len(b.base))
} else {
res = res[:0]
}
Outer: Outer:
// Justification that res can be the same slice as base: in this loop
// we move forward through base, and either skip an element or assign
// it to res at its current position or an earlier position.
for _, l := range b.base { for _, l := range b.base {
for _, n := range b.del { for _, n := range b.del {
if l.Name == n { if l.Name == n {

View file

@ -722,7 +722,7 @@ func TestBuilder(t *testing.T) {
b.Keep(tcase.keep...) b.Keep(tcase.keep...)
} }
b.Del(tcase.del...) b.Del(tcase.del...)
require.Equal(t, tcase.want, b.Labels()) require.Equal(t, tcase.want, b.Labels(tcase.base))
}) })
} }
} }

View file

@ -192,24 +192,29 @@ func (re Regexp) String() string {
// are applied in order of input. // are applied in order of input.
// If a label set is dropped, nil is returned. // If a label set is dropped, nil is returned.
// May return the input labelSet modified. // May return the input labelSet modified.
func Process(labels labels.Labels, cfgs ...*Config) labels.Labels { func Process(lbls labels.Labels, cfgs ...*Config) labels.Labels {
lb := labels.NewBuilder(nil)
for _, cfg := range cfgs { for _, cfg := range cfgs {
labels = relabel(labels, cfg) lbls = relabel(lbls, cfg, lb)
if labels == nil { if lbls == nil {
return nil return nil
} }
} }
return labels return lbls
} }
func relabel(lset labels.Labels, cfg *Config) labels.Labels { func relabel(lset labels.Labels, cfg *Config, lb *labels.Builder) labels.Labels {
values := make([]string, 0, len(cfg.SourceLabels)) var va [16]string
values := va[:0]
if len(cfg.SourceLabels) > cap(values) {
values = make([]string, 0, len(cfg.SourceLabels))
}
for _, ln := range cfg.SourceLabels { for _, ln := range cfg.SourceLabels {
values = append(values, lset.Get(string(ln))) values = append(values, lset.Get(string(ln)))
} }
val := strings.Join(values, cfg.Separator) val := strings.Join(values, cfg.Separator)
lb := labels.NewBuilder(lset) lb.Reset(lset)
switch cfg.Action { switch cfg.Action {
case Drop: case Drop:
@ -267,7 +272,7 @@ func relabel(lset labels.Labels, cfg *Config) labels.Labels {
panic(fmt.Errorf("relabel: unknown relabel action type %q", cfg.Action)) panic(fmt.Errorf("relabel: unknown relabel action type %q", cfg.Action))
} }
return lb.Labels() return lb.Labels(lset)
} }
// sum64 sums the md5 hash to an uint64. // sum64 sums the md5 hash to an uint64.

View file

@ -18,6 +18,7 @@ import (
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
) )
@ -500,3 +501,160 @@ func TestTargetLabelValidity(t *testing.T) {
"Expected %q to be %v", test.str, test.valid) "Expected %q to be %v", test.str, test.valid)
} }
} }
func BenchmarkRelabel(b *testing.B) {
tests := []struct {
name string
lbls labels.Labels
config string
cfgs []*Config
}{
{
name: "example", // From prometheus/config/testdata/conf.good.yml.
config: `
- source_labels: [job, __meta_dns_name]
regex: "(.*)some-[regex]"
target_label: job
replacement: foo-${1}
# action defaults to 'replace'
- source_labels: [abc]
target_label: cde
- replacement: static
target_label: abc
- regex:
replacement: static
target_label: abc`,
lbls: labels.FromStrings("__meta_dns_name", "example-some-x.com", "abc", "def", "job", "foo"),
},
{
name: "kubernetes",
config: `
- source_labels:
- __meta_kubernetes_pod_container_port_name
regex: .*-metrics
action: keep
- source_labels:
- __meta_kubernetes_pod_label_name
action: drop
regex: ""
- source_labels:
- __meta_kubernetes_pod_phase
regex: Succeeded|Failed
action: drop
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scrape
regex: "false"
action: drop
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scheme
target_label: __scheme__
regex: (https?)
replacement: $1
action: replace
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_path
target_label: __metrics_path__
regex: (.+)
replacement: $1
action: replace
- source_labels:
- __address__
- __meta_kubernetes_pod_annotation_prometheus_io_port
target_label: __address__
regex: (.+?)(\:\d+)?;(\d+)
replacement: $1:$3
action: replace
- regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+)
replacement: __param_$1
action: labelmap
- regex: __meta_kubernetes_pod_label_prometheus_io_label_(.+)
action: labelmap
- regex: __meta_kubernetes_pod_annotation_prometheus_io_label_(.+)
action: labelmap
- source_labels:
- __meta_kubernetes_namespace
- __meta_kubernetes_pod_label_name
separator: /
target_label: job
replacement: $1
action: replace
- source_labels:
- __meta_kubernetes_namespace
target_label: namespace
action: replace
- source_labels:
- __meta_kubernetes_pod_name
target_label: pod
action: replace
- source_labels:
- __meta_kubernetes_pod_container_name
target_label: container
action: replace
- source_labels:
- __meta_kubernetes_pod_name
- __meta_kubernetes_pod_container_name
- __meta_kubernetes_pod_container_port_name
separator: ':'
target_label: instance
action: replace
- target_label: cluster
replacement: dev-us-central-0
- source_labels:
- __meta_kubernetes_namespace
regex: hosted-grafana
action: drop
- source_labels:
- __address__
target_label: __tmp_hash
modulus: 3
action: hashmod
- source_labels:
- __tmp_hash
regex: ^0$
action: keep
- regex: __tmp_hash
action: labeldrop`,
lbls: labels.FromStrings(
"__address__", "10.132.183.40:80",
"__meta_kubernetes_namespace", "loki-boltdb-shipper",
"__meta_kubernetes_pod_annotation_promtail_loki_boltdb_shipper_hash", "50523b9759094a144adcec2eae0aa4ad",
"__meta_kubernetes_pod_annotationpresent_promtail_loki_boltdb_shipper_hash", "true",
"__meta_kubernetes_pod_container_init", "false",
"__meta_kubernetes_pod_container_name", "promtail",
"__meta_kubernetes_pod_container_port_name", "http-metrics",
"__meta_kubernetes_pod_container_port_number", "80",
"__meta_kubernetes_pod_container_port_protocol", "TCP",
"__meta_kubernetes_pod_controller_kind", "DaemonSet",
"__meta_kubernetes_pod_controller_name", "promtail-loki-boltdb-shipper",
"__meta_kubernetes_pod_host_ip", "10.128.0.178",
"__meta_kubernetes_pod_ip", "10.132.183.40",
"__meta_kubernetes_pod_label_controller_revision_hash", "555b77cd7d",
"__meta_kubernetes_pod_label_name", "promtail-loki-boltdb-shipper",
"__meta_kubernetes_pod_label_pod_template_generation", "45",
"__meta_kubernetes_pod_labelpresent_controller_revision_hash", "true",
"__meta_kubernetes_pod_labelpresent_name", "true",
"__meta_kubernetes_pod_labelpresent_pod_template_generation", "true",
"__meta_kubernetes_pod_name", "promtail-loki-boltdb-shipper-jgtr7",
"__meta_kubernetes_pod_node_name", "gke-dev-us-central-0-main-n2s8-2-14d53341-9hkr",
"__meta_kubernetes_pod_phase", "Running",
"__meta_kubernetes_pod_ready", "true",
"__meta_kubernetes_pod_uid", "4c586419-7f6c-448d-aeec-ca4fa5b05e60",
"__metrics_path__", "/metrics",
"__scheme__", "http",
"__scrape_interval__", "15s",
"__scrape_timeout__", "10s",
"job", "kubernetes-pods"),
},
}
for i := range tests {
err := yaml.UnmarshalStrict([]byte(tests[i].config), &tests[i].cfgs)
require.NoError(b, err)
}
for _, tt := range tests {
b.Run(tt.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Process(tt.lbls, tt.cfgs...)
}
})
}
}

View file

@ -361,7 +361,7 @@ func (n *Manager) Send(alerts ...*Alert) {
} }
} }
a.Labels = lb.Labels() a.Labels = lb.Labels(a.Labels)
} }
alerts = n.relabelAlerts(alerts) alerts = n.relabelAlerts(alerts)

View file

@ -2116,7 +2116,7 @@ func resultMetric(lhs, rhs labels.Labels, op parser.ItemType, matching *parser.V
} }
} }
ret := enh.lb.Labels() ret := enh.lb.Labels(nil)
enh.resultMetric[str] = ret enh.resultMetric[str] = ret
return ret return ret
} }
@ -2156,7 +2156,7 @@ func (ev *evaluator) VectorscalarBinop(op parser.ItemType, lhs Vector, rhs Scala
} }
func dropMetricName(l labels.Labels) labels.Labels { func dropMetricName(l labels.Labels) labels.Labels {
return labels.NewBuilder(l).Del(labels.MetricName).Labels() return labels.NewBuilder(l).Del(labels.MetricName).Labels(nil)
} }
// scalarBinop evaluates a binary operation between two Scalars. // scalarBinop evaluates a binary operation between two Scalars.
@ -2280,7 +2280,7 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
if op == parser.COUNT_VALUES { if op == parser.COUNT_VALUES {
lb.Reset(metric) lb.Reset(metric)
lb.Set(valueLabel, strconv.FormatFloat(s.V, 'f', -1, 64)) lb.Set(valueLabel, strconv.FormatFloat(s.V, 'f', -1, 64))
metric = lb.Labels() metric = lb.Labels(nil)
// We've changed the metric so we have to recompute the grouping key. // We've changed the metric so we have to recompute the grouping key.
recomputeGroupingKey = true recomputeGroupingKey = true
@ -2304,7 +2304,7 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
} else { } else {
lb.Keep(grouping...) lb.Keep(grouping...)
} }
m := lb.Labels() m := lb.Labels(nil)
newAgg := &groupedAggregation{ newAgg := &groupedAggregation{
labels: m, labels: m,
value: s.V, value: s.V,

View file

@ -813,7 +813,7 @@ func funcHistogramQuantile(vals []parser.Value, args parser.Expressions, enh *Ev
if !ok { if !ok {
el.Metric = labels.NewBuilder(el.Metric). el.Metric = labels.NewBuilder(el.Metric).
Del(excludedLabels...). Del(excludedLabels...).
Labels() Labels(nil)
mb = &metricWithBuckets{el.Metric, nil} mb = &metricWithBuckets{el.Metric, nil}
enh.signatureToMetricWithBuckets[string(enh.lblBuf)] = mb enh.signatureToMetricWithBuckets[string(enh.lblBuf)] = mb
@ -912,7 +912,7 @@ func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNod
if len(res) > 0 { if len(res) > 0 {
lb.Set(dst, string(res)) lb.Set(dst, string(res))
} }
outMetric = lb.Labels() outMetric = lb.Labels(nil)
enh.Dmn[h] = outMetric enh.Dmn[h] = outMetric
} }
} }
@ -980,7 +980,7 @@ func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
lb.Set(dst, strval) lb.Set(dst, strval)
} }
outMetric = lb.Labels() outMetric = lb.Labels(nil)
enh.Dmn[h] = outMetric enh.Dmn[h] = outMetric
} }
@ -1233,14 +1233,14 @@ func createLabelsForAbsentFunction(expr parser.Expr) labels.Labels {
continue continue
} }
if ma.Type == labels.MatchEqual && !m.Has(ma.Name) { if ma.Type == labels.MatchEqual && !m.Has(ma.Name) {
m = labels.NewBuilder(m).Set(ma.Name, ma.Value).Labels() m = labels.NewBuilder(m).Set(ma.Name, ma.Value).Labels(nil)
} else { } else {
empty = append(empty, ma.Name) empty = append(empty, ma.Name)
} }
} }
for _, v := range empty { for _, v := range empty {
m = labels.NewBuilder(m).Del(v).Labels() m = labels.NewBuilder(m).Del(v).Labels(nil)
} }
return m return m
} }

View file

@ -226,7 +226,7 @@ func (r *AlertingRule) sample(alert *Alert, ts time.Time) promql.Sample {
lb.Set(alertStateLabel, alert.State.String()) lb.Set(alertStateLabel, alert.State.String())
s := promql.Sample{ s := promql.Sample{
Metric: lb.Labels(), Metric: lb.Labels(nil),
Point: promql.Point{T: timestamp.FromTime(ts), V: 1}, Point: promql.Point{T: timestamp.FromTime(ts), V: 1},
} }
return s return s
@ -244,7 +244,7 @@ func (r *AlertingRule) forStateSample(alert *Alert, ts time.Time, v float64) pro
lb.Set(labels.AlertName, r.name) lb.Set(labels.AlertName, r.name)
s := promql.Sample{ s := promql.Sample{
Metric: lb.Labels(), Metric: lb.Labels(nil),
Point: promql.Point{T: timestamp.FromTime(ts), V: v}, Point: promql.Point{T: timestamp.FromTime(ts), V: v},
} }
return s return s
@ -373,7 +373,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc,
annotations = append(annotations, labels.Label{Name: a.Name, Value: expand(a.Value)}) annotations = append(annotations, labels.Label{Name: a.Name, Value: expand(a.Value)})
} }
lbs := lb.Labels() lbs := lb.Labels(nil)
h := lbs.Hash() h := lbs.Hash()
resultFPs[h] = struct{}{} resultFPs[h] = struct{}{}

View file

@ -89,7 +89,7 @@ func (rule *RecordingRule) Eval(ctx context.Context, ts time.Time, query QueryFu
lb.Set(l.Name, l.Value) lb.Set(l.Name, l.Value)
} }
sample.Metric = lb.Labels() sample.Metric = lb.Labels(nil)
} }
// Check that the rule does not produce identical metrics after applying // Check that the rule does not produce identical metrics after applying

View file

@ -676,7 +676,7 @@ func mutateSampleLabels(lset labels.Labels, target *Target, honor bool, rc []*re
} }
} }
res := lb.Labels() res := lb.Labels(nil)
if len(rc) > 0 { if len(rc) > 0 {
res = relabel.Process(res, rc...) res = relabel.Process(res, rc...)
@ -716,7 +716,7 @@ func mutateReportSampleLabels(lset labels.Labels, target *Target) labels.Labels
lb.Set(l.Name, l.Value) lb.Set(l.Name, l.Value)
} }
return lb.Labels() return lb.Labels(nil)
} }
// appender returns an appender for ingested samples from the target. // appender returns an appender for ingested samples from the target.

View file

@ -374,7 +374,7 @@ func PopulateLabels(lset labels.Labels, cfg *config.ScrapeConfig, noDefaultPort
} }
} }
preRelabelLabels := lb.Labels() preRelabelLabels := lb.Labels(nil)
lset = relabel.Process(preRelabelLabels, cfg.RelabelConfigs...) lset = relabel.Process(preRelabelLabels, cfg.RelabelConfigs...)
// Check if the target was dropped. // Check if the target was dropped.
@ -472,7 +472,7 @@ func PopulateLabels(lset labels.Labels, cfg *config.ScrapeConfig, noDefaultPort
lb.Set(model.InstanceLabel, addr) lb.Set(model.InstanceLabel, addr)
} }
res = lb.Labels() res = lb.Labels(nil)
for _, l := range res { for _, l := range res {
// Check label values are valid, drop the target if not. // Check label values are valid, drop the target if not.
if !model.LabelValue(l.Value).IsValid() { if !model.LabelValue(l.Value).IsValid() {

View file

@ -129,7 +129,7 @@ func newTestTarget(targetURL string, deadline time.Duration, lbls labels.Labels)
lb.Set(model.AddressLabel, strings.TrimPrefix(targetURL, "http://")) lb.Set(model.AddressLabel, strings.TrimPrefix(targetURL, "http://"))
lb.Set(model.MetricsPathLabel, "/metrics") lb.Set(model.MetricsPathLabel, "/metrics")
return &Target{labels: lb.Labels()} return &Target{labels: lb.Labels(nil)}
} }
func TestNewHTTPBearerToken(t *testing.T) { func TestNewHTTPBearerToken(t *testing.T) {