1
2
3
4
5
6
7
8 package profile
9
10 import (
11 "bufio"
12 "bytes"
13 "fmt"
14 "internal/lazyregexp"
15 "io"
16 "math"
17 "strconv"
18 "strings"
19 )
20
21 var (
22 countStartRE = lazyregexp.New(`\A(\w+) profile: total \d+\n\z`)
23 countRE = lazyregexp.New(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`)
24
25 heapHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`)
26 heapSampleRE = lazyregexp.New(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`)
27
28 contentionSampleRE = lazyregexp.New(`(\d+) *(\d+) @([ x0-9a-f]*)`)
29
30 hexNumberRE = lazyregexp.New(`0x[0-9a-f]+`)
31
32 growthHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`)
33
34 fragmentationHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`)
35
36 threadzStartRE = lazyregexp.New(`--- threadz \d+ ---`)
37 threadStartRE = lazyregexp.New(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`)
38
39 procMapsRE = lazyregexp.New(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`)
40
41 briefMapsRE = lazyregexp.New(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`)
42
43
44
45
46
47 LegacyHeapAllocated bool
48 )
49
50 func isSpaceOrComment(line string) bool {
51 trimmed := strings.TrimSpace(line)
52 return len(trimmed) == 0 || trimmed[0] == '#'
53 }
54
55
56
57 func parseGoCount(b []byte) (*Profile, error) {
58 r := bytes.NewBuffer(b)
59
60 var line string
61 var err error
62 for {
63
64 line, err = r.ReadString('\n')
65 if err != nil {
66 return nil, err
67 }
68 if !isSpaceOrComment(line) {
69 break
70 }
71 }
72
73 m := countStartRE.FindStringSubmatch(line)
74 if m == nil {
75 return nil, errUnrecognized
76 }
77 profileType := m[1]
78 p := &Profile{
79 PeriodType: &ValueType{Type: profileType, Unit: "count"},
80 Period: 1,
81 SampleType: []*ValueType{{Type: profileType, Unit: "count"}},
82 }
83 locations := make(map[uint64]*Location)
84 for {
85 line, err = r.ReadString('\n')
86 if err != nil {
87 if err == io.EOF {
88 break
89 }
90 return nil, err
91 }
92 if isSpaceOrComment(line) {
93 continue
94 }
95 if strings.HasPrefix(line, "---") {
96 break
97 }
98 m := countRE.FindStringSubmatch(line)
99 if m == nil {
100 return nil, errMalformed
101 }
102 n, err := strconv.ParseInt(m[1], 0, 64)
103 if err != nil {
104 return nil, errMalformed
105 }
106 fields := strings.Fields(m[2])
107 locs := make([]*Location, 0, len(fields))
108 for _, stk := range fields {
109 addr, err := strconv.ParseUint(stk, 0, 64)
110 if err != nil {
111 return nil, errMalformed
112 }
113
114 addr--
115 loc := locations[addr]
116 if loc == nil {
117 loc = &Location{
118 Address: addr,
119 }
120 locations[addr] = loc
121 p.Location = append(p.Location, loc)
122 }
123 locs = append(locs, loc)
124 }
125 p.Sample = append(p.Sample, &Sample{
126 Location: locs,
127 Value: []int64{n},
128 })
129 }
130
131 if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil {
132 return nil, err
133 }
134 return p, nil
135 }
136
137
138
139
140 func (p *Profile) remapLocationIDs() {
141 seen := make(map[*Location]bool, len(p.Location))
142 var locs []*Location
143
144 for _, s := range p.Sample {
145 for _, l := range s.Location {
146 if seen[l] {
147 continue
148 }
149 l.ID = uint64(len(locs) + 1)
150 locs = append(locs, l)
151 seen[l] = true
152 }
153 }
154 p.Location = locs
155 }
156
157 func (p *Profile) remapFunctionIDs() {
158 seen := make(map[*Function]bool, len(p.Function))
159 var fns []*Function
160
161 for _, l := range p.Location {
162 for _, ln := range l.Line {
163 fn := ln.Function
164 if fn == nil || seen[fn] {
165 continue
166 }
167 fn.ID = uint64(len(fns) + 1)
168 fns = append(fns, fn)
169 seen[fn] = true
170 }
171 }
172 p.Function = fns
173 }
174
175
176
177
178
179 func (p *Profile) remapMappingIDs() {
180 if len(p.Mapping) == 0 {
181 return
182 }
183
184
185
186
187
188
189 if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") {
190 if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start {
191 p.Mapping = p.Mapping[1:]
192 }
193 }
194
195 for _, l := range p.Location {
196 if a := l.Address; a != 0 {
197 for _, m := range p.Mapping {
198 if m.Start <= a && a < m.Limit {
199 l.Mapping = m
200 break
201 }
202 }
203 }
204 }
205
206
207 for i, m := range p.Mapping {
208 m.ID = uint64(i + 1)
209 }
210 }
211
212 var cpuInts = []func([]byte) (uint64, []byte){
213 get32l,
214 get32b,
215 get64l,
216 get64b,
217 }
218
219 func get32l(b []byte) (uint64, []byte) {
220 if len(b) < 4 {
221 return 0, nil
222 }
223 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:]
224 }
225
226 func get32b(b []byte) (uint64, []byte) {
227 if len(b) < 4 {
228 return 0, nil
229 }
230 return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:]
231 }
232
233 func get64l(b []byte) (uint64, []byte) {
234 if len(b) < 8 {
235 return 0, nil
236 }
237 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:]
238 }
239
240 func get64b(b []byte) (uint64, []byte) {
241 if len(b) < 8 {
242 return 0, nil
243 }
244 return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:]
245 }
246
247
248
249
250
251
252 func ParseTracebacks(b []byte) (*Profile, error) {
253 r := bytes.NewBuffer(b)
254
255 p := &Profile{
256 PeriodType: &ValueType{Type: "trace", Unit: "count"},
257 Period: 1,
258 SampleType: []*ValueType{
259 {Type: "trace", Unit: "count"},
260 },
261 }
262
263 var sources []string
264 var sloc []*Location
265
266 locs := make(map[uint64]*Location)
267 for {
268 l, err := r.ReadString('\n')
269 if err != nil {
270 if err != io.EOF {
271 return nil, err
272 }
273 if l == "" {
274 break
275 }
276 }
277 if sectionTrigger(l) == memoryMapSection {
278 break
279 }
280 if s, addrs := extractHexAddresses(l); len(s) > 0 {
281 for _, addr := range addrs {
282
283
284 addr--
285 loc := locs[addr]
286 if locs[addr] == nil {
287 loc = &Location{
288 Address: addr,
289 }
290 p.Location = append(p.Location, loc)
291 locs[addr] = loc
292 }
293 sloc = append(sloc, loc)
294 }
295
296 sources = append(sources, s...)
297 } else {
298 if len(sources) > 0 || len(sloc) > 0 {
299 addTracebackSample(sloc, sources, p)
300 sloc, sources = nil, nil
301 }
302 }
303 }
304
305
306 if len(sources) > 0 || len(sloc) > 0 {
307 addTracebackSample(sloc, sources, p)
308 }
309
310 if err := p.ParseMemoryMap(r); err != nil {
311 return nil, err
312 }
313 return p, nil
314 }
315
316 func addTracebackSample(l []*Location, s []string, p *Profile) {
317 p.Sample = append(p.Sample,
318 &Sample{
319 Value: []int64{1},
320 Location: l,
321 Label: map[string][]string{"source": s},
322 })
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336 func parseCPU(b []byte) (*Profile, error) {
337 var parse func([]byte) (uint64, []byte)
338 var n1, n2, n3, n4, n5 uint64
339 for _, parse = range cpuInts {
340 var tmp []byte
341 n1, tmp = parse(b)
342 n2, tmp = parse(tmp)
343 n3, tmp = parse(tmp)
344 n4, tmp = parse(tmp)
345 n5, tmp = parse(tmp)
346
347 if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 {
348 b = tmp
349 return cpuProfile(b, int64(n4), parse)
350 }
351 }
352 return nil, errUnrecognized
353 }
354
355
356
357
358
359 func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {
360 p := &Profile{
361 Period: period * 1000,
362 PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
363 SampleType: []*ValueType{
364 {Type: "samples", Unit: "count"},
365 {Type: "cpu", Unit: "nanoseconds"},
366 },
367 }
368 var err error
369 if b, _, err = parseCPUSamples(b, parse, true, p); err != nil {
370 return nil, err
371 }
372
373
374
375
376
377
378
379 if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 {
380 allSame := true
381 id1 := p.Sample[0].Location[1].Address
382 for _, s := range p.Sample {
383 if len(s.Location) < 2 || id1 != s.Location[1].Address {
384 allSame = false
385 break
386 }
387 }
388 if allSame {
389 for _, s := range p.Sample {
390 s.Location = append(s.Location[:1], s.Location[2:]...)
391 }
392 }
393 }
394
395 if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil {
396 return nil, err
397 }
398 return p, nil
399 }
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) {
423 locs := make(map[uint64]*Location)
424 for len(b) > 0 {
425 var count, nstk uint64
426 count, b = parse(b)
427 nstk, b = parse(b)
428 if b == nil || nstk > uint64(len(b)/4) {
429 return nil, nil, errUnrecognized
430 }
431 var sloc []*Location
432 addrs := make([]uint64, nstk)
433 for i := 0; i < int(nstk); i++ {
434 addrs[i], b = parse(b)
435 }
436
437 if count == 0 && nstk == 1 && addrs[0] == 0 {
438
439 break
440 }
441 for i, addr := range addrs {
442 if adjust && i > 0 {
443 addr--
444 }
445 loc := locs[addr]
446 if loc == nil {
447 loc = &Location{
448 Address: addr,
449 }
450 locs[addr] = loc
451 p.Location = append(p.Location, loc)
452 }
453 sloc = append(sloc, loc)
454 }
455 p.Sample = append(p.Sample,
456 &Sample{
457 Value: []int64{int64(count), int64(count) * p.Period},
458 Location: sloc,
459 })
460 }
461
462 return b, locs, nil
463 }
464
465
466
467 func parseHeap(b []byte) (p *Profile, err error) {
468 r := bytes.NewBuffer(b)
469 l, err := r.ReadString('\n')
470 if err != nil {
471 return nil, errUnrecognized
472 }
473
474 sampling := ""
475
476 if header := heapHeaderRE.FindStringSubmatch(l); header != nil {
477 p = &Profile{
478 SampleType: []*ValueType{
479 {Type: "objects", Unit: "count"},
480 {Type: "space", Unit: "bytes"},
481 },
482 PeriodType: &ValueType{Type: "objects", Unit: "bytes"},
483 }
484
485 var period int64
486 if len(header[6]) > 0 {
487 if period, err = strconv.ParseInt(header[6], 10, 64); err != nil {
488 return nil, errUnrecognized
489 }
490 }
491
492 switch header[5] {
493 case "heapz_v2", "heap_v2":
494 sampling, p.Period = "v2", period
495 case "heapprofile":
496 sampling, p.Period = "", 1
497 case "heap":
498 sampling, p.Period = "v2", period/2
499 default:
500 return nil, errUnrecognized
501 }
502 } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil {
503 p = &Profile{
504 SampleType: []*ValueType{
505 {Type: "objects", Unit: "count"},
506 {Type: "space", Unit: "bytes"},
507 },
508 PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"},
509 Period: 1,
510 }
511 } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil {
512 p = &Profile{
513 SampleType: []*ValueType{
514 {Type: "objects", Unit: "count"},
515 {Type: "space", Unit: "bytes"},
516 },
517 PeriodType: &ValueType{Type: "allocations", Unit: "count"},
518 Period: 1,
519 }
520 } else {
521 return nil, errUnrecognized
522 }
523
524 if LegacyHeapAllocated {
525 for _, st := range p.SampleType {
526 st.Type = "alloc_" + st.Type
527 }
528 } else {
529 for _, st := range p.SampleType {
530 st.Type = "inuse_" + st.Type
531 }
532 }
533
534 locs := make(map[uint64]*Location)
535 for {
536 l, err = r.ReadString('\n')
537 if err != nil {
538 if err != io.EOF {
539 return nil, err
540 }
541
542 if l == "" {
543 break
544 }
545 }
546
547 if isSpaceOrComment(l) {
548 continue
549 }
550 l = strings.TrimSpace(l)
551
552 if sectionTrigger(l) != unrecognizedSection {
553 break
554 }
555
556 value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling)
557 if err != nil {
558 return nil, err
559 }
560 var sloc []*Location
561 for _, addr := range addrs {
562
563
564 addr--
565 loc := locs[addr]
566 if locs[addr] == nil {
567 loc = &Location{
568 Address: addr,
569 }
570 p.Location = append(p.Location, loc)
571 locs[addr] = loc
572 }
573 sloc = append(sloc, loc)
574 }
575
576 p.Sample = append(p.Sample, &Sample{
577 Value: value,
578 Location: sloc,
579 NumLabel: map[string][]int64{"bytes": {blocksize}},
580 })
581 }
582
583 if err = parseAdditionalSections(l, r, p); err != nil {
584 return nil, err
585 }
586 return p, nil
587 }
588
589
590 func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) {
591 sampleData := heapSampleRE.FindStringSubmatch(line)
592 if len(sampleData) != 6 {
593 return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData))
594 }
595
596
597
598
599 valueIndex := 1
600 if LegacyHeapAllocated {
601 valueIndex = 3
602 }
603
604 var v1, v2 int64
605 if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil {
606 return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
607 }
608 if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil {
609 return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
610 }
611
612 if v1 == 0 {
613 if v2 != 0 {
614 return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2)
615 }
616 } else {
617 blocksize = v2 / v1
618 if sampling == "v2" {
619 v1, v2 = scaleHeapSample(v1, v2, rate)
620 }
621 }
622
623 value = []int64{v1, v2}
624 addrs = parseHexAddresses(sampleData[5])
625
626 return value, blocksize, addrs, nil
627 }
628
629
630
631 func extractHexAddresses(s string) ([]string, []uint64) {
632 hexStrings := hexNumberRE.FindAllString(s, -1)
633 var ids []uint64
634 for _, s := range hexStrings {
635 if id, err := strconv.ParseUint(s, 0, 64); err == nil {
636 ids = append(ids, id)
637 } else {
638
639 panic("failed to parse hex value:" + s)
640 }
641 }
642 return hexStrings, ids
643 }
644
645
646
647 func parseHexAddresses(s string) []uint64 {
648 _, ids := extractHexAddresses(s)
649 return ids
650 }
651
652
653
654
655
656
657
658
659
660
661 func scaleHeapSample(count, size, rate int64) (int64, int64) {
662 if count == 0 || size == 0 {
663 return 0, 0
664 }
665
666 if rate <= 1 {
667
668
669 return count, size
670 }
671
672 avgSize := float64(size) / float64(count)
673 scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
674
675 return int64(float64(count) * scale), int64(float64(size) * scale)
676 }
677
678
679
680
681
682
683 func parseContention(b []byte) (*Profile, error) {
684 r := bytes.NewBuffer(b)
685 var l string
686 var err error
687 for {
688
689 l, err = r.ReadString('\n')
690 if err != nil {
691 return nil, err
692 }
693 if !isSpaceOrComment(l) {
694 break
695 }
696 }
697
698 if strings.HasPrefix(l, "--- contentionz ") {
699 return parseCppContention(r)
700 } else if strings.HasPrefix(l, "--- mutex:") {
701 return parseCppContention(r)
702 } else if strings.HasPrefix(l, "--- contention:") {
703 return parseCppContention(r)
704 }
705 return nil, errUnrecognized
706 }
707
708
709
710
711 func parseCppContention(r *bytes.Buffer) (*Profile, error) {
712 p := &Profile{
713 PeriodType: &ValueType{Type: "contentions", Unit: "count"},
714 Period: 1,
715 SampleType: []*ValueType{
716 {Type: "contentions", Unit: "count"},
717 {Type: "delay", Unit: "nanoseconds"},
718 },
719 }
720
721 var cpuHz int64
722 var l string
723 var err error
724
725 const delimiter = '='
726 for {
727 l, err = r.ReadString('\n')
728 if err != nil {
729 if err != io.EOF {
730 return nil, err
731 }
732
733 if l == "" {
734 break
735 }
736 }
737 if isSpaceOrComment(l) {
738 continue
739 }
740
741 if l = strings.TrimSpace(l); l == "" {
742 continue
743 }
744
745 if strings.HasPrefix(l, "---") {
746 break
747 }
748
749 index := strings.IndexByte(l, delimiter)
750 if index < 0 {
751 break
752 }
753 key := l[:index]
754 val := l[index+1:]
755
756 key, val = strings.TrimSpace(key), strings.TrimSpace(val)
757 var err error
758 switch key {
759 case "cycles/second":
760 if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil {
761 return nil, errUnrecognized
762 }
763 case "sampling period":
764 if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil {
765 return nil, errUnrecognized
766 }
767 case "ms since reset":
768 ms, err := strconv.ParseInt(val, 0, 64)
769 if err != nil {
770 return nil, errUnrecognized
771 }
772 p.DurationNanos = ms * 1000 * 1000
773 case "format":
774
775 return nil, errUnrecognized
776 case "resolution":
777
778 return nil, errUnrecognized
779 case "discarded samples":
780 default:
781 return nil, errUnrecognized
782 }
783 }
784
785 locs := make(map[uint64]*Location)
786 for {
787 if !isSpaceOrComment(l) {
788 if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") {
789 break
790 }
791 value, addrs, err := parseContentionSample(l, p.Period, cpuHz)
792 if err != nil {
793 return nil, err
794 }
795 var sloc []*Location
796 for _, addr := range addrs {
797
798
799 addr--
800 loc := locs[addr]
801 if locs[addr] == nil {
802 loc = &Location{
803 Address: addr,
804 }
805 p.Location = append(p.Location, loc)
806 locs[addr] = loc
807 }
808 sloc = append(sloc, loc)
809 }
810 p.Sample = append(p.Sample, &Sample{
811 Value: value,
812 Location: sloc,
813 })
814 }
815
816 if l, err = r.ReadString('\n'); err != nil {
817 if err != io.EOF {
818 return nil, err
819 }
820 if l == "" {
821 break
822 }
823 }
824 }
825
826 if err = parseAdditionalSections(l, r, p); err != nil {
827 return nil, err
828 }
829
830 return p, nil
831 }
832
833
834
835 func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) {
836 sampleData := contentionSampleRE.FindStringSubmatch(line)
837 if sampleData == nil {
838 return value, addrs, errUnrecognized
839 }
840
841 v1, err := strconv.ParseInt(sampleData[1], 10, 64)
842 if err != nil {
843 return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
844 }
845 v2, err := strconv.ParseInt(sampleData[2], 10, 64)
846 if err != nil {
847 return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
848 }
849
850
851
852
853 if period > 0 {
854 if cpuHz > 0 {
855 cpuGHz := float64(cpuHz) / 1e9
856 v1 = int64(float64(v1) * float64(period) / cpuGHz)
857 }
858 v2 = v2 * period
859 }
860
861 value = []int64{v2, v1}
862 addrs = parseHexAddresses(sampleData[3])
863
864 return value, addrs, nil
865 }
866
867
868 func parseThread(b []byte) (*Profile, error) {
869 r := bytes.NewBuffer(b)
870
871 var line string
872 var err error
873 for {
874
875 line, err = r.ReadString('\n')
876 if err != nil {
877 return nil, err
878 }
879 if !isSpaceOrComment(line) {
880 break
881 }
882 }
883
884 if m := threadzStartRE.FindStringSubmatch(line); m != nil {
885
886 for {
887 line, err = r.ReadString('\n')
888 if err != nil {
889 if err != io.EOF {
890 return nil, err
891 }
892
893 if line == "" {
894 break
895 }
896 }
897 if sectionTrigger(line) != unrecognizedSection || line[0] == '-' {
898 break
899 }
900 }
901 } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
902 return nil, errUnrecognized
903 }
904
905 p := &Profile{
906 SampleType: []*ValueType{{Type: "thread", Unit: "count"}},
907 PeriodType: &ValueType{Type: "thread", Unit: "count"},
908 Period: 1,
909 }
910
911 locs := make(map[uint64]*Location)
912
913 for sectionTrigger(line) == unrecognizedSection {
914 if strings.HasPrefix(line, "---- no stack trace for") {
915 line = ""
916 break
917 }
918 if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
919 return nil, errUnrecognized
920 }
921
922 var addrs []uint64
923 line, addrs, err = parseThreadSample(r)
924 if err != nil {
925 return nil, errUnrecognized
926 }
927 if len(addrs) == 0 {
928
929 if len(p.Sample) > 0 {
930 s := p.Sample[len(p.Sample)-1]
931 s.Value[0]++
932 }
933 continue
934 }
935
936 var sloc []*Location
937 for _, addr := range addrs {
938
939
940 addr--
941 loc := locs[addr]
942 if locs[addr] == nil {
943 loc = &Location{
944 Address: addr,
945 }
946 p.Location = append(p.Location, loc)
947 locs[addr] = loc
948 }
949 sloc = append(sloc, loc)
950 }
951
952 p.Sample = append(p.Sample, &Sample{
953 Value: []int64{1},
954 Location: sloc,
955 })
956 }
957
958 if err = parseAdditionalSections(line, r, p); err != nil {
959 return nil, err
960 }
961
962 return p, nil
963 }
964
965
966
967
968 func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) {
969 var l string
970 sameAsPrevious := false
971 for {
972 if l, err = b.ReadString('\n'); err != nil {
973 if err != io.EOF {
974 return "", nil, err
975 }
976 if l == "" {
977 break
978 }
979 }
980 if l = strings.TrimSpace(l); l == "" {
981 continue
982 }
983
984 if strings.HasPrefix(l, "---") {
985 break
986 }
987 if strings.Contains(l, "same as previous thread") {
988 sameAsPrevious = true
989 continue
990 }
991
992 addrs = append(addrs, parseHexAddresses(l)...)
993 }
994
995 if sameAsPrevious {
996 return l, nil, nil
997 }
998 return l, addrs, nil
999 }
1000
1001
1002
1003 func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) {
1004 for {
1005 if sectionTrigger(l) == memoryMapSection {
1006 break
1007 }
1008
1009 if l, err := b.ReadString('\n'); err != nil {
1010 if err != io.EOF {
1011 return err
1012 }
1013 if l == "" {
1014 break
1015 }
1016 }
1017 }
1018 return p.ParseMemoryMap(b)
1019 }
1020
1021
1022
1023
1024 func (p *Profile) ParseMemoryMap(rd io.Reader) error {
1025 b := bufio.NewReader(rd)
1026
1027 var attrs []string
1028 var r *strings.Replacer
1029 const delimiter = '='
1030 for {
1031 l, err := b.ReadString('\n')
1032 if err != nil {
1033 if err != io.EOF {
1034 return err
1035 }
1036 if l == "" {
1037 break
1038 }
1039 }
1040 if l = strings.TrimSpace(l); l == "" {
1041 continue
1042 }
1043
1044 if r != nil {
1045 l = r.Replace(l)
1046 }
1047 m, err := parseMappingEntry(l)
1048 if err != nil {
1049 if err == errUnrecognized {
1050
1051
1052 idx := strings.IndexByte(l, delimiter)
1053 if idx >= 0 {
1054 attr := l[:idx]
1055 value := l[idx+1:]
1056 attrs = append(attrs, "$"+strings.TrimSpace(attr), strings.TrimSpace(value))
1057 r = strings.NewReplacer(attrs...)
1058 }
1059
1060 continue
1061 }
1062 return err
1063 }
1064 if m == nil || (m.File == "" && len(p.Mapping) != 0) {
1065
1066
1067
1068 continue
1069 }
1070 if len(p.Mapping) == 1 && p.Mapping[0].File == "" {
1071
1072 p.Mapping[0].File = m.File
1073 continue
1074 }
1075 p.Mapping = append(p.Mapping, m)
1076 }
1077 p.remapLocationIDs()
1078 p.remapFunctionIDs()
1079 p.remapMappingIDs()
1080 return nil
1081 }
1082
1083 func parseMappingEntry(l string) (*Mapping, error) {
1084 mapping := &Mapping{}
1085 var err error
1086 if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 {
1087 if !strings.Contains(me[3], "x") {
1088
1089 return nil, nil
1090 }
1091 if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil {
1092 return nil, errUnrecognized
1093 }
1094 if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil {
1095 return nil, errUnrecognized
1096 }
1097 if me[4] != "" {
1098 if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil {
1099 return nil, errUnrecognized
1100 }
1101 }
1102 mapping.File = me[8]
1103 return mapping, nil
1104 }
1105
1106 if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 {
1107 if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil {
1108 return nil, errUnrecognized
1109 }
1110 if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil {
1111 return nil, errUnrecognized
1112 }
1113 mapping.File = me[3]
1114 if me[5] != "" {
1115 if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil {
1116 return nil, errUnrecognized
1117 }
1118 }
1119 return mapping, nil
1120 }
1121
1122 return nil, errUnrecognized
1123 }
1124
1125 type sectionType int
1126
1127 const (
1128 unrecognizedSection sectionType = iota
1129 memoryMapSection
1130 )
1131
1132 var memoryMapTriggers = []string{
1133 "--- Memory map: ---",
1134 "MAPPED_LIBRARIES:",
1135 }
1136
1137 func sectionTrigger(line string) sectionType {
1138 for _, trigger := range memoryMapTriggers {
1139 if strings.Contains(line, trigger) {
1140 return memoryMapSection
1141 }
1142 }
1143 return unrecognizedSection
1144 }
1145
1146 func (p *Profile) addLegacyFrameInfo() {
1147 switch {
1148 case isProfileType(p, heapzSampleTypes) ||
1149 isProfileType(p, heapzInUseSampleTypes) ||
1150 isProfileType(p, heapzAllocSampleTypes):
1151 p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr
1152 case isProfileType(p, contentionzSampleTypes):
1153 p.DropFrames, p.KeepFrames = lockRxStr, ""
1154 default:
1155 p.DropFrames, p.KeepFrames = cpuProfilerRxStr, ""
1156 }
1157 }
1158
1159 var heapzSampleTypes = []string{"allocations", "size"}
1160 var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"}
1161 var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"}
1162 var contentionzSampleTypes = []string{"contentions", "delay"}
1163
1164 func isProfileType(p *Profile, t []string) bool {
1165 st := p.SampleType
1166 if len(st) != len(t) {
1167 return false
1168 }
1169
1170 for i := range st {
1171 if st[i].Type != t[i] {
1172 return false
1173 }
1174 }
1175 return true
1176 }
1177
1178 var allocRxStr = strings.Join([]string{
1179
1180 `calloc`,
1181 `cfree`,
1182 `malloc`,
1183 `free`,
1184 `memalign`,
1185 `do_memalign`,
1186 `(__)?posix_memalign`,
1187 `pvalloc`,
1188 `valloc`,
1189 `realloc`,
1190
1191
1192 `tcmalloc::.*`,
1193 `tc_calloc`,
1194 `tc_cfree`,
1195 `tc_malloc`,
1196 `tc_free`,
1197 `tc_memalign`,
1198 `tc_posix_memalign`,
1199 `tc_pvalloc`,
1200 `tc_valloc`,
1201 `tc_realloc`,
1202 `tc_new`,
1203 `tc_delete`,
1204 `tc_newarray`,
1205 `tc_deletearray`,
1206 `tc_new_nothrow`,
1207 `tc_newarray_nothrow`,
1208
1209
1210 `malloc_zone_malloc`,
1211 `malloc_zone_calloc`,
1212 `malloc_zone_valloc`,
1213 `malloc_zone_realloc`,
1214 `malloc_zone_memalign`,
1215 `malloc_zone_free`,
1216
1217
1218 `runtime\..*`,
1219
1220
1221 `BaseArena::.*`,
1222 `(::)?do_malloc_no_errno`,
1223 `(::)?do_malloc_pages`,
1224 `(::)?do_malloc`,
1225 `DoSampledAllocation`,
1226 `MallocedMemBlock::MallocedMemBlock`,
1227 `_M_allocate`,
1228 `__builtin_(vec_)?delete`,
1229 `__builtin_(vec_)?new`,
1230 `__gnu_cxx::new_allocator::allocate`,
1231 `__libc_malloc`,
1232 `__malloc_alloc_template::allocate`,
1233 `allocate`,
1234 `cpp_alloc`,
1235 `operator new(\[\])?`,
1236 `simple_alloc::allocate`,
1237 }, `|`)
1238
1239 var allocSkipRxStr = strings.Join([]string{
1240
1241
1242 `runtime\.panic`,
1243 `runtime\.reflectcall`,
1244 `runtime\.call[0-9]*`,
1245 }, `|`)
1246
1247 var cpuProfilerRxStr = strings.Join([]string{
1248 `ProfileData::Add`,
1249 `ProfileData::prof_handler`,
1250 `CpuProfiler::prof_handler`,
1251 `__pthread_sighandler`,
1252 `__restore`,
1253 }, `|`)
1254
1255 var lockRxStr = strings.Join([]string{
1256 `RecordLockProfileData`,
1257 `(base::)?RecordLockProfileData.*`,
1258 `(base::)?SubmitMutexProfileData.*`,
1259 `(base::)?SubmitSpinLockProfileData.*`,
1260 `(Mutex::)?AwaitCommon.*`,
1261 `(Mutex::)?Unlock.*`,
1262 `(Mutex::)?UnlockSlow.*`,
1263 `(Mutex::)?ReaderUnlock.*`,
1264 `(MutexLock::)?~MutexLock.*`,
1265 `(SpinLock::)?Unlock.*`,
1266 `(SpinLock::)?SlowUnlock.*`,
1267 `(SpinLockHolder::)?~SpinLockHolder.*`,
1268 }, `|`)
1269
View as plain text