github.com/grafana/pyroscope@v1.18.0/pkg/model/labels.go (about) 1 package model 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "fmt" 8 "slices" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/cespare/xxhash/v2" 14 pmodel "github.com/prometheus/common/model" 15 "github.com/prometheus/prometheus/model/labels" 16 semconv "go.opentelemetry.io/otel/semconv/v1.27.0" 17 18 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 19 "github.com/grafana/pyroscope/pkg/util" 20 ) 21 22 var seps = []byte{'\xff'} 23 24 const ( 25 LabelNameProfileType = "__profile_type__" 26 LabelNameServiceNamePrivate = "__service_name__" 27 LabelNameDelta = "__delta__" 28 LabelNameOTEL = "__otel__" 29 LabelNameProfileName = pmodel.MetricNameLabel 30 LabelNamePeriodType = "__period_type__" 31 LabelNamePeriodUnit = "__period_unit__" 32 LabelNameSessionID = "__session_id__" 33 LabelNameType = "__type__" 34 LabelNameUnit = "__unit__" 35 36 LabelNameServiceGitRef = "service_git_ref" 37 LabelNameServiceName = "service_name" 38 LabelNameServiceRepository = "service_repository" 39 LabelNameServiceRootPath = "service_root_path" 40 41 LabelNameOrder = "__order__" 42 LabelOrderEnforced = "enforced" 43 44 LabelNamePyroscopeSpy = "pyroscope_spy" 45 46 AttrProcessExecutableName = semconv.ProcessExecutableNameKey 47 48 AttrServiceName = semconv.ServiceNameKey 49 AttrServiceNameFallback = "unknown_service" 50 51 labelSep = '\xfe' 52 53 ProfileNameOffCpu = "off_cpu" // todo better name? 54 ) 55 56 // Labels is a sorted set of labels. Order has to be guaranteed upon 57 // instantiation. 58 type Labels []*typesv1.LabelPair 59 60 func (ls Labels) Len() int { return len(ls) } 61 func (ls Labels) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] } 62 func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name } 63 64 // Range calls f on each label. 65 func (ls Labels) Range(f func(l *typesv1.LabelPair)) { 66 for _, l := range ls { 67 f(l) 68 } 69 } 70 71 // EmptyLabels returns n empty Labels value, for convenience. 72 func EmptyLabels() Labels { 73 return Labels{} 74 } 75 76 // LabelsEnforcedOrder is a sort order of labels, where profile type and 77 // service name labels always go first. This is crucial for query performance 78 // as labels determine the physical order of the profiling data. 79 type LabelsEnforcedOrder []*typesv1.LabelPair 80 81 func (ls LabelsEnforcedOrder) Len() int { return len(ls) } 82 func (ls LabelsEnforcedOrder) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] } 83 84 func (ls LabelsEnforcedOrder) Less(i, j int) bool { 85 if ls[i].Name[0] == '_' || ls[j].Name[0] == '_' { 86 leftType := ls[i].Name == LabelNameProfileType 87 rightType := ls[j].Name == LabelNameProfileType 88 if leftType || rightType { 89 return leftType || !rightType 90 } 91 leftService := ls[i].Name == LabelNameServiceNamePrivate 92 rightService := ls[j].Name == LabelNameServiceNamePrivate 93 if leftService || rightService { 94 return leftService || !rightService 95 } 96 } 97 return ls[i].Name < ls[j].Name 98 } 99 100 // Hash returns a hash value for the label set. 101 func (ls Labels) Hash() uint64 { 102 // Use xxhash.Sum64(b) for fast path as it's faster. 103 b := make([]byte, 0, 1024) 104 for i, v := range ls { 105 if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) { 106 // If labels entry is 1KB+ do not allocate whole entry. 107 h := xxhash.New() 108 _, _ = h.Write(b) 109 for _, v := range ls[i:] { 110 _, _ = h.WriteString(v.Name) 111 _, _ = h.Write(seps) 112 _, _ = h.WriteString(v.Value) 113 _, _ = h.Write(seps) 114 } 115 return h.Sum64() 116 } 117 118 b = append(b, v.Name...) 119 b = append(b, seps[0]) 120 b = append(b, v.Value...) 121 b = append(b, seps[0]) 122 } 123 return xxhash.Sum64(b) 124 } 125 126 // BytesWithLabels is just as Bytes(), but only for labels matching names. 127 // It uses an byte invalid character as a separator and so should not be used for printing. 128 func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte { 129 buf = buf[:0] 130 buf = append(buf, labelSep) 131 for _, name := range names { 132 for _, l := range ls { 133 if l.Name == name { 134 if len(buf) > 1 { 135 buf = append(buf, seps[0]) 136 } 137 buf = append(buf, l.Name...) 138 buf = append(buf, seps[0]) 139 buf = append(buf, l.Value...) 140 break 141 } 142 } 143 } 144 return buf 145 } 146 147 func (ls Labels) ToPrometheusLabels() labels.Labels { 148 res := make([]labels.Label, len(ls)) 149 for i, l := range ls { 150 res[i] = labels.Label{Name: l.Name, Value: l.Value} 151 } 152 return labels.New(res...) 153 } 154 155 func (ls Labels) WithoutPrivateLabels() Labels { 156 res := make([]*typesv1.LabelPair, 0, len(ls)) 157 for _, l := range ls { 158 if !strings.HasPrefix(l.Name, "__") { 159 res = append(res, l) 160 } 161 } 162 return res 163 } 164 165 var allowedPrivateLabels = map[string]struct{}{ 166 LabelNameSessionID: {}, 167 } 168 169 func IsLabelAllowedForIngestion(name string) bool { 170 if !strings.HasPrefix(name, "__") { 171 return true 172 } 173 _, allowed := allowedPrivateLabels[name] 174 return allowed 175 } 176 177 // WithLabels returns a subset of Labels that match with the provided label names. 178 func (ls Labels) WithLabels(names ...string) Labels { 179 matched := make(Labels, 0, len(names)) 180 for _, name := range names { 181 for _, l := range ls { 182 if l.Name == name { 183 matched = append(matched, l) 184 break 185 } 186 } 187 } 188 return matched 189 } 190 191 // Get returns the value for the label with the given name. 192 // Returns an empty string if the label doesn't exist. 193 func (ls Labels) Get(name string) string { 194 for _, l := range ls { 195 if l.Name == name { 196 return l.Value 197 } 198 } 199 return "" 200 } 201 202 // GetLabel returns the label with the given name. 203 func (ls Labels) GetLabel(name string) (*typesv1.LabelPair, bool) { 204 for _, l := range ls { 205 if l.Name == name { 206 return l, true 207 } 208 } 209 return nil, false 210 } 211 212 // Delete removes the first label encountered with the name given in place. 213 func (ls Labels) Delete(name string) Labels { 214 for i, l := range ls { 215 if l.Name == name { 216 return slices.Delete(ls, i, i+1) 217 } 218 } 219 return ls 220 } 221 222 func (ls Labels) Subtract(labels Labels) Labels { 223 var i, j, k int 224 for i < len(ls) && j < len(labels) { 225 cmp := CompareLabelPairs2(ls[i], labels[j]) 226 switch { 227 case cmp == 0: 228 i++ 229 j++ 230 case cmp < 0: 231 if i != k { 232 ls[k] = ls[i] 233 } 234 k++ 235 i++ 236 default: 237 j++ 238 } 239 } 240 for i < len(ls) { 241 if i != k { 242 ls[k] = ls[i] 243 } 244 k++ 245 i++ 246 } 247 return ls[:k] 248 } 249 250 func (ls Labels) Intersect(labels Labels) Labels { 251 var i, j, k int 252 for i < len(ls) && j < len(labels) { 253 cmp := CompareLabelPairs2(ls[i], labels[j]) 254 if cmp == 0 { 255 ls[k] = ls[i] 256 k++ 257 i++ 258 j++ 259 } else if cmp < 0 { 260 i++ 261 } else { 262 j++ 263 } 264 } 265 return ls[:k] 266 } 267 268 // InsertSorted adds the given label to the set of labels. 269 // It assumes the labels are sorted lexicographically. 270 func (ls Labels) InsertSorted(name, value string) Labels { 271 // Find the index where the new label should be inserted. 272 // TODO: Use binary search on large label sets. 273 index := -1 274 for i, label := range ls { 275 if label.Name > name { 276 index = i 277 break 278 } 279 if label.Name == name { 280 label.Value = value 281 return ls 282 } 283 } 284 // Insert the new label at the found index. 285 l := &typesv1.LabelPair{ 286 Name: name, 287 Value: value, 288 } 289 c := append(ls, l) 290 if index == -1 { 291 return c 292 } 293 copy((c)[index+1:], (c)[index:]) 294 (c)[index] = l 295 return c 296 } 297 298 func (ls Labels) Clone() Labels { 299 result := make(Labels, len(ls)) 300 for i, l := range ls { 301 result[i] = &typesv1.LabelPair{ 302 Name: l.Name, 303 Value: l.Value, 304 } 305 } 306 return result 307 } 308 309 // Unique returns a set labels with unique keys. 310 // Labels expected to be sorted: underlying capacity 311 // is reused and the original order is preserved: 312 // the first key takes precedence over duplicates. 313 // Method receiver should not be used after the call. 314 func (ls Labels) Unique() Labels { 315 if len(ls) <= 1 { 316 return ls 317 } 318 var j int 319 for i := 1; i < len(ls); i++ { 320 if ls[i].Name != ls[j].Name { 321 j++ 322 ls[j] = ls[i] 323 } 324 } 325 return ls[:j+1] 326 } 327 328 // LabelPairsString returns a string representation of the label pairs. 329 func LabelPairsString(lbs []*typesv1.LabelPair) string { 330 var b bytes.Buffer 331 b.WriteByte('{') 332 for i, l := range lbs { 333 if i > 0 { 334 b.WriteByte(',') 335 b.WriteByte(' ') 336 } 337 b.WriteString(l.Name) 338 b.WriteByte('=') 339 b.WriteString(strconv.Quote(l.Value)) 340 } 341 b.WriteByte('}') 342 return b.String() 343 } 344 345 // LabelsFromMap returns new sorted Labels from the given map. 346 func LabelsFromMap(m map[string]string) Labels { 347 res := make(Labels, 0, len(m)) 348 for k, v := range m { 349 res = append(res, &typesv1.LabelPair{Name: k, Value: v}) 350 } 351 sort.Sort(res) 352 return res 353 } 354 355 // LabelsFromStrings creates new labels from pairs of strings. 356 func LabelsFromStrings(ss ...string) Labels { 357 if len(ss)%2 != 0 { 358 panic("invalid number of strings") 359 } 360 var res Labels 361 for i := 0; i < len(ss); i += 2 { 362 res = append(res, &typesv1.LabelPair{Name: ss[i], Value: ss[i+1]}) 363 } 364 365 sort.Sort(res) 366 return res 367 } 368 369 // CompareLabelPairs compares the two label sets. 370 // The result will be 0 if a==b, <0 if a < b, and >0 if a > b. 371 func CompareLabelPairs(a, b []*typesv1.LabelPair) int { 372 l := len(a) 373 if len(b) < l { 374 l = len(b) 375 } 376 377 for i := 0; i < l; i++ { 378 if a[i].Name != b[i].Name { 379 if a[i].Name < b[i].Name { 380 return -1 381 } 382 return 1 383 } 384 if a[i].Value != b[i].Value { 385 if a[i].Value < b[i].Value { 386 return -1 387 } 388 return 1 389 } 390 } 391 // If all labels so far were in common, the set with fewer labels comes first. 392 return len(a) - len(b) 393 } 394 395 func CompareLabels(a, b *typesv1.Labels) int { 396 return CompareLabelPairs(a.Labels, b.Labels) 397 } 398 399 func CompareLabelPairs2(a, b *typesv1.LabelPair) int { 400 if a.Name < b.Name { 401 return -1 402 } else if a.Name > b.Name { 403 return 1 404 } 405 if a.Value < b.Value { 406 return -1 407 } else if a.Value > b.Value { 408 return 1 409 } 410 return 0 411 } 412 413 // LabelsBuilder allows modifying Labels. 414 type LabelsBuilder struct { 415 base Labels 416 del []string 417 add []*typesv1.LabelPair 418 } 419 420 // NewLabelsBuilder returns a new LabelsBuilder. 421 func NewLabelsBuilder(base Labels) *LabelsBuilder { 422 b := &LabelsBuilder{ 423 del: make([]string, 0, 5), 424 add: make([]*typesv1.LabelPair, 0, 5), 425 } 426 b.Reset(base) 427 return b 428 } 429 430 // Reset clears all current state for the builder. 431 func (b *LabelsBuilder) Reset(base Labels) { 432 b.base = base 433 b.del = b.del[:0] 434 b.add = b.add[:0] 435 for _, l := range b.base { 436 if l.Value == "" { 437 b.del = append(b.del, l.Name) 438 } 439 } 440 } 441 442 // Del deletes the label of the given name. 443 func (b *LabelsBuilder) Del(ns ...string) *LabelsBuilder { 444 for _, n := range ns { 445 for i, a := range b.add { 446 if a.Name == n { 447 b.add = append(b.add[:i], b.add[i+1:]...) 448 } 449 } 450 b.del = append(b.del, n) 451 } 452 return b 453 } 454 455 // Set the name/value pair as a label. 456 func (b *LabelsBuilder) Set(n, v string) *LabelsBuilder { 457 if v == "" { 458 // Empty labels are the same as missing labels. 459 return b.Del(n) 460 } 461 for i, a := range b.add { 462 if a.Name == n { 463 b.add[i].Value = v 464 return b 465 } 466 } 467 b.add = append(b.add, &typesv1.LabelPair{Name: n, Value: v}) 468 469 return b 470 } 471 472 func (b *LabelsBuilder) Get(n string) string { 473 // Del() removes entries from .add but Set() does not remove from .del, so check .add first. 474 for _, a := range b.add { 475 if a.Name == n { 476 return a.Value 477 } 478 } 479 if slices.Contains(b.del, n) { 480 return "" 481 } 482 return b.base.Get(n) 483 } 484 485 // Range calls f on each label in the Builder. 486 func (b *LabelsBuilder) Range(f func(l *typesv1.LabelPair)) { 487 // Stack-based arrays to avoid heap allocation in most cases. 488 var addStack [128]*typesv1.LabelPair 489 var delStack [128]string 490 // Take a copy of add and del, so they are unaffected by calls to Set() or Del(). 491 origAdd, origDel := append(addStack[:0], b.add...), append(delStack[:0], b.del...) 492 b.base.Range(func(l *typesv1.LabelPair) { 493 if !slices.Contains(origDel, l.Name) && !contains(origAdd, l.Name) { 494 f(l) 495 } 496 }) 497 for _, a := range origAdd { 498 f(a) 499 } 500 } 501 502 func contains(s []*typesv1.LabelPair, n string) bool { 503 for _, a := range s { 504 if a.Name == n { 505 return true 506 } 507 } 508 return false 509 } 510 511 // Labels returns the labels from the builder. If no modifications 512 // were made, the original labels are returned. 513 func (b *LabelsBuilder) Labels() Labels { 514 res := b.LabelsUnsorted() 515 sort.Sort(res) 516 return res 517 } 518 519 // LabelsUnsorted returns the labels from the builder. If no modifications 520 // were made, the original labels are returned. 521 // 522 // The order is not deterministic. 523 func (b *LabelsBuilder) LabelsUnsorted() Labels { 524 if len(b.del) == 0 && len(b.add) == 0 { 525 return b.base 526 } 527 528 // In the general case, labels are removed, modified or moved 529 // rather than added. 530 res := make(Labels, 0, len(b.base)) 531 Outer: 532 for _, l := range b.base { 533 for _, n := range b.del { 534 if l.Name == n { 535 continue Outer 536 } 537 } 538 for _, la := range b.add { 539 if l.Name == la.Name { 540 continue Outer 541 } 542 } 543 res = append(res, l) 544 } 545 546 return append(res, b.add...) 547 } 548 549 type SessionID uint64 550 551 func (s SessionID) String() string { 552 var b [8]byte 553 binary.LittleEndian.PutUint64(b[:], uint64(s)) 554 return hex.EncodeToString(b[:]) 555 } 556 557 func ParseSessionID(s string) (SessionID, error) { 558 if len(s) != 16 { 559 return 0, fmt.Errorf("invalid session id length %d", len(s)) 560 } 561 var b [8]byte 562 if _, err := hex.Decode(b[:], util.YoloBuf(s)); err != nil { 563 return 0, err 564 } 565 return SessionID(binary.LittleEndian.Uint64(b[:])), nil 566 } 567 568 type ServiceVersion struct { 569 Repository string `json:"repository,omitempty"` 570 GitRef string `json:"git_ref,omitempty"` 571 BuildID string `json:"build_id,omitempty"` 572 RootPath string `json:"root_path,omitempty"` 573 } 574 575 // ServiceVersionFromLabels Attempts to extract a service version from the given labels. 576 // Returns false if no service version was found. 577 func ServiceVersionFromLabels(lbls Labels) (ServiceVersion, bool) { 578 repo := lbls.Get(LabelNameServiceRepository) 579 gitref := lbls.Get(LabelNameServiceGitRef) 580 rootPath := lbls.Get(LabelNameServiceRootPath) 581 return ServiceVersion{ 582 Repository: repo, 583 GitRef: gitref, 584 RootPath: rootPath, 585 }, repo != "" || gitref != "" || rootPath != "" 586 } 587 588 // IntersectAll returns only the labels that are present in all label sets 589 // with the same value. Used for merging exemplars with dynamic labels. 590 // Reuses the existing Intersect method by iteratively intersecting all label sets. 591 func IntersectAll(labelSets []Labels) Labels { 592 if len(labelSets) == 0 { 593 return nil 594 } 595 if len(labelSets) == 1 { 596 return labelSets[0] 597 } 598 599 result := labelSets[0].Clone() 600 for i := 1; i < len(labelSets); i++ { 601 result = result.Intersect(labelSets[i]) 602 if len(result) == 0 { 603 return nil 604 } 605 } 606 607 if len(result) == 0 { 608 return nil 609 } 610 return result 611 } 612 613 // WithoutLabels returns a new Labels with the specified label names removed. 614 func (ls Labels) WithoutLabels(names ...string) Labels { 615 if len(names) == 0 { 616 return ls 617 } 618 619 toRemove := make(Labels, 0, len(names)) 620 for _, name := range names { 621 for _, l := range ls { 622 if l.Name == name { 623 toRemove = append(toRemove, l) 624 break 625 } 626 } 627 } 628 629 if len(toRemove) == 0 { 630 return ls 631 } 632 633 result := ls.Clone() 634 return result.Subtract(toRemove) 635 }