From 31886aef3d2f853ae334fd6c6755a6d8af47bd2c Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Wed, 21 Dec 2022 19:55:57 -0800 Subject: [PATCH] lib/promrelabel: add support for `keepequal` and `dropequal` relabeling actions These actions are supported by Prometheus starting from v2.41.0 See https://github.com/prometheus/prometheus/pull/11564 , https://github.com/prometheus/prometheus/issues/11556 and https://github.com/prometheus/prometheus/issues/3756 Side note: It's a pity that Prometheus developers decided inventing `keepequal` and `dropequal` relabeling actions instead of adding support for `keep_if_equal` and `drop_if_equal` relabeling actions supported by VictoriaMetrics since June 2020 - see 2a39ba639d4446359d1dd77de85eaf9c8dca6771 . --- docs/CHANGELOG.md | 2 + lib/promrelabel/config.go | 28 +++++- lib/promrelabel/config_test.go | 96 ++++++++++++++++++- lib/promrelabel/relabel.go | 22 +++++ lib/promrelabel/relabel_test.go | 28 ++++++ .../testdata/relabel_configs_valid.yml | 6 ++ 6 files changed, 180 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 49d7c7ab2..1ee010201 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,8 @@ The following tip changes can be tested by building VictoriaMetrics components f ## tip +* FEATURE: [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling): add support for `keepequal` and `dropequal` relabeling actions, which are supported by Prometheus starting from [v2.41.0](https://github.com/prometheus/prometheus/releases/tag/v2.41.0). These relabeling actions are almost identical to `keep_if_equal` and `drop_if_equal` relabeling actions supported by VictoriaMetrics since `v1.38.0` - see [these docs](https://docs.victoriametrics.com/vmagent.html#relabeling-enhancements) - so it is recommended sticking to `keep_if_equal` and `drop_if_equal` actions instead of switching to `keepequal` and `dropequal`. + ## [v1.85.3](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.85.3) diff --git a/lib/promrelabel/config.go b/lib/promrelabel/config.go index fd98dcdb1..58cd4095f 100644 --- a/lib/promrelabel/config.go +++ b/lib/promrelabel/config.go @@ -278,7 +278,7 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) { return nil, fmt.Errorf("`replacement` cannot be used with `action=graphite`; see https://docs.victoriametrics.com/vmagent.html#graphite-relabeling") } if rc.Regex != nil { - return nil, fmt.Errorf("`regex` cannot be used with `action=graphite`; see https://docs.victoriametrics.com/vmagent.html#graphite-relabeling") + return nil, fmt.Errorf("`regex` cannot be used for `action=graphite`; see https://docs.victoriametrics.com/vmagent.html#graphite-relabeling") } case "replace": if targetLabel == "" { @@ -295,10 +295,36 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) { if len(sourceLabels) < 2 { return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=keep_if_equal`; got %q", sourceLabels) } + if targetLabel != "" { + return nil, fmt.Errorf("`target_label` cannot be used for `action=keep_if_equal`") + } + if rc.Regex != nil { + return nil, fmt.Errorf("`regex` cannot be used for `action=keep_if_equal`") + } case "drop_if_equal": if len(sourceLabels) < 2 { return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=drop_if_equal`; got %q", sourceLabels) } + if targetLabel != "" { + return nil, fmt.Errorf("`target_label` cannot be used for `action=drop_if_equal`") + } + if rc.Regex != nil { + return nil, fmt.Errorf("`regex` cannot be used for `action=drop_if_equal`") + } + case "keepequal": + if targetLabel == "" { + return nil, fmt.Errorf("missing `target_label` for `action=keepequal`") + } + if rc.Regex != nil { + return nil, fmt.Errorf("`regex` cannot be used for `action=keepequal`") + } + case "dropequal": + if targetLabel == "" { + return nil, fmt.Errorf("missing `target_label` for `action=dropequal`") + } + if rc.Regex != nil { + return nil, fmt.Errorf("`regex` cannot be used for `action=dropequal`") + } case "keep": if len(sourceLabels) == 0 && rc.If == nil { return nil, fmt.Errorf("missing `source_labels` for `action=keep`") diff --git a/lib/promrelabel/config_test.go b/lib/promrelabel/config_test.go index c1d945156..4876024e2 100644 --- a/lib/promrelabel/config_test.go +++ b/lib/promrelabel/config_test.go @@ -84,7 +84,7 @@ func TestLoadRelabelConfigsSuccess(t *testing.T) { if err != nil { t.Fatalf("cannot load relabel configs from %q: %s", path, err) } - nExpected := 16 + nExpected := 18 if n := pcs.Len(); n != nExpected { t.Fatalf("unexpected number of relabel configs loaded from %q; got %d; want %d", path, n, nExpected) } @@ -266,6 +266,26 @@ func TestParseRelabelConfigsFailure(t *testing.T) { }, }) }) + t.Run("keep_if_equal-unused-target-label", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "keep_if_equal", + SourceLabels: []string{"foo", "bar"}, + TargetLabel: "foo", + }, + }) + }) + t.Run("keep_if_equal-unused-regex", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "keep_if_equal", + SourceLabels: []string{"foo", "bar"}, + Regex: &MultiLineRegex{ + S: "bar", + }, + }, + }) + }) t.Run("drop_if_equal-missing-source-labels", func(t *testing.T) { f([]RelabelConfig{ { @@ -281,6 +301,80 @@ func TestParseRelabelConfigsFailure(t *testing.T) { }, }) }) + t.Run("drop_if_equal-unused-target-label", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "drop_if_equal", + SourceLabels: []string{"foo", "bar"}, + TargetLabel: "foo", + }, + }) + }) + t.Run("drop_if_equal-unused-regex", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "drop_if_equal", + SourceLabels: []string{"foo", "bar"}, + Regex: &MultiLineRegex{ + S: "bar", + }, + }, + }) + }) + t.Run("keepequal-missing-source-labels", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "keepequal", + }, + }) + }) + t.Run("keepequal-missing-target-label", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "keepequal", + SourceLabels: []string{"foo"}, + }, + }) + }) + t.Run("keepequal-unused-regex", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "keepequal", + SourceLabels: []string{"foo"}, + TargetLabel: "foo", + Regex: &MultiLineRegex{ + S: "bar", + }, + }, + }) + }) + t.Run("dropequal-missing-source-labels", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "dropequal", + }, + }) + }) + t.Run("dropequal-missing-target-label", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "dropequal", + SourceLabels: []string{"foo"}, + }, + }) + }) + t.Run("dropequal-unused-regex", func(t *testing.T) { + f([]RelabelConfig{ + { + Action: "dropequal", + SourceLabels: []string{"foo"}, + TargetLabel: "foo", + Regex: &MultiLineRegex{ + S: "bar", + }, + }, + }) + }) t.Run("drop-missing-source-labels", func(t *testing.T) { f([]RelabelConfig{ { diff --git a/lib/promrelabel/relabel.go b/lib/promrelabel/relabel.go index 125ddd08b..c1021753f 100644 --- a/lib/promrelabel/relabel.go +++ b/lib/promrelabel/relabel.go @@ -276,6 +276,28 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset return labels[:labelsOffset] } return labels + case "keepequal": + // Keep the entry if `source_labels` joined with `separator` matches `target_label` + bb := relabelBufPool.Get() + bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator) + targetValue := getLabelValue(labels[labelsOffset:], prc.TargetLabel) + keep := string(bb.B) == targetValue + relabelBufPool.Put(bb) + if keep { + return labels + } + return labels[:labelsOffset] + case "dropequal": + // Drop the entry if `source_labels` joined with `separator` doesn't match `target_label` + bb := relabelBufPool.Get() + bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator) + targetValue := getLabelValue(labels[labelsOffset:], prc.TargetLabel) + drop := string(bb.B) == targetValue + relabelBufPool.Put(bb) + if !drop { + return labels + } + return labels[:labelsOffset] case "keep": // Keep the target if `source_labels` joined with `separator` match the `regex`. if prc.RegexAnchored == defaultRegexForRelabelConfig { diff --git a/lib/promrelabel/relabel_test.go b/lib/promrelabel/relabel_test.go index 9c34a7ebf..35e60ff41 100644 --- a/lib/promrelabel/relabel_test.go +++ b/lib/promrelabel/relabel_test.go @@ -399,6 +399,34 @@ func TestParsedRelabelConfigsApply(t *testing.T) { - action: drop_if_equal source_labels: [xxx, bar] `, `{xxx="yyy",bar="yyy"}`, true, `{}`) + }) + t.Run("keepequal-hit", func(t *testing.T) { + f(` +- action: keepequal + source_labels: [foo] + target_label: bar +`, `{foo="a",bar="a"}`, true, `{bar="a",foo="a"}`) + }) + t.Run("keepequal-miss", func(t *testing.T) { + f(` +- action: keepequal + source_labels: [foo] + target_label: bar +`, `{foo="a",bar="x"}`, true, `{}`) + }) + t.Run("dropequal-hit", func(t *testing.T) { + f(` +- action: dropequal + source_labels: [foo] + target_label: bar +`, `{foo="a",bar="a"}`, true, `{}`) + }) + t.Run("dropequal-miss", func(t *testing.T) { + f(` +- action: dropequal + source_labels: [foo] + target_label: bar +`, `{foo="a",bar="x"}`, true, `{bar="x",foo="a"}`) }) t.Run("keep-miss", func(t *testing.T) { f(` diff --git a/lib/promrelabel/testdata/relabel_configs_valid.yml b/lib/promrelabel/testdata/relabel_configs_valid.yml index 5264d7953..eccd0c782 100644 --- a/lib/promrelabel/testdata/relabel_configs_valid.yml +++ b/lib/promrelabel/testdata/relabel_configs_valid.yml @@ -22,6 +22,12 @@ source_labels: [foo, bar] - action: drop_if_equal source_labels: [foo, bar] +- action: keepequal + source_labels: [foo] + target_label: bar +- action: dropequal + source_labels: [foo] + target_label: bar - action: keep source_labels: [__name__] regex: