github.com/grafana/pyroscope@v1.18.0/pkg/og/convert/pprof/strprofile/profile.go (about) 1 package strprofile 2 3 import ( 4 "encoding/binary" 5 "encoding/json" 6 "fmt" 7 "sort" 8 "strconv" 9 "strings" 10 11 "github.com/cespare/xxhash/v2" 12 13 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 14 ) 15 16 type Options struct { 17 NoPrettyPrint bool 18 NoDuration bool 19 NoTime bool 20 NoCompact bool 21 IncludeIDs bool 22 } 23 24 type Location struct { 25 ID uint64 `json:"id,omitempty"` 26 Address string `json:"address,omitempty"` 27 Lines []Line `json:"lines,omitempty"` 28 Mapping *Mapping `json:"mapping,omitempty"` 29 } 30 31 type Line struct { 32 Function *Function `json:"function"` 33 Line int64 `json:"line,omitempty"` 34 } 35 36 type Function struct { 37 ID uint64 `json:"id,omitempty"` 38 Name string `json:"name"` 39 SystemName string `json:"system_name,omitempty"` 40 Filename string `json:"filename,omitempty"` 41 StartLine int64 `json:"start_line,omitempty"` 42 } 43 44 type Mapping struct { 45 ID uint64 `json:"id,omitempty"` 46 Start string `json:"start"` 47 Limit string `json:"limit"` 48 Offset string `json:"offset,omitempty"` 49 Filename string `json:"filename,omitempty"` 50 BuildID string `json:"build_id,omitempty"` 51 } 52 53 type SampleType struct { 54 Type string `json:"type"` 55 Unit string `json:"unit"` 56 } 57 58 type Label struct { 59 Key string `json:"key"` 60 Value string `json:"value"` 61 } 62 63 type Sample struct { 64 Locations []Location `json:"locations,omitempty"` 65 Values []int64 `json:"values"` 66 Labels []Label `json:"labels,omitempty"` 67 } 68 69 type Profile struct { 70 SampleTypes []SampleType `json:"sample_types"` 71 Samples []Sample `json:"samples"` 72 TimeNanos string `json:"time_nanos,omitempty"` 73 DurationNanos string `json:"duration_nanos,omitempty"` 74 Period string `json:"period,omitempty"` 75 } 76 77 type CompactLocation struct { 78 ID uint64 `json:"id,omitempty"` 79 Address string `json:"address,omitempty"` 80 Lines []string `json:"lines,omitempty"` 81 Mapping string `json:"mapping,omitempty"` 82 } 83 84 type CompactSample struct { 85 Locations []CompactLocation `json:"locations,omitempty"` 86 Values string `json:"values"` 87 Labels string `json:"labels,omitempty"` 88 } 89 90 type CompactProfile struct { 91 SampleTypes []SampleType `json:"sample_types"` 92 Samples []CompactSample `json:"samples"` 93 TimeNanos string `json:"time_nanos,omitempty"` 94 DurationNanos string `json:"duration_nanos,omitempty"` 95 Period string `json:"period,omitempty"` 96 } 97 98 func Stringify(p *profilev1.Profile, opts Options) (string, error) { 99 var err error 100 var sp interface{} 101 102 if !opts.NoCompact { 103 sp = ToCompactProfile(p, opts) 104 } else { 105 sp = ToDetailedProfile(p, opts) 106 } 107 108 var jsonData []byte 109 if !opts.NoPrettyPrint { 110 jsonData, err = json.MarshalIndent(sp, "", " ") 111 } else { 112 jsonData, err = json.Marshal(sp) 113 } 114 if err != nil { 115 return "", err 116 } 117 118 return string(jsonData), nil 119 } 120 121 func ToDetailedProfile(p *profilev1.Profile, opts Options) Profile { 122 sp := Profile{ 123 Period: fmt.Sprintf("%d", p.Period), 124 } 125 if !opts.NoTime { 126 sp.TimeNanos = fmt.Sprintf("%d", p.TimeNanos) 127 } 128 if !opts.NoDuration { 129 sp.DurationNanos = fmt.Sprintf("%d", p.DurationNanos) 130 } 131 132 for _, st := range p.SampleType { 133 sp.SampleTypes = append(sp.SampleTypes, SampleType{ 134 Type: p.StringTable[st.Type], 135 Unit: p.StringTable[st.Unit], 136 }) 137 } 138 139 // Create maps for quick lookups 140 functionMap := make(map[uint64]*profilev1.Function) 141 for _, f := range p.Function { 142 functionMap[f.Id] = f 143 } 144 145 mappingMap := make(map[uint64]*profilev1.Mapping) 146 for _, m := range p.Mapping { 147 mappingMap[m.Id] = m 148 } 149 150 for _, sample := range p.Sample { 151 ss := Sample{ 152 Values: sample.Value, 153 } 154 155 for _, locID := range sample.LocationId { 156 loc := findLocation(p.Location, locID) 157 if loc == nil { 158 continue 159 } 160 161 sLoc := Location{ 162 Address: fmt.Sprintf("0x%x", loc.Address), 163 } 164 if opts.IncludeIDs { 165 sLoc.ID = loc.Id 166 } 167 168 if loc.MappingId != 0 { 169 if mapping := mappingMap[loc.MappingId]; mapping != nil { 170 m := &Mapping{ 171 Start: fmt.Sprintf("0x%x", mapping.MemoryStart), 172 Limit: fmt.Sprintf("0x%x", mapping.MemoryLimit), 173 Offset: fmt.Sprintf("0x%x", mapping.FileOffset), 174 Filename: p.StringTable[mapping.Filename], 175 BuildID: p.StringTable[mapping.BuildId], 176 } 177 if opts.IncludeIDs { 178 m.ID = mapping.Id 179 } 180 sLoc.Mapping = m 181 } 182 } 183 184 for _, line := range loc.Line { 185 if fn := functionMap[line.FunctionId]; fn != nil { 186 f := &Function{ 187 Name: p.StringTable[fn.Name], 188 SystemName: p.StringTable[fn.SystemName], 189 Filename: p.StringTable[fn.Filename], 190 StartLine: fn.StartLine, 191 } 192 if opts.IncludeIDs { 193 f.ID = fn.Id 194 } 195 sLine := Line{ 196 Function: f, 197 Line: line.Line, 198 } 199 sLoc.Lines = append(sLoc.Lines, sLine) 200 } 201 } 202 203 ss.Locations = append(ss.Locations, sLoc) 204 } 205 206 if len(sample.Label) > 0 { 207 ss.Labels = make([]Label, 0, len(sample.Label)) 208 for _, label := range sample.Label { 209 key := p.StringTable[label.Key] 210 var value string 211 if label.Str != 0 { 212 value = p.StringTable[label.Str] 213 } else { 214 value = strconv.FormatInt(label.Num, 10) 215 } 216 ss.Labels = append(ss.Labels, Label{ 217 Key: key, 218 Value: value, 219 }) 220 } 221 } 222 223 sp.Samples = append(sp.Samples, ss) 224 } 225 return sp 226 } 227 228 func ToCompactProfile(p *profilev1.Profile, opts Options) CompactProfile { 229 sp := CompactProfile{ 230 Period: fmt.Sprintf("%d", p.Period), 231 } 232 if !opts.NoTime { 233 sp.TimeNanos = fmt.Sprintf("%d", p.TimeNanos) 234 } 235 if !opts.NoDuration { 236 sp.DurationNanos = fmt.Sprintf("%d", p.DurationNanos) 237 } 238 239 for _, st := range p.SampleType { 240 sp.SampleTypes = append(sp.SampleTypes, SampleType{ 241 Type: p.StringTable[st.Type], 242 Unit: p.StringTable[st.Unit], 243 }) 244 } 245 246 functionMap := make(map[uint64]*profilev1.Function) 247 for _, f := range p.Function { 248 functionMap[f.Id] = f 249 } 250 251 mappingMap := make(map[uint64]*profilev1.Mapping) 252 for _, m := range p.Mapping { 253 mappingMap[m.Id] = m 254 } 255 256 for _, sample := range p.Sample { 257 values := make([]string, len(sample.Value)) 258 for i, v := range sample.Value { 259 values[i] = strconv.FormatInt(v, 10) 260 } 261 262 ss := CompactSample{ 263 Values: strings.Join(values, ","), 264 } 265 266 for _, locID := range sample.LocationId { 267 loc := findLocation(p.Location, locID) 268 if loc == nil { 269 continue 270 } 271 272 sLoc := CompactLocation{ 273 Address: fmt.Sprintf("0x%x", loc.Address), 274 } 275 if opts.IncludeIDs { 276 sLoc.ID = loc.Id 277 } 278 279 if loc.MappingId != 0 { 280 if mapping := mappingMap[loc.MappingId]; mapping != nil { 281 idStr := "" 282 if opts.IncludeIDs { 283 idStr = fmt.Sprintf("[id=%d]", mapping.Id) 284 } 285 sLoc.Mapping = fmt.Sprintf("0x%x-0x%x@0x%x %s(%s)%s", 286 mapping.MemoryStart, 287 mapping.MemoryLimit, 288 mapping.FileOffset, 289 p.StringTable[mapping.Filename], 290 p.StringTable[mapping.BuildId], 291 idStr) 292 } 293 } 294 295 for _, line := range loc.Line { 296 if fn := functionMap[line.FunctionId]; fn != nil { 297 idStr := "" 298 if opts.IncludeIDs { 299 idStr = fmt.Sprintf("[id=%d]", fn.Id) 300 } 301 lineStr := fmt.Sprintf("%s[%s]@%s:%d%s", 302 p.StringTable[fn.Name], 303 p.StringTable[fn.SystemName], 304 p.StringTable[fn.Filename], 305 line.Line, 306 idStr) 307 sLoc.Lines = append(sLoc.Lines, lineStr) 308 } 309 } 310 311 ss.Locations = append(ss.Locations, sLoc) 312 } 313 314 if len(sample.Label) > 0 { 315 labels := make([]string, 0, len(sample.Label)) 316 for _, label := range sample.Label { 317 key := p.StringTable[label.Key] 318 var value string 319 if label.Str != 0 { 320 value = p.StringTable[label.Str] 321 } else { 322 value = strconv.FormatInt(label.Num, 10) 323 } 324 labels = append(labels, fmt.Sprintf("%s=%s", key, value)) 325 } 326 sort.Strings(labels) 327 ss.Labels = strings.Join(labels, ",") 328 } 329 330 sp.Samples = append(sp.Samples, ss) 331 } 332 return sp 333 } 334 335 func findLocation(locations []*profilev1.Location, id uint64) *profilev1.Location { 336 for _, loc := range locations { 337 if loc.Id == id { 338 return loc 339 } 340 } 341 return nil 342 } 343 344 func SortProfileSamples(p CompactProfile) { 345 h := xxhash.New() 346 s := &sortedSample{ 347 samples: p.Samples, 348 hashes: make([]uint64, len(p.Samples)), 349 } 350 for i := range s.samples { 351 s.hashes[i] = sampleHash(h, s.samples[i]) 352 } 353 sort.Sort(s) 354 } 355 356 type sortedSample struct { 357 samples []CompactSample 358 hashes []uint64 359 } 360 361 func (s *sortedSample) Len() int { return len(s.samples) } 362 363 func (s *sortedSample) Less(i, j int) bool { return s.hashes[i] < s.hashes[j] } 364 365 func (s *sortedSample) Swap(i, j int) { 366 s.samples[i], s.samples[j] = s.samples[j], s.samples[i] 367 s.hashes[i], s.hashes[j] = s.hashes[j], s.hashes[i] 368 } 369 370 func sampleHash(d *xxhash.Digest, s CompactSample) uint64 { 371 d.Reset() 372 tmp := make([]byte, 8) 373 _, _ = d.WriteString(s.Labels) 374 _, _ = d.WriteString(s.Values) 375 for i := range s.Locations { 376 binary.LittleEndian.PutUint64(tmp, s.Locations[i].ID) 377 _, _ = d.Write(tmp) 378 _, _ = d.WriteString(s.Locations[i].Address) 379 _, _ = d.WriteString(s.Locations[i].Mapping) 380 for j := range s.Locations[i].Lines { 381 _, _ = d.WriteString(s.Locations[i].Lines[j]) 382 } 383 } 384 return d.Sum64() 385 }