Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime/pprof: block/mutex profile stacks can sometimes miss leaf frames #69747

Open
nsrip-dd opened this issue Oct 2, 2024 · 3 comments
Open
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@nsrip-dd
Copy link
Contributor

nsrip-dd commented Oct 2, 2024

Go version

go version go1.23.1 darwin/arm64

Output of go env in your module/workspace:

n/a

What did you do?

Built this program with PGO and ran it:

package main

import (
	"os"
	"runtime"
	"runtime/pprof"
)

func main() {
	runtime.SetBlockProfileRate(1)
	ch := make(chan struct{})
	go func() {
		close(ch)
	}()
	<-ch
	pprof.Lookup("block").WriteTo(os.Stdout, 1)
}

What did you see happen?

If I build with PGO using a profile where runtime.blockevent is hot, then I see this output:

--- contention:
cycles/second=999458200
97416 1 @ 0x4cd357 0x438c4b 0x4709a1
#	0x4cd356	main.main+0x76		/home/junk/simple.go:17
#	0x438c4a	runtime.main+0x28a	/usr/local/go/src/runtime/proc.go:272
Example program which generates such a CPU profile:
package main

import (
	"os"
	"runtime"
	"runtime/pprof"
	"time"
	_ "unsafe"
)

//go:linkname blockevent runtime.blockevent
func blockevent(cycles int64, skip int)

func main() {
	runtime.SetBlockProfileRate(1)
	pprof.StartCPUProfile(os.Stdout)
	defer pprof.StopCPUProfile()
	now := time.Now()
	for time.Since(now) < 5*time.Second {
		for i := 0; i < 100000; i++ {
			blockevent(1, 1)
		}
	}
}

What did you expect to see?

I expected to see chanrecv1 in the root frame:

--- contention:
cycles/second=999008970
54083 1 @ 0x406312 0x4cd357 0x438c4b 0x4709a1
#	0x406311	runtime.chanrecv1+0x11	/usr/local/go/src/runtime/chan.go:489
#	0x4cd356	main.main+0x76		/home/junk/simple.go:17
#	0x438c4a	runtime.main+0x28a	/usr/local/go/src/runtime/proc.go:272

I see that if I run the program with frame pointer unwinding disabled (GODEBUG=tracefpunwindoff=1) or if I run it with Go 1.22.

The problem is that if runtime.saveblockevent is inlined into runtime.blockevent, then getfp() will return one of runtime.saveblockevent's callers frame pointers here, and runtime.fpTracebackPartialExpand will start frame pointer unwinding from the wrong frame, making the skip values incorrect. Depending on how much inlining there is, we might see even more missing frames.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Oct 2, 2024
@mknyszek mknyszek added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Oct 2, 2024
@mknyszek mknyszek added this to the Backlog milestone Oct 2, 2024
@mknyszek
Copy link
Contributor

mknyszek commented Oct 2, 2024

In triage, we don't think it's super important to show chanrecv1 here (the line in the user code should already point to the channel operation), but we understand the general concern about maintaining parity with older versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
Development

No branches or pull requests

4 participants