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 }