mirror of
https://github.com/golang/go
synced 2024-11-19 02:04:42 -07:00
runtime/pprof: symbolize proto profiles
When generating pprof profiles in proto format, symbolize the profiles. Change-Id: I2471ed7f919483e5828868306418a63e41aff5c5 Reviewed-on: https://go-review.googlesource.com/34192 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
936749efb0
commit
cbef450df7
@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@ -91,6 +92,7 @@ func TranslateCPUProfile(b []byte, startTime time.Time) (*profile.Profile, error
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
symbolize(p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@ -103,3 +105,73 @@ func addMappings(p *profile.Profile) error {
|
||||
defer f.Close()
|
||||
return p.ParseMemoryMap(f)
|
||||
}
|
||||
|
||||
type function interface {
|
||||
Name() string
|
||||
FileLine(pc uintptr) (string, int)
|
||||
}
|
||||
|
||||
// funcForPC is a wrapper for runtime.FuncForPC. Defined as var for testing.
|
||||
var funcForPC = func(pc uintptr) function {
|
||||
if f := runtime.FuncForPC(pc); f != nil {
|
||||
return f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func symbolize(p *profile.Profile) {
|
||||
fns := profileFunctionMap{}
|
||||
for _, l := range p.Location {
|
||||
pc := uintptr(l.Address)
|
||||
f := funcForPC(pc)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
file, lineno := f.FileLine(pc)
|
||||
l.Line = []profile.Line{
|
||||
{
|
||||
Function: fns.findOrAddFunction(f.Name(), file, p),
|
||||
Line: int64(lineno),
|
||||
},
|
||||
}
|
||||
}
|
||||
// Trim runtime functions. Always hide runtime.goexit. Other runtime
|
||||
// functions are only hidden for heapz when they appear at the beginning.
|
||||
isHeapz := p.PeriodType != nil && p.PeriodType.Type == "space"
|
||||
for _, s := range p.Sample {
|
||||
show := !isHeapz
|
||||
var i int
|
||||
for _, l := range s.Location {
|
||||
if len(l.Line) > 0 && l.Line[0].Function != nil {
|
||||
name := l.Line[0].Function.Name
|
||||
if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
show = true
|
||||
s.Location[i] = l
|
||||
i++
|
||||
}
|
||||
s.Location = s.Location[:i]
|
||||
}
|
||||
}
|
||||
|
||||
type profileFunctionMap map[profile.Function]*profile.Function
|
||||
|
||||
func (fns profileFunctionMap) findOrAddFunction(name, filename string, p *profile.Profile) *profile.Function {
|
||||
f := profile.Function{
|
||||
Name: name,
|
||||
SystemName: name,
|
||||
Filename: filename,
|
||||
}
|
||||
if fp := fns[f]; fp != nil {
|
||||
return fp
|
||||
}
|
||||
fp := new(profile.Function)
|
||||
fns[f] = fp
|
||||
|
||||
*fp = f
|
||||
fp.ID = uint64(len(p.Function) + 1)
|
||||
p.Function = append(p.Function, fp)
|
||||
return fp
|
||||
}
|
||||
|
@ -169,3 +169,100 @@ func TestTranslateCPUProfileWithSamples(t *testing.T) {
|
||||
getSampleAsString(p.Sample))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended.
|
||||
func TestRuntimeRunctionTrimming(t *testing.T) {
|
||||
fakeFuncMap := map[uintptr]*fakeFunc{
|
||||
0x10: &fakeFunc{"runtime.goexit", "runtime.go", 10},
|
||||
0x20: &fakeFunc{"runtime.other", "runtime.go", 20},
|
||||
0x30: &fakeFunc{"foo", "foo.go", 30},
|
||||
0x40: &fakeFunc{"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: testLoc,
|
||||
}
|
||||
testProfiles := make([]*profile.Profile, 2)
|
||||
testProfiles[0] = testProfile.Copy()
|
||||
testProfiles[1] = testProfile.Copy()
|
||||
// Test case for profilez.
|
||||
testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
|
||||
// Test case for heapz.
|
||||
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: 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: wantLoc,
|
||||
Function: wantFunc,
|
||||
},
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
symbolize(testProfiles[i])
|
||||
if !reflect.DeepEqual(testProfiles[i], wantProfiles[i]) {
|
||||
t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user