Skip to main content

Module console

Module console 

Source
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.spawn span, 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::waker event, 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 thread field tells them apart.

  • The subscriber has to be the global default, which console_subscriber::init makes 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_on nested 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 kind and does not report a lost waker for it.

  • A task spawned by an async fn is attributed to that function rather than to its caller, since #[track_caller] is a no-op on async fns and SpawnMeta therefore cannot be forwarded through them. A function that wants the caller instead can be a plain fn returning a future, capturing the SpawnMeta before the async block 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_caller is not a substitute: it reports the caller of poll, which is the .await when a future is awaited directly, but a line inside join!, 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§

SpawnMeta
Metadata of a spawned task, reported to the console.