github.com/grafana/pyroscope@v1.18.0/pkg/model/profile.go (about) 1 package model 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "fmt" 7 "path/filepath" 8 "strings" 9 10 "github.com/cespare/xxhash/v2" 11 "github.com/prometheus/prometheus/model/labels" 12 13 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 14 ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1" 15 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 16 "github.com/grafana/pyroscope/pkg/util" 17 ) 18 19 // CompareProfile compares the two profiles. 20 func CompareProfile(a, b *ingestv1.Profile) int64 { 21 if a.Timestamp == b.Timestamp { 22 return int64(CompareLabelPairs(a.Labels, b.Labels)) 23 } 24 return a.Timestamp - b.Timestamp 25 } 26 27 // ParseProfileTypeSelector parses the profile selector string. 28 func ParseProfileTypeSelector(id string) (*typesv1.ProfileType, error) { 29 parts := strings.Split(id, ":") 30 31 if len(parts) != 5 && len(parts) != 6 { 32 return nil, fmt.Errorf("profile-type selection must be of the form <name>:<sample-type>:<sample-unit>:<period-type>:<period-unit>(:delta), got(%d): %q", len(parts), id) 33 } 34 name, sampleType, sampleUnit, periodType, periodUnit := parts[0], parts[1], parts[2], parts[3], parts[4] 35 return &typesv1.ProfileType{ 36 Name: name, 37 ID: id, 38 SampleType: sampleType, 39 SampleUnit: sampleUnit, 40 PeriodType: periodType, 41 PeriodUnit: periodUnit, 42 }, nil 43 } 44 45 // SelectorFromProfileType builds a *label.Matcher from an profile type struct 46 func SelectorFromProfileType(profileType *typesv1.ProfileType) *labels.Matcher { 47 return &labels.Matcher{ 48 Type: labels.MatchEqual, 49 Name: LabelNameProfileType, 50 Value: profileType.Name + ":" + profileType.SampleType + ":" + profileType.SampleUnit + ":" + profileType.PeriodType + ":" + profileType.PeriodUnit, 51 } 52 } 53 54 type SpanSelector map[uint64]struct{} 55 56 func NewSpanSelector(spans []string) (SpanSelector, error) { 57 m := make(map[uint64]struct{}, len(spans)) 58 b := make([]byte, 8) 59 for _, s := range spans { 60 if len(s) != 16 { 61 return nil, fmt.Errorf("invalid span id length: %q", s) 62 } 63 if _, err := hex.Decode(b, util.YoloBuf(s)); err != nil { 64 return nil, err 65 } 66 m[binary.LittleEndian.Uint64(b)] = struct{}{} 67 } 68 return m, nil 69 } 70 71 func SymbolsPartitionForProfile(ls Labels, partitionLabel string, p *profilev1.Profile) uint64 { 72 return xxhash.Sum64String(symbolsPartitionKeyForProfile(ls, partitionLabel, p)) 73 } 74 75 func symbolsPartitionKeyForProfile(ls Labels, partitionLabel string, p *profilev1.Profile) string { 76 if partitionLabel == "" { 77 // Only use the main binary's file basename as the partition key 78 // if the partition label is not specified. 79 if len(p.Mapping) > 0 { 80 if filenameID := p.Mapping[0].Filename; filenameID > 0 { 81 if filename := extractMappingFilename(p.StringTable[filenameID]); filename != "" { 82 return filename 83 } 84 } 85 } 86 partitionLabel = LabelNameServiceName 87 } 88 if value := ls.Get(partitionLabel); value != "" { 89 return value 90 } 91 return "unknown" 92 } 93 94 func extractMappingFilename(filename string) string { 95 // See github.com/google/pprof/profile/profile.go 96 // It's unlikely that the main binary mapping is one of them. 97 if filename == "" || 98 strings.HasPrefix(filename, "[") || 99 strings.HasPrefix(filename, "linux-vdso") || 100 strings.HasPrefix(filename, "/dev/dri/") || 101 strings.HasPrefix(filename, "//anon") { 102 return "" 103 } 104 // Like filepath.ToSlash but doesn't rely on OS. 105 n := strings.ReplaceAll(filename, `\`, `/`) 106 return strings.TrimSpace(filepath.Base(filepath.Clean(n))) 107 }