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 2a39ba639d .
This commit is contained in:
Aliaksandr Valialkin 2022-12-21 19:55:57 -08:00
parent 3300546eab
commit 31886aef3d
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
6 changed files with 180 additions and 2 deletions

View file

@ -15,6 +15,8 @@ The following tip changes can be tested by building VictoriaMetrics components f
## tip ## 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) ## [v1.85.3](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.85.3)

View file

@ -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") return nil, fmt.Errorf("`replacement` cannot be used with `action=graphite`; see https://docs.victoriametrics.com/vmagent.html#graphite-relabeling")
} }
if rc.Regex != nil { 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": case "replace":
if targetLabel == "" { if targetLabel == "" {
@ -295,10 +295,36 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
if len(sourceLabels) < 2 { if len(sourceLabels) < 2 {
return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=keep_if_equal`; got %q", sourceLabels) 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": case "drop_if_equal":
if len(sourceLabels) < 2 { if len(sourceLabels) < 2 {
return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=drop_if_equal`; got %q", sourceLabels) 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": case "keep":
if len(sourceLabels) == 0 && rc.If == nil { if len(sourceLabels) == 0 && rc.If == nil {
return nil, fmt.Errorf("missing `source_labels` for `action=keep`") return nil, fmt.Errorf("missing `source_labels` for `action=keep`")

View file

@ -84,7 +84,7 @@ func TestLoadRelabelConfigsSuccess(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("cannot load relabel configs from %q: %s", path, err) t.Fatalf("cannot load relabel configs from %q: %s", path, err)
} }
nExpected := 16 nExpected := 18
if n := pcs.Len(); n != nExpected { if n := pcs.Len(); n != nExpected {
t.Fatalf("unexpected number of relabel configs loaded from %q; got %d; want %d", path, 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) { t.Run("drop_if_equal-missing-source-labels", func(t *testing.T) {
f([]RelabelConfig{ 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) { t.Run("drop-missing-source-labels", func(t *testing.T) {
f([]RelabelConfig{ f([]RelabelConfig{
{ {

View file

@ -276,6 +276,28 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
return labels[:labelsOffset] return labels[:labelsOffset]
} }
return labels 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": case "keep":
// Keep the target if `source_labels` joined with `separator` match the `regex`. // Keep the target if `source_labels` joined with `separator` match the `regex`.
if prc.RegexAnchored == defaultRegexForRelabelConfig { if prc.RegexAnchored == defaultRegexForRelabelConfig {

View file

@ -399,6 +399,34 @@ func TestParsedRelabelConfigsApply(t *testing.T) {
- action: drop_if_equal - action: drop_if_equal
source_labels: [xxx, bar] source_labels: [xxx, bar]
`, `{xxx="yyy",bar="yyy"}`, true, `{}`) `, `{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) { t.Run("keep-miss", func(t *testing.T) {
f(` f(`

View file

@ -22,6 +22,12 @@
source_labels: [foo, bar] source_labels: [foo, bar]
- action: drop_if_equal - action: drop_if_equal
source_labels: [foo, bar] source_labels: [foo, bar]
- action: keepequal
source_labels: [foo]
target_label: bar
- action: dropequal
source_labels: [foo]
target_label: bar
- action: keep - action: keep
source_labels: [__name__] source_labels: [__name__]
regex: regex: