lib/promrelabel: add support for conditional relabeling via if filter

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1998
This commit is contained in:
Aliaksandr Valialkin 2022-02-24 02:26:15 +02:00
parent d128a5bf99
commit 3f49bdaeff
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
8 changed files with 878 additions and 7 deletions

View file

@ -264,7 +264,7 @@ Labels can be added to metrics by the following mechanisms:
## Relabeling
`vmagent` and VictoriaMetrics support Prometheus-compatible relabeling.
VictoriaMetrics components (including `vmagent`) support Prometheus-compatible relabeling.
They provide the following additional actions on top of actions from the [Prometheus relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config):
* `replace_all`: replaces all of the occurences of `regex` in the values of `source_labels` with the `replacement` and stores the results in the `target_label`.
@ -289,6 +289,21 @@ The `regex` value can be split into multiple lines for improved readability and
- "foo_.+"
```
VictoriaMetrics components support an optional `if` filter, which can be used for conditional relabeling. The `if` filter may contain arbitrary [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors). For example, the following relabeling rule drops targets, which don't match `foo{bar="baz"}` series selector:
```yaml
- action: keep
if: 'foo{bar="baz"}'
```
This is equivalent to less clear traditional relabeling rule:
```yaml
- action: keep
source_labels: [__name__, bar]
regex: 'foo;baz'
```
The relabeling can be defined in the following places:
* At the `scrape_config -> relabel_configs` section in `-promscrape.config` file. This relabeling is applied to target labels. This relabeling can be debugged by passing `relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs target labels before and after the relabeling and then drops the logged target.

View file

@ -14,6 +14,23 @@ The following tip changes can be tested by building VictoriaMetrics components f
## tip
* FEATURE: add support for conditional relabeling via `if` filter. The `if` filter can contain arbitrary [series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors). For example, the following rule drops targets matching `foo{bar="baz"}` series selector:
```yml
- action: drop
if: 'foo{bar="baz"}'
```
This rule is equivalent to less clear traditional one:
```yml
- action: drop
source_labels: [__name__, bar]
regex: 'foo;baz'
```
See [relabeling docs](https://docs.victoriametrics.com/vmagent.html#relabeling) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1998) for more details.
* FEATURE: reduce memory usage for various caches under [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate).
* BUGFIX: [vmgateway](https://docs.victoriametrics.com/vmgateway.html): properly parse JWT tokens if they are encoded with [URL-safe base64 encoding](https://datatracker.ietf.org/doc/html/rfc4648#section-5).

View file

@ -268,7 +268,7 @@ Labels can be added to metrics by the following mechanisms:
## Relabeling
`vmagent` and VictoriaMetrics support Prometheus-compatible relabeling.
VictoriaMetrics components (including `vmagent`) support Prometheus-compatible relabeling.
They provide the following additional actions on top of actions from the [Prometheus relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config):
* `replace_all`: replaces all of the occurences of `regex` in the values of `source_labels` with the `replacement` and stores the results in the `target_label`.
@ -293,6 +293,21 @@ The `regex` value can be split into multiple lines for improved readability and
- "foo_.+"
```
VictoriaMetrics components support an optional `if` filter, which can be used for conditional relabeling. The `if` filter may contain arbitrary [time series selector](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors). For example, the following relabeling rule drops targets, which don't match `foo{bar="baz"}` series selector:
```yaml
- action: keep
if: 'foo{bar="baz"}'
```
This is equivalent to less clear traditional relabeling rule:
```yaml
- action: keep
source_labels: [__name__, bar]
regex: 'foo;baz'
```
The relabeling can be defined in the following places:
* At the `scrape_config -> relabel_configs` section in `-promscrape.config` file. This relabeling is applied to target labels. This relabeling can be debugged by passing `relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs target labels before and after the relabeling and then drops the logged target.

View file

@ -22,6 +22,7 @@ type RelabelConfig struct {
Modulus uint64 `yaml:"modulus,omitempty"`
Replacement *string `yaml:"replacement,omitempty"`
Action string `yaml:"action,omitempty"`
If *IfExpression `yaml:"if,omitempty"`
}
// MultiLineRegex contains a regex, which can be split into multiple lines.
@ -44,7 +45,7 @@ type MultiLineRegex struct {
func (mlr *MultiLineRegex) UnmarshalYAML(f func(interface{}) error) error {
var v interface{}
if err := f(&v); err != nil {
return err
return fmt.Errorf("cannot parse multiline regex: %w", err)
}
s, err := stringValue(v)
if err != nil {
@ -224,11 +225,11 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
return nil, fmt.Errorf("`source_labels` must contain at least two entries for `action=drop_if_equal`; got %q", sourceLabels)
}
case "keep":
if len(sourceLabels) == 0 {
if len(sourceLabels) == 0 && rc.If == nil {
return nil, fmt.Errorf("missing `source_labels` for `action=keep`")
}
case "drop":
if len(sourceLabels) == 0 {
if len(sourceLabels) == 0 && rc.If == nil {
return nil, fmt.Errorf("missing `source_labels` for `action=drop`")
}
case "hashmod":
@ -242,7 +243,7 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
return nil, fmt.Errorf("unexpected `modulus` for `action=hashmod`: %d; must be greater than 0", modulus)
}
case "keep_metrics":
if rc.Regex == nil || rc.Regex.s == "" {
if (rc.Regex == nil || rc.Regex.s == "") && rc.If == nil {
return nil, fmt.Errorf("`regex` must be non-empty for `action=keep_metrics`")
}
if len(sourceLabels) > 0 {
@ -251,7 +252,7 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
sourceLabels = []string{"__name__"}
action = "keep"
case "drop_metrics":
if rc.Regex == nil || rc.Regex.s == "" {
if (rc.Regex == nil || rc.Regex.s == "") && rc.If == nil {
return nil, fmt.Errorf("`regex` must be non-empty for `action=drop_metrics`")
}
if len(sourceLabels) > 0 {
@ -274,6 +275,7 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
Modulus: modulus,
Replacement: replacement,
Action: action,
If: rc.If,
regexOriginal: regexOriginalCompiled,
hasCaptureGroupInTargetLabel: strings.Contains(targetLabel, "$"),

View file

@ -0,0 +1,169 @@
package promrelabel
import (
"fmt"
"regexp"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/metricsql"
)
// IfExpression represents `if` expression at RelabelConfig.
//
// The `if` expression can contain arbitrary PromQL-like label filters such as `metric_name{filters...}`
type IfExpression struct {
s string
lfs []*labelFilter
}
// UnmarshalYAML unmarshals ie from YAML passed to f.
func (ie *IfExpression) UnmarshalYAML(f func(interface{}) error) error {
var s string
if err := f(&s); err != nil {
return fmt.Errorf("cannot unmarshal `if` option: %w", err)
}
expr, err := metricsql.Parse(s)
if err != nil {
return fmt.Errorf("cannot parse `if` series selector: %w", err)
}
me, ok := expr.(*metricsql.MetricExpr)
if !ok {
return fmt.Errorf("expecting `if` series selector; got %q", expr.AppendString(nil))
}
lfs, err := metricExprToLabelFilters(me)
if err != nil {
return fmt.Errorf("cannot parse `if` filters: %w", err)
}
ie.s = s
ie.lfs = lfs
return nil
}
// MarshalYAML marshals ie to YAML.
func (ie *IfExpression) MarshalYAML() (interface{}, error) {
return ie.s, nil
}
// Match returns true if ie matches the given labels.
func (ie *IfExpression) Match(labels []prompbmarshal.Label) bool {
for _, lf := range ie.lfs {
if !lf.match(labels) {
return false
}
}
return true
}
func metricExprToLabelFilters(me *metricsql.MetricExpr) ([]*labelFilter, error) {
lfs := make([]*labelFilter, len(me.LabelFilters))
for i := range me.LabelFilters {
lf, err := newLabelFilter(&me.LabelFilters[i])
if err != nil {
return nil, fmt.Errorf("cannot parse %s: %w", me.AppendString(nil), err)
}
lfs[i] = lf
}
return lfs, nil
}
// labelFilter contains PromQL filter for `{label op "value"}`
type labelFilter struct {
label string
op string
value string
// re contains compiled regexp for `=~` and `!~` op.
re *regexp.Regexp
}
func newLabelFilter(mlf *metricsql.LabelFilter) (*labelFilter, error) {
lf := &labelFilter{
label: toCanonicalLabelName(mlf.Label),
op: getFilterOp(mlf),
value: mlf.Value,
}
if lf.op == "=~" || lf.op == "!~" {
// PromQL regexps are anchored by default.
// See https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors
reString := "^(?:" + lf.value + ")$"
re, err := regexp.Compile(reString)
if err != nil {
return nil, fmt.Errorf("cannot parse regexp for %s: %w", mlf.AppendString(nil), err)
}
lf.re = re
}
return lf, nil
}
func (lf *labelFilter) match(labels []prompbmarshal.Label) bool {
switch lf.op {
case "=":
return lf.equalValue(labels)
case "!=":
return !lf.equalValue(labels)
case "=~":
return lf.equalRegexp(labels)
case "!~":
return !lf.equalRegexp(labels)
default:
logger.Panicf("BUG: unexpected operation for label filter: %s", lf.op)
}
return false
}
func (lf *labelFilter) equalValue(labels []prompbmarshal.Label) bool {
labelNameMatches := 0
for _, label := range labels {
if toCanonicalLabelName(label.Name) != lf.label {
continue
}
labelNameMatches++
if label.Value == lf.value {
return true
}
}
if labelNameMatches == 0 {
// Special case for {non_existing_label=""}, which matches anything except of non-empty non_existing_label
return lf.value == ""
}
return false
}
func (lf *labelFilter) equalRegexp(labels []prompbmarshal.Label) bool {
labelNameMatches := 0
for _, label := range labels {
if toCanonicalLabelName(label.Name) != lf.label {
continue
}
labelNameMatches++
if lf.re.MatchString(label.Value) {
return true
}
}
if labelNameMatches == 0 {
// Special case for {non_existing_label=~"something|"}, which matches empty non_existing_label
return lf.re.MatchString("")
}
return false
}
func toCanonicalLabelName(labelName string) string {
if labelName == "__name__" {
return ""
}
return labelName
}
func getFilterOp(mlf *metricsql.LabelFilter) string {
if mlf.IsNegative {
if mlf.IsRegexp {
return "!~"
}
return "!="
}
if mlf.IsRegexp {
return "=~"
}
return "="
}

View file

@ -0,0 +1,162 @@
package promrelabel
import (
"bytes"
"fmt"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
"gopkg.in/yaml.v2"
)
func TestIfExpressionUnmarshalFailure(t *testing.T) {
f := func(s string) {
t.Helper()
var ie IfExpression
err := yaml.UnmarshalStrict([]byte(s), &ie)
if err == nil {
t.Fatalf("expecting non-nil error")
}
}
f(`{`)
f(`{x:y}`)
f(`[]`)
f(`"{"`)
f(`'{'`)
f(`foo{bar`)
f(`foo{bar}`)
f(`foo{bar=`)
f(`foo{bar="`)
f(`foo{bar='`)
f(`foo{bar=~"("}`)
f(`foo{bar!~"("}`)
f(`foo{bar==aaa}`)
f(`foo{bar=="b"}`)
f(`'foo+bar'`)
f(`'foo{bar=~"a[b"}'`)
}
func TestIfExpressionUnmarshalSuccess(t *testing.T) {
f := func(s string) {
t.Helper()
var ie IfExpression
if err := yaml.UnmarshalStrict([]byte(s), &ie); err != nil {
t.Fatalf("unexpected error during unmarshal: %s", err)
}
b, err := yaml.Marshal(&ie)
if err != nil {
t.Fatalf("unexpected error during marshal: %s", err)
}
b = bytes.TrimSpace(b)
if string(b) != s {
t.Fatalf("unexpected marshaled data;\ngot\n%s\nwant\n%s", b, s)
}
}
f(`'{}'`)
f(`foo`)
f(`foo{bar="baz"}`)
f(`'{a="b", c!="d", e=~"g", h!~"d"}'`)
f(`foo{bar="zs",a=~"b|c"}`)
}
func TestIfExpressionMatch(t *testing.T) {
f := func(ifExpr, metricWithLabels string) {
t.Helper()
var ie IfExpression
if err := yaml.UnmarshalStrict([]byte(ifExpr), &ie); err != nil {
t.Fatalf("unexpected error during unmarshal: %s", err)
}
labels, err := parseMetricWithLabels(metricWithLabels)
if err != nil {
t.Fatalf("cannot parse %s: %s", metricWithLabels, err)
}
if !ie.Match(labels) {
t.Fatalf("unexpected mismatch of ifExpr=%s for %s", ifExpr, metricWithLabels)
}
}
f(`foo`, `foo`)
f(`foo`, `foo{bar="baz",a="b"}`)
f(`foo{bar="a"}`, `foo{bar="a"}`)
f(`foo{bar="a"}`, `foo{x="y",bar="a",baz="b"}`)
f(`'{a=~"x|abc",y!="z"}'`, `m{x="aa",a="abc"}`)
f(`'{a=~"x|abc",y!="z"}'`, `m{x="aa",a="abc",y="qwe"}`)
f(`'{__name__="foo"}'`, `foo{bar="baz"}`)
f(`'{__name__=~"foo|bar"}'`, `bar`)
f(`'{__name__!=""}'`, `foo`)
f(`'{__name__!=""}'`, `bar{baz="aa",b="c"}`)
f(`'{__name__!~"a.+"}'`, `bar{baz="aa",b="c"}`)
f(`foo{a!~"a.+"}`, `foo{a="baa"}`)
f(`'{foo=""}'`, `bar`)
f(`'{foo!=""}'`, `aa{foo="b"}`)
f(`'{foo=~".*"}'`, `abc`)
f(`'{foo=~".*"}'`, `abc{foo="bar"}`)
f(`'{foo!~".+"}'`, `abc`)
f(`'{foo=~"bar|"}'`, `abc`)
f(`'{foo=~"bar|"}'`, `abc{foo="bar"}`)
f(`'{foo!~"bar|"}'`, `abc{foo="baz"}`)
}
func TestIfExpressionMismatch(t *testing.T) {
f := func(ifExpr, metricWithLabels string) {
t.Helper()
var ie IfExpression
if err := yaml.UnmarshalStrict([]byte(ifExpr), &ie); err != nil {
t.Fatalf("unexpected error during unmarshal: %s", err)
}
labels, err := parseMetricWithLabels(metricWithLabels)
if err != nil {
t.Fatalf("cannot parse %s: %s", metricWithLabels, err)
}
if ie.Match(labels) {
t.Fatalf("unexpected match of ifExpr=%s for %s", ifExpr, metricWithLabels)
}
}
f(`foo`, `bar`)
f(`foo`, `a{foo="bar"}`)
f(`foo{bar="a"}`, `foo`)
f(`foo{bar="a"}`, `foo{bar="b"}`)
f(`foo{bar="a"}`, `foo{baz="b",a="b"}`)
f(`'{a=~"x|abc",y!="z"}'`, `m{x="aa",a="xabc"}`)
f(`'{a=~"x|abc",y!="z"}'`, `m{x="aa",a="abc",y="z"}`)
f(`'{__name__!~".+"}'`, `foo`)
f(`'{a!~"a.+"}'`, `foo{a="abc"}`)
f(`'{foo=""}'`, `bar{foo="aa"}`)
f(`'{foo!=""}'`, `aa`)
f(`'{foo=~".+"}'`, `abc`)
f(`'{foo!~".+"}'`, `abc{foo="x"}`)
f(`'{foo=~"bar|"}'`, `abc{foo="baz"}`)
f(`'{foo!~"bar|"}'`, `abc`)
f(`'{foo!~"bar|"}'`, `abc{foo="bar"}`)
}
func parseMetricWithLabels(metricWithLabels string) ([]prompbmarshal.Label, error) {
// add a value to metricWithLabels, so it could be parsed by prometheus protocol parser.
s := metricWithLabels + " 123"
var rows prometheus.Rows
var err error
rows.UnmarshalWithErrLogger(s, func(s string) {
err = fmt.Errorf("error during metric parse: %s", s)
})
if err != nil {
return nil, err
}
if len(rows.Rows) != 1 {
return nil, fmt.Errorf("unexpected number of rows parsed; got %d; want 1", len(rows.Rows))
}
r := rows.Rows[0]
var lfs []prompbmarshal.Label
if r.Metric != "" {
lfs = append(lfs, prompbmarshal.Label{
Name: "__name__",
Value: r.Metric,
})
}
for _, tag := range r.Tags {
lfs = append(lfs, prompbmarshal.Label{
Name: tag.Key,
Value: tag.Value,
})
}
return lfs, nil
}

View file

@ -23,6 +23,7 @@ type parsedRelabelConfig struct {
Modulus uint64
Replacement string
Action string
If *IfExpression
regexOriginal *regexp.Regexp
hasCaptureGroupInTargetLabel bool
@ -137,8 +138,17 @@ func FinalizeLabels(dst, src []prompbmarshal.Label) []prompbmarshal.Label {
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset int) []prompbmarshal.Label {
src := labels[labelsOffset:]
if prc.If != nil && !prc.If.Match(labels) {
if prc.Action == "keep" {
// Drop the target on `if` mismatch for `action: keep`
return labels[:labelsOffset]
}
// Do not apply prc actions on `if` mismatch.
return labels
}
switch prc.Action {
case "replace":
// Store `replacement` at `target_label` if the `regex` matches `source_labels` joined with `separator`
bb := relabelBufPool.Get()
bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
if prc.Regex == defaultRegexForRelabelConfig && !prc.hasCaptureGroupInTargetLabel {
@ -174,6 +184,8 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
relabelBufPool.Put(bb)
return setLabelValue(labels, labelsOffset, nameStr, valueStr)
case "replace_all":
// Replace all the occurences of `regex` at `source_labels` joined with `separator` with the `replacement`
// and store the result at `target_label`
bb := relabelBufPool.Get()
bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
sourceStr := string(bb.B)
@ -208,6 +220,15 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
}
return labels
case "keep":
// Keep the target if `source_labels` joined with `separator` match the `regex`.
if prc.Regex == defaultRegexForRelabelConfig {
// Fast path for the case with `if` and without explicitly set `regex`:
//
// - action: keep
// if: 'some{label=~"filters"}'
//
return labels
}
bb := relabelBufPool.Get()
bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
keep := prc.matchString(bytesutil.ToUnsafeString(bb.B))
@ -217,6 +238,15 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
}
return labels
case "drop":
// Drop the target if `source_labels` joined with `separator` don't match the `regex`.
if prc.Regex == defaultRegexForRelabelConfig {
// Fast path for the case with `if` and without explicitly set `regex`:
//
// - action: drop
// if: 'some{label=~"filters"}'
//
return labels[:labelsOffset]
}
bb := relabelBufPool.Get()
bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
drop := prc.matchString(bytesutil.ToUnsafeString(bb.B))
@ -226,6 +256,7 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
}
return labels
case "hashmod":
// Calculate the `modulus` from the hash of `source_labels` joined with `separator` and store it at `target_label`
bb := relabelBufPool.Get()
bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
h := xxhash.Sum64(bb.B) % prc.Modulus
@ -233,6 +264,7 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
relabelBufPool.Put(bb)
return setLabelValue(labels, labelsOffset, prc.TargetLabel, value)
case "labelmap":
// Replace label names with the `replacement` if they match `regex`
for i := range src {
label := &src[i]
labelName, ok := prc.replaceFullString(label.Name, prc.Replacement, prc.hasCaptureGroupInReplacement)
@ -242,12 +274,14 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
}
return labels
case "labelmap_all":
// Replace all the occurences of `regex` at label names with `replacement`
for i := range src {
label := &src[i]
label.Name, _ = prc.replaceStringSubmatches(label.Name, prc.Replacement, prc.hasCaptureGroupInReplacement)
}
return labels
case "labeldrop":
// Drop labels with names matching the `regex`
dst := labels[:labelsOffset]
for i := range src {
label := &src[i]
@ -257,6 +291,7 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
}
return dst
case "labelkeep":
// Keep labels with names matching the `regex`
dst := labels[:labelsOffset]
for i := range src {
label := &src[i]

View file

@ -134,6 +134,25 @@ func TestApplyRelabelConfigs(t *testing.T) {
source_labels: ["foo"]
target_label: "bar"
regex: ".+"
`, []prompbmarshal.Label{
{
Name: "xxx",
Value: "yyy",
},
}, false, []prompbmarshal.Label{
{
Name: "xxx",
Value: "yyy",
},
})
})
t.Run("replace-if-miss", func(t *testing.T) {
f(`
- action: replace
if: '{foo="bar"}'
source_labels: ["xxx", "foo"]
target_label: "bar"
replacement: "a-$1-b"
`, []prompbmarshal.Label{
{
Name: "xxx",
@ -152,6 +171,29 @@ func TestApplyRelabelConfigs(t *testing.T) {
source_labels: ["xxx", "foo"]
target_label: "bar"
replacement: "a-$1-b"
`, []prompbmarshal.Label{
{
Name: "xxx",
Value: "yyy",
},
}, false, []prompbmarshal.Label{
{
Name: "bar",
Value: "a-yyy;-b",
},
{
Name: "xxx",
Value: "yyy",
},
})
})
t.Run("replace-if-hit", func(t *testing.T) {
f(`
- action: replace
if: '{xxx=~".y."}'
source_labels: ["xxx", "foo"]
target_label: "bar"
replacement: "a-$1-b"
`, []prompbmarshal.Label{
{
Name: "xxx",
@ -333,6 +375,26 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
})
})
t.Run("replace_all-if-miss", func(t *testing.T) {
f(`
- action: replace_all
if: 'foo'
source_labels: ["xxx"]
target_label: "xxx"
regex: "-"
replacement: "."
`, []prompbmarshal.Label{
{
Name: "xxx",
Value: "a-b-c",
},
}, false, []prompbmarshal.Label{
{
Name: "xxx",
Value: "a-b-c",
},
})
})
t.Run("replace_all-hit", func(t *testing.T) {
f(`
- action: replace_all
@ -340,6 +402,26 @@ func TestApplyRelabelConfigs(t *testing.T) {
target_label: "xxx"
regex: "-"
replacement: "."
`, []prompbmarshal.Label{
{
Name: "xxx",
Value: "a-b-c",
},
}, false, []prompbmarshal.Label{
{
Name: "xxx",
Value: "a.b.c",
},
})
})
t.Run("replace_all-if-hit", func(t *testing.T) {
f(`
- action: replace_all
if: '{non_existing_label=~".*"}'
source_labels: ["xxx"]
target_label: "xxx"
regex: "-"
replacement: "."
`, []prompbmarshal.Label{
{
Name: "xxx",
@ -530,6 +612,33 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
}, true, []prompbmarshal.Label{})
})
t.Run("keep-if-miss", func(t *testing.T) {
f(`
- action: keep
if: '{foo="bar"}'
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
}, false, []prompbmarshal.Label{})
})
t.Run("keep-if-hit", func(t *testing.T) {
f(`
- action: keep
if: '{foo="yyy"}'
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
}, false, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
})
})
t.Run("keep-hit", func(t *testing.T) {
f(`
- action: keep
@ -577,6 +686,33 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
}, true, []prompbmarshal.Label{})
})
t.Run("keep_metrics-if-miss", func(t *testing.T) {
f(`
- action: keep_metrics
if: 'bar'
`, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
}, true, []prompbmarshal.Label{})
})
t.Run("keep_metrics-if-hit", func(t *testing.T) {
f(`
- action: keep_metrics
if: 'foo'
`, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
}, true, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
})
})
t.Run("keep_metrics-hit", func(t *testing.T) {
f(`
- action: keep_metrics
@ -617,6 +753,33 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
})
})
t.Run("drop-if-miss", func(t *testing.T) {
f(`
- action: drop
if: '{foo="bar"}'
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
}, true, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
})
})
t.Run("drop-if-hit", func(t *testing.T) {
f(`
- action: drop
if: '{foo="yyy"}'
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
}, true, []prompbmarshal.Label{})
})
t.Run("drop-hit", func(t *testing.T) {
f(`
- action: drop
@ -659,6 +822,33 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
})
})
t.Run("drop_metrics-if-miss", func(t *testing.T) {
f(`
- action: drop_metrics
if: bar
`, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
}, true, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
})
})
t.Run("drop_metrics-if-hit", func(t *testing.T) {
f(`
- action: drop_metrics
if: foo
`, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
}, true, []prompbmarshal.Label{})
})
t.Run("drop_metrics-hit", func(t *testing.T) {
f(`
- action: drop_metrics
@ -694,6 +884,48 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
})
})
t.Run("hashmod-if-miss", func(t *testing.T) {
f(`
- action: hashmod
if: '{foo="bar"}'
source_labels: [foo]
target_label: aaa
modulus: 123
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
}, true, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
})
})
t.Run("hashmod-if-hit", func(t *testing.T) {
f(`
- action: hashmod
if: '{foo="yyy"}'
source_labels: [foo]
target_label: aaa
modulus: 123
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
}, true, []prompbmarshal.Label{
{
Name: "aaa",
Value: "73",
},
{
Name: "foo",
Value: "yyy",
},
})
})
t.Run("hashmod-hit", func(t *testing.T) {
f(`
- action: hashmod
@ -716,6 +948,62 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
})
})
t.Run("labelmap-copy-label-if-miss", func(t *testing.T) {
f(`
- action: labelmap
if: '{foo="yyy",foobar="aab"}'
regex: "foo"
replacement: "bar"
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
}, true, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
})
})
t.Run("labelmap-copy-label-if-hit", func(t *testing.T) {
f(`
- action: labelmap
if: '{foo="yyy",foobar="aaa"}'
regex: "foo"
replacement: "bar"
`, []prompbmarshal.Label{
{
Name: "foo",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
}, true, []prompbmarshal.Label{
{
Name: "bar",
Value: "yyy",
},
{
Name: "foo",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
})
})
t.Run("labelmap-copy-label", func(t *testing.T) {
f(`
- action: labelmap
@ -830,6 +1118,58 @@ func TestApplyRelabelConfigs(t *testing.T) {
},
})
})
t.Run("labelmap_all-if-miss", func(t *testing.T) {
f(`
- action: labelmap_all
if: foobar
regex: "\\."
replacement: "-"
`, []prompbmarshal.Label{
{
Name: "foo.bar.baz",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
}, true, []prompbmarshal.Label{
{
Name: "foo.bar.baz",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
})
})
t.Run("labelmap_all-if-hit", func(t *testing.T) {
f(`
- action: labelmap_all
if: '{foo.bar.baz="yyy"}'
regex: "\\."
replacement: "-"
`, []prompbmarshal.Label{
{
Name: "foo.bar.baz",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
}, true, []prompbmarshal.Label{
{
Name: "foo-bar-baz",
Value: "yyy",
},
{
Name: "foobar",
Value: "aaa",
},
})
})
t.Run("labelmap_all", func(t *testing.T) {
f(`
- action: labelmap_all
@ -895,6 +1235,66 @@ func TestApplyRelabelConfigs(t *testing.T) {
Value: "bbb",
},
})
// if-miss
f(`
- action: labeldrop
if: foo
regex: dropme
`, []prompbmarshal.Label{
{
Name: "xxx",
Value: "yyy",
},
{
Name: "dropme",
Value: "aaa",
},
{
Name: "foo",
Value: "bar",
},
}, false, []prompbmarshal.Label{
{
Name: "dropme",
Value: "aaa",
},
{
Name: "foo",
Value: "bar",
},
{
Name: "xxx",
Value: "yyy",
},
})
// if-hit
f(`
- action: labeldrop
if: '{xxx="yyy"}'
regex: dropme
`, []prompbmarshal.Label{
{
Name: "xxx",
Value: "yyy",
},
{
Name: "dropme",
Value: "aaa",
},
{
Name: "foo",
Value: "bar",
},
}, false, []prompbmarshal.Label{
{
Name: "foo",
Value: "bar",
},
{
Name: "xxx",
Value: "yyy",
},
})
f(`
- action: labeldrop
regex: dropme
@ -1059,6 +1459,62 @@ func TestApplyRelabelConfigs(t *testing.T) {
Value: "aaa",
},
})
// if-miss
f(`
- action: labelkeep
if: '{aaaa="awefx"}'
regex: keepme
`, []prompbmarshal.Label{
{
Name: "keepme",
Value: "aaa",
},
{
Name: "aaaa",
Value: "awef",
},
{
Name: "keepme-aaa",
Value: "234",
},
}, false, []prompbmarshal.Label{
{
Name: "aaaa",
Value: "awef",
},
{
Name: "keepme",
Value: "aaa",
},
{
Name: "keepme-aaa",
Value: "234",
},
})
// if-hit
f(`
- action: labelkeep
if: '{aaaa="awef"}'
regex: keepme
`, []prompbmarshal.Label{
{
Name: "keepme",
Value: "aaa",
},
{
Name: "aaaa",
Value: "awef",
},
{
Name: "keepme-aaa",
Value: "234",
},
}, false, []prompbmarshal.Label{
{
Name: "keepme",
Value: "aaa",
},
})
f(`
- action: labelkeep
regex: keepme