Commit c1ac70ff authored by Austin Clements's avatar Austin Clements

runtime/pprof: use symbol information already in profile in tests

Currently the pprof tests re-symbolize PCs in profiles, and do so in a
way that can't handle inlining. Proto profiles already contain full
symbol information, so this modifies the tests to use the symbol
information already present in the profile.

Change-Id: I63cd491de7197080fd158b1e4f782630f1bbbb56
Reviewed-on: https://go-review.googlesource.com/41255
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarMichael Matloob <matloob@golang.org>
parent 2133d63f
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"context" "context"
"fmt" "fmt"
"internal/testenv" "internal/testenv"
"io"
"math/big" "math/big"
"os" "os"
"os/exec" "os/exec"
...@@ -86,18 +87,14 @@ func TestCPUProfileMultithreaded(t *testing.T) { ...@@ -86,18 +87,14 @@ func TestCPUProfileMultithreaded(t *testing.T) {
}) })
} }
func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []uintptr, map[string][]string)) { func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Location, map[string][]string)) {
p, err := profile.Parse(bytes.NewReader(valBytes)) p, err := profile.Parse(bytes.NewReader(valBytes))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, sample := range p.Sample { for _, sample := range p.Sample {
count := uintptr(sample.Value[0]) count := uintptr(sample.Value[0])
stk := make([]uintptr, len(sample.Location)) f(count, sample.Location, sample.Label)
for i := range sample.Location {
stk[i] = uintptr(sample.Location[i].Address)
}
f(count, stk, sample.Label)
} }
} }
...@@ -181,26 +178,23 @@ func profileOk(t *testing.T, need []string, prof bytes.Buffer, duration time.Dur ...@@ -181,26 +178,23 @@ func profileOk(t *testing.T, need []string, prof bytes.Buffer, duration time.Dur
have := make([]uintptr, len(need)) have := make([]uintptr, len(need))
var samples uintptr var samples uintptr
var buf bytes.Buffer var buf bytes.Buffer
parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr, labels map[string][]string) { parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, labels map[string][]string) {
fmt.Fprintf(&buf, "%d:", count) fmt.Fprintf(&buf, "%d:", count)
fprintStack(&buf, stk)
samples += count samples += count
for _, pc := range stk { for i, name := range need {
fmt.Fprintf(&buf, " %#x", pc) if semi := strings.Index(name, ";"); semi > -1 {
f := runtime.FuncForPC(pc) kv := strings.SplitN(name[semi+1:], "=", 2)
if f == nil { if len(kv) != 2 || !contains(labels[kv[0]], kv[1]) {
continue continue
}
name = name[:semi]
} }
fmt.Fprintf(&buf, "(%s)", f.Name()) for _, loc := range stk {
for i, name := range need { for _, line := range loc.Line {
if semi := strings.Index(name, ";"); semi > -1 { if strings.Contains(line.Function.Name, name) {
kv := strings.SplitN(name[semi+1:], "=", 2) have[i] += count
if len(kv) != 2 || !contains(labels[kv[0]], kv[1]) {
continue
} }
name = name[:semi]
}
if strings.Contains(f.Name(), name) {
have[i] += count
} }
} }
} }
...@@ -313,36 +307,43 @@ func TestGoroutineSwitch(t *testing.T) { ...@@ -313,36 +307,43 @@ func TestGoroutineSwitch(t *testing.T) {
// Read profile to look for entries for runtime.gogo with an attempt at a traceback. // Read profile to look for entries for runtime.gogo with an attempt at a traceback.
// The special entry // The special entry
parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr, _ map[string][]string) { parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, _ map[string][]string) {
// An entry with two frames with 'System' in its top frame // An entry with two frames with 'System' in its top frame
// exists to record a PC without a traceback. Those are okay. // exists to record a PC without a traceback. Those are okay.
if len(stk) == 2 { if len(stk) == 2 {
f := runtime.FuncForPC(stk[1]) name := stk[1].Line[0].Function.Name
if f != nil && (f.Name() == "runtime._System" || f.Name() == "runtime._ExternalCode" || f.Name() == "runtime._GC") { if name == "runtime._System" || name == "runtime._ExternalCode" || name == "runtime._GC" {
return return
} }
} }
// Otherwise, should not see runtime.gogo. // Otherwise, should not see runtime.gogo.
// The place we'd see it would be the inner most frame. // The place we'd see it would be the inner most frame.
f := runtime.FuncForPC(stk[0]) name := stk[0].Line[0].Function.Name
if f != nil && f.Name() == "runtime.gogo" { if name == "runtime.gogo" {
var buf bytes.Buffer var buf bytes.Buffer
for _, pc := range stk { fprintStack(&buf, stk)
f := runtime.FuncForPC(pc)
if f == nil {
fmt.Fprintf(&buf, "%#x ?:0\n", pc)
} else {
file, line := f.FileLine(pc)
fmt.Fprintf(&buf, "%#x %s:%d\n", pc, file, line)
}
}
t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String()) t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String())
} }
}) })
} }
} }
func fprintStack(w io.Writer, stk []*profile.Location) {
for _, loc := range stk {
fmt.Fprintf(w, " %#x", loc.Address)
fmt.Fprintf(w, " (")
for i, line := range loc.Line {
if i > 0 {
fmt.Fprintf(w, " ")
}
fmt.Fprintf(w, "%s:%d", line.Function.Name, line.Line)
}
fmt.Fprintf(w, ")")
}
fmt.Fprintf(w, "\n")
}
// Test that profiling of division operations is okay, especially on ARM. See issue 6681. // Test that profiling of division operations is okay, especially on ARM. See issue 6681.
func TestMathBigDivide(t *testing.T) { func TestMathBigDivide(t *testing.T) {
testCPUProfile(t, nil, func(duration time.Duration) { testCPUProfile(t, nil, func(duration time.Duration) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment