runtime: make traceStack testable and add a benchmark

Change-Id: Ide4daa5eee3fd4f3007d6ef23aa84b8916562c39
Reviewed-on: https://go-review.googlesource.com/c/go/+/684457
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Michael Anthony Knyszek 2025-06-27 16:40:43 +00:00 committed by Gopher Robot
parent 20978f46fd
commit 75b43f9a97
6 changed files with 65 additions and 8 deletions

View file

@ -1917,3 +1917,13 @@ const (
BubbleAssocCurrentBubble = bubbleAssocCurrentBubble
BubbleAssocOtherBubble = bubbleAssocOtherBubble
)
type TraceStackTable traceStackTable
func (t *TraceStackTable) Reset() {
t.tab.reset()
}
func TraceStack(gp *G, tab *TraceStackTable) {
traceStack(0, gp, (*traceStackTable)(tab))
}

View file

@ -981,6 +981,9 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint
// matches the cached contents.
const debugCheckCache = false
// If true, skip checking the cache entirely.
const skipCache = false
if off == 0 {
return -1, 0
}
@ -991,7 +994,7 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint
var checkVal int32
var checkPC uintptr
ck := pcvalueCacheKey(targetpc)
{
if !skipCache {
mp := acquirem()
cache := &mp.pcvalueCache
// The cache can be used by the signal handler on this M. Avoid

View file

@ -396,7 +396,7 @@ func traceAdvance(stopTrace bool) {
ug.status = readgstatus(s.g) &^ _Gscan
ug.waitreason = s.g.waitreason
ug.inMarkAssist = s.g.inMarkAssist
ug.stackID = traceStack(0, gp, gen)
ug.stackID = traceStack(0, gp, &trace.stackTab[gen%2])
}
resumeG(s)
casgstatus(me, _Gwaiting, _Grunning)

View file

@ -56,7 +56,7 @@ func (e traceEventWriter) event(ev tracev2.EventType, args ...traceArg) {
// It then returns a traceArg representing that stack which may be
// passed to write.
func (tl traceLocker) stack(skip int) traceArg {
return traceArg(traceStack(skip, nil, tl.gen))
return traceArg(traceStack(skip, nil, &trace.stackTab[tl.gen%2]))
}
// startPC takes a start PC for a goroutine and produces a unique

View file

@ -28,10 +28,8 @@ const (
// skip controls the number of leaf frames to omit in order to hide tracer internals
// from stack traces, see CL 5523.
//
// Avoid calling this function directly. gen needs to be the current generation
// that this stack trace is being written out for, which needs to be synchronized with
// generations moving forward. Prefer traceEventWriter.stack.
func traceStack(skip int, gp *g, gen uintptr) uint64 {
// Avoid calling this function directly. Prefer traceEventWriter.stack.
func traceStack(skip int, gp *g, tab *traceStackTable) uint64 {
var pcBuf [tracev2.MaxFramesPerStack]uintptr
// Figure out gp and mp for the backtrace.
@ -134,7 +132,7 @@ func traceStack(skip int, gp *g, gen uintptr) uint64 {
if nstk > 0 && gp.goid == 1 {
nstk-- // skip runtime.main
}
id := trace.stackTab[gen%2].put(pcBuf[:nstk])
id := tab.put(pcBuf[:nstk])
return id
}

View file

@ -0,0 +1,46 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"runtime"
"strconv"
"testing"
)
func BenchmarkTraceStack(b *testing.B) {
for _, stackDepth := range []int{1, 10, 100} {
b.Run("stackDepth="+strconv.Itoa(stackDepth), func(b *testing.B) {
benchmarkTraceStack(b, stackDepth)
})
}
}
func benchmarkTraceStack(b *testing.B, stackDepth int) {
var tab runtime.TraceStackTable
defer tab.Reset()
wait := make(chan struct{})
ready := make(chan struct{})
done := make(chan struct{})
var gp *runtime.G
go func() {
gp = runtime.Getg()
useStackAndCall(stackDepth, func() {
ready <- struct{}{}
<-wait
})
done <- struct{}{}
}()
<-ready
for b.Loop() {
runtime.TraceStack(gp, &tab)
}
// Clean up.
wait <- struct{}{}
<-done
}