github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/segment/key.go (about)

     1  package segment
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/pyroscope-io/pyroscope/pkg/flameql"
    12  	"github.com/pyroscope-io/pyroscope/pkg/structs/sortedmap"
    13  )
    14  
    15  // TODO(kolesnikovae):
    16  //   Rename tags to labels
    17  //   Segment key -> LabelSet
    18  //   Segment key to be moved to /model package
    19  //   FlameQL to be split.
    20  
    21  type Key struct {
    22  	labels map[string]string
    23  }
    24  
    25  type ParserState int
    26  
    27  const (
    28  	nameParserState ParserState = iota
    29  	tagKeyParserState
    30  	tagValueParserState
    31  	doneParserState
    32  )
    33  
    34  func NewKey(labels map[string]string) *Key { return &Key{labels: labels} }
    35  
    36  func ParseKey(name string) (*Key, error) {
    37  	k := &Key{labels: make(map[string]string)}
    38  	p := parserPool.Get().(*parser)
    39  	defer parserPool.Put(p)
    40  	p.reset()
    41  	var err error
    42  	for _, r := range name + "{" {
    43  		switch p.parserState {
    44  		case nameParserState:
    45  			err = p.nameParserCase(r, k)
    46  		case tagKeyParserState:
    47  			p.tagKeyParserCase(r)
    48  		case tagValueParserState:
    49  			err = p.tagValueParserCase(r, k)
    50  		}
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  	}
    55  	return k, nil
    56  }
    57  
    58  func ValidateKey(k *Key) error {
    59  	if k == nil {
    60  		return flameql.ErrInvalidTagKey
    61  	}
    62  
    63  	for key, v := range k.labels {
    64  		if key == "__name__" {
    65  			if err := flameql.ValidateAppName(v); err != nil {
    66  				return err
    67  			}
    68  		} else {
    69  			if err := flameql.ValidateTagKey(key); err != nil {
    70  				return err
    71  			}
    72  		}
    73  	}
    74  	return nil
    75  }
    76  
    77  type parser struct {
    78  	parserState ParserState
    79  	key         *bytes.Buffer
    80  	value       *bytes.Buffer
    81  }
    82  
    83  var parserPool = sync.Pool{
    84  	New: func() any {
    85  		return &parser{
    86  			parserState: nameParserState,
    87  			key:         new(bytes.Buffer),
    88  			value:       new(bytes.Buffer),
    89  		}
    90  	},
    91  }
    92  
    93  func (p *parser) reset() {
    94  	p.parserState = nameParserState
    95  	p.key.Reset()
    96  	p.value.Reset()
    97  }
    98  
    99  // ParseKey's nameParserState switch case
   100  func (p *parser) nameParserCase(r int32, k *Key) error {
   101  	switch r {
   102  	case '{':
   103  		p.parserState = tagKeyParserState
   104  		appName := strings.TrimSpace(p.value.String())
   105  		if err := flameql.ValidateAppName(appName); err != nil {
   106  			return err
   107  		}
   108  		k.labels["__name__"] = appName
   109  	default:
   110  		p.value.WriteRune(r)
   111  	}
   112  	return nil
   113  }
   114  
   115  // ParseKey's tagKeyParserState switch case
   116  func (p *parser) tagKeyParserCase(r rune) {
   117  	switch r {
   118  	case '}':
   119  		p.parserState = doneParserState
   120  	case '=':
   121  		p.parserState = tagValueParserState
   122  		p.value.Reset()
   123  	default:
   124  		p.key.WriteRune(r)
   125  	}
   126  }
   127  
   128  // ParseKey's tagValueParserState switch case
   129  func (p *parser) tagValueParserCase(r rune, k *Key) error {
   130  	switch r {
   131  	case ',', '}':
   132  		p.parserState = tagKeyParserState
   133  		key := strings.TrimSpace(p.key.String())
   134  		if !flameql.IsTagKeyReserved(key) {
   135  			if err := flameql.ValidateTagKey(key); err != nil {
   136  				return err
   137  			}
   138  		}
   139  		k.labels[key] = strings.TrimSpace(p.value.String())
   140  		p.key.Reset()
   141  	default:
   142  		p.value.WriteRune(r)
   143  	}
   144  	return nil
   145  }
   146  
   147  func (k *Key) SegmentKey() string {
   148  	return k.Normalized()
   149  }
   150  
   151  const ProfileIDLabelName = "profile_id"
   152  
   153  func (k *Key) HasProfileID() bool {
   154  	v, ok := k.labels[ProfileIDLabelName]
   155  	return ok && v != ""
   156  }
   157  
   158  func (k *Key) ProfileID() (string, bool) {
   159  	id, ok := k.labels[ProfileIDLabelName]
   160  	return id, ok
   161  }
   162  
   163  func AppSegmentKey(appName string) string { return appName + "{}" }
   164  
   165  func TreeKey(k string, depth int, unixTime int64) string {
   166  	return k + ":" + strconv.Itoa(depth) + ":" + strconv.FormatInt(unixTime, 10)
   167  }
   168  
   169  func (k *Key) TreeKey(depth int, t time.Time) string {
   170  	return TreeKey(k.Normalized(), depth, t.Unix())
   171  }
   172  
   173  var errKeyInvalid = errors.New("invalid key")
   174  
   175  // ParseTreeKey retrieves tree time and depth level from the given key.
   176  func ParseTreeKey(k string) (time.Time, int, error) {
   177  	a := strings.Split(k, ":")
   178  	if len(a) < 3 {
   179  		return time.Time{}, 0, errKeyInvalid
   180  	}
   181  	level, err := strconv.Atoi(a[1])
   182  	if err != nil {
   183  		return time.Time{}, 0, err
   184  	}
   185  	v, err := strconv.Atoi(a[2])
   186  	if err != nil {
   187  		return time.Time{}, 0, err
   188  	}
   189  	return time.Unix(int64(v), 0), level, err
   190  }
   191  
   192  func (k *Key) DictKey() string {
   193  	return k.labels["__name__"]
   194  }
   195  
   196  // FromTreeToDictKey returns app name from tree key k: given tree key
   197  // "foo{}:0:1234567890", the call returns "foo".
   198  //
   199  // Before tags support, segment key form (i.e. app name + tags: foo{key=value})
   200  // has been used to reference a dictionary (trie).
   201  func FromTreeToDictKey(k string) string {
   202  	return k[0:strings.IndexAny(k, "{")]
   203  }
   204  
   205  func (k *Key) Normalized() string {
   206  	var sb strings.Builder
   207  
   208  	sortedMap := sortedmap.New()
   209  	for k, v := range k.labels {
   210  		if k == "__name__" {
   211  			sb.WriteString(v)
   212  		} else {
   213  			sortedMap.Put(k, v)
   214  		}
   215  	}
   216  
   217  	sb.WriteString("{")
   218  	for i, k := range sortedMap.Keys() {
   219  		v := sortedMap.Get(k).(string)
   220  		if i != 0 {
   221  			sb.WriteString(",")
   222  		}
   223  		sb.WriteString(k)
   224  		sb.WriteString("=")
   225  		sb.WriteString(v)
   226  	}
   227  	sb.WriteString("}")
   228  
   229  	return sb.String()
   230  }
   231  
   232  func (k *Key) Clone() *Key {
   233  	newMap := make(map[string]string)
   234  	for k, v := range k.labels {
   235  		newMap[k] = v
   236  	}
   237  	return &Key{labels: newMap}
   238  }
   239  
   240  func (k *Key) AppName() string {
   241  	return k.labels["__name__"]
   242  }
   243  
   244  func (k *Key) Labels() map[string]string {
   245  	return k.labels
   246  }
   247  
   248  func (k *Key) Add(key, value string) {
   249  	if value == "" {
   250  		delete(k.labels, key)
   251  	} else {
   252  		k.labels[key] = value
   253  	}
   254  }
   255  
   256  // Match reports whether the key matches the query.
   257  func (k *Key) Match(q *flameql.Query) bool {
   258  	if k.AppName() != q.AppName {
   259  		return false
   260  	}
   261  	for _, m := range q.Matchers {
   262  		var ok bool
   263  		for labelKey, labelValue := range k.labels {
   264  			if m.Key != labelKey {
   265  				continue
   266  			}
   267  			if m.Match(labelValue) {
   268  				if !m.IsNegation() {
   269  					ok = true
   270  					break
   271  				}
   272  			} else if m.IsNegation() {
   273  				return false
   274  			}
   275  		}
   276  		if !ok && !m.IsNegation() {
   277  			return false
   278  		}
   279  	}
   280  	return true
   281  }