|
@@ -29,8 +29,8 @@ import (
|
29
|
29
|
)
|
30
|
30
|
|
31
|
31
|
var (
|
32
|
|
- countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`)
|
33
|
|
- countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`)
|
|
32
|
+ countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\z`)
|
|
33
|
+ countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\z`)
|
34
|
34
|
|
35
|
35
|
heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`)
|
36
|
36
|
heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`)
|
|
@@ -59,22 +59,14 @@ func isSpaceOrComment(line string) bool {
|
59
|
59
|
// parseGoCount parses a Go count profile (e.g., threadcreate or
|
60
|
60
|
// goroutine) and returns a new Profile.
|
61
|
61
|
func parseGoCount(b []byte) (*Profile, error) {
|
62
|
|
- r := bytes.NewBuffer(b)
|
63
|
|
-
|
64
|
|
- var line string
|
65
|
|
- var err error
|
66
|
|
- for {
|
67
|
|
- // Skip past comments and empty lines seeking a real header.
|
68
|
|
- line, err = r.ReadString('\n')
|
69
|
|
- if err != nil {
|
70
|
|
- return nil, err
|
71
|
|
- }
|
72
|
|
- if !isSpaceOrComment(line) {
|
73
|
|
- break
|
74
|
|
- }
|
|
62
|
+ s := bufio.NewScanner(bytes.NewBuffer(b))
|
|
63
|
+ // Skip comments at the beginning of the file.
|
|
64
|
+ for s.Scan() && isSpaceOrComment(s.Text()) {
|
75
|
65
|
}
|
76
|
|
-
|
77
|
|
- m := countStartRE.FindStringSubmatch(line)
|
|
66
|
+ if err := s.Err(); err != nil {
|
|
67
|
+ return nil, err
|
|
68
|
+ }
|
|
69
|
+ m := countStartRE.FindStringSubmatch(s.Text())
|
78
|
70
|
if m == nil {
|
79
|
71
|
return nil, errUnrecognized
|
80
|
72
|
}
|
|
@@ -85,14 +77,8 @@ func parseGoCount(b []byte) (*Profile, error) {
|
85
|
77
|
SampleType: []*ValueType{{Type: profileType, Unit: "count"}},
|
86
|
78
|
}
|
87
|
79
|
locations := make(map[uint64]*Location)
|
88
|
|
- for {
|
89
|
|
- line, err = r.ReadString('\n')
|
90
|
|
- if err != nil {
|
91
|
|
- if err == io.EOF {
|
92
|
|
- break
|
93
|
|
- }
|
94
|
|
- return nil, err
|
95
|
|
- }
|
|
80
|
+ for s.Scan() {
|
|
81
|
+ line := s.Text()
|
96
|
82
|
if isSpaceOrComment(line) {
|
97
|
83
|
continue
|
98
|
84
|
}
|
|
@@ -131,8 +117,11 @@ func parseGoCount(b []byte) (*Profile, error) {
|
131
|
117
|
Value: []int64{n},
|
132
|
118
|
})
|
133
|
119
|
}
|
|
120
|
+ if err := s.Err(); err != nil {
|
|
121
|
+ return nil, err
|
|
122
|
+ }
|
134
|
123
|
|
135
|
|
- if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil {
|
|
124
|
+ if err := parseAdditionalSections(s, p); err != nil {
|
136
|
125
|
return nil, err
|
137
|
126
|
}
|
138
|
127
|
return p, nil
|
|
@@ -455,26 +444,25 @@ func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust boo
|
455
|
444
|
// parseHeap parses a heapz legacy or a growthz profile and
|
456
|
445
|
// returns a newly populated Profile.
|
457
|
446
|
func parseHeap(b []byte) (p *Profile, err error) {
|
458
|
|
- r := bytes.NewBuffer(b)
|
459
|
|
- l, err := r.ReadString('\n')
|
460
|
|
- if err != nil {
|
461
|
|
- return nil, errUnrecognized
|
|
447
|
+ s := bufio.NewScanner(bytes.NewBuffer(b))
|
|
448
|
+ if !s.Scan() {
|
|
449
|
+ return nil, s.Err()
|
462
|
450
|
}
|
463
|
|
-
|
464
|
451
|
p = &Profile{}
|
465
|
452
|
|
466
|
453
|
sampling := ""
|
467
|
454
|
hasAlloc := false
|
468
|
455
|
|
|
456
|
+ line := s.Text()
|
469
|
457
|
p.PeriodType = &ValueType{Type: "space", Unit: "bytes"}
|
470
|
|
- if header := heapHeaderRE.FindStringSubmatch(l); header != nil {
|
471
|
|
- sampling, p.Period, hasAlloc, err = parseHeapHeader(l)
|
|
458
|
+ if header := heapHeaderRE.FindStringSubmatch(line); header != nil {
|
|
459
|
+ sampling, p.Period, hasAlloc, err = parseHeapHeader(line)
|
472
|
460
|
if err != nil {
|
473
|
461
|
return nil, err
|
474
|
462
|
}
|
475
|
|
- } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil {
|
|
463
|
+ } else if header = growthHeaderRE.FindStringSubmatch(line); header != nil {
|
476
|
464
|
p.Period = 1
|
477
|
|
- } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil {
|
|
465
|
+ } else if header = fragmentationHeaderRE.FindStringSubmatch(line); header != nil {
|
478
|
466
|
p.Period = 1
|
479
|
467
|
} else {
|
480
|
468
|
return nil, errUnrecognized
|
|
@@ -497,28 +485,18 @@ func parseHeap(b []byte) (p *Profile, err error) {
|
497
|
485
|
}
|
498
|
486
|
|
499
|
487
|
locs := make(map[uint64]*Location)
|
500
|
|
- for {
|
501
|
|
- l, err = r.ReadString('\n')
|
502
|
|
- if err != nil {
|
503
|
|
- if err != io.EOF {
|
504
|
|
- return nil, err
|
505
|
|
- }
|
|
488
|
+ for s.Scan() {
|
|
489
|
+ line := strings.TrimSpace(s.Text())
|
506
|
490
|
|
507
|
|
- if l == "" {
|
508
|
|
- break
|
509
|
|
- }
|
510
|
|
- }
|
511
|
|
-
|
512
|
|
- if isSpaceOrComment(l) {
|
|
491
|
+ if isSpaceOrComment(line) {
|
513
|
492
|
continue
|
514
|
493
|
}
|
515
|
|
- l = strings.TrimSpace(l)
|
516
|
494
|
|
517
|
|
- if sectionTrigger(l) != unrecognizedSection {
|
|
495
|
+ if sectionTrigger(line) != unrecognizedSection {
|
518
|
496
|
break
|
519
|
497
|
}
|
520
|
498
|
|
521
|
|
- value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling, hasAlloc)
|
|
499
|
+ value, blocksize, addrs, err := parseHeapSample(line, p.Period, sampling, hasAlloc)
|
522
|
500
|
if err != nil {
|
523
|
501
|
return nil, err
|
524
|
502
|
}
|
|
@@ -545,8 +523,10 @@ func parseHeap(b []byte) (p *Profile, err error) {
|
545
|
523
|
NumLabel: map[string][]int64{"bytes": {blocksize}},
|
546
|
524
|
})
|
547
|
525
|
}
|
548
|
|
-
|
549
|
|
- if err = parseAdditionalSections(l, r, p); err != nil {
|
|
526
|
+ if err := s.Err(); err != nil {
|
|
527
|
+ return nil, err
|
|
528
|
+ }
|
|
529
|
+ if err := parseAdditionalSections(s, p); err != nil {
|
550
|
530
|
return nil, err
|
551
|
531
|
}
|
552
|
532
|
return p, nil
|
|
@@ -678,13 +658,13 @@ func scaleHeapSample(count, size, rate int64) (int64, int64) {
|
678
|
658
|
// parseContention parses a contentionz profile and returns a newly
|
679
|
659
|
// populated Profile.
|
680
|
660
|
func parseContention(b []byte) (p *Profile, err error) {
|
681
|
|
- r := bytes.NewBuffer(b)
|
682
|
|
- l, err := r.ReadString('\n')
|
683
|
|
- if err != nil {
|
684
|
|
- return nil, errUnrecognized
|
|
661
|
+ s := bufio.NewScanner(bytes.NewBuffer(b))
|
|
662
|
+ if !s.Scan() {
|
|
663
|
+ return nil, s.Err()
|
685
|
664
|
}
|
|
665
|
+ line := s.Text()
|
686
|
666
|
|
687
|
|
- if !strings.HasPrefix(l, "--- contention") {
|
|
667
|
+ if !strings.HasPrefix(line, "--- contention") {
|
688
|
668
|
return nil, errUnrecognized
|
689
|
669
|
}
|
690
|
670
|
|
|
@@ -700,27 +680,18 @@ func parseContention(b []byte) (p *Profile, err error) {
|
700
|
680
|
var cpuHz int64
|
701
|
681
|
// Parse text of the form "attribute = value" before the samples.
|
702
|
682
|
const delimiter = "="
|
703
|
|
- for {
|
704
|
|
- l, err = r.ReadString('\n')
|
705
|
|
- if err != nil {
|
706
|
|
- if err != io.EOF {
|
707
|
|
- return nil, err
|
708
|
|
- }
|
709
|
|
-
|
710
|
|
- if l == "" {
|
711
|
|
- break
|
712
|
|
- }
|
713
|
|
- }
|
|
683
|
+ for s.Scan() {
|
|
684
|
+ line := s.Text()
|
714
|
685
|
|
715
|
|
- if l = strings.TrimSpace(l); l == "" {
|
|
686
|
+ if line = strings.TrimSpace(line); line == "" {
|
716
|
687
|
continue
|
717
|
688
|
}
|
718
|
689
|
|
719
|
|
- if strings.HasPrefix(l, "---") {
|
|
690
|
+ if strings.HasPrefix(line, "---") {
|
720
|
691
|
break
|
721
|
692
|
}
|
722
|
693
|
|
723
|
|
- attr := strings.SplitN(l, delimiter, 2)
|
|
694
|
+ attr := strings.SplitN(line, delimiter, 2)
|
724
|
695
|
if len(attr) != 2 {
|
725
|
696
|
break
|
726
|
697
|
}
|
|
@@ -752,13 +723,17 @@ func parseContention(b []byte) (p *Profile, err error) {
|
752
|
723
|
return nil, errUnrecognized
|
753
|
724
|
}
|
754
|
725
|
}
|
|
726
|
+ if err := s.Err(); err != nil {
|
|
727
|
+ return nil, err
|
|
728
|
+ }
|
755
|
729
|
|
756
|
730
|
locs := make(map[uint64]*Location)
|
757
|
731
|
for {
|
758
|
|
- if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") {
|
|
732
|
+ line := strings.TrimSpace(s.Text())
|
|
733
|
+ if strings.HasPrefix(line, "---") {
|
759
|
734
|
break
|
760
|
735
|
}
|
761
|
|
- value, addrs, err := parseContentionSample(l, p.Period, cpuHz)
|
|
736
|
+ value, addrs, err := parseContentionSample(line, p.Period, cpuHz)
|
762
|
737
|
if err != nil {
|
763
|
738
|
return nil, err
|
764
|
739
|
}
|
|
@@ -782,17 +757,15 @@ func parseContention(b []byte) (p *Profile, err error) {
|
782
|
757
|
Location: sloc,
|
783
|
758
|
})
|
784
|
759
|
|
785
|
|
- if l, err = r.ReadString('\n'); err != nil {
|
786
|
|
- if err != io.EOF {
|
787
|
|
- return nil, err
|
788
|
|
- }
|
789
|
|
- if l == "" {
|
790
|
|
- break
|
791
|
|
- }
|
|
760
|
+ if !s.Scan() {
|
|
761
|
+ break
|
792
|
762
|
}
|
793
|
763
|
}
|
|
764
|
+ if err := s.Err(); err != nil {
|
|
765
|
+ return nil, err
|
|
766
|
+ }
|
794
|
767
|
|
795
|
|
- if err = parseAdditionalSections(l, r, p); err != nil {
|
|
768
|
+ if err = parseAdditionalSections(s, p); err != nil {
|
796
|
769
|
return nil, err
|
797
|
770
|
}
|
798
|
771
|
|
|
@@ -835,35 +808,16 @@ func parseContentionSample(line string, period, cpuHz int64) (value []int64, add
|
835
|
808
|
|
836
|
809
|
// parseThread parses a Threadz profile and returns a new Profile.
|
837
|
810
|
func parseThread(b []byte) (*Profile, error) {
|
838
|
|
- r := bytes.NewBuffer(b)
|
839
|
|
-
|
840
|
|
- var line string
|
841
|
|
- var err error
|
842
|
|
- for {
|
843
|
|
- // Skip past comments and empty lines seeking a real header.
|
844
|
|
- line, err = r.ReadString('\n')
|
845
|
|
- if err != nil {
|
846
|
|
- return nil, err
|
847
|
|
- }
|
848
|
|
- if !isSpaceOrComment(line) {
|
849
|
|
- break
|
850
|
|
- }
|
|
811
|
+ s := bufio.NewScanner(bytes.NewBuffer(b))
|
|
812
|
+ // Skip past comments and empty lines seeking a real header.
|
|
813
|
+ for s.Scan() && isSpaceOrComment(s.Text()) {
|
851
|
814
|
}
|
852
|
815
|
|
|
816
|
+ line := s.Text()
|
853
|
817
|
if m := threadzStartRE.FindStringSubmatch(line); m != nil {
|
854
|
818
|
// Advance over initial comments until first stack trace.
|
855
|
|
- for {
|
856
|
|
- line, err = r.ReadString('\n')
|
857
|
|
- if err != nil {
|
858
|
|
- if err != io.EOF {
|
859
|
|
- return nil, err
|
860
|
|
- }
|
861
|
|
-
|
862
|
|
- if line == "" {
|
863
|
|
- break
|
864
|
|
- }
|
865
|
|
- }
|
866
|
|
- if sectionTrigger(line) != unrecognizedSection || line[0] == '-' {
|
|
819
|
+ for s.Scan() {
|
|
820
|
+ if line = s.Text(); sectionTrigger(line) != unrecognizedSection || strings.HasPrefix(line, "-") {
|
867
|
821
|
break
|
868
|
822
|
}
|
869
|
823
|
}
|
|
@@ -889,7 +843,8 @@ func parseThread(b []byte) (*Profile, error) {
|
889
|
843
|
}
|
890
|
844
|
|
891
|
845
|
var addrs []uint64
|
892
|
|
- line, addrs, err = parseThreadSample(r)
|
|
846
|
+ var err error
|
|
847
|
+ line, addrs, err = parseThreadSample(s)
|
893
|
848
|
if err != nil {
|
894
|
849
|
return nil, errUnrecognized
|
895
|
850
|
}
|
|
@@ -927,7 +882,7 @@ func parseThread(b []byte) (*Profile, error) {
|
927
|
882
|
})
|
928
|
883
|
}
|
929
|
884
|
|
930
|
|
- if err = parseAdditionalSections(line, r, p); err != nil {
|
|
885
|
+ if err := parseAdditionalSections(s, p); err != nil {
|
931
|
886
|
return nil, err
|
932
|
887
|
}
|
933
|
888
|
|
|
@@ -938,58 +893,43 @@ func parseThread(b []byte) (*Profile, error) {
|
938
|
893
|
// parseThreadSample parses a symbolized or unsymbolized stack trace.
|
939
|
894
|
// Returns the first line after the traceback, the sample (or nil if
|
940
|
895
|
// it hits a 'same-as-previous' marker) and an error.
|
941
|
|
-func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) {
|
942
|
|
- var l string
|
|
896
|
+func parseThreadSample(s *bufio.Scanner) (nextl string, addrs []uint64, err error) {
|
|
897
|
+ var line string
|
943
|
898
|
sameAsPrevious := false
|
944
|
|
- for {
|
945
|
|
- if l, err = b.ReadString('\n'); err != nil {
|
946
|
|
- if err != io.EOF {
|
947
|
|
- return "", nil, err
|
948
|
|
- }
|
949
|
|
- if l == "" {
|
950
|
|
- break
|
951
|
|
- }
|
952
|
|
- }
|
953
|
|
- if l = strings.TrimSpace(l); l == "" {
|
|
899
|
+ for s.Scan() {
|
|
900
|
+ line = strings.TrimSpace(s.Text())
|
|
901
|
+ if line == "" {
|
954
|
902
|
continue
|
955
|
903
|
}
|
956
|
904
|
|
957
|
|
- if strings.HasPrefix(l, "---") {
|
|
905
|
+ if strings.HasPrefix(line, "---") {
|
958
|
906
|
break
|
959
|
907
|
}
|
960
|
|
- if strings.Contains(l, "same as previous thread") {
|
|
908
|
+ if strings.Contains(line, "same as previous thread") {
|
961
|
909
|
sameAsPrevious = true
|
962
|
910
|
continue
|
963
|
911
|
}
|
964
|
912
|
|
965
|
|
- addrs = append(addrs, parseHexAddresses(l)...)
|
|
913
|
+ addrs = append(addrs, parseHexAddresses(line)...)
|
|
914
|
+ }
|
|
915
|
+ if s.Err() != nil {
|
|
916
|
+ return "", nil, s.Err()
|
966
|
917
|
}
|
967
|
|
-
|
968
|
918
|
if sameAsPrevious {
|
969
|
|
- return l, nil, nil
|
|
919
|
+ return line, nil, nil
|
970
|
920
|
}
|
971
|
|
- return l, addrs, nil
|
|
921
|
+ return line, addrs, nil
|
972
|
922
|
}
|
973
|
923
|
|
974
|
924
|
// parseAdditionalSections parses any additional sections in the
|
975
|
925
|
// profile, ignoring any unrecognized sections.
|
976
|
|
-func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) error {
|
977
|
|
- for {
|
978
|
|
- if sectionTrigger(l) == memoryMapSection {
|
979
|
|
- break
|
980
|
|
- }
|
981
|
|
- // Ignore any unrecognized sections.
|
982
|
|
- var err error
|
983
|
|
- if l, err = b.ReadString('\n'); err != nil {
|
984
|
|
- if err != io.EOF {
|
985
|
|
- return err
|
986
|
|
- }
|
987
|
|
- if l == "" {
|
988
|
|
- break
|
989
|
|
- }
|
990
|
|
- }
|
|
926
|
+func parseAdditionalSections(s *bufio.Scanner, p *Profile) error {
|
|
927
|
+ for sectionTrigger(s.Text()) != memoryMapSection && s.Scan() {
|
991
|
928
|
}
|
992
|
|
- return p.ParseMemoryMap(b)
|
|
929
|
+ if err := s.Err(); err != nil {
|
|
930
|
+ return err
|
|
931
|
+ }
|
|
932
|
+ return p.parseMemoryMapFromScanner(s)
|
993
|
933
|
}
|
994
|
934
|
|
995
|
935
|
// ParseProcMaps parses a memory map in the format of /proc/self/maps.
|
|
@@ -997,36 +937,31 @@ func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) error {
|
997
|
937
|
// associate locations to the corresponding mapping based on their
|
998
|
938
|
// address.
|
999
|
939
|
func ParseProcMaps(rd io.Reader) ([]*Mapping, error) {
|
1000
|
|
- var mapping []*Mapping
|
|
940
|
+ s := bufio.NewScanner(rd)
|
|
941
|
+ return parseProcMapsFromScanner(s)
|
|
942
|
+}
|
1001
|
943
|
|
1002
|
|
- b := bufio.NewReader(rd)
|
|
944
|
+func parseProcMapsFromScanner(s *bufio.Scanner) ([]*Mapping, error) {
|
|
945
|
+ var mapping []*Mapping
|
1003
|
946
|
|
1004
|
947
|
var attrs []string
|
1005
|
948
|
var r *strings.Replacer
|
1006
|
949
|
const delimiter = "="
|
1007
|
|
- for {
|
1008
|
|
- l, err := b.ReadString('\n')
|
1009
|
|
- if err != nil {
|
1010
|
|
- if err != io.EOF {
|
1011
|
|
- return nil, err
|
1012
|
|
- }
|
1013
|
|
- if l == "" {
|
1014
|
|
- break
|
1015
|
|
- }
|
1016
|
|
- }
|
1017
|
|
- if l = strings.TrimSpace(l); l == "" {
|
|
950
|
+ for s.Scan() {
|
|
951
|
+ line := strings.TrimSpace(s.Text())
|
|
952
|
+ if line == "" {
|
1018
|
953
|
continue
|
1019
|
954
|
}
|
1020
|
955
|
|
1021
|
956
|
if r != nil {
|
1022
|
|
- l = r.Replace(l)
|
|
957
|
+ line = r.Replace(line)
|
1023
|
958
|
}
|
1024
|
|
- m, err := parseMappingEntry(l)
|
|
959
|
+ m, err := parseMappingEntry(line)
|
1025
|
960
|
if err != nil {
|
1026
|
961
|
if err == errUnrecognized {
|
1027
|
962
|
// Recognize assignments of the form: attr=value, and replace
|
1028
|
963
|
// $attr with value on subsequent mappings.
|
1029
|
|
- if attr := strings.SplitN(l, delimiter, 2); len(attr) == 2 {
|
|
964
|
+ if attr := strings.SplitN(line, delimiter, 2); len(attr) == 2 {
|
1030
|
965
|
attrs = append(attrs, "$"+strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1]))
|
1031
|
966
|
r = strings.NewReplacer(attrs...)
|
1032
|
967
|
}
|
|
@@ -1040,6 +975,9 @@ func ParseProcMaps(rd io.Reader) ([]*Mapping, error) {
|
1040
|
975
|
}
|
1041
|
976
|
mapping = append(mapping, m)
|
1042
|
977
|
}
|
|
978
|
+ if err := s.Err(); err != nil {
|
|
979
|
+ return nil, err
|
|
980
|
+ }
|
1043
|
981
|
return mapping, nil
|
1044
|
982
|
}
|
1045
|
983
|
|
|
@@ -1047,7 +985,11 @@ func ParseProcMaps(rd io.Reader) ([]*Mapping, error) {
|
1047
|
985
|
// /proc/self/maps, and overrides the mappings in the current profile.
|
1048
|
986
|
// It renumbers the samples and locations in the profile correspondingly.
|
1049
|
987
|
func (p *Profile) ParseMemoryMap(rd io.Reader) error {
|
1050
|
|
- mapping, err := ParseProcMaps(rd)
|
|
988
|
+ return p.parseMemoryMapFromScanner(bufio.NewScanner(rd))
|
|
989
|
+}
|
|
990
|
+
|
|
991
|
+func (p *Profile) parseMemoryMapFromScanner(s *bufio.Scanner) error {
|
|
992
|
+ mapping, err := parseProcMapsFromScanner(s)
|
1051
|
993
|
if err != nil {
|
1052
|
994
|
return err
|
1053
|
995
|
}
|