github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/tsdb/index.go (about) 1 // originally from https://github.com/cortexproject/cortex/blob/868898a2921c662dcd4f90683e8b95c927a8edd8/pkg/ingester/index/index.go 2 // but modified to support sharding queries. 3 package tsdb 4 5 import ( 6 "bytes" 7 "crypto/sha256" 8 "encoding/base64" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16 "unicode/utf8" 17 "unsafe" 18 19 "github.com/prometheus/common/model" 20 "github.com/prometheus/prometheus/model/labels" 21 22 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 23 phlaremodel "github.com/grafana/pyroscope/pkg/model" 24 "github.com/grafana/pyroscope/pkg/phlaredb/tsdb/shard" 25 ) 26 27 var ( 28 // Bitmap used by func isRegexMetaCharacter to check whether a character needs to be escaped. 29 regexMetaCharacterBytes [16]byte 30 ErrInvalidShardQuery = errors.New("incompatible index shard query") 31 ) 32 33 // isRegexMetaCharacter reports whether byte b needs to be escaped. 34 func isRegexMetaCharacter(b byte) bool { 35 return b < utf8.RuneSelf && regexMetaCharacterBytes[b%16]&(1<<(b/16)) != 0 36 } 37 38 func init() { 39 for _, b := range []byte(`.+*?()|[]{}^$`) { 40 regexMetaCharacterBytes[b%16] |= 1 << (b / 16) 41 } 42 } 43 44 const DefaultIndexShards = 32 45 46 type Interface interface { 47 Add(labels []*typesv1.LabelPair, fp model.Fingerprint) labels.Labels 48 Lookup(matchers []*labels.Matcher, shard *shard.Annotation) ([]model.Fingerprint, error) 49 LabelNames(shard *shard.Annotation) ([]string, error) 50 LabelValues(name string, shard *shard.Annotation) ([]string, error) 51 Delete(labels labels.Labels, fp model.Fingerprint) 52 } 53 54 // InvertedIndex implements a in-memory inverted index from label pairs to fingerprints. 55 // It is sharded to reduce lock contention on writes. 56 type InvertedIndex struct { 57 totalShards uint32 58 shards []*indexShard 59 } 60 61 func NewWithShards(totalShards uint32) *InvertedIndex { 62 shards := make([]*indexShard, totalShards) 63 for i := uint32(0); i < totalShards; i++ { 64 shards[i] = &indexShard{ 65 idx: map[string]indexEntry{}, 66 shard: i, 67 } 68 } 69 return &InvertedIndex{ 70 totalShards: totalShards, 71 shards: shards, 72 } 73 } 74 75 func (ii *InvertedIndex) getShards(shard *shard.Annotation) []*indexShard { 76 if shard == nil { 77 return ii.shards 78 } 79 80 totalRequested := int(ii.totalShards) / shard.Of 81 result := make([]*indexShard, totalRequested) 82 var j int 83 for i := 0; i < totalRequested; i++ { 84 subShard := ((shard.Shard) + (i * shard.Of)) 85 result[j] = ii.shards[subShard] 86 j++ 87 } 88 return result 89 } 90 91 func (ii *InvertedIndex) validateShard(shard *shard.Annotation) error { 92 if shard == nil { 93 return nil 94 } 95 if int(ii.totalShards)%shard.Of != 0 || uint32(shard.Of) > ii.totalShards { 96 return fmt.Errorf("%w index_shard:%d query_shard:%v", ErrInvalidShardQuery, ii.totalShards, shard) 97 } 98 return nil 99 } 100 101 // Add a fingerprint under the specified labels. 102 // NOTE: memory for `labels` is unsafe; anything retained beyond the 103 // life of this function must be copied 104 func (ii *InvertedIndex) Add(labels phlaremodel.Labels, fp model.Fingerprint) phlaremodel.Labels { 105 shardIndex := labelsSeriesIDHash(labels) 106 shard := ii.shards[shardIndex%ii.totalShards] 107 return shard.add(labels, fp) // add() returns 'interned' values so the original labels are not retained 108 } 109 110 var ( 111 bufferPool = sync.Pool{ 112 New: func() interface{} { 113 return bytes.NewBuffer(make([]byte, 0, 1000)) 114 }, 115 } 116 base64Pool = sync.Pool{ 117 New: func() interface{} { 118 return bytes.NewBuffer(make([]byte, 0, base64.RawStdEncoding.EncodedLen(sha256.Size))) 119 }, 120 } 121 ) 122 123 func labelsSeriesIDHash(ls []*typesv1.LabelPair) uint32 { 124 b64 := base64Pool.Get().(*bytes.Buffer) 125 defer func() { 126 base64Pool.Put(b64) 127 }() 128 buf := b64.Bytes()[:b64.Cap()] 129 labelsSeriesID(ls, buf) 130 return binary.BigEndian.Uint32(buf) 131 } 132 133 func labelsSeriesID(ls []*typesv1.LabelPair, dest []byte) { 134 buf := bufferPool.Get().(*bytes.Buffer) 135 defer func() { 136 buf.Reset() 137 bufferPool.Put(buf) 138 }() 139 labelsString(buf, ls) 140 h := sha256.Sum256(buf.Bytes()) 141 dest = dest[:base64.RawStdEncoding.EncodedLen(len(h))] 142 base64.RawStdEncoding.Encode(dest, h[:]) 143 } 144 145 // Backwards-compatible with model.Metric.String() 146 func labelsString(b *bytes.Buffer, ls []*typesv1.LabelPair) { 147 // metrics name is used in the store for computing shards. 148 // see chunk/schema_util.go for more details. `labelsString()` 149 for _, l := range ls { 150 if l.Name == labels.MetricName { 151 b.WriteString(l.Value) 152 break 153 } 154 } 155 b.WriteByte('{') 156 i := 0 157 for _, l := range ls { 158 if l.Name == labels.MetricName { 159 continue 160 } 161 if i > 0 { 162 b.WriteByte(',') 163 b.WriteByte(' ') 164 } 165 b.WriteString(l.Name) 166 b.WriteByte('=') 167 var buf [1000]byte 168 b.Write(strconv.AppendQuote(buf[:0], l.Value)) 169 i++ 170 } 171 b.WriteByte('}') 172 } 173 174 // Lookup all fingerprints for the provided matchers. 175 func (ii *InvertedIndex) Lookup(matchers []*labels.Matcher, shard *shard.Annotation) ([]model.Fingerprint, error) { 176 if err := ii.validateShard(shard); err != nil { 177 return nil, err 178 } 179 180 var result []model.Fingerprint 181 shards := ii.getShards(shard) 182 183 // if no matcher is specified, all fingerprints would be returned 184 if len(matchers) == 0 { 185 for i := range shards { 186 fps := shards[i].allFPs() 187 result = append(result, fps...) 188 } 189 return result, nil 190 } 191 192 for i := range shards { 193 fps := shards[i].lookup(matchers) 194 result = append(result, fps...) 195 } 196 return result, nil 197 } 198 199 // LabelNames returns all label names. 200 func (ii *InvertedIndex) LabelNames(shard *shard.Annotation) ([]string, error) { 201 if err := ii.validateShard(shard); err != nil { 202 return nil, err 203 } 204 shards := ii.getShards(shard) 205 results := make([][]string, 0, len(shards)) 206 for i := range shards { 207 shardResult := shards[i].labelNames(nil) 208 results = append(results, shardResult) 209 } 210 211 return mergeStringSlices(results), nil 212 } 213 214 // LabelValues returns the values for the given label. 215 func (ii *InvertedIndex) LabelValues(name string, shard *shard.Annotation) ([]string, error) { 216 if err := ii.validateShard(shard); err != nil { 217 return nil, err 218 } 219 shards := ii.getShards(shard) 220 results := make([][]string, 0, len(shards)) 221 222 for i := range shards { 223 shardResult := shards[i].labelValues(name, nil) 224 results = append(results, shardResult) 225 } 226 227 return mergeStringSlices(results), nil 228 } 229 230 // Delete a fingerprint with the given label pairs. 231 func (ii *InvertedIndex) Delete(labels []*typesv1.LabelPair, fp model.Fingerprint) { 232 shard := ii.shards[labelsSeriesIDHash(labels)%ii.totalShards] 233 shard.delete(labels, fp) 234 } 235 236 // NB slice entries are sorted in fp order. 237 type indexEntry struct { 238 name string 239 fps map[string]indexValueEntry 240 } 241 242 type indexValueEntry struct { 243 value string 244 fps []model.Fingerprint 245 } 246 247 type unlockIndex map[string]indexEntry 248 249 // This is the prevalent value for Intel and AMD CPUs as-at 2018. 250 // nolint unused 251 const cacheLineSize = 64 252 253 // Roughly 254 // map[labelName] => map[labelValue] => []fingerprint 255 type indexShard struct { 256 shard uint32 257 mtx sync.RWMutex 258 idx unlockIndex 259 //nolint structcheck,unused 260 pad [cacheLineSize - unsafe.Sizeof(sync.Mutex{}) - unsafe.Sizeof(unlockIndex{})]byte 261 } 262 263 func copyString(s string) string { 264 return string([]byte(s)) 265 } 266 267 // add metric to the index; return all the name/value pairs as a fresh 268 // sorted slice, referencing 'interned' strings from the index so that 269 // no references are retained to the memory of `metric`. 270 func (shard *indexShard) add(metric []*typesv1.LabelPair, fp model.Fingerprint) phlaremodel.Labels { 271 shard.mtx.Lock() 272 defer shard.mtx.Unlock() 273 274 internedLabels := make(phlaremodel.Labels, len(metric)) 275 276 for i, pair := range metric { 277 values, ok := shard.idx[pair.Name] 278 if !ok { 279 values = indexEntry{ 280 name: copyString(pair.Name), 281 fps: map[string]indexValueEntry{}, 282 } 283 shard.idx[values.name] = values 284 } 285 fingerprints, ok := values.fps[pair.Value] 286 if !ok { 287 fingerprints = indexValueEntry{ 288 value: copyString(pair.Value), 289 } 290 } 291 // Insert into the right position to keep fingerprints sorted 292 j := sort.Search(len(fingerprints.fps), func(i int) bool { 293 return fingerprints.fps[i] >= fp 294 }) 295 fingerprints.fps = append(fingerprints.fps, 0) 296 copy(fingerprints.fps[j+1:], fingerprints.fps[j:]) 297 fingerprints.fps[j] = fp 298 values.fps[fingerprints.value] = fingerprints 299 internedLabels[i] = &typesv1.LabelPair{Name: values.name, Value: fingerprints.value} 300 } 301 return internedLabels 302 } 303 304 func (shard *indexShard) lookup(matchers []*labels.Matcher) []model.Fingerprint { 305 // index slice values must only be accessed under lock, so all 306 // code paths must take a copy before returning 307 shard.mtx.RLock() 308 defer shard.mtx.RUnlock() 309 310 // per-shard intersection is initially nil, which is a special case 311 // meaning "everything" when passed to intersect() 312 // loop invariant: result is sorted 313 var result []model.Fingerprint 314 for _, matcher := range matchers { 315 values, ok := shard.idx[matcher.Name] 316 if !ok { 317 return nil 318 } 319 var toIntersect model.Fingerprints 320 if matcher.Type == labels.MatchEqual { 321 fps := values.fps[matcher.Value] 322 toIntersect = append(toIntersect, fps.fps...) // deliberate copy 323 } else if matcher.Type == labels.MatchRegexp && len(FindSetMatches(matcher.Value)) > 0 { 324 // The lookup is of the form `=~"a|b|c|d"` 325 set := FindSetMatches(matcher.Value) 326 for _, value := range set { 327 toIntersect = append(toIntersect, values.fps[value].fps...) 328 } 329 sort.Sort(toIntersect) 330 } else { 331 // accumulate the matching fingerprints (which are all distinct) 332 // then sort to maintain the invariant 333 for value, fps := range values.fps { 334 if matcher.Matches(value) { 335 toIntersect = append(toIntersect, fps.fps...) 336 } 337 } 338 sort.Sort(toIntersect) 339 } 340 result = intersect(result, toIntersect) 341 if len(result) == 0 { 342 return nil 343 } 344 } 345 346 return result 347 } 348 349 func (shard *indexShard) allFPs() model.Fingerprints { 350 shard.mtx.RLock() 351 defer shard.mtx.RUnlock() 352 353 var fps model.Fingerprints 354 for _, ie := range shard.idx { 355 for _, ive := range ie.fps { 356 fps = append(fps, ive.fps...) 357 } 358 } 359 if len(fps) == 0 { 360 return nil 361 } 362 363 var result model.Fingerprints 364 m := map[model.Fingerprint]struct{}{} 365 for _, fp := range fps { 366 if _, ok := m[fp]; !ok { 367 m[fp] = struct{}{} 368 result = append(result, fp) 369 } 370 } 371 return result 372 } 373 374 func (shard *indexShard) labelNames(extractor func(unlockIndex) []string) []string { 375 shard.mtx.RLock() 376 defer shard.mtx.RUnlock() 377 378 results := make([]string, 0, len(shard.idx)) 379 if extractor != nil { 380 results = append(results, extractor(shard.idx)...) 381 } else { 382 for name := range shard.idx { 383 results = append(results, name) 384 } 385 } 386 387 sort.Strings(results) 388 return results 389 } 390 391 func (shard *indexShard) labelValues( 392 name string, 393 extractor func(indexEntry) []string, 394 ) []string { 395 shard.mtx.RLock() 396 defer shard.mtx.RUnlock() 397 398 values, ok := shard.idx[name] 399 if !ok { 400 return nil 401 } 402 403 if extractor == nil { 404 results := make([]string, 0, len(values.fps)) 405 for val := range values.fps { 406 results = append(results, val) 407 } 408 sort.Strings(results) 409 return results 410 } 411 412 return extractor(values) 413 } 414 415 func (shard *indexShard) delete(labels []*typesv1.LabelPair, fp model.Fingerprint) { 416 shard.mtx.Lock() 417 defer shard.mtx.Unlock() 418 419 for _, pair := range labels { 420 name, value := pair.Name, pair.Value 421 values, ok := shard.idx[name] 422 if !ok { 423 continue 424 } 425 fingerprints, ok := values.fps[value] 426 if !ok { 427 continue 428 } 429 430 j := sort.Search(len(fingerprints.fps), func(i int) bool { 431 return fingerprints.fps[i] >= fp 432 }) 433 434 // see if search didn't find fp which matches the condition which means we don't have to do anything. 435 if j >= len(fingerprints.fps) || fingerprints.fps[j] != fp { 436 continue 437 } 438 fingerprints.fps = fingerprints.fps[:j+copy(fingerprints.fps[j:], fingerprints.fps[j+1:])] 439 440 if len(fingerprints.fps) == 0 { 441 delete(values.fps, value) 442 } else { 443 values.fps[value] = fingerprints 444 } 445 446 if len(values.fps) == 0 { 447 delete(shard.idx, name) 448 } else { 449 shard.idx[name] = values 450 } 451 } 452 } 453 454 // intersect two sorted lists of fingerprints. Assumes there are no duplicate 455 // fingerprints within the input lists. 456 func intersect(a, b []model.Fingerprint) []model.Fingerprint { 457 if a == nil { 458 return b 459 } 460 result := []model.Fingerprint{} 461 for i, j := 0, 0; i < len(a) && j < len(b); { 462 if a[i] == b[j] { 463 result = append(result, a[i]) 464 } 465 if a[i] < b[j] { 466 i++ 467 } else { 468 j++ 469 } 470 } 471 return result 472 } 473 474 func mergeStringSlices(ss [][]string) []string { 475 switch len(ss) { 476 case 0: 477 return nil 478 case 1: 479 return ss[0] 480 case 2: 481 return mergeTwoStringSlices(ss[0], ss[1]) 482 default: 483 halfway := len(ss) / 2 484 return mergeTwoStringSlices( 485 mergeStringSlices(ss[:halfway]), 486 mergeStringSlices(ss[halfway:]), 487 ) 488 } 489 } 490 491 func mergeTwoStringSlices(a, b []string) []string { 492 result := make([]string, 0, len(a)+len(b)) 493 i, j := 0, 0 494 for i < len(a) && j < len(b) { 495 if a[i] < b[j] { 496 result = append(result, a[i]) 497 i++ 498 } else if a[i] > b[j] { 499 result = append(result, b[j]) 500 j++ 501 } else { 502 result = append(result, a[i]) 503 i++ 504 j++ 505 } 506 } 507 result = append(result, a[i:]...) 508 result = append(result, b[j:]...) 509 return result 510 } 511 512 func FindSetMatches(pattern string) []string { 513 // Return empty matches if the wrapper from Prometheus is missing. 514 if len(pattern) < 6 || pattern[:4] != "^(?:" || pattern[len(pattern)-2:] != ")$" { 515 return nil 516 } 517 escaped := false 518 sets := []*strings.Builder{{}} 519 for i := 4; i < len(pattern)-2; i++ { 520 if escaped { 521 switch { 522 case isRegexMetaCharacter(pattern[i]): 523 sets[len(sets)-1].WriteByte(pattern[i]) 524 case pattern[i] == '\\': 525 sets[len(sets)-1].WriteByte('\\') 526 default: 527 return nil 528 } 529 escaped = false 530 } else { 531 switch { 532 case isRegexMetaCharacter(pattern[i]): 533 if pattern[i] == '|' { 534 sets = append(sets, &strings.Builder{}) 535 } else { 536 return nil 537 } 538 case pattern[i] == '\\': 539 escaped = true 540 default: 541 sets[len(sets)-1].WriteByte(pattern[i]) 542 } 543 } 544 } 545 matches := make([]string, 0, len(sets)) 546 for _, s := range sets { 547 if s.Len() > 0 { 548 matches = append(matches, s.String()) 549 } 550 } 551 return matches 552 }