diff --git a/pkg/relabel/relabel.go b/pkg/relabel/relabel.go new file mode 100644 index 0000000000..775051474c --- /dev/null +++ b/pkg/relabel/relabel.go @@ -0,0 +1,115 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package relabel + +import ( + "crypto/md5" + "fmt" + "strings" + + "github.com/prometheus/common/model" + + "github.com/prometheus/prometheus/config" + "github.com/prometheus/prometheus/pkg/labels" +) + +// Process returns a relabeled copy of the given label set. The relabel configurations +// 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.RelabelConfig) labels.Labels { + for _, cfg := range cfgs { + labels = relabel(labels, cfg) + if labels == nil { + return nil + } + } + return labels +} + +func relabel(lset labels.Labels, cfg *config.RelabelConfig) labels.Labels { + 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) + + switch cfg.Action { + case config.RelabelDrop: + if cfg.Regex.MatchString(val) { + return nil + } + case config.RelabelKeep: + if !cfg.Regex.MatchString(val) { + return nil + } + case config.RelabelReplace: + indexes := cfg.Regex.FindStringSubmatchIndex(val) + // If there is no match no replacement must take place. + if indexes == nil { + break + } + target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes)) + if !target.IsValid() { + lb.Del(cfg.TargetLabel) + break + } + res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes) + if len(res) == 0 { + lb.Del(cfg.TargetLabel) + break + } + lb.Set(string(target), string(res)) + case config.RelabelHashMod: + mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus + lb.Set(cfg.TargetLabel, fmt.Sprintf("%d", mod)) + case config.RelabelLabelMap: + for _, l := range lset { + if cfg.Regex.MatchString(l.Name) { + res := cfg.Regex.ReplaceAllString(l.Name, cfg.Replacement) + lb.Set(res, l.Value) + } + } + case config.RelabelLabelDrop: + for _, l := range lset { + if cfg.Regex.MatchString(l.Name) { + lb.Del(l.Name) + } + } + case config.RelabelLabelKeep: + for _, l := range lset { + if !cfg.Regex.MatchString(l.Name) { + lb.Del(l.Name) + } + } + default: + panic(fmt.Errorf("retrieval.relabel: unknown relabel action type %q", cfg.Action)) + } + + return lb.Labels() +} + +// sum64 sums the md5 hash to an uint64. +func sum64(hash [md5.Size]byte) uint64 { + var s uint64 + + for i, b := range hash { + shift := uint64((md5.Size - i - 1) * 8) + + s |= uint64(b) << shift + } + return s +} diff --git a/pkg/relabel/relabel_test.go b/pkg/relabel/relabel_test.go new file mode 100644 index 0000000000..64c3fe5484 --- /dev/null +++ b/pkg/relabel/relabel_test.go @@ -0,0 +1,423 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package relabel + +import ( + "reflect" + "testing" + + "github.com/prometheus/common/model" + + "github.com/prometheus/prometheus/config" + "github.com/prometheus/prometheus/pkg/labels" +) + +func TestRelabel(t *testing.T) { + tests := []struct { + input labels.Labels + relabel []*config.RelabelConfig + output labels.Labels + }{ + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b": "bar", + "c": "baz", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("f(.*)"), + TargetLabel: "d", + Separator: ";", + Replacement: "ch${1}-ch${1}", + Action: config.RelabelReplace, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + "b": "bar", + "c": "baz", + "d": "choo-choo", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b": "bar", + "c": "baz", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a", "b"}, + Regex: config.MustNewRegexp("f(.*);(.*)r"), + TargetLabel: "a", + Separator: ";", + Replacement: "b${1}${2}m", // boobam + Action: config.RelabelReplace, + }, + { + SourceLabels: model.LabelNames{"c", "a"}, + Regex: config.MustNewRegexp("(b).*b(.*)ba(.*)"), + TargetLabel: "d", + Separator: ";", + Replacement: "$1$2$2$3", + Action: config.RelabelReplace, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "boobam", + "b": "bar", + "c": "baz", + "d": "boooom", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp(".*o.*"), + Action: config.RelabelDrop, + }, { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("f(.*)"), + TargetLabel: "d", + Separator: ";", + Replacement: "ch$1-ch$1", + Action: config.RelabelReplace, + }, + }, + output: nil, + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b": "bar", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp(".*o.*"), + Action: config.RelabelDrop, + }, + }, + output: nil, + }, + { + input: labels.FromMap(map[string]string{ + "a": "abc", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp(".*(b).*"), + TargetLabel: "d", + Separator: ";", + Replacement: "$1", + Action: config.RelabelReplace, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "abc", + "d": "b", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("no-match"), + Action: config.RelabelDrop, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("f|o"), + Action: config.RelabelDrop, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("no-match"), + Action: config.RelabelKeep, + }, + }, + output: nil, + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("f.*"), + Action: config.RelabelKeep, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + }), + }, + { + // No replacement must be applied if there is no match. + input: labels.FromMap(map[string]string{ + "a": "boo", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("f"), + TargetLabel: "b", + Replacement: "bar", + Action: config.RelabelReplace, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "boo", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b": "bar", + "c": "baz", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"c"}, + TargetLabel: "d", + Separator: ";", + Action: config.RelabelHashMod, + Modulus: 1000, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + "b": "bar", + "c": "baz", + "d": "976", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b1": "bar", + "b2": "baz", + }), + relabel: []*config.RelabelConfig{ + { + Regex: config.MustNewRegexp("(b.*)"), + Replacement: "bar_${1}", + Action: config.RelabelLabelMap, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + "b1": "bar", + "b2": "baz", + "bar_b1": "bar", + "bar_b2": "baz", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "__meta_my_bar": "aaa", + "__meta_my_baz": "bbb", + "__meta_other": "ccc", + }), + relabel: []*config.RelabelConfig{ + { + Regex: config.MustNewRegexp("__meta_(my.*)"), + Replacement: "${1}", + Action: config.RelabelLabelMap, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + "__meta_my_bar": "aaa", + "__meta_my_baz": "bbb", + "__meta_other": "ccc", + "my_bar": "aaa", + "my_baz": "bbb", + }), + }, + { // valid case + input: labels.FromMap(map[string]string{ + "a": "some-name-value", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), + Action: config.RelabelReplace, + Replacement: "${2}", + TargetLabel: "${1}", + }, + }, + output: labels.FromMap(map[string]string{ + "a": "some-name-value", + "name": "value", + }), + }, + { // invalid replacement "" + input: labels.FromMap(map[string]string{ + "a": "some-name-value", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), + Action: config.RelabelReplace, + Replacement: "${3}", + TargetLabel: "${1}", + }, + }, + output: labels.FromMap(map[string]string{ + "a": "some-name-value", + }), + }, + { // invalid target_labels + input: labels.FromMap(map[string]string{ + "a": "some-name-value", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), + Action: config.RelabelReplace, + Replacement: "${1}", + TargetLabel: "${3}", + }, + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), + Action: config.RelabelReplace, + Replacement: "${1}", + TargetLabel: "0${3}", + }, + { + SourceLabels: model.LabelNames{"a"}, + Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), + Action: config.RelabelReplace, + Replacement: "${1}", + TargetLabel: "-${3}", + }, + }, + output: labels.FromMap(map[string]string{ + "a": "some-name-value", + }), + }, + { // more complex real-life like usecase + input: labels.FromMap(map[string]string{ + "__meta_sd_tags": "path:/secret,job:some-job,label:foo=bar", + }), + relabel: []*config.RelabelConfig{ + { + SourceLabels: model.LabelNames{"__meta_sd_tags"}, + Regex: config.MustNewRegexp("(?:.+,|^)path:(/[^,]+).*"), + Action: config.RelabelReplace, + Replacement: "${1}", + TargetLabel: "__metrics_path__", + }, + { + SourceLabels: model.LabelNames{"__meta_sd_tags"}, + Regex: config.MustNewRegexp("(?:.+,|^)job:([^,]+).*"), + Action: config.RelabelReplace, + Replacement: "${1}", + TargetLabel: "job", + }, + { + SourceLabels: model.LabelNames{"__meta_sd_tags"}, + Regex: config.MustNewRegexp("(?:.+,|^)label:([^=]+)=([^,]+).*"), + Action: config.RelabelReplace, + Replacement: "${2}", + TargetLabel: "${1}", + }, + }, + output: labels.FromMap(map[string]string{ + "__meta_sd_tags": "path:/secret,job:some-job,label:foo=bar", + "__metrics_path__": "/secret", + "job": "some-job", + "foo": "bar", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b1": "bar", + "b2": "baz", + }), + relabel: []*config.RelabelConfig{ + { + Regex: config.MustNewRegexp("(b.*)"), + Action: config.RelabelLabelKeep, + }, + }, + output: labels.FromMap(map[string]string{ + "b1": "bar", + "b2": "baz", + }), + }, + { + input: labels.FromMap(map[string]string{ + "a": "foo", + "b1": "bar", + "b2": "baz", + }), + relabel: []*config.RelabelConfig{ + { + Regex: config.MustNewRegexp("(b.*)"), + Action: config.RelabelLabelDrop, + }, + }, + output: labels.FromMap(map[string]string{ + "a": "foo", + }), + }, + } + + for i, test := range tests { + res := Process(test.input, test.relabel...) + + if !reflect.DeepEqual(res, test.output) { + t.Errorf("Test %d: relabel output mismatch: expected %#v, got %#v", i+1, test.output, res) + } + } +}