| // Copyright 2017 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package profiler |
| |
| import ( |
| "bytes" |
| "testing" |
| |
| "cloud.google.com/go/internal/testutil" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "github.com/google/pprof/profile" |
| ) |
| |
| type fakeFunc struct { |
| name string |
| file string |
| lineno int |
| } |
| |
| func (f *fakeFunc) Name() string { |
| return f.name |
| } |
| func (f *fakeFunc) FileLine(_ uintptr) (string, int) { |
| return f.file, f.lineno |
| } |
| |
| var cmpOpt = cmpopts.IgnoreUnexported(profile.Profile{}, profile.Function{}, |
| profile.Line{}, profile.Location{}, profile.Sample{}, profile.ValueType{}) |
| |
| // TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended. |
| func TestRuntimeFunctionTrimming(t *testing.T) { |
| fakeFuncMap := map[uintptr]*fakeFunc{ |
| 0x10: {"runtime.goexit", "runtime.go", 10}, |
| 0x20: {"runtime.other", "runtime.go", 20}, |
| 0x30: {"foo", "foo.go", 30}, |
| 0x40: {"bar", "bar.go", 40}, |
| } |
| backupFuncForPC := funcForPC |
| funcForPC = func(pc uintptr) function { |
| return fakeFuncMap[pc] |
| } |
| defer func() { |
| funcForPC = backupFuncForPC |
| }() |
| testLoc := []*profile.Location{ |
| {ID: 1, Address: 0x10}, |
| {ID: 2, Address: 0x20}, |
| {ID: 3, Address: 0x30}, |
| {ID: 4, Address: 0x40}, |
| } |
| testProfile := &profile.Profile{ |
| Sample: []*profile.Sample{ |
| {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}}, |
| {Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}}, |
| {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}}, |
| {Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}}, |
| {Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}}, |
| {Location: []*profile.Location{testLoc[1], testLoc[0]}}, |
| }, |
| Location: testLoc, |
| } |
| testProfiles := make([]*profile.Profile, 2) |
| testProfiles[0] = testProfile.Copy() |
| testProfiles[1] = testProfile.Copy() |
| // Test case for CPU profile. |
| testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} |
| // Test case for heap profile. |
| testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"} |
| wantFunc := []*profile.Function{ |
| {ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"}, |
| {ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"}, |
| {ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"}, |
| {ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"}, |
| } |
| wantLoc := []*profile.Location{ |
| {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, |
| {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, |
| {ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}}, |
| {ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}}, |
| } |
| wantProfiles := []*profile.Profile{ |
| { |
| PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, |
| Sample: []*profile.Sample{ |
| {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, |
| {Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}}, |
| {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, |
| {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, |
| {Location: []*profile.Location{wantLoc[1], wantLoc[3]}}, |
| {Location: []*profile.Location{wantLoc[1]}}, |
| }, |
| Location: wantLoc, |
| Function: wantFunc, |
| }, |
| { |
| PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"}, |
| Sample: []*profile.Sample{ |
| {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, |
| {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, |
| {Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}}, |
| {Location: []*profile.Location{wantLoc[3], wantLoc[2]}}, |
| {Location: []*profile.Location{wantLoc[3]}}, |
| {Location: []*profile.Location{wantLoc[0]}}, |
| }, |
| Location: wantLoc, |
| Function: wantFunc, |
| }, |
| } |
| for i := 0; i < 2; i++ { |
| symbolize(testProfiles[i]) |
| if !testutil.Equal(testProfiles[i], wantProfiles[i], cmpOpt) { |
| t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i]) |
| } |
| } |
| } |
| |
| // TestParseAndSymbolize tests if parseAndSymbolize parses and symbolizes |
| // profiles as intended. |
| func TestParseAndSymbolize(t *testing.T) { |
| fakeFuncMap := map[uintptr]*fakeFunc{ |
| 0x10: {"foo", "foo.go", 10}, |
| 0x20: {"bar", "bar.go", 20}, |
| } |
| backupFuncForPC := funcForPC |
| funcForPC = func(pc uintptr) function { |
| return fakeFuncMap[pc] |
| } |
| defer func() { |
| funcForPC = backupFuncForPC |
| }() |
| |
| testLoc := []*profile.Location{ |
| {ID: 1, Address: 0x10}, |
| {ID: 2, Address: 0x20}, |
| } |
| testProfile := &profile.Profile{ |
| SampleType: []*profile.ValueType{ |
| {Type: "cpu", Unit: "nanoseconds"}, |
| }, |
| PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, |
| Sample: []*profile.Sample{ |
| {Location: []*profile.Location{testLoc[0], testLoc[1]}, Value: []int64{1}}, |
| {Location: []*profile.Location{testLoc[1]}, Value: []int64{1}}, |
| }, |
| Location: testLoc, |
| } |
| testProfiles := make([]*profile.Profile, 2) |
| testProfiles[0] = testProfile.Copy() |
| testProfiles[1] = testProfile.Copy() |
| |
| wantFunc := []*profile.Function{ |
| {ID: 1, Name: "foo", SystemName: "foo", Filename: "foo.go"}, |
| {ID: 2, Name: "bar", SystemName: "bar", Filename: "bar.go"}, |
| } |
| wantLoc := []*profile.Location{ |
| {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, |
| {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, |
| } |
| wantProfile := &profile.Profile{ |
| SampleType: []*profile.ValueType{ |
| {Type: "cpu", Unit: "nanoseconds"}, |
| }, |
| PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}, |
| Sample: []*profile.Sample{ |
| {Location: []*profile.Location{wantLoc[0], wantLoc[1]}, Value: []int64{1}}, |
| {Location: []*profile.Location{wantLoc[1]}, Value: []int64{1}}, |
| }, |
| Location: wantLoc, |
| Function: wantFunc, |
| } |
| |
| // Profile already symbolized. |
| testProfiles[1].Location = []*profile.Location{ |
| {ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}}, |
| {ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}}, |
| } |
| testProfiles[1].Function = []*profile.Function{ |
| {ID: 1, Name: "foo", SystemName: "foo", Filename: "foo.go"}, |
| {ID: 2, Name: "bar", SystemName: "bar", Filename: "bar.go"}, |
| } |
| for i := 0; i < 2; i++ { |
| var prof bytes.Buffer |
| testProfiles[i].Write(&prof) |
| |
| parseAndSymbolize(&prof) |
| gotProfile, err := profile.ParseData(prof.Bytes()) |
| if err != nil { |
| t.Errorf("parsing symbolized profile (testcase = %d) got err: %v, want no error", i, err) |
| } |
| if !testutil.Equal(gotProfile, wantProfile, cmpOpt) { |
| t.Errorf("incorrect symbolization (testcase = %d): got {%v}, want {%v}", i, gotProfile, wantProfile) |
| } |
| } |
| } |
| |
| func TestIsSymbolizedGoVersion(t *testing.T) { |
| for _, tc := range []struct { |
| input string |
| want bool |
| }{ |
| {"go1.9beta2", true}, |
| {"go1.9", true}, |
| {"go1.9.1", true}, |
| {"go1.10", true}, |
| {"go1.10.1", true}, |
| {"go2.0", true}, |
| {"go3.1", true}, |
| {"go1.8", false}, |
| {"go1.8.1", false}, |
| {"go1.7", false}, |
| {"devel ", false}, |
| } { |
| if got := isSymbolizedGoVersion(tc.input); got != tc.want { |
| t.Errorf("isSymbolizedGoVersion(%v) got %v, want %v", tc.input, got, tc.want) |
| } |
| } |
| } |