// 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) } } }