What does GODEBUG=schedtrace=1000 cost a running Go process, and what does adding scheddetail=1 change?
answer
- the number is a period, not a count
- milliseconds, so 1000 means once a second
- one of the two settings is useless alone
- the detailed form walks every P, M and goroutine
- printed while the global scheduler lock is held
basics
~20 sThe value is a period in milliseconds, so schedtrace=1000 prints one summary line about Go's goroutine scheduler every second, on standard error. Adding scheddetail=1 turns each tick into a dump of every P, M and goroutine.
solid answer
~50 s`GODEBUG=schedtrace=N` sets a period in **milliseconds**, not a count: the runtime's background monitor thread prints one summary line about Go's goroutine scheduler every N ms, on standard error. `scheddetail=1` on its own prints nothing — it only chooses the detailed form, and the runtime emits nothing unless `schedtrace` is also set. With both, each tick becomes a multi-line dump walking every P, every M and every goroutine, printed while the runtime holds its global scheduler lock. On a service with tens of thousands of goroutines that is a large volume of output and a visible stall, so the diagnostic perturbs exactly the scheduling you are trying to observe. Enabling `schedtrace` also keeps the monitor thread out of its deep sleep, so an otherwise idle process keeps waking to print. Pick a period of seconds, send stderr to its own file, and keep the window short.
code
text · 8 lines# one summary line per second, on standard error
$ GODEBUG=schedtrace=1000 ./worker 2>sched.log
# per-P, per-M and per-goroutine detail, every second
$ GODEBUG=schedtrace=1000,scheddetail=1 ./worker 2>sched.log
# prints nothing at all: scheddetail does nothing without schedtrace
$ GODEBUG=scheddetail=1 ./workergo deeper
Know that GODEBUG=schedtrace=N asks Go's goroutine scheduler for a periodic summary line on standard error, and that N is a period in milliseconds.
Explain that the runtime's monitor thread emits the line, that scheddetail=1 does nothing unless schedtrace is set, and that the detailed form is a per-P, per-M and per-goroutine dump on every tick.
Show restraint: pick a period of seconds, capture stderr separately, keep the window short, and be able to say why a detailed dump on a goroutine-heavy process distorts the very scheduling behaviour you are trying to observe.
Decide what an on-call engineer may enable on a live service, given that these settings need a restart to take effect and the detailed form can measurably slow the process for as long as it is on.
## The two settings and how they relate `GODEBUG=schedtrace=N` asks the Go runtime for a periodic report on its goroutine scheduler. The number is a **period in milliseconds**: `schedtrace=1000` means one line per second, `schedtrace=5000` one line every five seconds. Reading it as "1000 lines" or "1000 events" is the classic first mistake. `scheddetail=1` is not independent. The runtime checks whether `schedtrace` is greater than zero before emitting anything at all, and only then uses `scheddetail` to choose between the one-line summary and the detailed form. Launching a binary with `GODEBUG=scheddetail=1` alone therefore produces no output whatsoever, which reliably confuses people into thinking the setting was removed. The correct form is `GODEBUG=schedtrace=1000,scheddetail=1`. Both are read at process start-up, like the rest of the GODEBUG runtime tracers, so you choose the period when you launch the process and changing it means restarting. ## Who prints it, and where The lines are emitted by the runtime's background monitor thread — the same thread that handles retaking Ps from long syscalls and forcing periodic collections. It writes with the runtime's own low-level printer to standard error: no timestamps of your choosing, no structure, no route through `log` or `log/slog`, interleaved with whatever else the process writes to stderr. If the launcher captures only stdout, the output vanishes. ## What it costs Three costs are worth naming, in increasing order of severity. **The monitor thread stays awake.** That thread normally slides into progressively longer sleeps when the process is idle. While `schedtrace` is set, it is kept out of deep sleep so it can print on schedule. For a busy service this is noise; for a process that is meant to sit still between bursts, it is a small but real change in behaviour, and it will show in wakeup counts and idle power. **The line is printed under the global scheduler lock.** The runtime takes its central scheduler lock, gathers the numbers and prints, then releases it. For a single summary line that is brief. It is still a lock every scheduling decision in the process contends on. **`scheddetail=1` multiplies that.** The detailed form walks every P, every M and every goroutine, printing a block for each, all inside the same lock hold. On a service with fifty thousand goroutines that is fifty thousand-plus lines per tick, written synchronously to a file descriptor, while goroutine scheduling waits behind it. Set that with a one-second period on a busy production process and you have not measured the scheduler, you have replaced its workload with your diagnostic. The observer effect here is not a theoretical caveat — it is the dominant effect. ## How to run it responsibly - Choose a period in **seconds**: `schedtrace=5000` is usually enough to watch a trend, and it costs a hundredth of what `schedtrace=50` does. - Turn on `scheddetail=1` only when you specifically need per-P or per-goroutine state, on a process whose goroutine count you know is small, and for a short window. - Redirect stderr to its own file at launch, so the diagnostic stream stays out of the application log and cannot be lost to a runner that captures only stdout. - Remember the format is documented as subject to change between releases; it is for a human reading one run, not for a parser. - Because the setting is start-up-only, plan the run: relaunch with it, gather, relaunch without it. ## The judgment being tested Anyone can find `schedtrace` in the runtime documentation. What an interviewer is listening for is whether you know that turning a runtime diagnostic on has a price, that the price of the detailed variant scales with the number of goroutines rather than being a fixed overhead, and that you would therefore reach for the summary line at a slow period first and only escalate if it is not enough.
- Why does GODEBUG=scheddetail=1 on its own produce no output?Because the runtime gates all of this output on `schedtrace` being greater than zero. `scheddetail` only selects between the one-line summary and the detailed multi-line dump; with no period set there is no tick, so nothing is ever emitted. The working form is `schedtrace=N,scheddetail=1`.
- What makes scheddetail=1 expensive on a process running tens of thousands of goroutines?Every tick walks all Ps, Ms and goroutines and prints a block for each, all while the runtime holds its global scheduler lock. Scheduling in the whole process waits behind that walk, and the output volume scales with the goroutine count. Use a long period, a short window, and stderr redirected to a file.
- Does enabling schedtrace change how an idle process behaves?Slightly, yes. The runtime's monitor thread normally sinks into longer and longer sleeps when nothing is running; while schedtrace is set it is kept out of deep sleep so it can print on schedule. On a busy service this is invisible, but on a process designed to sit idle between bursts it is a measurable change.
The summary line is a glance at a dashboard; scheddetail is stopping the whole production line to photograph every station, once a second.
saying these in an interview costs you the question
- Reads schedtrace=1000 as a count of 1000 trace lines
- Sets scheddetail=1 without schedtrace and expects output
- Describes schedtrace as a sampling profiler of goroutines
- Leaves scheddetail=1 on in production because it is 'only logging'
- Expects the lines on standard output beside the app's logs