github.com/uber-go/tally/v4@v4.1.17/key_gen.go (about)

     1  // Copyright (c) 2021 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 tally
    22  
    23  const (
    24  	prefixSplitter  = '+'
    25  	keyPairSplitter = ','
    26  	keyNameSplitter = '='
    27  	nilString       = ""
    28  )
    29  
    30  // KeyForStringMap generates a unique key for a map string set combination.
    31  func KeyForStringMap(
    32  	stringMap map[string]string,
    33  ) string {
    34  	return KeyForPrefixedStringMap(nilString, stringMap)
    35  }
    36  
    37  // KeyForPrefixedStringMap generates a unique key for a
    38  // a prefix and a map string set combination.
    39  func KeyForPrefixedStringMap(
    40  	prefix string,
    41  	stringMap map[string]string,
    42  ) string {
    43  	return keyForPrefixedStringMaps(prefix, stringMap)
    44  }
    45  
    46  // keyForPrefixedStringMapsAsKey writes a key using the prefix and the tags in a canonical form to
    47  // the given input byte slice and returns a reference to the byte slice. Callers of this method can
    48  // use a stack allocated byte slice to remove heap allocation.
    49  func keyForPrefixedStringMapsAsKey(buf []byte, prefix string, maps ...map[string]string) []byte {
    50  	// stack allocated
    51  	keys := make([]string, 0, 32)
    52  	for _, m := range maps {
    53  		for k := range m {
    54  			keys = append(keys, k)
    55  		}
    56  	}
    57  
    58  	insertionSort(keys)
    59  
    60  	if prefix != nilString {
    61  		buf = append(buf, prefix...)
    62  		buf = append(buf, prefixSplitter)
    63  	}
    64  
    65  	var lastKey string // last key written to the buffer
    66  	for _, k := range keys {
    67  		if len(lastKey) > 0 {
    68  			if k == lastKey {
    69  				// Already wrote this key.
    70  				continue
    71  			}
    72  			buf = append(buf, keyPairSplitter)
    73  		}
    74  		lastKey = k
    75  
    76  		buf = append(buf, k...)
    77  		buf = append(buf, keyNameSplitter)
    78  
    79  		// Find and write the value for this key. Rightmost map takes
    80  		// precedence.
    81  		for j := len(maps) - 1; j >= 0; j-- {
    82  			if v, ok := maps[j][k]; ok {
    83  				buf = append(buf, v...)
    84  				break
    85  			}
    86  		}
    87  	}
    88  
    89  	return buf
    90  }
    91  
    92  // keyForPrefixedStringMaps generates a unique key for a prefix and a series
    93  // of maps containing tags.
    94  //
    95  // If a key occurs in multiple maps, keys on the right take precedence.
    96  func keyForPrefixedStringMaps(prefix string, maps ...map[string]string) string {
    97  	return string(keyForPrefixedStringMapsAsKey(make([]byte, 0, 256), prefix, maps...))
    98  }
    99  
   100  func insertionSort(keys []string) {
   101  	n := len(keys)
   102  	for i := 1; i < n; i++ {
   103  		for j := i; j > 0 && keys[j] < keys[j-1]; j-- {
   104  			keys[j], keys[j-1] = keys[j-1], keys[j]
   105  		}
   106  	}
   107  }