github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/src/runtime/pprof/internal/protopprof/protopprof_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 protopprof 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 }