Skip to content

Commit a0d15cb

Browse files
felixgeprattmic
authored andcommitted
[release-branch.go1.23] runtime: fix MutexProfile missing root frames
Fix a regression introduced in CL 598515 causing runtime.MutexProfile stack traces to omit their root frames. In most cases this was merely causing the `runtime.goexit` frame to go missing. But in the case of runtime._LostContendedRuntimeLock, an empty stack trace was being produced. Add a test that catches this regression by checking for a stack trace with the `runtime.goexit` frame. Also fix a separate problem in expandFrame that could cause out-of-bounds panics when profstackdepth is set to a value below 32. There is no test for this fix because profstackdepth can't be changed at runtime right now. Fixes #69865 Change-Id: I1600fe62548ea84981df0916d25072c3ddf1ea1a Reviewed-on: https://go-review.googlesource.com/c/go/+/611615 Reviewed-by: David Chase <[email protected]> Reviewed-by: Nick Ripley <[email protected]> Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> (cherry picked from commit c64ca8c) Reviewed-on: https://go-review.googlesource.com/c/go/+/621276 Reviewed-by: Cherry Mui <[email protected]>
1 parent 958f3a0 commit a0d15cb

File tree

3 files changed

+45
-6
lines changed

3 files changed

+45
-6
lines changed

src/runtime/mprof.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1136,11 +1136,12 @@ func expandFrames(p []BlockProfileRecord) {
11361136
for i := range p {
11371137
cf := CallersFrames(p[i].Stack())
11381138
j := 0
1139-
for ; j < len(expandedStack); j++ {
1139+
for j < len(expandedStack) {
11401140
f, more := cf.Next()
11411141
// f.PC is a "call PC", but later consumers will expect
11421142
// "return PCs"
11431143
expandedStack[j] = f.PC + 1
1144+
j++
11441145
if !more {
11451146
break
11461147
}

src/runtime/pprof/mprof_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func TestMemoryProfiler(t *testing.T) {
145145
}
146146
t.Logf("Profile = %v", p)
147147

148-
stks := stacks(p)
148+
stks := profileStacks(p)
149149
for _, test := range tests {
150150
if !containsStack(stks, test.stk) {
151151
t.Fatalf("No matching stack entry for %q\n\nProfile:\n%v\n", test.stk, p)

src/runtime/pprof/pprof_test.go

+42-4
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ func TestBlockProfile(t *testing.T) {
982982
t.Fatalf("invalid profile: %v", err)
983983
}
984984

985-
stks := stacks(p)
985+
stks := profileStacks(p)
986986
for _, test := range tests {
987987
if !containsStack(stks, test.stk) {
988988
t.Errorf("No matching stack entry for %v, want %+v", test.name, test.stk)
@@ -992,7 +992,7 @@ func TestBlockProfile(t *testing.T) {
992992

993993
}
994994

995-
func stacks(p *profile.Profile) (res [][]string) {
995+
func profileStacks(p *profile.Profile) (res [][]string) {
996996
for _, s := range p.Sample {
997997
var stk []string
998998
for _, l := range s.Location {
@@ -1005,6 +1005,22 @@ func stacks(p *profile.Profile) (res [][]string) {
10051005
return res
10061006
}
10071007

1008+
func blockRecordStacks(records []runtime.BlockProfileRecord) (res [][]string) {
1009+
for _, record := range records {
1010+
frames := runtime.CallersFrames(record.Stack())
1011+
var stk []string
1012+
for {
1013+
frame, more := frames.Next()
1014+
stk = append(stk, frame.Function)
1015+
if !more {
1016+
break
1017+
}
1018+
}
1019+
res = append(res, stk)
1020+
}
1021+
return res
1022+
}
1023+
10081024
func containsStack(got [][]string, want []string) bool {
10091025
for _, stk := range got {
10101026
if len(stk) < len(want) {
@@ -1289,7 +1305,7 @@ func TestMutexProfile(t *testing.T) {
12891305
t.Fatalf("invalid profile: %v", err)
12901306
}
12911307

1292-
stks := stacks(p)
1308+
stks := profileStacks(p)
12931309
for _, want := range [][]string{
12941310
{"sync.(*Mutex).Unlock", "runtime/pprof.blockMutexN.func1"},
12951311
} {
@@ -1329,6 +1345,28 @@ func TestMutexProfile(t *testing.T) {
13291345
t.Fatalf("profile samples total %v, want within range [%v, %v] (target: %v)", d, lo, hi, N*D)
13301346
}
13311347
})
1348+
1349+
t.Run("records", func(t *testing.T) {
1350+
// Record a mutex profile using the structured record API.
1351+
var records []runtime.BlockProfileRecord
1352+
for {
1353+
n, ok := runtime.MutexProfile(records)
1354+
if ok {
1355+
records = records[:n]
1356+
break
1357+
}
1358+
records = make([]runtime.BlockProfileRecord, n*2)
1359+
}
1360+
1361+
// Check that we see the same stack trace as the proto profile. For
1362+
// historical reason we expect a runtime.goexit root frame here that is
1363+
// omitted in the proto profile.
1364+
stks := blockRecordStacks(records)
1365+
want := []string{"sync.(*Mutex).Unlock", "runtime/pprof.blockMutexN.func1", "runtime.goexit"}
1366+
if !containsStack(stks, want) {
1367+
t.Errorf("No matching stack entry for %+v", want)
1368+
}
1369+
})
13321370
}
13331371

13341372
func TestMutexProfileRateAdjust(t *testing.T) {
@@ -2514,7 +2552,7 @@ func TestProfilerStackDepth(t *testing.T) {
25142552
}
25152553
t.Logf("Profile = %v", p)
25162554

2517-
stks := stacks(p)
2555+
stks := profileStacks(p)
25182556
var stk []string
25192557
for _, s := range stks {
25202558
if hasPrefix(s, test.prefix) {

0 commit comments

Comments
 (0)