runtime/pprof: fix goroutine leak profile tests for noopt

The goroutine leak profile tests currently rely on a function being
inlined, which results in a slightly different representation in the
pprof proto. This function is not inlined on the noopt builder.

Disable inlining of the one function which could be inlined to align but
the regular and noopt builder versions of this test. We're not
interested in testing the inlining functionality of profiles with this
test, we care about certain stack frames appearing in a certain order.

This also simplifies a bunch of the checking in the test.

Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-noopt
Change-Id: I28902cc4c9fae32d1e3fa41b93b00c3be4d6074a
Reviewed-on: https://go-review.googlesource.com/c/go/+/720100
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Michael Anthony Knyszek 2025-11-13 00:09:05 +00:00 committed by Michael Knyszek
parent 4684a26c26
commit 625d8e9b9c

View file

@ -1569,6 +1569,9 @@ func containsCountsLabels(prof *profile.Profile, countLabels map[int64]map[strin
return true
}
// Inlining disabled to make identification simpler.
//
//go:noinline
func goroutineLeakExample() {
<-make(chan struct{})
panic("unreachable")
@ -1595,12 +1598,12 @@ func TestGoroutineLeakProfileConcurrency(t *testing.T) {
checkFrame := func(i int, j int, locations []*profile.Location, expectedFunctionName string) {
if len(locations) <= i {
t.Errorf("leaked goroutine stack locations out of range at %d of %d", i+1, len(locations))
t.Errorf("leaked goroutine stack locations: out of range index %d, length %d", i, len(locations))
return
}
location := locations[i]
if len(location.Line) <= j {
t.Errorf("leaked goroutine stack location lines out of range at %d of %d", j+1, len(location.Line))
t.Errorf("leaked goroutine stack location lines: out of range index %d, length %d", j, len(location.Line))
return
}
if location.Line[j].Function.Name != expectedFunctionName {
@ -1650,26 +1653,7 @@ func TestGoroutineLeakProfileConcurrency(t *testing.T) {
t.Errorf("expected %d leaked goroutines with specific stack configurations, but found %d", leakCount, pc)
return
}
switch len(locations) {
case 4:
// We expect a receive operation. This is the typical stack.
checkFrame(0, 0, locations, "runtime.gopark")
checkFrame(1, 0, locations, "runtime.chanrecv")
checkFrame(2, 0, locations, "runtime.chanrecv1")
switch len(locations[3].Line) {
case 2:
// Running `go func() { goroutineLeakExample() }()` will produce a stack with 2 lines.
// The anonymous function will have the call to goroutineLeakExample inlined.
checkFrame(3, 1, locations, "runtime/pprof.TestGoroutineLeakProfileConcurrency.func5")
fallthrough
case 1:
// Running `go goroutineLeakExample()` will produce a stack with 1 line.
checkFrame(3, 0, locations, "runtime/pprof.goroutineLeakExample")
default:
t.Errorf("leaked goroutine stack location expected 1 or 2 lines in the 4th location but found %d", len(locations[3].Line))
return
}
default:
if len(locations) < 4 || len(locations) > 5 {
message := fmt.Sprintf("leaked goroutine stack expected 4 or 5 locations but found %d", len(locations))
for _, location := range locations {
for _, line := range location.Line {
@ -1677,6 +1661,15 @@ func TestGoroutineLeakProfileConcurrency(t *testing.T) {
}
}
t.Errorf("%s", message)
return
}
// We expect a receive operation. This is the typical stack.
checkFrame(0, 0, locations, "runtime.gopark")
checkFrame(1, 0, locations, "runtime.chanrecv")
checkFrame(2, 0, locations, "runtime.chanrecv1")
checkFrame(3, 0, locations, "runtime/pprof.goroutineLeakExample")
if len(locations) == 5 {
checkFrame(4, 0, locations, "runtime/pprof.TestGoroutineLeakProfileConcurrency.func5")
}
}
}