github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/metric/id/m3/id.go (about)

     1  // Copyright (c) 2017 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 m3 describes m3 metric id information.
    22  //
    23  // Each M3 metric id contains a metric name and a set of tag pairs. In particular,
    24  // it conforms to the following format:
    25  //
    26  // m3+<metric_name>+tagName1=tagValue1,tagName2=tagValue2,...tagNameN=tagValueN,
    27  //
    28  // Where the tag names are sorted alphabetically in ascending order.
    29  //
    30  // An example m3 metrid id is as follows:
    31  // m3+response_code+env=bar,service=foo,status=404,type=counters
    32  package m3
    33  
    34  import (
    35  	"bytes"
    36  	"errors"
    37  	"sort"
    38  
    39  	"github.com/m3db/m3/src/metrics/metric/id"
    40  )
    41  
    42  const (
    43  	componentSplitter = '+'
    44  	tagNameSplitter   = '='
    45  	tagPairSplitter   = ','
    46  )
    47  
    48  var (
    49  	errInvalidM3Metric = errors.New("invalid m3 metric")
    50  	m3Prefix           = []byte("m3+")
    51  	rollupTagPair      = id.TagPair{
    52  		Name:  []byte("m3_rollup"),
    53  		Value: []byte("true"),
    54  	}
    55  )
    56  
    57  // NewRollupID generates a new rollup id given the new metric name
    58  // and a list of tag pairs. Note that tagPairs are mutated in place.
    59  func NewRollupID(name []byte, tagPairs []id.TagPair) []byte {
    60  	var buf bytes.Buffer
    61  
    62  	// Adding rollup tag pair to the list of tag pairs.
    63  	tagPairs = append(tagPairs, rollupTagPair)
    64  	sort.Sort(id.TagPairsByNameAsc(tagPairs))
    65  
    66  	buf.Write(m3Prefix)
    67  	buf.Write(name)
    68  	buf.WriteByte(componentSplitter)
    69  	for i, p := range tagPairs {
    70  		buf.Write(p.Name)
    71  		buf.WriteByte(tagNameSplitter)
    72  		buf.Write(p.Value)
    73  		if i < len(tagPairs)-1 {
    74  			buf.WriteByte(tagPairSplitter)
    75  		}
    76  	}
    77  
    78  	return buf.Bytes()
    79  }
    80  
    81  // IsRollupID determines whether an id is a rollup id.
    82  // Caller may optionally pass in a sorted tag iterator
    83  // pool for efficiency reasons.
    84  // nolint: unparam
    85  func IsRollupID(name []byte, tags []byte, iterPool id.SortedTagIteratorPool) bool {
    86  	var iter id.SortedTagIterator
    87  	if iterPool == nil {
    88  		iter = NewSortedTagIterator(tags)
    89  	} else {
    90  		iter = iterPool.Get()
    91  		iter.Reset(tags)
    92  	}
    93  	defer iter.Close()
    94  
    95  	for iter.Next() {
    96  		name, val := iter.Current()
    97  		if bytes.Equal(name, rollupTagPair.Name) && bytes.Equal(val, rollupTagPair.Value) {
    98  			return true
    99  		}
   100  	}
   101  	return false
   102  }
   103  
   104  // TODO(xichen): pool the mids.
   105  type metricID struct {
   106  	iterPool id.SortedTagIteratorPool
   107  	id       []byte
   108  }
   109  
   110  // NewID creates a new m3 metric id.
   111  func NewID(id []byte, iterPool id.SortedTagIteratorPool) id.ID {
   112  	return metricID{id: id, iterPool: iterPool}
   113  }
   114  
   115  func (id metricID) Bytes() []byte { return id.id }
   116  
   117  func (id metricID) TagValue(tagName []byte) ([]byte, bool) {
   118  	_, tagPairs, err := NameAndTags(id.Bytes())
   119  	if err != nil {
   120  		return nil, false
   121  	}
   122  
   123  	it := id.iterPool.Get()
   124  	it.Reset(tagPairs)
   125  	defer it.Close()
   126  
   127  	for it.Next() {
   128  		n, v := it.Current()
   129  		if bytes.Equal(tagName, n) {
   130  			return v, true
   131  		}
   132  	}
   133  	return nil, false
   134  }
   135  
   136  // NameAndTags returns the name and the tags of the given id.
   137  func NameAndTags(id []byte) ([]byte, []byte, error) {
   138  	firstSplitterIdx := bytes.IndexByte(id, componentSplitter)
   139  	if !bytes.HasSuffix(id[:firstSplitterIdx+1], m3Prefix) {
   140  		return nil, nil, errInvalidM3Metric
   141  	}
   142  	secondSplitterIdx := bytes.IndexByte(id[firstSplitterIdx+1:], componentSplitter)
   143  	if secondSplitterIdx == -1 {
   144  		return nil, nil, errInvalidM3Metric
   145  	}
   146  	secondSplitterIdx = firstSplitterIdx + 1 + secondSplitterIdx
   147  	name := id[firstSplitterIdx+1 : secondSplitterIdx]
   148  	tags := id[secondSplitterIdx+1:]
   149  	return name, tags, nil
   150  }
   151  
   152  type sortedTagIterator struct {
   153  	err            error
   154  	pool           id.SortedTagIteratorPool
   155  	sortedTagPairs []byte
   156  	tagName        []byte
   157  	tagValue       []byte
   158  	idx            int
   159  }
   160  
   161  // NewSortedTagIterator creates a new sorted tag iterator.
   162  func NewSortedTagIterator(sortedTagPairs []byte) id.SortedTagIterator {
   163  	return NewPooledSortedTagIterator(sortedTagPairs, nil)
   164  }
   165  
   166  // NewPooledSortedTagIterator creates a new pooled sorted tag iterator.
   167  func NewPooledSortedTagIterator(sortedTagPairs []byte, pool id.SortedTagIteratorPool) id.SortedTagIterator {
   168  	it := &sortedTagIterator{pool: pool}
   169  	it.Reset(sortedTagPairs)
   170  	return it
   171  }
   172  
   173  func (it *sortedTagIterator) Reset(sortedTagPairs []byte) {
   174  	it.sortedTagPairs = sortedTagPairs
   175  	it.idx = 0
   176  	it.tagName = nil
   177  	it.tagValue = nil
   178  	it.err = nil
   179  }
   180  
   181  func (it *sortedTagIterator) Next() bool {
   182  	if it.err != nil || it.idx >= len(it.sortedTagPairs) {
   183  		return false
   184  	}
   185  	nameSplitterIdx := bytes.IndexByte(it.sortedTagPairs[it.idx:], tagNameSplitter)
   186  	if nameSplitterIdx == -1 {
   187  		it.err = errInvalidM3Metric
   188  		return false
   189  	}
   190  	nameSplitterIdx = it.idx + nameSplitterIdx
   191  	pairSplitterIdx := bytes.IndexByte(it.sortedTagPairs[nameSplitterIdx+1:], tagPairSplitter)
   192  	if pairSplitterIdx != -1 {
   193  		pairSplitterIdx = nameSplitterIdx + 1 + pairSplitterIdx
   194  	} else {
   195  		pairSplitterIdx = len(it.sortedTagPairs)
   196  	}
   197  	it.tagName = it.sortedTagPairs[it.idx:nameSplitterIdx]
   198  	it.tagValue = it.sortedTagPairs[nameSplitterIdx+1 : pairSplitterIdx]
   199  	it.idx = pairSplitterIdx + 1
   200  	return true
   201  }
   202  
   203  func (it *sortedTagIterator) Current() ([]byte, []byte) {
   204  	return it.tagName, it.tagValue
   205  }
   206  
   207  func (it *sortedTagIterator) Err() error {
   208  	return it.err
   209  }
   210  
   211  func (it *sortedTagIterator) Close() {
   212  	it.sortedTagPairs = nil
   213  	it.idx = 0
   214  	it.tagName = nil
   215  	it.tagValue = nil
   216  	it.err = nil
   217  	if it.pool != nil {
   218  		it.pool.Put(it)
   219  	}
   220  }