github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/relabel/relabel.go (about)

     1  // Copyright 2015 The Prometheus Authors
     2  // Copyright 2021 The Pyroscope Authors
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  // http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package relabel
    17  
    18  import (
    19  	"crypto/md5"
    20  	"fmt"
    21  	"regexp"
    22  	"strings"
    23  
    24  	"github.com/pyroscope-io/pyroscope/pkg/scrape/labels"
    25  	"github.com/pyroscope-io/pyroscope/pkg/scrape/model"
    26  )
    27  
    28  var (
    29  	relabelTarget = regexp.MustCompile(`^(?:(?:[a-zA-Z_]|\$(?:\{\w+\}|\w+))+\w*)+$`)
    30  
    31  	DefaultRelabelConfig = Config{
    32  		Action:      Replace,
    33  		Separator:   ";",
    34  		Regex:       MustNewRegexp("(.*)"),
    35  		Replacement: "$1",
    36  	}
    37  )
    38  
    39  // Action is the action to be performed on relabeling.
    40  type Action string
    41  
    42  const (
    43  	// Replace performs a regex replacement.
    44  	Replace Action = "replace"
    45  	// Keep drops targets for which the input does not match the regex.
    46  	Keep Action = "keep"
    47  	// Drop drops targets for which the input does match the regex.
    48  	Drop Action = "drop"
    49  	// HashMod sets a label to the modulus of a hash of labels.
    50  	HashMod Action = "hashmod"
    51  	// LabelMap copies labels to other labelnames based on a regex.
    52  	LabelMap Action = "labelmap"
    53  	// LabelDrop drops any label matching the regex.
    54  	LabelDrop Action = "labeldrop"
    55  	// LabelKeep drops any label not matching the regex.
    56  	LabelKeep Action = "labelkeep"
    57  )
    58  
    59  // UnmarshalYAML implements the yaml.Unmarshaler interface.
    60  func (a *Action) UnmarshalYAML(unmarshal func(interface{}) error) error {
    61  	var s string
    62  	if err := unmarshal(&s); err != nil {
    63  		return err
    64  	}
    65  	switch act := Action(strings.ToLower(s)); act {
    66  	case Replace, Keep, Drop, HashMod, LabelMap, LabelDrop, LabelKeep:
    67  		*a = act
    68  		return nil
    69  	}
    70  	return fmt.Errorf("unknown relabel action %q", s)
    71  }
    72  
    73  // Config is the configuration for relabeling of target label sets.
    74  type Config struct {
    75  	// A list of labels from which values are taken and concatenated
    76  	// with the configured separator in order.
    77  	SourceLabels model.LabelNames `yaml:"source-labels,flow,omitempty"`
    78  	// Separator is the string between concatenated values from the source labels.
    79  	Separator string `yaml:"separator,omitempty"`
    80  	// Regex against which the concatenation is matched.
    81  	Regex Regexp `yaml:"regex,omitempty"`
    82  	// Modulus to take of the hash of concatenated values from the source labels.
    83  	Modulus uint64 `yaml:"modulus,omitempty"`
    84  	// TargetLabel is the label to which the resulting string is written in a replacement.
    85  	// Regexp interpolation is allowed for the replace action.
    86  	TargetLabel string `yaml:"target-label,omitempty"`
    87  	// Replacement is the regex replacement pattern to be used.
    88  	Replacement string `yaml:"replacement,omitempty"`
    89  	// Action is the action to be performed for the relabeling.
    90  	Action Action `yaml:"action,omitempty"`
    91  }
    92  
    93  // UnmarshalYAML implements the yaml.Unmarshaler interface.
    94  func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
    95  	*c = DefaultRelabelConfig
    96  	type plain Config
    97  	if err := unmarshal((*plain)(c)); err != nil {
    98  		return err
    99  	}
   100  	if c.Regex.Regexp == nil {
   101  		c.Regex = MustNewRegexp("")
   102  	}
   103  	if c.Action == "" {
   104  		return fmt.Errorf("relabel action cannot be empty")
   105  	}
   106  	if c.Modulus == 0 && c.Action == HashMod {
   107  		return fmt.Errorf("relabel configuration for hashmod requires non-zero modulus")
   108  	}
   109  	if (c.Action == Replace || c.Action == HashMod) && c.TargetLabel == "" {
   110  		return fmt.Errorf("relabel configuration for %s action requires 'target-label' value", c.Action)
   111  	}
   112  	if c.Action == Replace && !relabelTarget.MatchString(c.TargetLabel) {
   113  		return fmt.Errorf("%q is invalid 'target-label' for %s action", c.TargetLabel, c.Action)
   114  	}
   115  	if c.Action == LabelMap && !relabelTarget.MatchString(c.Replacement) {
   116  		return fmt.Errorf("%q is invalid 'replacement' for %s action", c.Replacement, c.Action)
   117  	}
   118  	if c.Action == HashMod && !model.LabelName(c.TargetLabel).IsValid() {
   119  		return fmt.Errorf("%q is invalid 'target-label' for %s action", c.TargetLabel, c.Action)
   120  	}
   121  
   122  	if c.Action == LabelDrop || c.Action == LabelKeep {
   123  		if c.SourceLabels != nil ||
   124  			c.TargetLabel != DefaultRelabelConfig.TargetLabel ||
   125  			c.Modulus != DefaultRelabelConfig.Modulus ||
   126  			c.Separator != DefaultRelabelConfig.Separator ||
   127  			c.Replacement != DefaultRelabelConfig.Replacement {
   128  			return fmt.Errorf("%s action requires only 'regex', and no other fields", c.Action)
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
   136  type Regexp struct {
   137  	*regexp.Regexp
   138  	original string
   139  }
   140  
   141  // NewRegexp creates a new anchored Regexp and returns an error if the
   142  // passed-in regular expression does not compile.
   143  func NewRegexp(s string) (Regexp, error) {
   144  	regex, err := regexp.Compile("^(?:" + s + ")$")
   145  	return Regexp{
   146  		Regexp:   regex,
   147  		original: s,
   148  	}, err
   149  }
   150  
   151  // MustNewRegexp works like NewRegexp, but panics if the regular expression does not compile.
   152  func MustNewRegexp(s string) Regexp {
   153  	re, err := NewRegexp(s)
   154  	if err != nil {
   155  		panic(err)
   156  	}
   157  	return re
   158  }
   159  
   160  // UnmarshalYAML implements the yaml.Unmarshaler interface.
   161  func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
   162  	var s string
   163  	if err := unmarshal(&s); err != nil {
   164  		return err
   165  	}
   166  	r, err := NewRegexp(s)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	*re = r
   171  	return nil
   172  }
   173  
   174  // MarshalYAML implements the yaml.Marshaler interface.
   175  func (re Regexp) MarshalYAML() (interface{}, error) {
   176  	if re.original != "" {
   177  		return re.original, nil
   178  	}
   179  	return nil, nil
   180  }
   181  
   182  // Process returns a relabeled copy of the given label set. The relabel configurations
   183  // are applied in order of input.
   184  // If a label set is dropped, nil is returned.
   185  // May return the input labelSet modified.
   186  func Process(l labels.Labels, cfgs ...*Config) labels.Labels {
   187  	for _, cfg := range cfgs {
   188  		l = relabel(l, cfg)
   189  		if l == nil {
   190  			return nil
   191  		}
   192  	}
   193  	return l
   194  }
   195  
   196  func relabel(lset labels.Labels, cfg *Config) labels.Labels {
   197  	values := make([]string, 0, len(cfg.SourceLabels))
   198  	for _, ln := range cfg.SourceLabels {
   199  		values = append(values, lset.Get(string(ln)))
   200  	}
   201  	val := strings.Join(values, cfg.Separator)
   202  
   203  	lb := labels.NewBuilder(lset)
   204  
   205  	switch cfg.Action {
   206  	case Drop:
   207  		if cfg.Regex.MatchString(val) {
   208  			return nil
   209  		}
   210  	case Keep:
   211  		if !cfg.Regex.MatchString(val) {
   212  			return nil
   213  		}
   214  	case Replace:
   215  		indexes := cfg.Regex.FindStringSubmatchIndex(val)
   216  		// If there is no match no replacement must take place.
   217  		if indexes == nil {
   218  			break
   219  		}
   220  		target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes))
   221  		if !target.IsValid() {
   222  			lb.Del(cfg.TargetLabel)
   223  			break
   224  		}
   225  		res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes)
   226  		if len(res) == 0 {
   227  			lb.Del(cfg.TargetLabel)
   228  			break
   229  		}
   230  		lb.Set(string(target), string(res))
   231  	case HashMod:
   232  		mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus
   233  		lb.Set(cfg.TargetLabel, fmt.Sprintf("%d", mod))
   234  	case LabelMap:
   235  		for _, l := range lset {
   236  			if cfg.Regex.MatchString(l.Name) {
   237  				res := cfg.Regex.ReplaceAllString(l.Name, cfg.Replacement)
   238  				lb.Set(res, l.Value)
   239  			}
   240  		}
   241  	case LabelDrop:
   242  		for _, l := range lset {
   243  			if cfg.Regex.MatchString(l.Name) {
   244  				lb.Del(l.Name)
   245  			}
   246  		}
   247  	case LabelKeep:
   248  		for _, l := range lset {
   249  			if !cfg.Regex.MatchString(l.Name) {
   250  				lb.Del(l.Name)
   251  			}
   252  		}
   253  	default:
   254  		panic(fmt.Errorf("relabel: unknown relabel action type %q", cfg.Action))
   255  	}
   256  
   257  	return lb.Labels()
   258  }
   259  
   260  // sum64 sums the md5 hash to an uint64.
   261  func sum64(hash [md5.Size]byte) uint64 {
   262  	var s uint64
   263  
   264  	for i, b := range hash {
   265  		shift := uint64((md5.Size - i - 1) * 8)
   266  
   267  		s |= uint64(b) << shift
   268  	}
   269  	return s
   270  }