}
func TestCPUProfile(t *testing.T) {
- testCPUProfile(t, []string{"runtime/pprof.cpuHog1"}, func(dur time.Duration) {
+ testCPUProfile(t, stackContains, []string{"runtime/pprof.cpuHog1"}, func(dur time.Duration) {
cpuHogger(cpuHog1, &salt1, dur)
})
}
func TestCPUProfileMultithreaded(t *testing.T) {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
- testCPUProfile(t, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, func(dur time.Duration) {
+ testCPUProfile(t, stackContains, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, func(dur time.Duration) {
c := make(chan int)
go func() {
cpuHogger(cpuHog1, &salt1, dur)
}
func TestCPUProfileInlining(t *testing.T) {
- testCPUProfile(t, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, func(dur time.Duration) {
+ testCPUProfile(t, stackContains, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, func(dur time.Duration) {
cpuHogger(inlinedCaller, &salt1, dur)
})
}
}
}
-func testCPUProfile(t *testing.T, need []string, f func(dur time.Duration)) {
+// testCPUProfile runs f under the CPU profiler, checking for some conditions specified by need,
+// as interpreted by matches.
+func testCPUProfile(t *testing.T, matches matchFunc, need []string, f func(dur time.Duration)) {
switch runtime.GOOS {
case "darwin":
switch runtime.GOARCH {
f(duration)
StopCPUProfile()
- if profileOk(t, need, prof, duration) {
+ if profileOk(t, need, matches, prof, duration) {
return
}
return false
}
-func profileOk(t *testing.T, need []string, prof bytes.Buffer, duration time.Duration) (ok bool) {
+// stackContains matches if a function named spec appears anywhere in the stack trace.
+func stackContains(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
+ for _, loc := range stk {
+ for _, line := range loc.Line {
+ if strings.Contains(line.Function.Name, spec) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+type matchFunc func(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool
+
+func profileOk(t *testing.T, need []string, matches matchFunc, prof bytes.Buffer, duration time.Duration) (ok bool) {
ok = true
// Check that profile is well formed and contains need.
fmt.Fprintf(&buf, "%d:", count)
fprintStack(&buf, stk)
samples += count
- for i, name := range need {
- if semi := strings.Index(name, ";"); semi > -1 {
- kv := strings.SplitN(name[semi+1:], "=", 2)
- if len(kv) != 2 || !contains(labels[kv[0]], kv[1]) {
- continue
- }
- name = name[:semi]
- }
- for _, loc := range stk {
- for _, line := range loc.Line {
- if strings.Contains(line.Function.Name, name) {
- have[i] += count
- }
- }
+ for i, spec := range need {
+ if matches(spec, count, stk, labels) {
+ have[i] += count
}
}
fmt.Fprintf(&buf, "\n")
// Test that profiling of division operations is okay, especially on ARM. See issue 6681.
func TestMathBigDivide(t *testing.T) {
- testCPUProfile(t, nil, func(duration time.Duration) {
+ testCPUProfile(t, nil, nil, func(duration time.Duration) {
t := time.After(duration)
pi := new(big.Int)
for {
})
}
+// stackContainsAll matches if all functions in spec (comma-separated) appear somewhere in the stack trace.
+func stackContainsAll(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
+ for _, f := range strings.Split(spec, ",") {
+ if !stackContains(f, count, stk, labels) {
+ return false
+ }
+ }
+ return true
+}
+
+func TestMorestack(t *testing.T) {
+ testCPUProfile(t, stackContainsAll, []string{"runtime.newstack,runtime/pprof.growstack"}, func(duration time.Duration) {
+ t := time.After(duration)
+ c := make(chan bool)
+ for {
+ go func() {
+ growstack1()
+ c <- true
+ }()
+ select {
+ case <-t:
+ return
+ case <-c:
+ }
+ }
+ })
+}
+
+//go:noinline
+func growstack1() {
+ growstack()
+}
+
+//go:noinline
+func growstack() {
+ var buf [8 << 10]byte
+ use(buf)
+}
+
+//go:noinline
+func use(x [8 << 10]byte) {}
+
func TestBlockProfile(t *testing.T) {
type TestCase struct {
name string
}
}
+// stackContainsLabeled takes a spec like funcname;key=value and matches if the stack has that key
+// and value and has funcname somewhere in the stack.
+func stackContainsLabeled(spec string, count uintptr, stk []*profile.Location, labels map[string][]string) bool {
+ semi := strings.Index(spec, ";")
+ if semi == -1 {
+ panic("no semicolon in key/value spec")
+ }
+ kv := strings.SplitN(spec[semi+1:], "=", 2)
+ if len(kv) != 2 {
+ panic("missing = in key/value spec")
+ }
+ if !contains(labels[kv[0]], kv[1]) {
+ return false
+ }
+ return stackContains(spec[:semi], count, stk, labels)
+}
+
func TestCPUProfileLabel(t *testing.T) {
- testCPUProfile(t, []string{"runtime/pprof.cpuHogger;key=value"}, func(dur time.Duration) {
+ testCPUProfile(t, stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, func(dur time.Duration) {
Do(context.Background(), Labels("key", "value"), func(context.Context) {
cpuHogger(cpuHog1, &salt1, dur)
})
// Test the race detector annotations for synchronization
// between settings labels and consuming them from the
// profile.
- testCPUProfile(t, []string{"runtime/pprof.cpuHogger;key=value"}, func(dur time.Duration) {
+ testCPUProfile(t, stackContainsLabeled, []string{"runtime/pprof.cpuHogger;key=value"}, func(dur time.Duration) {
start := time.Now()
var wg sync.WaitGroup
for time.Since(start) < dur {
// Found an actual function.
// Derive frame pointer and link register.
if frame.fp == 0 {
- // We want to jump over the systemstack switch. If we're running on the
- // g0, this systemstack is at the top of the stack.
- // if we're not on g0 or there's a no curg, then this is a regular call.
- sp := frame.sp
- if flags&_TraceJumpStack != 0 && f.funcID == funcID_systemstack && gp == gp.m.g0 && gp.m.curg != nil {
- sp = gp.m.curg.sched.sp
- frame.sp = sp
- cgoCtxt = gp.m.curg.cgoCtxt
+ // Jump over system stack transitions. If we're on g0 and there's a user
+ // goroutine, try to jump. Otherwise this is a regular call.
+ if flags&_TraceJumpStack != 0 && gp == gp.m.g0 && gp.m.curg != nil {
+ switch f.funcID {
+ case funcID_morestack:
+ // morestack does not return normally -- newstack()
+ // gogo's to curg.sched. Match that.
+ // This keeps morestack() from showing up in the backtrace,
+ // but that makes some sense since it'll never be returned
+ // to.
+ frame.pc = gp.m.curg.sched.pc
+ frame.fn = findfunc(frame.pc)
+ f = frame.fn
+ frame.sp = gp.m.curg.sched.sp
+ cgoCtxt = gp.m.curg.cgoCtxt
+ case funcID_systemstack:
+ // systemstack returns normally, so just follow the
+ // stack transition.
+ frame.sp = gp.m.curg.sched.sp
+ cgoCtxt = gp.m.curg.cgoCtxt
+ }
}
- frame.fp = sp + uintptr(funcspdelta(f, frame.pc, &cache))
+ frame.fp = frame.sp + uintptr(funcspdelta(f, frame.pc, &cache))
if !usesLR {
// On x86, call instruction pushes return PC before entering new function.
frame.fp += sys.RegSize