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