diff --git a/src/runtime/metrics_test.go b/src/runtime/metrics_test.go index d28a589adc2e1ce627b3fd2ad0598a25c93e55e6..7074abfd698a3f0095dfa38f2ac57152c3c4edde 100644 --- a/src/runtime/metrics_test.go +++ b/src/runtime/metrics_test.go @@ -1018,7 +1018,7 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { return metricGrowth, profileGrowth, p } - testcase := func(strictTiming bool, stk []string, workers int, fn func() bool) func(t *testing.T) (metricGrowth, profileGrowth float64, n, value int64) { + testcase := func(strictTiming bool, acceptStacks [][]string, workers int, fn func() bool) func(t *testing.T) (metricGrowth, profileGrowth float64, n, value int64) { return func(t *testing.T) (metricGrowth, profileGrowth float64, n, value int64) { metricGrowth, profileGrowth, p := measureDelta(t, func() { var started, stopped sync.WaitGroup @@ -1056,19 +1056,24 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { t.Logf("lock contention growth in runtime/pprof's view (%fs)", profileGrowth) t.Logf("lock contention growth in runtime/metrics' view (%fs)", metricGrowth) - if goexperiment.StaticLockRanking { - if !slices.ContainsFunc(stk, func(s string) bool { - return s == "runtime.systemstack" || s == "runtime.mcall" || s == "runtime.mstart" - }) { - // stk is a call stack that is still on the user stack when - // it calls runtime.unlock. Add the extra function that - // we'll see, when the static lock ranking implementation of - // runtime.unlockWithRank switches to the system stack. - stk = append([]string{"runtime.unlockWithRank"}, stk...) + acceptStacks = append([][]string(nil), acceptStacks...) + for i, stk := range acceptStacks { + if goexperiment.StaticLockRanking { + if !slices.ContainsFunc(stk, func(s string) bool { + return s == "runtime.systemstack" || s == "runtime.mcall" || s == "runtime.mstart" + }) { + // stk is a call stack that is still on the user stack when + // it calls runtime.unlock. Add the extra function that + // we'll see, when the static lock ranking implementation of + // runtime.unlockWithRank switches to the system stack. + stk = append([]string{"runtime.unlockWithRank"}, stk...) + } } + acceptStacks[i] = stk } var stks [][]string + values := make([][2]int64, len(acceptStacks)) for _, s := range p.Sample { var have []string for _, loc := range s.Location { @@ -1077,18 +1082,26 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { } } stks = append(stks, have) - if slices.Equal(have, stk) { - n += s.Value[0] - value += s.Value[1] + for i, stk := range acceptStacks { + if slices.Equal(have, stk) { + values[i][0] += s.Value[0] + values[i][1] += s.Value[1] + } } } - t.Logf("stack %v has samples totaling n=%d value=%d", stk, n, value) + for i, stk := range acceptStacks { + n += values[i][0] + value += values[i][1] + t.Logf("stack %v has samples totaling n=%d value=%d", stk, values[i][0], values[i][1]) + } if n == 0 && value == 0 { t.Logf("profile:\n%s", p) for _, have := range stks { t.Logf("have stack %v", have) } - t.Errorf("want stack %v", stk) + for _, stk := range acceptStacks { + t.Errorf("want stack %v", stk) + } } return metricGrowth, profileGrowth, n, value @@ -1140,18 +1153,18 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { return true } - stk := []string{ + stks := [][]string{{ "runtime.unlock", "runtime_test." + name + ".func5.1", "runtime_test.(*contentionWorker).run", - } + }} t.Run("sample-1", func(t *testing.T) { old := runtime.SetMutexProfileFraction(1) defer runtime.SetMutexProfileFraction(old) needContention.Store(int64(len(mus) - 1)) - metricGrowth, profileGrowth, n, _ := testcase(true, stk, workers, fn)(t) + metricGrowth, profileGrowth, n, _ := testcase(true, stks, workers, fn)(t) if have, want := metricGrowth, delay.Seconds()*float64(len(mus)); have < want { // The test imposes a delay with usleep, verified with calls to @@ -1175,7 +1188,7 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { defer runtime.SetMutexProfileFraction(old) needContention.Store(int64(len(mus) - 1)) - metricGrowth, profileGrowth, n, _ := testcase(true, stk, workers, fn)(t) + metricGrowth, profileGrowth, n, _ := testcase(true, stks, workers, fn)(t) // With 100 trials and profile fraction of 2, we expect to capture // 50 samples. Allow the test to pass if we get at least 20 samples; @@ -1231,11 +1244,20 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { return true } - stk := []string{ - "runtime.unlock", - "runtime.semrelease1", - "runtime_test.TestRuntimeLockMetricsAndProfile.func6.1", - "runtime_test.(*contentionWorker).run", + stks := [][]string{ + { + "runtime.unlock", + "runtime.semrelease1", + "runtime_test.TestRuntimeLockMetricsAndProfile.func6.1", + "runtime_test.(*contentionWorker).run", + }, + { + "runtime.unlock", + "runtime.semacquire1", + "runtime.semacquire", + "runtime_test.TestRuntimeLockMetricsAndProfile.func6.1", + "runtime_test.(*contentionWorker).run", + }, } // Verify that we get call stack we expect, with anything more than zero @@ -1243,7 +1265,11 @@ func TestRuntimeLockMetricsAndProfile(t *testing.T) { // small relative to the expected overhead for us to verify its value // more directly. Leave that to the explicit lock/unlock test. - testcase(false, stk, workers, fn)(t) + testcase(false, stks, workers, fn)(t) + + if remaining := tries.Load(); remaining >= 0 { + t.Logf("finished test early (%d tries remaining)", remaining) + } }) }