1// Copyright 2017 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package profiler 16 17import ( 18 "bytes" 19 "testing" 20 21 "cloud.google.com/go/internal/testutil" 22 "github.com/google/go-cmp/cmp/cmpopts" 23 "github.com/google/pprof/profile" 24) 25 26type fakeFunc struct { 27 name string 28 file string 29 lineno int 30} 31 32func (f *fakeFunc) Name() string { 33 return f.name 34} 35func (f *fakeFunc) FileLine(_ uintptr) (string, int) { 36 return f.file, f.lineno 37} 38 39var cmpOpt = cmpopts.IgnoreUnexported(profile.Profile{}, profile.Function{}, 40 profile.Line{}, profile.Location{}, profile.Sample{}, profile.ValueType{}) 41 42// TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended. 43func TestRuntimeFunctionTrimming(t *testing.T) { 44 fakeFuncMap := map[uintptr]*fakeFunc{ 45 0x10: {"runtime.goexit", "runtime.go", 10}, 46 0x20: {"runtime.other", "runtime.go", 20}, 47 0x30: {"foo", "foo.go", 30}, 48 0x40: {"bar", "bar.go", 40}, 49 } 50 backupFuncForPC := funcForPC 51 funcForPC = func(pc uintptr) function { 52 return fakeFuncMap[pc] 53 } 54 defer func() { 55 funcForPC = backupFuncForPC 56 }() 57 testLoc := []*profile.Location{ 58 {ID: 1, Address: 0x10}, 59 {ID: 2, Address: 0x20}, 60 {ID: 3, Address: 0x30}, 61 {ID: 4, Address: 0x40}, 62 } 63 testProfile := &profile.Profile{ 64 Sample: []*profile.Sample{ 65 {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}}, 66 {Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}}, 67 {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}}, 68 {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}}, 69 {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}}, 70 {Location: []*profile.Location{testLoc[1], testLoc[0]}}, 71 }, 72 Location: testLoc, 73 } 74 testProfiles := make([]*profile.Profile, 2) 75 testProfiles[0] = testProfile.Copy() 76 testProfiles[1] = testProfile.Copy() 77 // Test case for CPU profile. 78 testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} 79 // Test case for heap profile. 80 testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"} 81 wantFunc := []*profile.Function{ 82 {ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"}, 83 {ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"}, 84 {ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"}, 85 {ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"}, 86 } 87 wantLoc := []*profile.Location{ 88 {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, 89 {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, 90 {ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}}, 91 {ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}}, 92 } 93 wantProfiles := []*profile.Profile{ 94 { 95 PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, 96 Sample: []*profile.Sample{ 97 {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, 98 {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, 99 {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, 100 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 101 {Location: []*profile.Location{wantLoc[1], wantLoc[3]}}, 102 {Location: []*profile.Location{wantLoc[1]}}, 103 }, 104 Location: wantLoc, 105 Function: wantFunc, 106 }, 107 { 108 PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"}, 109 Sample: []*profile.Sample{ 110 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 111 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 112 {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, 113 {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, 114 {Location: []*profile.Location{wantLoc[3]}}, 115 {Location: []*profile.Location{wantLoc[0]}}, 116 }, 117 Location: wantLoc, 118 Function: wantFunc, 119 }, 120 } 121 for i := 0; i < 2; i++ { 122 symbolize(testProfiles[i]) 123 if !testutil.Equal(testProfiles[i], wantProfiles[i], cmpOpt) { 124 t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i]) 125 } 126 } 127} 128 129// TestParseAndSymbolize tests if parseAndSymbolize parses and symbolizes 130// profiles as intended. 131func TestParseAndSymbolize(t *testing.T) { 132 fakeFuncMap := map[uintptr]*fakeFunc{ 133 0x10: {"foo", "foo.go", 10}, 134 0x20: {"bar", "bar.go", 20}, 135 } 136 backupFuncForPC := funcForPC 137 funcForPC = func(pc uintptr) function { 138 return fakeFuncMap[pc] 139 } 140 defer func() { 141 funcForPC = backupFuncForPC 142 }() 143 144 testLoc := []*profile.Location{ 145 {ID: 1, Address: 0x10}, 146 {ID: 2, Address: 0x20}, 147 } 148 testProfile := &profile.Profile{ 149 SampleType: []*profile.ValueType{ 150 {Type: "cpu", Unit: "nanoseconds"}, 151 }, 152 PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, 153 Sample: []*profile.Sample{ 154 {Location: []*profile.Location{testLoc[0], testLoc[1]}, Value: []int64{1}}, 155 {Location: []*profile.Location{testLoc[1]}, Value: []int64{1}}, 156 }, 157 Location: testLoc, 158 } 159 testProfiles := make([]*profile.Profile, 2) 160 testProfiles[0] = testProfile.Copy() 161 testProfiles[1] = testProfile.Copy() 162 163 wantFunc := []*profile.Function{ 164 {ID: 1, Name: "foo", SystemName: "foo", Filename: "foo.go"}, 165 {ID: 2, Name: "bar", SystemName: "bar", Filename: "bar.go"}, 166 } 167 wantLoc := []*profile.Location{ 168 {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, 169 {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, 170 } 171 wantProfile := &profile.Profile{ 172 SampleType: []*profile.ValueType{ 173 {Type: "cpu", Unit: "nanoseconds"}, 174 }, 175 PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, 176 Sample: []*profile.Sample{ 177 {Location: []*profile.Location{wantLoc[0], wantLoc[1]}, Value: []int64{1}}, 178 {Location: []*profile.Location{wantLoc[1]}, Value: []int64{1}}, 179 }, 180 Location: wantLoc, 181 Function: wantFunc, 182 } 183 184 // Profile already symbolized. 185 testProfiles[1].Location = []*profile.Location{ 186 {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, 187 {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, 188 } 189 testProfiles[1].Function = []*profile.Function{ 190 {ID: 1, Name: "foo", SystemName: "foo", Filename: "foo.go"}, 191 {ID: 2, Name: "bar", SystemName: "bar", Filename: "bar.go"}, 192 } 193 for i := 0; i < 2; i++ { 194 var prof bytes.Buffer 195 testProfiles[i].Write(&prof) 196 197 parseAndSymbolize(&prof) 198 gotProfile, err := profile.ParseData(prof.Bytes()) 199 if err != nil { 200 t.Errorf("parsing symbolized profile (testcase = %d) got err: %v, want no error", i, err) 201 } 202 if !testutil.Equal(gotProfile, wantProfile, cmpOpt) { 203 t.Errorf("incorrect symbolization (testcase = %d): got {%v}, want {%v}", i, gotProfile, wantProfile) 204 } 205 } 206} 207 208func TestIsSymbolizedGoVersion(t *testing.T) { 209 for _, tc := range []struct { 210 input string 211 want bool 212 }{ 213 {"go1.9beta2", true}, 214 {"go1.9", true}, 215 {"go1.9.1", true}, 216 {"go1.10", true}, 217 {"go1.10.1", true}, 218 {"go2.0", true}, 219 {"go3.1", true}, 220 {"go1.8", false}, 221 {"go1.8.1", false}, 222 {"go1.7", false}, 223 {"devel ", false}, 224 } { 225 if got := isSymbolizedGoVersion(tc.input); got != tc.want { 226 t.Errorf("isSymbolizedGoVersion(%v) got %v, want %v", tc.input, got, tc.want) 227 } 228 } 229} 230