mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
lib/promrelabel: add support for conditional relabeling via if
filter
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1998
This commit is contained in:
parent
d128a5bf99
commit
3f49bdaeff
@ -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.
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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, "$"),
|
||||
|
169
lib/promrelabel/if_expression.go
Normal file
169
lib/promrelabel/if_expression.go
Normal 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 "="
|
||||
}
|
162
lib/promrelabel/if_expression_test.go
Normal file
162
lib/promrelabel/if_expression_test.go
Normal 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
|
||||
}
|
@ -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]
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user