Expand description
tokio-console instrumentation.
tokio-console collects its data through tracing spans and events
that follow a fixed naming convention. It is not tied to tokio’s internals
in any way, so any executor emitting the same spans and events can be
observed with it.
Enable the console feature to make this executor emit them:
- every task gets a
runtime.spawnspan, entered while the task is polled, so that the console can compute poll counts, busy/idle/scheduled times and the poll time histogram; - every waker operation emits a
runtime::wakerevent, so that the console can compute waker counts and detect self-wakes and lost wakers; - a closure handed to the blocking pool gets such a span too, entered around the closure instead of around a poll, so that the time spent in it is reported as busy time rather than as idle time.
When the feature is disabled, all of this compiles down to nothing: the types in this module become zero-sized and every method an empty inlined function.
§Usage
console-subscriber refuses to run unless it can prove that the runtime is
instrumented, which for tokio means the tokio_unstable cfg. For other
runtimes it provides the console_without_tokio_unstable escape hatch, so a
binary observing compio needs:
# .cargo/config.toml
[build]
rustflags = ["--cfg", "console_without_tokio_unstable"]Depending on console-subscriber and installing it is then all it takes:
console_subscriber::init();
compio::runtime::Runtime::new().unwrap().block_on(async {
// ...
});§Limitations
-
The console’s data model has one runtime per process, while compio is thread-per-core and has one executor per thread. The tasks of all of them are listed together; the
threadfield tells them apart. -
The subscriber has to be the global default, which
console_subscriber::initmakes it. A span carries the subscriber it was created with, but an event goes to whichever one is current on the thread emitting it, so a thread-local subscriber misses the waker operations other threads perform. Wakers cross threads routinely — that is what waking a task from another executor is — and the clone and drop counts of one that does no longer balance, leaving the console to report a lost waker that is not lost. -
A
block_onnested inside a task — a runtime built within another one — reports the two as separate tasks, but both of their spans are entered on the same stack. The console attributes the polls to the inner one for as long as that is the case. -
A blocking task has no waker operations, since it is a closure rather than a future. The console knows this from its
kindand does not report a lost waker for it. -
A task spawned by an
async fnis attributed to that function rather than to its caller, since#[track_caller]is a no-op onasync fns andSpawnMetatherefore cannot be forwarded through them. A function that wants the caller instead can be a plainfnreturning a future, capturing theSpawnMetabefore theasyncblock it returns — at the cost of an opaque return type, and of running whatever precedes the block when it is called rather than when it is first polled. The ones compio spawns itself are named either way.Nightly’s
async_fn_track_calleris not a substitute: it reports the caller ofpoll, which is the.awaitwhen a future is awaited directly, but a line insidejoin!,select!or whichever combinator drives it otherwise. -
The resources tab stays empty: timers and in-flight operations are not instrumented yet.
-
A task’s span is closed even when the thread is unwinding, or the console would show the task as running forever. The subscriber therefore runs during a panic, where a panic of its own aborts instead of unwinding.
Structs§
- Spawn
Meta - Metadata of a spawned task, reported to the console.