1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package profile
18
19 import (
20 "bytes"
21 "compress/gzip"
22 "fmt"
23 "io"
24 "math"
25 "path/filepath"
26 "regexp"
27 "sort"
28 "strings"
29 "sync"
30 "time"
31 )
32
33
34 type Profile struct {
35 SampleType []*ValueType
36 DefaultSampleType string
37 Sample []*Sample
38 Mapping []*Mapping
39 Location []*Location
40 Function []*Function
41 Comments []string
42
43 DropFrames string
44 KeepFrames string
45
46 TimeNanos int64
47 DurationNanos int64
48 PeriodType *ValueType
49 Period int64
50
51
52
53 encodeMu sync.Mutex
54
55 commentX []int64
56 dropFramesX int64
57 keepFramesX int64
58 stringTable []string
59 defaultSampleTypeX int64
60 }
61
62
63 type ValueType struct {
64 Type string
65 Unit string
66
67 typeX int64
68 unitX int64
69 }
70
71
72 type Sample struct {
73 Location []*Location
74 Value []int64
75
76
77
78
79
80
81
82 Label map[string][]string
83
84
85 NumLabel map[string][]int64
86
87
88
89
90
91 NumUnit map[string][]string
92
93 locationIDX []uint64
94 labelX []label
95 }
96
97
98 type label struct {
99 keyX int64
100
101 strX int64
102 numX int64
103
104 unitX int64
105 }
106
107
108 type Mapping struct {
109 ID uint64
110 Start uint64
111 Limit uint64
112 Offset uint64
113 File string
114 BuildID string
115 HasFunctions bool
116 HasFilenames bool
117 HasLineNumbers bool
118 HasInlineFrames bool
119
120 fileX int64
121 buildIDX int64
122
123
124
125
126
127
128
129
130 KernelRelocationSymbol string
131 }
132
133
134 type Location struct {
135 ID uint64
136 Mapping *Mapping
137 Address uint64
138 Line []Line
139 IsFolded bool
140
141 mappingIDX uint64
142 }
143
144
145 type Line struct {
146 Function *Function
147 Line int64
148
149 functionIDX uint64
150 }
151
152
153 type Function struct {
154 ID uint64
155 Name string
156 SystemName string
157 Filename string
158 StartLine int64
159
160 nameX int64
161 systemNameX int64
162 filenameX int64
163 }
164
165
166
167
168 func Parse(r io.Reader) (*Profile, error) {
169 data, err := io.ReadAll(r)
170 if err != nil {
171 return nil, err
172 }
173 return ParseData(data)
174 }
175
176
177
178 func ParseData(data []byte) (*Profile, error) {
179 var p *Profile
180 var err error
181 if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
182 gz, err := gzip.NewReader(bytes.NewBuffer(data))
183 if err == nil {
184 data, err = io.ReadAll(gz)
185 }
186 if err != nil {
187 return nil, fmt.Errorf("decompressing profile: %v", err)
188 }
189 }
190 if p, err = ParseUncompressed(data); err != nil && err != errNoData && err != errConcatProfile {
191 p, err = parseLegacy(data)
192 }
193
194 if err != nil {
195 return nil, fmt.Errorf("parsing profile: %v", err)
196 }
197
198 if err := p.CheckValid(); err != nil {
199 return nil, fmt.Errorf("malformed profile: %v", err)
200 }
201 return p, nil
202 }
203
204 var errUnrecognized = fmt.Errorf("unrecognized profile format")
205 var errMalformed = fmt.Errorf("malformed profile format")
206 var errNoData = fmt.Errorf("empty input file")
207 var errConcatProfile = fmt.Errorf("concatenated profiles detected")
208
209 func parseLegacy(data []byte) (*Profile, error) {
210 parsers := []func([]byte) (*Profile, error){
211 parseCPU,
212 parseHeap,
213 parseGoCount,
214 parseThread,
215 parseContention,
216 parseJavaProfile,
217 }
218
219 for _, parser := range parsers {
220 p, err := parser(data)
221 if err == nil {
222 p.addLegacyFrameInfo()
223 return p, nil
224 }
225 if err != errUnrecognized {
226 return nil, err
227 }
228 }
229 return nil, errUnrecognized
230 }
231
232
233 func ParseUncompressed(data []byte) (*Profile, error) {
234 if len(data) == 0 {
235 return nil, errNoData
236 }
237 p := &Profile{}
238 if err := unmarshal(data, p); err != nil {
239 return nil, err
240 }
241
242 if err := p.postDecode(); err != nil {
243 return nil, err
244 }
245
246 return p, nil
247 }
248
249 var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
250
251
252
253 func (p *Profile) massageMappings() {
254
255 if len(p.Mapping) > 1 {
256 mappings := []*Mapping{p.Mapping[0]}
257 for _, m := range p.Mapping[1:] {
258 lm := mappings[len(mappings)-1]
259 if adjacent(lm, m) {
260 lm.Limit = m.Limit
261 if m.File != "" {
262 lm.File = m.File
263 }
264 if m.BuildID != "" {
265 lm.BuildID = m.BuildID
266 }
267 p.updateLocationMapping(m, lm)
268 continue
269 }
270 mappings = append(mappings, m)
271 }
272 p.Mapping = mappings
273 }
274
275
276 for i, m := range p.Mapping {
277 file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
278 if len(file) == 0 {
279 continue
280 }
281 if len(libRx.FindStringSubmatch(file)) > 0 {
282 continue
283 }
284 if file[0] == '[' {
285 continue
286 }
287
288 p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
289 break
290 }
291
292
293 for i, m := range p.Mapping {
294 m.ID = uint64(i + 1)
295 }
296 }
297
298
299
300
301 func adjacent(m1, m2 *Mapping) bool {
302 if m1.File != "" && m2.File != "" {
303 if m1.File != m2.File {
304 return false
305 }
306 }
307 if m1.BuildID != "" && m2.BuildID != "" {
308 if m1.BuildID != m2.BuildID {
309 return false
310 }
311 }
312 if m1.Limit != m2.Start {
313 return false
314 }
315 if m1.Offset != 0 && m2.Offset != 0 {
316 offset := m1.Offset + (m1.Limit - m1.Start)
317 if offset != m2.Offset {
318 return false
319 }
320 }
321 return true
322 }
323
324 func (p *Profile) updateLocationMapping(from, to *Mapping) {
325 for _, l := range p.Location {
326 if l.Mapping == from {
327 l.Mapping = to
328 }
329 }
330 }
331
332 func serialize(p *Profile) []byte {
333 p.encodeMu.Lock()
334 p.preEncode()
335 b := marshal(p)
336 p.encodeMu.Unlock()
337 return b
338 }
339
340
341 func (p *Profile) Write(w io.Writer) error {
342 zw := gzip.NewWriter(w)
343 defer zw.Close()
344 _, err := zw.Write(serialize(p))
345 return err
346 }
347
348
349 func (p *Profile) WriteUncompressed(w io.Writer) error {
350 _, err := w.Write(serialize(p))
351 return err
352 }
353
354
355
356
357
358 func (p *Profile) CheckValid() error {
359
360 sampleLen := len(p.SampleType)
361 if sampleLen == 0 && len(p.Sample) != 0 {
362 return fmt.Errorf("missing sample type information")
363 }
364 for _, s := range p.Sample {
365 if s == nil {
366 return fmt.Errorf("profile has nil sample")
367 }
368 if len(s.Value) != sampleLen {
369 return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(s.Value), len(p.SampleType))
370 }
371 for _, l := range s.Location {
372 if l == nil {
373 return fmt.Errorf("sample has nil location")
374 }
375 }
376 }
377
378
379
380 mappings := make(map[uint64]*Mapping, len(p.Mapping))
381 for _, m := range p.Mapping {
382 if m == nil {
383 return fmt.Errorf("profile has nil mapping")
384 }
385 if m.ID == 0 {
386 return fmt.Errorf("found mapping with reserved ID=0")
387 }
388 if mappings[m.ID] != nil {
389 return fmt.Errorf("multiple mappings with same id: %d", m.ID)
390 }
391 mappings[m.ID] = m
392 }
393 functions := make(map[uint64]*Function, len(p.Function))
394 for _, f := range p.Function {
395 if f == nil {
396 return fmt.Errorf("profile has nil function")
397 }
398 if f.ID == 0 {
399 return fmt.Errorf("found function with reserved ID=0")
400 }
401 if functions[f.ID] != nil {
402 return fmt.Errorf("multiple functions with same id: %d", f.ID)
403 }
404 functions[f.ID] = f
405 }
406 locations := make(map[uint64]*Location, len(p.Location))
407 for _, l := range p.Location {
408 if l == nil {
409 return fmt.Errorf("profile has nil location")
410 }
411 if l.ID == 0 {
412 return fmt.Errorf("found location with reserved id=0")
413 }
414 if locations[l.ID] != nil {
415 return fmt.Errorf("multiple locations with same id: %d", l.ID)
416 }
417 locations[l.ID] = l
418 if m := l.Mapping; m != nil {
419 if m.ID == 0 || mappings[m.ID] != m {
420 return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
421 }
422 }
423 for _, ln := range l.Line {
424 f := ln.Function
425 if f == nil {
426 return fmt.Errorf("location id: %d has a line with nil function", l.ID)
427 }
428 if f.ID == 0 || functions[f.ID] != f {
429 return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
430 }
431 }
432 }
433 return nil
434 }
435
436
437
438
439 func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
440 for _, m := range p.Mapping {
441 m.HasInlineFrames = m.HasInlineFrames && inlineFrame
442 m.HasFunctions = m.HasFunctions && function
443 m.HasFilenames = m.HasFilenames && filename
444 m.HasLineNumbers = m.HasLineNumbers && linenumber
445 }
446
447
448 if !function || !filename {
449 for _, f := range p.Function {
450 if !function {
451 f.Name = ""
452 f.SystemName = ""
453 }
454 if !filename {
455 f.Filename = ""
456 }
457 }
458 }
459
460
461 if !inlineFrame || !address || !linenumber {
462 for _, l := range p.Location {
463 if !inlineFrame && len(l.Line) > 1 {
464 l.Line = l.Line[len(l.Line)-1:]
465 }
466 if !linenumber {
467 for i := range l.Line {
468 l.Line[i].Line = 0
469 }
470 }
471 if !address {
472 l.Address = 0
473 }
474 }
475 }
476
477 return p.CheckValid()
478 }
479
480
481
482
483
484
485
486
487
488
489 func (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {
490 numLabelUnits := map[string]string{}
491 ignoredUnits := map[string]map[string]bool{}
492 encounteredKeys := map[string]bool{}
493
494
495 for _, s := range p.Sample {
496 for k := range s.NumLabel {
497 encounteredKeys[k] = true
498 for _, unit := range s.NumUnit[k] {
499 if unit == "" {
500 continue
501 }
502 if wantUnit, ok := numLabelUnits[k]; !ok {
503 numLabelUnits[k] = unit
504 } else if wantUnit != unit {
505 if v, ok := ignoredUnits[k]; ok {
506 v[unit] = true
507 } else {
508 ignoredUnits[k] = map[string]bool{unit: true}
509 }
510 }
511 }
512 }
513 }
514
515
516 for key := range encounteredKeys {
517 unit := numLabelUnits[key]
518 if unit == "" {
519 switch key {
520 case "alignment", "request":
521 numLabelUnits[key] = "bytes"
522 default:
523 numLabelUnits[key] = key
524 }
525 }
526 }
527
528
529 unitsIgnored := make(map[string][]string, len(ignoredUnits))
530 for key, values := range ignoredUnits {
531 units := make([]string, len(values))
532 i := 0
533 for unit := range values {
534 units[i] = unit
535 i++
536 }
537 sort.Strings(units)
538 unitsIgnored[key] = units
539 }
540
541 return numLabelUnits, unitsIgnored
542 }
543
544
545
546 func (p *Profile) String() string {
547 ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))
548 for _, c := range p.Comments {
549 ss = append(ss, "Comment: "+c)
550 }
551 if pt := p.PeriodType; pt != nil {
552 ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
553 }
554 ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
555 if p.TimeNanos != 0 {
556 ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
557 }
558 if p.DurationNanos != 0 {
559 ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
560 }
561
562 ss = append(ss, "Samples:")
563 var sh1 string
564 for _, s := range p.SampleType {
565 dflt := ""
566 if s.Type == p.DefaultSampleType {
567 dflt = "[dflt]"
568 }
569 sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
570 }
571 ss = append(ss, strings.TrimSpace(sh1))
572 for _, s := range p.Sample {
573 ss = append(ss, s.string())
574 }
575
576 ss = append(ss, "Locations")
577 for _, l := range p.Location {
578 ss = append(ss, l.string())
579 }
580
581 ss = append(ss, "Mappings")
582 for _, m := range p.Mapping {
583 ss = append(ss, m.string())
584 }
585
586 return strings.Join(ss, "\n") + "\n"
587 }
588
589
590
591 func (m *Mapping) string() string {
592 bits := ""
593 if m.HasFunctions {
594 bits = bits + "[FN]"
595 }
596 if m.HasFilenames {
597 bits = bits + "[FL]"
598 }
599 if m.HasLineNumbers {
600 bits = bits + "[LN]"
601 }
602 if m.HasInlineFrames {
603 bits = bits + "[IN]"
604 }
605 return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
606 m.ID,
607 m.Start, m.Limit, m.Offset,
608 m.File,
609 m.BuildID,
610 bits)
611 }
612
613
614
615 func (l *Location) string() string {
616 ss := []string{}
617 locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
618 if m := l.Mapping; m != nil {
619 locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
620 }
621 if l.IsFolded {
622 locStr = locStr + "[F] "
623 }
624 if len(l.Line) == 0 {
625 ss = append(ss, locStr)
626 }
627 for li := range l.Line {
628 lnStr := "??"
629 if fn := l.Line[li].Function; fn != nil {
630 lnStr = fmt.Sprintf("%s %s:%d s=%d",
631 fn.Name,
632 fn.Filename,
633 l.Line[li].Line,
634 fn.StartLine)
635 if fn.Name != fn.SystemName {
636 lnStr = lnStr + "(" + fn.SystemName + ")"
637 }
638 }
639 ss = append(ss, locStr+lnStr)
640
641 locStr = " "
642 }
643 return strings.Join(ss, "\n")
644 }
645
646
647
648 func (s *Sample) string() string {
649 ss := []string{}
650 var sv string
651 for _, v := range s.Value {
652 sv = fmt.Sprintf("%s %10d", sv, v)
653 }
654 sv = sv + ": "
655 for _, l := range s.Location {
656 sv = sv + fmt.Sprintf("%d ", l.ID)
657 }
658 ss = append(ss, sv)
659 const labelHeader = " "
660 if len(s.Label) > 0 {
661 ss = append(ss, labelHeader+labelsToString(s.Label))
662 }
663 if len(s.NumLabel) > 0 {
664 ss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))
665 }
666 return strings.Join(ss, "\n")
667 }
668
669
670
671 func labelsToString(labels map[string][]string) string {
672 ls := []string{}
673 for k, v := range labels {
674 ls = append(ls, fmt.Sprintf("%s:%v", k, v))
675 }
676 sort.Strings(ls)
677 return strings.Join(ls, " ")
678 }
679
680
681
682 func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {
683 ls := []string{}
684 for k, v := range numLabels {
685 units := numUnits[k]
686 var labelString string
687 if len(units) == len(v) {
688 values := make([]string, len(v))
689 for i, vv := range v {
690 values[i] = fmt.Sprintf("%d %s", vv, units[i])
691 }
692 labelString = fmt.Sprintf("%s:%v", k, values)
693 } else {
694 labelString = fmt.Sprintf("%s:%v", k, v)
695 }
696 ls = append(ls, labelString)
697 }
698 sort.Strings(ls)
699 return strings.Join(ls, " ")
700 }
701
702
703
704 func (p *Profile) SetLabel(key string, value []string) {
705 for _, sample := range p.Sample {
706 if sample.Label == nil {
707 sample.Label = map[string][]string{key: value}
708 } else {
709 sample.Label[key] = value
710 }
711 }
712 }
713
714
715
716 func (p *Profile) RemoveLabel(key string) {
717 for _, sample := range p.Sample {
718 delete(sample.Label, key)
719 }
720 }
721
722
723 func (s *Sample) HasLabel(key, value string) bool {
724 for _, v := range s.Label[key] {
725 if v == value {
726 return true
727 }
728 }
729 return false
730 }
731
732
733
734
735
736
737 func (p *Profile) SetNumLabel(key string, value []int64, unit []string) {
738 for _, sample := range p.Sample {
739 if sample.NumLabel == nil {
740 sample.NumLabel = map[string][]int64{key: value}
741 } else {
742 sample.NumLabel[key] = value
743 }
744 if sample.NumUnit == nil {
745 sample.NumUnit = map[string][]string{key: unit}
746 } else {
747 sample.NumUnit[key] = unit
748 }
749 }
750 }
751
752
753
754 func (p *Profile) RemoveNumLabel(key string) {
755 for _, sample := range p.Sample {
756 delete(sample.NumLabel, key)
757 delete(sample.NumUnit, key)
758 }
759 }
760
761
762
763 func (s *Sample) DiffBaseSample() bool {
764 return s.HasLabel("pprof::base", "true")
765 }
766
767
768
769 func (p *Profile) Scale(ratio float64) {
770 if ratio == 1 {
771 return
772 }
773 ratios := make([]float64, len(p.SampleType))
774 for i := range p.SampleType {
775 ratios[i] = ratio
776 }
777 p.ScaleN(ratios)
778 }
779
780
781
782 func (p *Profile) ScaleN(ratios []float64) error {
783 if len(p.SampleType) != len(ratios) {
784 return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
785 }
786 allOnes := true
787 for _, r := range ratios {
788 if r != 1 {
789 allOnes = false
790 break
791 }
792 }
793 if allOnes {
794 return nil
795 }
796 fillIdx := 0
797 for _, s := range p.Sample {
798 keepSample := false
799 for i, v := range s.Value {
800 if ratios[i] != 1 {
801 val := int64(math.Round(float64(v) * ratios[i]))
802 s.Value[i] = val
803 keepSample = keepSample || val != 0
804 }
805 }
806 if keepSample {
807 p.Sample[fillIdx] = s
808 fillIdx++
809 }
810 }
811 p.Sample = p.Sample[:fillIdx]
812 return nil
813 }
814
815
816
817 func (p *Profile) HasFunctions() bool {
818 for _, l := range p.Location {
819 if l.Mapping != nil && !l.Mapping.HasFunctions {
820 return false
821 }
822 }
823 return true
824 }
825
826
827
828 func (p *Profile) HasFileLines() bool {
829 for _, l := range p.Location {
830 if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
831 return false
832 }
833 }
834 return true
835 }
836
837
838
839
840 func (m *Mapping) Unsymbolizable() bool {
841 name := filepath.Base(m.File)
842 return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
843 }
844
845
846 func (p *Profile) Copy() *Profile {
847 pp := &Profile{}
848 if err := unmarshal(serialize(p), pp); err != nil {
849 panic(err)
850 }
851 if err := pp.postDecode(); err != nil {
852 panic(err)
853 }
854
855 return pp
856 }
857
View as plain text