1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "fmt"
10 "internal/testenv"
11 "log"
12 "os"
13 "runtime"
14 "runtime/debug"
15 "runtime/metrics"
16 "strings"
17 "sync/atomic"
18 "syscall"
19 "time"
20 )
21
22 func init() {
23 register("SchedMetrics", SchedMetrics)
24 }
25
26
27
28
29
30 func SchedMetrics() {
31 const (
32 notInGo = iota
33 runnable
34 running
35 waiting
36 created
37 threads
38 numSamples
39 )
40 var s [numSamples]metrics.Sample
41 s[notInGo].Name = "/sched/goroutines/not-in-go:goroutines"
42 s[runnable].Name = "/sched/goroutines/runnable:goroutines"
43 s[running].Name = "/sched/goroutines/running:goroutines"
44 s[waiting].Name = "/sched/goroutines/waiting:goroutines"
45 s[created].Name = "/sched/goroutines-created:goroutines"
46 s[threads].Name = "/sched/threads/total:threads"
47
48 var failed bool
49 var out bytes.Buffer
50 logger := log.New(&out, "", 0)
51 indent := 0
52 logf := func(s string, a ...any) {
53 var prefix strings.Builder
54 for range indent {
55 prefix.WriteString("\t")
56 }
57 logger.Printf(prefix.String()+s, a...)
58 }
59 errorf := func(s string, a ...any) {
60 logf(s, a...)
61 failed = true
62 }
63 run := func(name string, f func()) {
64 logf("=== Checking %q", name)
65 indent++
66 f()
67 indent--
68 }
69 logMetrics := func(s []metrics.Sample) {
70 for i := range s {
71 logf("%s: %d", s[i].Name, s[i].Value.Uint64())
72 }
73 }
74
75
76
77
78 generalSlack := uint64(4)
79
80
81
82
83 waitingSlack := generalSlack + uint64(2*runtime.GOMAXPROCS(-1))
84
85
86
87
88
89
90
91
92 const threadsSlack = 5
93
94
95
96 defer debug.SetGCPercent(debug.SetGCPercent(-1))
97 runtime.GC()
98
99 check := func(s *metrics.Sample, min, max uint64) {
100 val := s.Value.Uint64()
101 if val < min {
102 errorf("%s too low; %d < %d", s.Name, val, min)
103 }
104 if val > max {
105 errorf("%s too high; %d > %d", s.Name, val, max)
106 }
107 }
108 checkEq := func(s *metrics.Sample, value uint64) {
109 check(s, value, value)
110 }
111 spinUntil := func(f func() bool) bool {
112 for {
113 if f() {
114 return true
115 }
116 time.Sleep(50 * time.Millisecond)
117 }
118 }
119
120
121 run("base", func() {
122 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
123 metrics.Read(s[:])
124 logMetrics(s[:])
125 check(&s[notInGo], 0, generalSlack)
126 check(&s[runnable], 0, generalSlack)
127 checkEq(&s[running], 1)
128 check(&s[waiting], 0, waitingSlack)
129 })
130
131 metrics.Read(s[:])
132 createdAfterBase := s[created].Value.Uint64()
133
134
135
136 const count = 10
137 var ready, exit atomic.Uint32
138 for range count {
139 go func() {
140 ready.Add(1)
141 for exit.Load() == 0 {
142
143
144
145 start := time.Now()
146 for time.Since(start) < 10*time.Millisecond && exit.Load() == 0 {
147 }
148 runtime.Gosched()
149 }
150 }()
151 }
152 for ready.Load() < count {
153 runtime.Gosched()
154 }
155
156
157
158
159
160
161 if testenv.HasParallelism() {
162 run("created", func() {
163 metrics.Read(s[:])
164 logMetrics(s[:])
165 checkEq(&s[created], createdAfterBase+count)
166 })
167 run("running", func() {
168 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(count + 4))
169
170
171
172 spinUntil(func() bool {
173 metrics.Read(s[:])
174 return s[running].Value.Uint64() >= count
175 })
176 logMetrics(s[:])
177 check(&s[running], count, count+4)
178 check(&s[threads], count, count+4+threadsSlack)
179 })
180
181
182 run("runnable", func() {
183 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
184 metrics.Read(s[:])
185 logMetrics(s[:])
186 checkEq(&s[running], 1)
187 check(&s[runnable], count-1, count+generalSlack)
188 })
189
190
191 exit.Store(1)
192 } else {
193
194
195 metrics.Read(s[:])
196
197
198 exit.Store(1)
199
200
201 run("created", func() {
202
203
204 checkEq(&s[created], createdAfterBase+count-1)
205 })
206 run("running", func() {
207 logMetrics(s[:])
208 checkEq(&s[running], 1)
209 checkEq(&s[threads], 1)
210 })
211 run("runnable", func() {
212 logMetrics(s[:])
213 check(&s[runnable], count-1, count+generalSlack)
214 })
215 }
216
217
218
219
220 run("not-in-go", func() {
221
222 pr, pw, err := pipe()
223 if err != nil {
224 switch runtime.GOOS {
225 case "js", "wasip1":
226 logf("creating pipe: %v", err)
227 return
228 }
229 panic(fmt.Sprintf("creating pipe: %v", err))
230 }
231 for i := 0; i < count; i++ {
232 go syscall.Read(pr, make([]byte, 1))
233 }
234
235
236 spinUntil(func() bool {
237 metrics.Read(s[:])
238 return s[notInGo].Value.Uint64() >= count
239 })
240 logMetrics(s[:])
241 check(&s[notInGo], count, count+generalSlack)
242
243 syscall.Close(pw)
244 syscall.Close(pr)
245 })
246
247 run("waiting", func() {
248
249 const waitingCount = 1000
250 stop := make(chan bool)
251 for i := 0; i < waitingCount; i++ {
252 go func() { <-stop }()
253 }
254
255
256 spinUntil(func() bool {
257 metrics.Read(s[:])
258 return s[waiting].Value.Uint64() >= waitingCount
259 })
260 logMetrics(s[:])
261 check(&s[waiting], waitingCount, waitingCount+waitingSlack)
262
263 close(stop)
264 })
265
266 if failed {
267 fmt.Fprintln(os.Stderr, out.String())
268 os.Exit(1)
269 } else {
270 fmt.Fprintln(os.Stderr, "OK")
271 }
272 }
273
View as plain text