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 BubbleAssocCurrentBubble = bubbleAssocCurrentBubble
BubbleAssocOtherBubble = bubbleAssocOtherBubble 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. // matches the cached contents.
const debugCheckCache = false const debugCheckCache = false
// If true, skip checking the cache entirely.
const skipCache = false
if off == 0 { if off == 0 {
return -1, 0 return -1, 0
} }
@ -991,7 +994,7 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint
var checkVal int32 var checkVal int32
var checkPC uintptr var checkPC uintptr
ck := pcvalueCacheKey(targetpc) ck := pcvalueCacheKey(targetpc)
{ if !skipCache {
mp := acquirem() mp := acquirem()
cache := &mp.pcvalueCache cache := &mp.pcvalueCache
// The cache can be used by the signal handler on this M. Avoid // 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.status = readgstatus(s.g) &^ _Gscan
ug.waitreason = s.g.waitreason ug.waitreason = s.g.waitreason
ug.inMarkAssist = s.g.inMarkAssist ug.inMarkAssist = s.g.inMarkAssist
ug.stackID = traceStack(0, gp, gen) ug.stackID = traceStack(0, gp, &trace.stackTab[gen%2])
} }
resumeG(s) resumeG(s)
casgstatus(me, _Gwaiting, _Grunning) 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 // It then returns a traceArg representing that stack which may be
// passed to write. // passed to write.
func (tl traceLocker) stack(skip int) traceArg { 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 // 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 // skip controls the number of leaf frames to omit in order to hide tracer internals
// from stack traces, see CL 5523. // from stack traces, see CL 5523.
// //
// Avoid calling this function directly. gen needs to be the current generation // Avoid calling this function directly. Prefer traceEventWriter.stack.
// that this stack trace is being written out for, which needs to be synchronized with func traceStack(skip int, gp *g, tab *traceStackTable) uint64 {
// generations moving forward. Prefer traceEventWriter.stack.
func traceStack(skip int, gp *g, gen uintptr) uint64 {
var pcBuf [tracev2.MaxFramesPerStack]uintptr var pcBuf [tracev2.MaxFramesPerStack]uintptr
// Figure out gp and mp for the backtrace. // 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 { if nstk > 0 && gp.goid == 1 {
nstk-- // skip runtime.main nstk-- // skip runtime.main
} }
id := trace.stackTab[gen%2].put(pcBuf[:nstk]) id := tab.put(pcBuf[:nstk])
return id 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
}