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