github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/tag/replace.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package tag
    22  
    23  import (
    24  	"fmt"
    25  	"regexp"
    26  
    27  	"github.com/m3db/m3/src/query/block"
    28  	"github.com/m3db/m3/src/query/models"
    29  )
    30  
    31  // TagReplaceType creates a new tag for all metrics containing a tag
    32  // with the given name whose value matches the given regex.
    33  // NB: This does not actually remove the original tag, but will override
    34  // the existing tag if the source and destination name parameters are equal.
    35  const TagReplaceType = "label_replace"
    36  
    37  // Builds and adds the replaced tag if the source tag is found in the list,
    38  // and if the value of that tag matches the given regex. Returns false if the series
    39  // does not contain the source tag name, regardless of if the value matches the regex.
    40  func addTagIfFoundAndValid(
    41  	tags models.Tags,
    42  	val []byte,
    43  	destinationName []byte,
    44  	destinationValRegex []byte,
    45  	regex *regexp.Regexp,
    46  ) (models.Tag, bool) {
    47  	indices := regex.FindSubmatchIndex(val)
    48  	if indices == nil {
    49  		return models.Tag{}, false
    50  	}
    51  
    52  	destinationVal := regex.Expand([]byte{}, destinationValRegex, val, indices)
    53  	return models.Tag{Name: destinationName, Value: destinationVal}, true
    54  }
    55  
    56  func makeTagReplaceFunc(params []string) (tagTransformFunc, error) {
    57  	if len(params) != 4 {
    58  		return nil, fmt.Errorf("invalid number of args for tag replace: %d", len(params))
    59  	}
    60  
    61  	regex, err := regexp.Compile(params[3])
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	destinationName := []byte(params[0])
    67  	destinationValRegex := []byte(params[1])
    68  	sourceName := []byte(params[2])
    69  
    70  	return func(
    71  		meta block.Metadata,
    72  		seriesMeta []block.SeriesMeta,
    73  	) (block.Metadata, []block.SeriesMeta) {
    74  		// Optimization if all joining series are shared by the block,
    75  		// or if there is only a shared metadata and no single series metas.
    76  		val, found := meta.Tags.Get(sourceName)
    77  		if found {
    78  			tag, valid := addTagIfFoundAndValid(
    79  				meta.Tags,
    80  				val,
    81  				destinationName,
    82  				destinationValRegex,
    83  				regex,
    84  			)
    85  
    86  			// NB: If the tag exists in shared block tag list, it cannot also exist
    87  			// in the tag lists for the series metadatas, so it's valid to short
    88  			// circuit here.
    89  			if valid {
    90  				meta.Tags = meta.Tags.AddOrUpdateTag(tag)
    91  			}
    92  
    93  			return meta, seriesMeta
    94  		}
    95  
    96  		if len(seriesMeta) == 0 {
    97  			return meta, seriesMeta
    98  		}
    99  
   100  		for i, meta := range seriesMeta {
   101  			val, found := meta.Tags.Get(sourceName)
   102  			if !found {
   103  				continue
   104  			}
   105  
   106  			if tag, valid := addTagIfFoundAndValid(
   107  				meta.Tags,
   108  				val,
   109  				destinationName,
   110  				destinationValRegex,
   111  				regex,
   112  			); valid {
   113  				seriesMeta[i].Tags = seriesMeta[i].Tags.AddOrUpdateTag(tag)
   114  			}
   115  		}
   116  
   117  		return meta, seriesMeta
   118  	}, nil
   119  }