github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/src/runtime/pprof/proto_test.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package pprof 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "reflect" 13 "runtime" 14 "runtime/pprof/internal/profile" 15 "strings" 16 "testing" 17 ) 18 19 // translateCPUProfile parses binary CPU profiling stack trace data 20 // generated by runtime.CPUProfile() into a profile struct. 21 // This is only used for testing. Real conversions stream the 22 // data into the profileBuilder as it becomes available. 23 func translateCPUProfile(data []uint64) (*profile.Profile, error) { 24 var buf bytes.Buffer 25 b := newProfileBuilder(&buf) 26 if err := b.addCPUData(data, nil); err != nil { 27 return nil, err 28 } 29 b.build() 30 return profile.Parse(&buf) 31 } 32 33 // fmtJSON returns a pretty-printed JSON form for x. 34 // It works reasonbly well for printing protocol-buffer 35 // data structures like profile.Profile. 36 func fmtJSON(x interface{}) string { 37 js, _ := json.MarshalIndent(x, "", "\t") 38 return string(js) 39 } 40 41 func TestConvertCPUProfileEmpty(t *testing.T) { 42 // A test server with mock cpu profile data. 43 var buf bytes.Buffer 44 45 b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period) 46 p, err := translateCPUProfile(b) 47 if err != nil { 48 t.Fatalf("translateCPUProfile: %v", err) 49 } 50 if err := p.Write(&buf); err != nil { 51 t.Fatalf("writing profile: %v", err) 52 } 53 54 p, err = profile.Parse(&buf) 55 if err != nil { 56 t.Fatalf("profile.Parse: %v", err) 57 } 58 59 // Expected PeriodType and SampleType. 60 periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 61 sampleType := []*profile.ValueType{ 62 {Type: "samples", Unit: "count"}, 63 {Type: "cpu", Unit: "nanoseconds"}, 64 } 65 66 checkProfile(t, p, 2000*1000, periodType, sampleType, nil) 67 } 68 69 func f1() { f1() } 70 func f2() { f2() } 71 72 // testPCs returns two PCs and two corresponding memory mappings 73 // to use in test profiles. 74 func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) { 75 switch runtime.GOOS { 76 case "linux", "android", "netbsd": 77 // Figure out two addresses from /proc/self/maps. 78 mmap, err := ioutil.ReadFile("/proc/self/maps") 79 if err != nil { 80 t.Fatal(err) 81 } 82 mprof := &profile.Profile{} 83 if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil { 84 t.Fatalf("parsing /proc/self/maps: %v", err) 85 } 86 if len(mprof.Mapping) < 2 { 87 // It is possible for a binary to only have 1 executable 88 // region of memory. 89 t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping)) 90 } 91 addr1 = mprof.Mapping[0].Start 92 map1 = mprof.Mapping[0] 93 map1.BuildID, _ = elfBuildID(map1.File) 94 addr2 = mprof.Mapping[1].Start 95 map2 = mprof.Mapping[1] 96 map2.BuildID, _ = elfBuildID(map2.File) 97 default: 98 addr1 = uint64(funcPC(f1)) 99 addr2 = uint64(funcPC(f2)) 100 } 101 return 102 } 103 104 func TestConvertCPUProfile(t *testing.T) { 105 addr1, addr2, map1, map2 := testPCs(t) 106 107 b := []uint64{ 108 3, 0, 500, // hz = 500 109 5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1 110 5, 0, 40, uint64(addr2), uint64(addr2 + 2), // 40 samples in addr2 111 5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1 112 } 113 p, err := translateCPUProfile(b) 114 if err != nil { 115 t.Fatalf("translating profile: %v", err) 116 } 117 period := int64(2000 * 1000) 118 periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 119 sampleType := []*profile.ValueType{ 120 {Type: "samples", Unit: "count"}, 121 {Type: "cpu", Unit: "nanoseconds"}, 122 } 123 samples := []*profile.Sample{ 124 {Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{ 125 {ID: 1, Mapping: map1, Address: addr1}, 126 {ID: 2, Mapping: map1, Address: addr1 + 1}, 127 }}, 128 {Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{ 129 {ID: 3, Mapping: map2, Address: addr2}, 130 {ID: 4, Mapping: map2, Address: addr2 + 1}, 131 }}, 132 } 133 checkProfile(t, p, period, periodType, sampleType, samples) 134 } 135 136 func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample) { 137 if p.Period != period { 138 t.Fatalf("p.Period = %d, want %d", p.Period, period) 139 } 140 if !reflect.DeepEqual(p.PeriodType, periodType) { 141 t.Fatalf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType)) 142 } 143 if !reflect.DeepEqual(p.SampleType, sampleType) { 144 t.Fatalf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType)) 145 } 146 // Clear line info since it is not in the expected samples. 147 // If we used f1 and f2 above, then the samples will have line info. 148 for _, s := range p.Sample { 149 for _, l := range s.Location { 150 l.Line = nil 151 } 152 } 153 if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields 154 if len(p.Sample) == len(samples) { 155 for i := range p.Sample { 156 if !reflect.DeepEqual(p.Sample[i], samples[i]) { 157 t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i])) 158 } 159 } 160 if t.Failed() { 161 t.FailNow() 162 } 163 } 164 t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples)) 165 } 166 } 167 168 var profSelfMapsTests = ` 169 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat 170 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat 171 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat 172 014ab000-014cc000 rw-p 00000000 00:00 0 [heap] 173 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive 174 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 175 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 176 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 177 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 178 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 179 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 180 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 181 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 182 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 183 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 184 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 185 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack] 186 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso] 187 ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0 [vsyscall] 188 -> 189 00400000 0040b000 00000000 /bin/cat 190 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so 191 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so 192 7ffc34343000 7ffc34345000 00000000 [vdso] 193 ffffffffff600000 ffffffffff601000 00000090 [vsyscall] 194 195 00400000-07000000 r-xp 00000000 00:00 0 196 07000000-07093000 r-xp 06c00000 00:2e 536754 /path/to/gobench_server_main 197 07093000-0722d000 rw-p 06c92000 00:2e 536754 /path/to/gobench_server_main 198 0722d000-07b21000 rw-p 00000000 00:00 0 199 c000000000-c000036000 rw-p 00000000 00:00 0 200 -> 201 07000000 07093000 06c00000 /path/to/gobench_server_main 202 ` 203 204 func TestProcSelfMaps(t *testing.T) { 205 for tx, tt := range strings.Split(profSelfMapsTests, "\n\n") { 206 i := strings.Index(tt, "->\n") 207 if i < 0 { 208 t.Fatal("malformed test case") 209 } 210 in, out := tt[:i], tt[i+len("->\n"):] 211 if len(out) > 0 && out[len(out)-1] != '\n' { 212 out += "\n" 213 } 214 var buf bytes.Buffer 215 parseProcSelfMaps([]byte(in), func(lo, hi, offset uint64, file, buildID string) { 216 fmt.Fprintf(&buf, "%08x %08x %08x %s\n", lo, hi, offset, file) 217 }) 218 if buf.String() != out { 219 t.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx, buf.String(), out, buf.String(), out) 220 } 221 } 222 }