github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/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 "io/ioutil" 11 "reflect" 12 "runtime" 13 "runtime/pprof/internal/profile" 14 "testing" 15 ) 16 17 // translateCPUProfile parses binary CPU profiling stack trace data 18 // generated by runtime.CPUProfile() into a profile struct. 19 // This is only used for testing. Real conversions stream the 20 // data into the profileBuilder as it becomes available. 21 func translateCPUProfile(data []uint64) (*profile.Profile, error) { 22 var buf bytes.Buffer 23 b := newProfileBuilder(&buf) 24 if err := b.addCPUData(data, nil); err != nil { 25 return nil, err 26 } 27 b.build() 28 return profile.Parse(&buf) 29 } 30 31 // fmtJSON returns a pretty-printed JSON form for x. 32 // It works reasonbly well for printing protocol-buffer 33 // data structures like profile.Profile. 34 func fmtJSON(x interface{}) string { 35 js, _ := json.MarshalIndent(x, "", "\t") 36 return string(js) 37 } 38 39 func TestConvertCPUProfileEmpty(t *testing.T) { 40 // A test server with mock cpu profile data. 41 var buf bytes.Buffer 42 43 b := []uint64{3, 0, 2000} // empty profile with 2ms sample period 44 p, err := translateCPUProfile(b) 45 if err != nil { 46 t.Fatalf("translateCPUProfile: %v", err) 47 } 48 if err := p.Write(&buf); err != nil { 49 t.Fatalf("writing profile: %v", err) 50 } 51 52 p, err = profile.Parse(&buf) 53 if err != nil { 54 t.Fatalf("profile.Parse: %v", err) 55 } 56 57 // Expected PeriodType and SampleType. 58 periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 59 sampleType := []*profile.ValueType{ 60 {Type: "samples", Unit: "count"}, 61 {Type: "cpu", Unit: "nanoseconds"}, 62 } 63 64 checkProfile(t, p, 2000*1000, periodType, sampleType, nil) 65 } 66 67 func f1() { f1() } 68 func f2() { f2() } 69 70 // testPCs returns two PCs and two corresponding memory mappings 71 // to use in test profiles. 72 func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) { 73 switch runtime.GOOS { 74 case "linux", "android", "netbsd": 75 // Figure out two addresses from /proc/self/maps. 76 mmap, err := ioutil.ReadFile("/proc/self/maps") 77 if err != nil { 78 t.Fatal(err) 79 } 80 mprof := &profile.Profile{} 81 if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil { 82 t.Fatalf("parsing /proc/self/maps: %v", err) 83 } 84 if len(mprof.Mapping) < 2 { 85 // It is possible for a binary to only have 1 executable 86 // region of memory. 87 t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping)) 88 } 89 addr1 = mprof.Mapping[0].Start 90 map1 = mprof.Mapping[0] 91 map1.BuildID, _ = elfBuildID(map1.File) 92 addr2 = mprof.Mapping[1].Start 93 map2 = mprof.Mapping[1] 94 map2.BuildID, _ = elfBuildID(map2.File) 95 default: 96 addr1 = uint64(funcPC(f1)) 97 addr2 = uint64(funcPC(f2)) 98 } 99 return 100 } 101 102 func TestConvertCPUProfile(t *testing.T) { 103 addr1, addr2, map1, map2 := testPCs(t) 104 105 b := []uint64{ 106 3, 0, 2000, // periodMs = 2000 107 5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1 108 5, 0, 40, uint64(addr2), uint64(addr2 + 2), // 40 samples in addr2 109 5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1 110 } 111 p, err := translateCPUProfile(b) 112 if err != nil { 113 t.Fatalf("translating profile: %v", err) 114 } 115 period := int64(2000 * 1000) 116 periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 117 sampleType := []*profile.ValueType{ 118 {Type: "samples", Unit: "count"}, 119 {Type: "cpu", Unit: "nanoseconds"}, 120 } 121 samples := []*profile.Sample{ 122 {Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{ 123 {ID: 1, Mapping: map1, Address: addr1}, 124 {ID: 2, Mapping: map1, Address: addr1 + 1}, 125 }}, 126 {Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{ 127 {ID: 3, Mapping: map2, Address: addr2}, 128 {ID: 4, Mapping: map2, Address: addr2 + 1}, 129 }}, 130 } 131 checkProfile(t, p, period, periodType, sampleType, samples) 132 } 133 134 func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample) { 135 if p.Period != period { 136 t.Fatalf("p.Period = %d, want %d", p.Period, period) 137 } 138 if !reflect.DeepEqual(p.PeriodType, periodType) { 139 t.Fatalf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType)) 140 } 141 if !reflect.DeepEqual(p.SampleType, sampleType) { 142 t.Fatalf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType)) 143 } 144 // Clear line info since it is not in the expected samples. 145 // If we used f1 and f2 above, then the samples will have line info. 146 for _, s := range p.Sample { 147 for _, l := range s.Location { 148 l.Line = nil 149 } 150 } 151 if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields 152 if len(p.Sample) == len(samples) { 153 for i := range p.Sample { 154 if !reflect.DeepEqual(p.Sample[i], samples[i]) { 155 t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i])) 156 } 157 } 158 if t.Failed() { 159 t.FailNow() 160 } 161 } 162 t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples)) 163 } 164 } 165 166 type fakeFunc struct { 167 name string 168 file string 169 lineno int 170 } 171 172 func (f *fakeFunc) Name() string { 173 return f.name 174 } 175 func (f *fakeFunc) FileLine(uintptr) (string, int) { 176 return f.file, f.lineno 177 } 178 179 /* 180 // TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended. 181 func TestRuntimeRunctionTrimming(t *testing.T) { 182 fakeFuncMap := map[uintptr]*fakeFunc{ 183 0x10: &fakeFunc{"runtime.goexit", "runtime.go", 10}, 184 0x20: &fakeFunc{"runtime.other", "runtime.go", 20}, 185 0x30: &fakeFunc{"foo", "foo.go", 30}, 186 0x40: &fakeFunc{"bar", "bar.go", 40}, 187 } 188 backupFuncForPC := funcForPC 189 funcForPC = func(pc uintptr) function { 190 return fakeFuncMap[pc] 191 } 192 defer func() { 193 funcForPC = backupFuncForPC 194 }() 195 testLoc := []*profile.Location{ 196 {ID: 1, Address: 0x10}, 197 {ID: 2, Address: 0x20}, 198 {ID: 3, Address: 0x30}, 199 {ID: 4, Address: 0x40}, 200 } 201 testProfile := &profile.Profile{ 202 Sample: []*profile.Sample{ 203 {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}}, 204 {Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}}, 205 {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}}, 206 {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}}, 207 {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}}, 208 }, 209 Location: testLoc, 210 } 211 testProfiles := make([]*profile.Profile, 2) 212 testProfiles[0] = testProfile.Copy() 213 testProfiles[1] = testProfile.Copy() 214 // Test case for profilez. 215 testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 216 // Test case for heapz. 217 testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"} 218 wantFunc := []*profile.Function{ 219 {ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"}, 220 {ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"}, 221 {ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"}, 222 {ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"}, 223 } 224 wantLoc := []*profile.Location{ 225 {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, 226 {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, 227 {ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}}, 228 {ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}}, 229 } 230 wantProfiles := []*profile.Profile{ 231 { 232 PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, 233 Sample: []*profile.Sample{ 234 {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, 235 {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, 236 {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, 237 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 238 {Location: []*profile.Location{wantLoc[1], wantLoc[3]}}, 239 }, 240 Location: wantLoc, 241 Function: wantFunc, 242 }, 243 { 244 PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"}, 245 Sample: []*profile.Sample{ 246 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 247 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 248 {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, 249 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 250 {Location: []*profile.Location{wantLoc[3]}}, 251 }, 252 Location: wantLoc, 253 Function: wantFunc, 254 }, 255 } 256 for i := 0; i < 2; i++ { 257 symbolize(testProfiles[i]) 258 if !reflect.DeepEqual(testProfiles[i], wantProfiles[i]) { 259 t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i]) 260 } 261 } 262 } 263 */