diff --git a/cmd/promtool/rules.go b/cmd/promtool/rules.go index 2a3525da0..4dbef34eb 100644 --- a/cmd/promtool/rules.go +++ b/cmd/promtool/rules.go @@ -165,7 +165,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName lb.Set(labels.MetricName, ruleName) 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) } } diff --git a/model/labels/labels.go b/model/labels/labels.go index 27423d19c..da1b057a4 100644 --- a/model/labels/labels.go +++ b/model/labels/labels.go @@ -467,17 +467,25 @@ func (b *Builder) Set(n, v string) *Builder { return b } -// Labels returns the labels from the builder. If no modifications -// were made, the original labels are returned. -func (b *Builder) Labels() Labels { +// Labels returns the labels from the builder, adding them to res if non-nil. +// Argument res can be the same as b.base, if caller wants to overwrite that slice. +// 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 { return b.base } - // In the general case, labels are removed, modified or moved - // rather than added. - res := make(Labels, 0, len(b.base)) + if res == nil { + // In the general case, labels are removed, modified or moved + // rather than added. + res = make(Labels, 0, len(b.base)) + } else { + res = res[:0] + } 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 _, n := range b.del { if l.Name == n { diff --git a/model/labels/labels_test.go b/model/labels/labels_test.go index 21b35efce..8e32e8ed0 100644 --- a/model/labels/labels_test.go +++ b/model/labels/labels_test.go @@ -722,7 +722,7 @@ func TestBuilder(t *testing.T) { b.Keep(tcase.keep...) } b.Del(tcase.del...) - require.Equal(t, tcase.want, b.Labels()) + require.Equal(t, tcase.want, b.Labels(tcase.base)) }) } } diff --git a/model/relabel/relabel.go b/model/relabel/relabel.go index 05c32a41b..e0d7f6ddf 100644 --- a/model/relabel/relabel.go +++ b/model/relabel/relabel.go @@ -192,24 +192,29 @@ func (re Regexp) String() string { // are applied in order of input. // If a label set is dropped, nil is returned. // 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 { - labels = relabel(labels, cfg) - if labels == nil { + lbls = relabel(lbls, cfg, lb) + if lbls == nil { return nil } } - return labels + return lbls } -func relabel(lset labels.Labels, cfg *Config) labels.Labels { - values := make([]string, 0, len(cfg.SourceLabels)) +func relabel(lset labels.Labels, cfg *Config, lb *labels.Builder) labels.Labels { + 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 { values = append(values, lset.Get(string(ln))) } val := strings.Join(values, cfg.Separator) - lb := labels.NewBuilder(lset) + lb.Reset(lset) switch cfg.Action { 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)) } - return lb.Labels() + return lb.Labels(lset) } // sum64 sums the md5 hash to an uint64. diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go index 5e9bd1438..c437e5c1c 100644 --- a/model/relabel/relabel_test.go +++ b/model/relabel/relabel_test.go @@ -18,6 +18,7 @@ import ( "github.com/prometheus/common/model" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" "github.com/prometheus/prometheus/model/labels" ) @@ -500,3 +501,160 @@ func TestTargetLabelValidity(t *testing.T) { "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...) + } + }) + } +} diff --git a/notifier/notifier.go b/notifier/notifier.go index 3d2ee5949..99886bd4b 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -361,7 +361,7 @@ func (n *Manager) Send(alerts ...*Alert) { } } - a.Labels = lb.Labels() + a.Labels = lb.Labels(a.Labels) } alerts = n.relabelAlerts(alerts) diff --git a/promql/engine.go b/promql/engine.go index 34ea2a3e9..b6842e8f4 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -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 return ret } @@ -2156,7 +2156,7 @@ func (ev *evaluator) VectorscalarBinop(op parser.ItemType, lhs Vector, rhs Scala } 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. @@ -2280,7 +2280,7 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without if op == parser.COUNT_VALUES { lb.Reset(metric) 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. recomputeGroupingKey = true @@ -2304,7 +2304,7 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without } else { lb.Keep(grouping...) } - m := lb.Labels() + m := lb.Labels(nil) newAgg := &groupedAggregation{ labels: m, value: s.V, diff --git a/promql/functions.go b/promql/functions.go index d133fef05..8f17b970c 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -813,7 +813,7 @@ func funcHistogramQuantile(vals []parser.Value, args parser.Expressions, enh *Ev if !ok { el.Metric = labels.NewBuilder(el.Metric). Del(excludedLabels...). - Labels() + Labels(nil) mb = &metricWithBuckets{el.Metric, nil} enh.signatureToMetricWithBuckets[string(enh.lblBuf)] = mb @@ -912,7 +912,7 @@ func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNod if len(res) > 0 { lb.Set(dst, string(res)) } - outMetric = lb.Labels() + outMetric = lb.Labels(nil) enh.Dmn[h] = outMetric } } @@ -980,7 +980,7 @@ func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe lb.Set(dst, strval) } - outMetric = lb.Labels() + outMetric = lb.Labels(nil) enh.Dmn[h] = outMetric } @@ -1233,14 +1233,14 @@ func createLabelsForAbsentFunction(expr parser.Expr) labels.Labels { continue } 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 { empty = append(empty, ma.Name) } } for _, v := range empty { - m = labels.NewBuilder(m).Del(v).Labels() + m = labels.NewBuilder(m).Del(v).Labels(nil) } return m } diff --git a/rules/alerting.go b/rules/alerting.go index 4740d3457..bffd3e230 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -226,7 +226,7 @@ func (r *AlertingRule) sample(alert *Alert, ts time.Time) promql.Sample { lb.Set(alertStateLabel, alert.State.String()) s := promql.Sample{ - Metric: lb.Labels(), + Metric: lb.Labels(nil), Point: promql.Point{T: timestamp.FromTime(ts), V: 1}, } return s @@ -244,7 +244,7 @@ func (r *AlertingRule) forStateSample(alert *Alert, ts time.Time, v float64) pro lb.Set(labels.AlertName, r.name) s := promql.Sample{ - Metric: lb.Labels(), + Metric: lb.Labels(nil), Point: promql.Point{T: timestamp.FromTime(ts), V: v}, } 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)}) } - lbs := lb.Labels() + lbs := lb.Labels(nil) h := lbs.Hash() resultFPs[h] = struct{}{} diff --git a/rules/recording.go b/rules/recording.go index 6d16a80b0..f9d732e7c 100644 --- a/rules/recording.go +++ b/rules/recording.go @@ -89,7 +89,7 @@ func (rule *RecordingRule) Eval(ctx context.Context, ts time.Time, query QueryFu 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 diff --git a/scrape/scrape.go b/scrape/scrape.go index e044e72e1..9a5750f78 100644 --- a/scrape/scrape.go +++ b/scrape/scrape.go @@ -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 { res = relabel.Process(res, rc...) @@ -716,7 +716,7 @@ func mutateReportSampleLabels(lset labels.Labels, target *Target) labels.Labels lb.Set(l.Name, l.Value) } - return lb.Labels() + return lb.Labels(nil) } // appender returns an appender for ingested samples from the target. diff --git a/scrape/target.go b/scrape/target.go index 55d6d29d7..f54623954 100644 --- a/scrape/target.go +++ b/scrape/target.go @@ -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...) // Check if the target was dropped. @@ -472,7 +472,7 @@ func PopulateLabels(lset labels.Labels, cfg *config.ScrapeConfig, noDefaultPort lb.Set(model.InstanceLabel, addr) } - res = lb.Labels() + res = lb.Labels(nil) for _, l := range res { // Check label values are valid, drop the target if not. if !model.LabelValue(l.Value).IsValid() { diff --git a/scrape/target_test.go b/scrape/target_test.go index 4b9589d66..ce6a471ee 100644 --- a/scrape/target_test.go +++ b/scrape/target_test.go @@ -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.MetricsPathLabel, "/metrics") - return &Target{labels: lb.Labels()} + return &Target{labels: lb.Labels(nil)} } func TestNewHTTPBearerToken(t *testing.T) {