github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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 "fmt" 10 "internal/pprof/profile" 11 "io/ioutil" 12 "reflect" 13 "runtime" 14 "testing" 15 "time" 16 "unsafe" 17 ) 18 19 // Helper function to initialize empty cpu profile with sampling period provided. 20 func createEmptyProfileWithPeriod(t *testing.T, periodMs uint64) bytes.Buffer { 21 // Mock the sample header produced by cpu profiler. Write a sample 22 // period of 2000 microseconds, followed by no samples. 23 buf := new(bytes.Buffer) 24 // Profile header is as follows: 25 // The first, third and fifth words are 0. The second word is 3. 26 // The fourth word is the period. 27 // EOD marker: 28 // The sixth word -- count is initialized to 0 above. 29 // The code below sets the seventh word -- nstk to 1 30 // The eighth word -- addr is initialized to 0 above. 31 words := []int{0, 3, 0, int(periodMs), 0, 0, 1, 0} 32 n := int(unsafe.Sizeof(0)) * len(words) 33 data := ((*[1 << 29]byte)(unsafe.Pointer(&words[0])))[:n:n] 34 if _, err := buf.Write(data); err != nil { 35 t.Fatalf("createEmptyProfileWithPeriod failed: %v", err) 36 } 37 return *buf 38 } 39 40 // Helper function to initialize cpu profile with two sample values. 41 func createProfileWithTwoSamples(t *testing.T, periodMs uintptr, count1 uintptr, count2 uintptr, 42 address1 uintptr, address2 uintptr) bytes.Buffer { 43 // Mock the sample header produced by cpu profiler. Write a sample 44 // period of 2000 microseconds, followed by no samples. 45 buf := new(bytes.Buffer) 46 words := []uintptr{0, 3, 0, uintptr(periodMs), 0, uintptr(count1), 2, 47 uintptr(address1), uintptr(address1 + 2), 48 uintptr(count2), 2, uintptr(address2), uintptr(address2 + 2), 49 0, 1, 0} 50 for _, n := range words { 51 var err error 52 switch unsafe.Sizeof(int(0)) { 53 case 8: 54 _, err = buf.Write((*[8]byte)(unsafe.Pointer(&n))[:8:8]) 55 case 4: 56 _, err = buf.Write((*[4]byte)(unsafe.Pointer(&n))[:4:4]) 57 } 58 if err != nil { 59 t.Fatalf("createProfileWithTwoSamples failed: %v", err) 60 } 61 } 62 return *buf 63 } 64 65 // Tests translateCPUProfile parses correct sampling period in an otherwise empty cpu profile. 66 func TestTranlateCPUProfileSamplingPeriod(t *testing.T) { 67 // A test server with mock cpu profile data. 68 var buf bytes.Buffer 69 70 startTime := time.Now() 71 b := createEmptyProfileWithPeriod(t, 2000) 72 p, err := translateCPUProfile(b.Bytes(), startTime) 73 if err != nil { 74 t.Fatalf("translate failed: %v", err) 75 } 76 if err := p.Write(&buf); err != nil { 77 t.Fatalf("write failed: %v", err) 78 } 79 80 p, err = profile.Parse(&buf) 81 if err != nil { 82 t.Fatalf("Could not parse Profile profile: %v", err) 83 } 84 85 // Expected PeriodType and SampleType. 86 expectedPeriodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 87 expectedSampleType := []*profile.ValueType{ 88 {Type: "samples", Unit: "count"}, 89 {Type: "cpu", Unit: "nanoseconds"}, 90 } 91 if p.Period != 2000*1000 || !reflect.DeepEqual(p.PeriodType, expectedPeriodType) || 92 !reflect.DeepEqual(p.SampleType, expectedSampleType) || p.Sample != nil { 93 t.Fatalf("Unexpected Profile fields") 94 } 95 } 96 97 func getSampleAsString(sample []*profile.Sample) string { 98 var str string 99 for _, x := range sample { 100 for _, y := range x.Location { 101 if y.Mapping != nil { 102 str += fmt.Sprintf("Mapping:%v\n", *y.Mapping) 103 } 104 str += fmt.Sprintf("Location:%v\n", y) 105 } 106 str += fmt.Sprintf("Sample:%v\n", *x) 107 } 108 return str 109 } 110 111 // Tests translateCPUProfile parses a cpu profile with sample values present. 112 func TestTranslateCPUProfileWithSamples(t *testing.T) { 113 if runtime.GOOS != "linux" { 114 t.Skip("test requires a system with /proc/self/maps") 115 } 116 // Figure out two addresses from /proc/self/maps. 117 mmap, err := ioutil.ReadFile("/proc/self/maps") 118 if err != nil { 119 t.Fatal("Cannot read /proc/self/maps") 120 } 121 rd := bytes.NewReader(mmap) 122 mprof := &profile.Profile{} 123 if err = mprof.ParseMemoryMap(rd); err != nil { 124 t.Fatalf("Cannot parse /proc/self/maps") 125 } 126 if len(mprof.Mapping) < 2 { 127 // It is possible for a binary to only have 1 executable 128 // region of memory. 129 t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping)) 130 } 131 address1 := mprof.Mapping[0].Start 132 address2 := mprof.Mapping[1].Start 133 // A test server with mock cpu profile data. 134 135 startTime := time.Now() 136 b := createProfileWithTwoSamples(t, 2000, 20, 40, uintptr(address1), uintptr(address2)) 137 p, err := translateCPUProfile(b.Bytes(), startTime) 138 139 if err != nil { 140 t.Fatalf("Could not parse Profile profile: %v", err) 141 } 142 // Expected PeriodType, SampleType and Sample. 143 expectedPeriodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 144 expectedSampleType := []*profile.ValueType{ 145 {Type: "samples", Unit: "count"}, 146 {Type: "cpu", Unit: "nanoseconds"}, 147 } 148 expectedSample := []*profile.Sample{ 149 {Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{ 150 {ID: 1, Mapping: mprof.Mapping[0], Address: address1}, 151 {ID: 2, Mapping: mprof.Mapping[0], Address: address1 + 1}, 152 }}, 153 {Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{ 154 {ID: 3, Mapping: mprof.Mapping[1], Address: address2}, 155 {ID: 4, Mapping: mprof.Mapping[1], Address: address2 + 1}, 156 }}, 157 } 158 if p.Period != 2000*1000 { 159 t.Fatalf("Sampling periods do not match") 160 } 161 if !reflect.DeepEqual(p.PeriodType, expectedPeriodType) { 162 t.Fatalf("Period types do not match") 163 } 164 if !reflect.DeepEqual(p.SampleType, expectedSampleType) { 165 t.Fatalf("Sample types do not match") 166 } 167 if !reflect.DeepEqual(p.Sample, expectedSample) { 168 t.Fatalf("Samples do not match: Expected: %v, Got:%v", getSampleAsString(expectedSample), 169 getSampleAsString(p.Sample)) 170 } 171 } 172 173 type fakeFunc struct { 174 name string 175 file string 176 lineno int 177 } 178 179 func (f *fakeFunc) Name() string { 180 return f.name 181 } 182 func (f *fakeFunc) FileLine(_ uintptr) (string, int) { 183 return f.file, f.lineno 184 } 185 186 // TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended. 187 func TestRuntimeRunctionTrimming(t *testing.T) { 188 fakeFuncMap := map[uintptr]*fakeFunc{ 189 0x10: &fakeFunc{"runtime.goexit", "runtime.go", 10}, 190 0x20: &fakeFunc{"runtime.other", "runtime.go", 20}, 191 0x30: &fakeFunc{"foo", "foo.go", 30}, 192 0x40: &fakeFunc{"bar", "bar.go", 40}, 193 } 194 backupFuncForPC := funcForPC 195 funcForPC = func(pc uintptr) function { 196 return fakeFuncMap[pc] 197 } 198 defer func() { 199 funcForPC = backupFuncForPC 200 }() 201 testLoc := []*profile.Location{ 202 {ID: 1, Address: 0x10}, 203 {ID: 2, Address: 0x20}, 204 {ID: 3, Address: 0x30}, 205 {ID: 4, Address: 0x40}, 206 } 207 testProfile := &profile.Profile{ 208 Sample: []*profile.Sample{ 209 {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}}, 210 {Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}}, 211 {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}}, 212 {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}}, 213 {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}}, 214 }, 215 Location: testLoc, 216 } 217 testProfiles := make([]*profile.Profile, 2) 218 testProfiles[0] = testProfile.Copy() 219 testProfiles[1] = testProfile.Copy() 220 // Test case for profilez. 221 testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 222 // Test case for heapz. 223 testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"} 224 wantFunc := []*profile.Function{ 225 {ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"}, 226 {ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"}, 227 {ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"}, 228 {ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"}, 229 } 230 wantLoc := []*profile.Location{ 231 {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, 232 {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, 233 {ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}}, 234 {ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}}, 235 } 236 wantProfiles := []*profile.Profile{ 237 { 238 PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, 239 Sample: []*profile.Sample{ 240 {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, 241 {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, 242 {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, 243 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 244 {Location: []*profile.Location{wantLoc[1], wantLoc[3]}}, 245 }, 246 Location: wantLoc, 247 Function: wantFunc, 248 }, 249 { 250 PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"}, 251 Sample: []*profile.Sample{ 252 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 253 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 254 {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, 255 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 256 {Location: []*profile.Location{wantLoc[3]}}, 257 }, 258 Location: wantLoc, 259 Function: wantFunc, 260 }, 261 } 262 for i := 0; i < 2; i++ { 263 symbolize(testProfiles[i]) 264 if !reflect.DeepEqual(testProfiles[i], wantProfiles[i]) { 265 t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i]) 266 } 267 } 268 }