Skip to content

Commit c02036b

Browse files
felixgepull[bot]
authored andcommitted
runtime: make profstackdepth a GODEBUG option
Allow users to decrease the profiling stack depth back to 32 in case they experience any problems with the new default of 128. Users may also use this option to increase the depth up to 1024. Change-Id: Ieaab2513024915a223239278dd97a6e161dde1cf Reviewed-on: https://go-review.googlesource.com/c/go/+/581917 Reviewed-by: Austin Clements <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent cd139ae commit c02036b

File tree

5 files changed

+46
-26
lines changed

5 files changed

+46
-26
lines changed

src/runtime/extern.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ It is a comma-separated list of name=val pairs setting these named variables:
142142
When set to 0 memory profiling is disabled. Refer to the description of
143143
MemProfileRate for the default value.
144144
145+
profstackdepth: profstackdepth=128 (the default) will set the maximum stack
146+
depth used by all pprof profilers except for the CPU profiler to 128 frames.
147+
Stack traces that exceed this limit will be truncated to the limit starting
148+
from the leaf frame. Setting profstackdepth to any value above 1024 will
149+
silently default to 1024. Future versions of Go may remove this limitation
150+
and extend profstackdepth to apply to the CPU profiler and execution tracer.
151+
145152
pagetrace: setting pagetrace=/path/to/file will write out a trace of page events
146153
that can be viewed, analyzed, and visualized using the x/debug/cmd/pagetrace tool.
147154
Build your program with GOEXPERIMENT=pagetrace to enable this functionality. Do not

src/runtime/mprof.go

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,6 @@ const (
4040
// size of bucket hash table
4141
buckHashSize = 179999
4242

43-
// maxStack is the max depth of stack to record in bucket.
44-
// Note that it's only used internally as a guard against
45-
// wildly out-of-bounds slicing of the PCs that come after
46-
// a bucket struct, and it could increase in the future.
47-
// The term "1" accounts for the first stack entry being
48-
// taken up by a "skip" sentinel value for profilers which
49-
// defer inline frame expansion until the profile is reported.
50-
// The term "maxSkip" is for frame pointer unwinding, where we
51-
// want to end up with maxLogicalStack frames but will discard
52-
// some "physical" frames to account for skipping.
53-
maxStack = 1 + maxSkip + maxLogicalStack
54-
55-
// maxLogicalStack is the maximum stack size of a call stack
56-
// to encode in a profile. This counts "logical" frames, which
57-
// includes inlined frames. We may record more than this many
58-
// "physical" frames when using frame pointer unwinding to account
59-
// for deferred handling of skipping frames & inline expansion.
60-
maxLogicalStack = 128
6143
// maxSkip is to account for deferred inline expansion
6244
// when using frame pointer unwinding. We record the stack
6345
// with "physical" frame pointers but handle skipping "logical"
@@ -67,6 +49,11 @@ const (
6749
// This should be at least as large as the largest skip value
6850
// used for profiling; otherwise stacks may be truncated inconsistently
6951
maxSkip = 5
52+
53+
// maxProfStackDepth is the highest valid value for debug.profstackdepth.
54+
// It's used for the bucket.stk func.
55+
// TODO(fg): can we get rid of this?
56+
maxProfStackDepth = 1024
7057
)
7158

7259
type bucketType int
@@ -254,10 +241,11 @@ func newBucket(typ bucketType, nstk int) *bucket {
254241
return b
255242
}
256243

257-
// stk returns the slice in b holding the stack.
244+
// stk returns the slice in b holding the stack. The caller can asssume that the
245+
// backing array is immutable.
258246
func (b *bucket) stk() []uintptr {
259-
stk := (*[maxStack]uintptr)(add(unsafe.Pointer(b), unsafe.Sizeof(*b)))
260-
if b.nstk > maxStack {
247+
stk := (*[maxProfStackDepth]uintptr)(add(unsafe.Pointer(b), unsafe.Sizeof(*b)))
248+
if b.nstk > maxProfStackDepth {
261249
// prove that slicing works; otherwise a failure requires a P
262250
throw("bad profile stack count")
263251
}
@@ -455,7 +443,7 @@ func mProf_Malloc(mp *m, p unsafe.Pointer, size uintptr) {
455443
}
456444
// Only use the part of mp.profStack we need and ignore the extra space
457445
// reserved for delayed inline expansion with frame pointer unwinding.
458-
nstk := callers(4, mp.profStack[:maxLogicalStack])
446+
nstk := callers(4, mp.profStack[:debug.profstackdepth])
459447
index := (mProfCycle.read() + 2) % uint32(len(memRecord{}.future))
460448

461449
b := stkbucket(memProfile, size, mp.profStack[:nstk], true)
@@ -542,12 +530,18 @@ func blocksampled(cycles, rate int64) bool {
542530
// skip should be positive if this event is recorded from the current stack
543531
// (e.g. when this is not called from a system stack)
544532
func saveblockevent(cycles, rate int64, skip int, which bucketType) {
533+
if debug.profstackdepth == 0 {
534+
// profstackdepth is set to 0 by the user, so mp.profStack is nil and we
535+
// can't record a stack trace.
536+
return
537+
}
545538
if skip > maxSkip {
546539
print("requested skip=", skip)
547540
throw("invalid skip value")
548541
}
549542
gp := getg()
550543
mp := acquirem() // we must not be preempted while accessing profstack
544+
551545
nstk := 1
552546
if tracefpunwindoff() || gp.m.hasCgoOnStack() {
553547
mp.profStack[0] = logicalStackSentinel
@@ -736,6 +730,12 @@ func (prof *mLockProfile) recordUnlock(l *mutex) {
736730
}
737731

738732
func (prof *mLockProfile) captureStack() {
733+
if debug.profstackdepth == 0 {
734+
// profstackdepth is set to 0 by the user, so mp.profStack is nil and we
735+
// can't record a stack trace.
736+
return
737+
}
738+
739739
skip := 3 // runtime.(*mLockProfile).recordUnlock runtime.unlock2 runtime.unlockWithRank
740740
if staticLockRanking {
741741
// When static lock ranking is enabled, we'll always be on the system
@@ -780,7 +780,7 @@ func (prof *mLockProfile) store() {
780780
mp := acquirem()
781781
prof.disabled = true
782782

783-
nstk := maxStack
783+
nstk := int(debug.profstackdepth)
784784
for i := 0; i < nstk; i++ {
785785
if pc := prof.stack[i]; pc == 0 {
786786
nstk = i

src/runtime/proc.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,9 @@ func schedinit() {
818818
MemProfileRate = 0
819819
}
820820

821+
// mcommoninit runs before parsedebugvars, so init profstacks again.
822+
mProfStackInit(gp.m)
823+
821824
lock(&sched.lock)
822825
sched.lastpoll.Store(nanotime())
823826
procs := ncpu
@@ -930,6 +933,11 @@ func mcommoninit(mp *m, id int64) {
930933
// malloc and runtime locks for mLockProfile.
931934
// TODO(mknyszek): Implement lazy allocation if this becomes a problem.
932935
func mProfStackInit(mp *m) {
936+
if debug.profstackdepth == 0 {
937+
// debug.profstack is set to 0 by the user, or we're being called from
938+
// schedinit before parsedebugvars.
939+
return
940+
}
933941
mp.profStack = makeProfStackFP()
934942
mp.mLockProfile.stack = makeProfStackFP()
935943
}
@@ -944,12 +952,12 @@ func makeProfStackFP() []uintptr {
944952
// The "maxSkip" term is for frame pointer unwinding, where we
945953
// want to end up with debug.profstackdebth frames but will discard
946954
// some "physical" frames to account for skipping.
947-
return make([]uintptr, 1+maxSkip+maxLogicalStack)
955+
return make([]uintptr, 1+maxSkip+debug.profstackdepth)
948956
}
949957

950958
// makeProfStack returns a buffer large enough to hold a maximum-sized stack
951959
// trace.
952-
func makeProfStack() []uintptr { return make([]uintptr, maxLogicalStack) }
960+
func makeProfStack() []uintptr { return make([]uintptr, debug.profstackdepth) }
953961

954962
//go:linkname pprof_makeProfStack
955963
func pprof_makeProfStack() []uintptr { return makeProfStack() }

src/runtime/runtime1.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ var debug struct {
330330
tracefpunwindoff int32
331331
traceadvanceperiod int32
332332
traceCheckStackOwnership int32
333+
profstackdepth int32
333334

334335
// debug.malloc is used as a combined debug check
335336
// in the malloc function and should be set
@@ -379,6 +380,7 @@ var dbgvars = []*dbgVar{
379380
{name: "invalidptr", value: &debug.invalidptr},
380381
{name: "madvdontneed", value: &debug.madvdontneed},
381382
{name: "panicnil", atomic: &debug.panicnil},
383+
{name: "profstackdepth", value: &debug.profstackdepth, def: 128},
382384
{name: "runtimecontentionstacks", atomic: &debug.runtimeContentionStacks},
383385
{name: "sbrk", value: &debug.sbrk},
384386
{name: "scavtrace", value: &debug.scavtrace},
@@ -434,6 +436,7 @@ func parsedebugvars() {
434436
parsegodebug(godebug, nil)
435437

436438
debug.malloc = (debug.inittrace | debug.sbrk) != 0
439+
debug.profstackdepth = min(debug.profstackdepth, maxProfStackDepth)
437440

438441
setTraceback(gogetenv("GOTRACEBACK"))
439442
traceback_env = traceback_cache

src/runtime/tracestack.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ func pprof_fpunwindExpand(dst, src []uintptr) int {
277277
// sentinel. Physical frames are turned into logical frames via inline unwinding
278278
// and by applying the skip value that's stored in pcBuf[0].
279279
func fpunwindExpand(dst, pcBuf []uintptr) int {
280-
if len(pcBuf) > 0 && pcBuf[0] == logicalStackSentinel {
280+
if len(pcBuf) == 0 {
281+
return 0
282+
} else if len(pcBuf) > 0 && pcBuf[0] == logicalStackSentinel {
281283
// pcBuf contains logical rather than inlined frames, skip has already been
282284
// applied, just return it without the sentinel value in pcBuf[0].
283285
return copy(dst, pcBuf[1:])

0 commit comments

Comments
 (0)