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 }