]> Cypherpunks repositories - gostls13.git/commitdiff
runtime/pprof: symbolize proto profiles
authorMichael Matloob <matloob@golang.org>
Thu, 8 Dec 2016 21:54:07 +0000 (16:54 -0500)
committerRuss Cox <rsc@golang.org>
Tue, 7 Feb 2017 14:03:13 +0000 (14:03 +0000)
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>
src/runtime/pprof/internal/protopprof/protopprof.go
src/runtime/pprof/internal/protopprof/protopprof_test.go

index 5d269c4f652ea84022282b705de005d6078c5a90..8a10716f0ac3ee193ce4a691d82ba9be368c27d5 100644 (file)
@@ -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
+}
index f1937b5bd06ca647571a3437fb33ecd99936cf72..33d19d85660519bd80301deb8811abd081e72fbc 100644 (file)
@@ -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])
+               }
+       }
+}