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