| |
| |
| |
|
|
| package main |
|
|
| import ( |
| "bytes" |
| "fmt" |
| "internal/testenv" |
| "log" |
| "os" |
| "runtime" |
| "runtime/debug" |
| "runtime/metrics" |
| "strings" |
| "sync/atomic" |
| "syscall" |
| "time" |
| ) |
|
|
| func init() { |
| register("SchedMetrics", SchedMetrics) |
| } |
|
|
| |
| |
| |
| |
| func SchedMetrics() { |
| const ( |
| notInGo = iota |
| runnable |
| running |
| waiting |
| created |
| threads |
| numSamples |
| ) |
| var s [numSamples]metrics.Sample |
| s[notInGo].Name = "/sched/goroutines/not-in-go:goroutines" |
| s[runnable].Name = "/sched/goroutines/runnable:goroutines" |
| s[running].Name = "/sched/goroutines/running:goroutines" |
| s[waiting].Name = "/sched/goroutines/waiting:goroutines" |
| s[created].Name = "/sched/goroutines-created:goroutines" |
| s[threads].Name = "/sched/threads/total:threads" |
|
|
| var failed bool |
| var out bytes.Buffer |
| logger := log.New(&out, "", 0) |
| indent := 0 |
| logf := func(s string, a ...any) { |
| var prefix strings.Builder |
| for range indent { |
| prefix.WriteString("\t") |
| } |
| logger.Printf(prefix.String()+s, a...) |
| } |
| errorf := func(s string, a ...any) { |
| logf(s, a...) |
| failed = true |
| } |
| run := func(name string, f func()) { |
| logf("=== Checking %q", name) |
| indent++ |
| f() |
| indent-- |
| } |
| logMetrics := func(s []metrics.Sample) { |
| for i := range s { |
| logf("%s: %d", s[i].Name, s[i].Value.Uint64()) |
| } |
| } |
|
|
| initialGMP := runtime.GOMAXPROCS(-1) |
| logf("Initial GOMAXPROCS=%d", initialGMP) |
|
|
| |
| |
| |
| generalSlack := uint64(4) |
|
|
| |
| |
| |
| waitingSlack := generalSlack + uint64(2*initialGMP) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| threadsSlack := 5 + uint64(initialGMP) |
|
|
| |
| |
| defer debug.SetGCPercent(debug.SetGCPercent(-1)) |
| runtime.GC() |
|
|
| check := func(s *metrics.Sample, min, max uint64) { |
| val := s.Value.Uint64() |
| if val < min { |
| errorf("%s too low; %d < %d", s.Name, val, min) |
| } |
| if val > max { |
| errorf("%s too high; %d > %d", s.Name, val, max) |
| } |
| } |
| checkEq := func(s *metrics.Sample, value uint64) { |
| check(s, value, value) |
| } |
| spinUntil := func(f func() bool) bool { |
| for { |
| if f() { |
| return true |
| } |
| time.Sleep(50 * time.Millisecond) |
| } |
| } |
|
|
| |
| run("base", func() { |
| defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) |
| metrics.Read(s[:]) |
| logMetrics(s[:]) |
| check(&s[notInGo], 0, generalSlack) |
| check(&s[runnable], 0, generalSlack) |
| checkEq(&s[running], 1) |
| check(&s[waiting], 0, waitingSlack) |
| }) |
|
|
| metrics.Read(s[:]) |
| createdAfterBase := s[created].Value.Uint64() |
|
|
| |
| |
| const count = 10 |
| var ready, exit atomic.Uint32 |
| for range count { |
| go func() { |
| ready.Add(1) |
| for exit.Load() == 0 { |
| |
| |
| |
| start := time.Now() |
| for time.Since(start) < 10*time.Millisecond && exit.Load() == 0 { |
| } |
| runtime.Gosched() |
| } |
| }() |
| } |
| for ready.Load() < count { |
| runtime.Gosched() |
| } |
|
|
| |
| |
| |
| |
| |
| if testenv.HasParallelism() { |
| run("created", func() { |
| metrics.Read(s[:]) |
| logMetrics(s[:]) |
| checkEq(&s[created], createdAfterBase+count) |
| }) |
| run("running", func() { |
| defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(count + 4)) |
| |
| |
| |
| spinUntil(func() bool { |
| metrics.Read(s[:]) |
| return s[running].Value.Uint64() >= count |
| }) |
| logMetrics(s[:]) |
| check(&s[running], count, count+4) |
| check(&s[threads], count, count+4+threadsSlack) |
| }) |
|
|
| |
| run("runnable", func() { |
| defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) |
| metrics.Read(s[:]) |
| logMetrics(s[:]) |
| checkEq(&s[running], 1) |
| check(&s[runnable], count-1, count+generalSlack) |
| }) |
|
|
| |
| exit.Store(1) |
| } else { |
| |
| |
| metrics.Read(s[:]) |
|
|
| |
| exit.Store(1) |
|
|
| |
| run("created", func() { |
| |
| |
| checkEq(&s[created], createdAfterBase+count-1) |
| }) |
| run("running", func() { |
| logMetrics(s[:]) |
| checkEq(&s[running], 1) |
| checkEq(&s[threads], 1) |
| }) |
| run("runnable", func() { |
| logMetrics(s[:]) |
| check(&s[runnable], count-1, count+generalSlack) |
| }) |
| } |
|
|
| |
| |
| |
| run("not-in-go", func() { |
| |
| pr, pw, err := pipe() |
| if err != nil { |
| switch runtime.GOOS { |
| case "js", "wasip1": |
| logf("creating pipe: %v", err) |
| return |
| } |
| panic(fmt.Sprintf("creating pipe: %v", err)) |
| } |
| for i := 0; i < count; i++ { |
| go syscall.Read(pr, make([]byte, 1)) |
| } |
|
|
| |
| spinUntil(func() bool { |
| metrics.Read(s[:]) |
| return s[notInGo].Value.Uint64() >= count |
| }) |
| logMetrics(s[:]) |
| check(&s[notInGo], count, count+generalSlack) |
|
|
| syscall.Close(pw) |
| syscall.Close(pr) |
| }) |
|
|
| run("waiting", func() { |
| |
| const waitingCount = 1000 |
| stop := make(chan bool) |
| for i := 0; i < waitingCount; i++ { |
| go func() { <-stop }() |
| } |
|
|
| |
| spinUntil(func() bool { |
| metrics.Read(s[:]) |
| return s[waiting].Value.Uint64() >= waitingCount |
| }) |
| logMetrics(s[:]) |
| check(&s[waiting], waitingCount, waitingCount+waitingSlack) |
|
|
| close(stop) |
| }) |
|
|
| if failed { |
| fmt.Fprintln(os.Stderr, out.String()) |
| os.Exit(1) |
| } else { |
| fmt.Fprintln(os.Stderr, "OK") |
| } |
| } |
|
|