Source file src/runtime/testdata/testprog/stw_trace.go
1 // Copyright 2025 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "context" 9 "log" 10 "math/rand/v2" 11 "os" 12 "runtime" 13 "runtime/debug" 14 "runtime/metrics" 15 "runtime/trace" 16 "sync/atomic" 17 ) 18 19 func init() { 20 register("TraceSTW", TraceSTW) 21 register("TraceGCSTW", TraceGCSTW) 22 } 23 24 // The parent writes to ping and waits for the children to write back 25 // via pong to show that they are running. 26 var ping atomic.Uint32 27 var pong [2]atomic.Uint32 28 29 // Tell runners to stop. 30 var stop atomic.Bool 31 32 func traceSTWTarget(i int) { 33 for !stop.Load() { 34 // Async preemption often takes 100ms+ to preempt this loop on 35 // windows-386. This makes the test flaky, as the traceReadCPU 36 // timer often fires by the time STW finishes, jumbling the 37 // goroutine scheduling. As a workaround, ensure we have a 38 // morestack call for prompt preemption. 39 ensureMorestack() 40 41 pong[i].Store(ping.Load()) 42 } 43 } 44 45 func TraceSTW() { 46 ctx := context.Background() 47 48 // The idea here is to have 2 target goroutines that are constantly 49 // running. When the world restarts after STW, we expect these 50 // goroutines to continue execution on the same M and P. 51 // 52 // Set GOMAXPROCS=4 to make room for the 2 target goroutines, 1 parent, 53 // and 1 slack for potential misscheduling. 54 // 55 // Disable the GC because GC STW generally moves goroutines (see 56 // https://go.dev/issue/65694). Alternatively, we could just ignore the 57 // trace if the GC runs. 58 runtime.GOMAXPROCS(4) 59 debug.SetGCPercent(-1) 60 61 if err := trace.Start(os.Stdout); err != nil { 62 log.Fatalf("failed to start tracing: %v", err) 63 } 64 defer trace.Stop() 65 66 for i := range 2 { 67 go traceSTWTarget(i) 68 } 69 70 // Wait for children to start running. 71 ping.Store(1) 72 for pong[0].Load() != 1 {} 73 for pong[1].Load() != 1 {} 74 75 trace.Log(ctx, "TraceSTW", "start") 76 77 // STW 78 var ms runtime.MemStats 79 runtime.ReadMemStats(&ms) 80 81 // Make sure to run long enough for the children to schedule again 82 // after STW. 83 ping.Store(2) 84 for pong[0].Load() != 2 {} 85 for pong[1].Load() != 2 {} 86 87 trace.Log(ctx, "TraceSTW", "end") 88 89 stop.Store(true) 90 } 91 92 // Variant of TraceSTW for GC STWs. We want the GC mark workers to start on 93 // previously-idle Ps, rather than bumping the current P. 94 func TraceGCSTW() { 95 ctx := context.Background() 96 97 // The idea here is to have 2 target goroutines that are constantly 98 // running. When the world restarts after STW, we expect these 99 // goroutines to continue execution on the same M and P. 100 // 101 // Set GOMAXPROCS=8 to make room for the 2 target goroutines, 1 parent, 102 // 2 dedicated workers, and a bit of slack. 103 // 104 // Disable the GC initially so we can be sure it only triggers once we 105 // are ready. 106 runtime.GOMAXPROCS(8) 107 debug.SetGCPercent(-1) 108 109 if err := trace.Start(os.Stdout); err != nil { 110 log.Fatalf("failed to start tracing: %v", err) 111 } 112 defer trace.Stop() 113 114 for i := range 2 { 115 go traceSTWTarget(i) 116 } 117 118 // Wait for children to start running. 119 ping.Store(1) 120 for pong[0].Load() != 1 {} 121 for pong[1].Load() != 1 {} 122 123 trace.Log(ctx, "TraceSTW", "start") 124 125 // STW 126 triggerGC() 127 128 // Make sure to run long enough for the children to schedule again 129 // after STW. This is included for good measure, but the goroutines 130 // really ought to have already scheduled since the entire GC 131 // completed. 132 ping.Store(2) 133 for pong[0].Load() != 2 {} 134 for pong[1].Load() != 2 {} 135 136 trace.Log(ctx, "TraceSTW", "end") 137 138 stop.Store(true) 139 } 140 141 func triggerGC() { 142 // Allocate a bunch to trigger the GC rather than using runtime.GC. The 143 // latter blocks until the GC is complete, which is convenient, but 144 // messes with scheduling as it gives this P a chance to steal the 145 // other goroutines before their Ps get up and running again. 146 147 // Bring heap size up prior to enabling the GC to ensure that there is 148 // a decent amount of work in case the GC triggers immediately upon 149 // re-enabling. 150 for range 1000 { 151 alloc() 152 } 153 154 sample := make([]metrics.Sample, 1) 155 sample[0].Name = "/gc/cycles/total:gc-cycles" 156 metrics.Read(sample) 157 158 start := sample[0].Value.Uint64() 159 160 debug.SetGCPercent(100) 161 162 // Keep allocating until the GC is complete. We really only need to 163 // continue until the mark workers are scheduled, but there isn't a 164 // good way to measure that. 165 for { 166 metrics.Read(sample) 167 if sample[0].Value.Uint64() != start { 168 return 169 } 170 171 alloc() 172 } 173 } 174 175 // Allocate a tree data structure to generate plenty of scan work for the GC. 176 177 type node struct { 178 children []*node 179 } 180 181 var gcSink node 182 183 func alloc() { 184 // 10% chance of adding a node a each layer. 185 186 curr := &gcSink 187 for { 188 if len(curr.children) == 0 || rand.Float32() < 0.1 { 189 curr.children = append(curr.children, new(node)) 190 return 191 } 192 193 i := rand.IntN(len(curr.children)) 194 curr = curr.children[i] 195 } 196 } 197 198 // Manually insert a morestack call. Leaf functions can omit morestack, but 199 // non-leaf functions should include them. 200 201 //go:noinline 202 func ensureMorestack() { 203 ensureMorestack1() 204 } 205 206 //go:noinline 207 func ensureMorestack1() { 208 } 209