Tokio Console
tokio.rs
tokio.rs
Color coding OOM (order-of-magnitude) is smart, but the colors chosen seem quite hard to distinguish. Consider picking different default colors; also consider adding a redundant representation of scale, like an integer representing OOM scale.
It is strange that a thoughtful design around OOM would also choose keep so many digits. If the goal is to summarize, then please throw those extra digits away. (e.g. 10.3992s is specifying the time down to the millisecond, but those extra ms are not meaningful at the second time scale.)
What is this about the runtime polling tasks? Does that happen in Tokio? Why? I am only familiar with 2 async runtimes (node, vert.x) and I was under the impression that neither of these "poll their threads" in any meaningful way. Threads (or process or fiber or whatever you want to call it in this context) never initiate action on their own. They are ALWAYS waiting for something to happen to them, and that includes timeouts, which would be triggered by passing a thread a clock tick. The runtime's job is to centrally manage resources that must persist between thread behavior, so at most it is going to be polling external resources, and not it's own threads.
Or maybe I don't understand what polling means here? I would interpret it as meaning "keep checking a well-known place for changes, and then do something if a change is found". But since async threads can't initiate any change on their own, this is nonsensical.
Regarding the color scheme, glad you like the idea. Because it's a terminal application, the choice of the colors was constrained a bit by the ANSI 256 color palette (https://www.ditig.com/256-colors-cheat-sheet); I wanted it to be obviously a gradient, so I just picked colors that were immediately adjacent to each other in the ANSI palette. It might be better to pick colors that are one step apart from each other, instead, so they're more distinguishable visually...but there's kind of a balancing act between distinguishability and having a clear gradient. We'll keep playing with it!
- Picking from the 256 color pallete will likely give you colors with different brightness. This may hurt readability of darker colors on a dark background, and may make some color stand out unintentionally. Consider using something like HSLuv [1] to pick colors with the same lightness, then convert to the closest Xterm color [2].
- To make it obvious there is a gradient, I'd pick one lightness (assuming HSLuv) and one saturation (I usually stick to 100%), then pick a distance in hue for each step. For example if I expect to see a maximum of 7 steps on the screen at once, one way is to start at 0, then 30, then 60, etc. You may choose to go over 180, but keep in mind 360 will be the same as 0 so maybe stop at 240. Note how by picking adjacent colors from the table you are still picking a distance, but the distance is too small so it's hard to see.
- You may want to choose a different starting point than 0, and maybe different direction for the steps, depending on whether you want the colors to "mean" anything. For example red is commonly associated with warning, so you can arrange to have the top of the range aligned with red. Or arrange to avoid the red region if you don't want that association.
[2] https://codegolf.stackexchange.com/q/156918 <- I'm sure there are more readable ways but can't find them in a quick search
I think it depends a lot on jitter in the system. Sigfigs are one kind of error bar, one where I often have to haul my coworkers or myself out of trying to read things into the data that aren't there.
There are times where a 10ms change in a 2 second response actually matter to me, because that's half a percent and not all improvements which are easy are also straightforward. Sometimes you're scrambling for 3% here and 2.2% there. But if the noise in the system is ±50ms then people declaring that they've shaved 15ms off of response time are likely deluding themselves and then deluding the rest of us.
I know how to do some of these things by hand, I'm not sure how you automate them, or in the case of a dashboard, typeset them.
I really like the way Haskell's Criterion library formats numbers. It always displays four digits total and selects the SI prefix appropriately. I've ported the algorithm to C here, feel free to use it as an inspiration: https://gist.github.com/pkkm/629a66d47ecd16aa89e8b67ba5abd77...
Side note, it turns out that detecting what color palettes a terminal supports 24-bit colors is surprisingly fraught. There are a couple env variables that may be set...but not every terminal emulator will set them. And then you can use `tput`...but the terminal may not have correct data in the tput database. So that was fun to learn about!
Note that many terminals support 24bit (truecolor) these days.
This page goes into a bit more depth and shows an example of how one would implement a (very simple) runtime/executor: https://tokio.rs/tokio/tutorial/async
I don't know Rust. If I had to guess, this means that you've reused Rust's threads for tasks, such that they may not be done computing when a resource is available? In any event, I want to circle back to the OP, and note that runtime visualizations like this are awesome, and conversations like this is why. I personally don't think anyone spends enough time dwelling in their runtime(s), and certainly no async runtime has good visualizations, so its pretty cool to me that tokio-console is taking the lead here. I've been bullish on Rust for 5 years; maybe it's time to try it out for reals.
Instead, for async, rust implemented the ability to basically encode a functions stack frame and instruction pointer into a "normal" (but opaque) struct. What an async runtime like tokio does is (through a few levels of useful indirection that I won't talk about) store a list of these structs, and decide when it's a good idea to "call" one of them. When called, the structs either return a final value, or return a value saying "call me again later", in which case the runtime presumably puts it back into it's list of structs and calls it again sometime later.
Figuring out when to call it is left up to the runtime, but the useful ones will do things like record what operation it's waiting for and call it when that operation is ready.
Rust had (optional) user-space threads a long time ago, but that was removed in the pre-1.0 days as it added a lot of complexity and had some unavoidable performance loss even when opting for native threads (it forced dynamic dispatch on anything related to threading or I/O). There was a lot of discussion here but eventually it was declared that the OS thread scheduler was in fact perfectly capable of handling large numbers of threads and that virtual memory mapping meant the stack space allocation for each thread wasn’t a big deal and so green threads were removed.
But ultimately both of them take the "rest of the computation"/continuation and store it somewhere (i.e. on some sleeping task/callback list) to be awakened/invoked later.
Their inherent resilience to spurious wakeups is quite useful in that regard. They also work with exotic FD's, as long as those can still be registered with epoll. For example, pidfd can be polled for readability (despite any read(2) call failing with EINVAL), triggering when the corresponding process terminated.
I guess the benefit is that at least on Linux pre-io_uring, the async syscall way of doing things was via poll/select/epoll to notice when an fd unblocked, followed by waking whatever corouting/statemachine was interested in that event. It composes quite well.
However, async Rust includes an additional concept: Wakers. When your runtime (Tokio) calls poll on a future, it gives the future a waker object, and when the future is ready to continue work, something needs to call wake on the waker. Once this happens, Tokio will poll the task again soon, and Tokio wont poll tasks that have not been woken.
For example, for timers, Tokio includes a timer wheel (a sort of priority queue) with all the registered timers, and the timer wheel calls wake on the appropriate waker whenever a timer expires. Similarly with a message passing channel, the sender calls wake on the receiver's waker when a message is sent.
I actually spent a whole summer trying to do this in Crystal and I was very nearly successful, however a few low level limitations got in my way at the end of the day. In Crystal it is actually possible to do this kind of REPL if you have a perfect ability to deep marshal/copy any object, and I almost, almost got that working here: https://github.com/sam0x17/marshal
I've written some on it [0] and there was a recent reddit thread discussing it [1]
[0] https://epage.github.io/blog/2021/09/learning-rust/
[1] https://www.reddit.com/r/rust/comments/rddokp/media_most_up_...
In practice, I personally just use REPLs mostly for quick testing out of a small expression or something...and honestly, I usually just use the Rust playground (https://play.rust-lang.org/) for this. Small examples are compiled fast enough in the playground that it's kind of a REPL-like experience for testing stuff out semi-interactively...but it's not the same as connecting to a running application and running new code inside of that application. That's something that seems very difficult to add to Rust, a compiled, statically-linked language with limited support for hot reloading...
You can follow the progress of that here: https://github.com/tokio-rs/tracing/pull/1608
I believe it's currently just waiting for a crates.io release of `valuable`!
Curious to hear, in general, how it's been used.
Right now, we wanted to get the first release out and start getting people using it and collect feedback to help prioritize future development.
I've also recently started accepting donations on my personal GitHub Sponsors page (https://github.com/sponsors/hawkw) if you're interested in supporting my open-source work in particular.
This is such amazing tooling. There's so much best in class engineering going on with this language, Tokio/async runtimes, graphics, web libraries, etc.
In the screenshots I can see that tasks have descriptive names. Does anybody know how to set the name for a task? tokio::spawn doesn't take a name parameter. Does it require `tracing`?
[1]: https://github.com/tokio-rs/tokio/blob/master/tokio/src/task...
I expect the new APIs like the task builder will stabilize (no longer require the `tokio_unstable` flag) over the course of 2022.
Rust, C#, and JS all have similar concepts of async, but they're all slightly different. None of them would be trivial to adapt to other system langs like C and C++ - Rust requires compiler support to take apart async functions and put them back together as state machine, and the others lean on their GC. (And also compiler support, IIRC) I think there is a proposal to add coroutines in the new C++ standard, but I'm not sure how it would be done in the kernel.
And sometimes I see people saying, "async is very bad, just use coroutines." Having only used Lua coroutines, I don't understand what the big difference is supposed to be.
But mostly, these runtimes don't need OS-level support. Async is sort of a way to do concurrency without a kernel-level context switch for every task switch, right? If it's working so well in-process, why involve the OS at all?
> wondering if at this point we need an OS at all
Depends what you mean by OS. If you deploy in a container, of course your OS shares its kernel with the host. But for some user stories, (glares at Android) "OS" means all the software, including a Blink-based web browser, a plethora of GUI programs, and other things that I would rather call a "desktop environment" than an OS.
C# actually does the same thing, though it indeed still relies on GC.
One could imagine a similar, runtime-independent console for UMCG. Note, however, that the programming model for such a runtime would be much more similar to 1:1 threading (i.e. blocking I/O with threads) than async/await.
Per Dan Ingalls [1], an operating system is a collection of things that don't fit into a language. There shouldn't be one.
[1] Design Principles Behind Smalltalk <https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk....>
In the particular case of Smalltalk, the operational(?) semantics of which are specified by a virtual machine, one can just compile targeting the virtual machine. c.f compiling Smalltalk and Java to the Self virtual machine, or all the compilers targeting the JVM nowadays.
https://perf.wiki.kernel.org/index.php/Main_Page
As as a side note, Go and pretty good profiling tools. You can see a trace of goroutine execution and why a goroutine got scheduled out and when GC kicks in.
https://about.sourcegraph.com/go/an-introduction-to-go-tool-...
People have written unikernels, but they're not that interesting anymore when you can trivially constrain Linux to a single core, keep the remaining cores for you app (no context switching), and keep all of the Linux-y goodness for admin and debugging (SSH, gdb, etc.). All of the performance, none of the admin and deployment headaches.
Basically, unless you truly can't afford that extra core, unikernels are all downside at this point.
Actually in a way, POSIX is the missing C's runtime that wasn't made part of ISO C.
Have you considered to also expose this information in an interactive web interface? Using a zoomable timeline view (https://www.tensorflow.org/tensorboard/tensorboard_profiling...), both for after the fact analysis (taking a fixed N second trace and then inspecting it) as well as interactive visualization (automatically scrolling timeline with option to pause and scrub).
We're also thinking about factoring out the Tokio Console command-line application's internal data model and client code into its own library (https://github.com/tokio-rs/console/issues/227) to make it easier to build other UIs on top of that.
Most languages don't seem to do a lot for run-time debugging. Being able to `gdb` and step-through on a local binary is a far-cry from detailed metrics / visualizations. We end up resorting to stuff like Honeycomb, but I'm waiting for the days of a programming language built for the ground-up for runtime debugging.
Anyways, this feels like an important step in the right direction and I'm excited to try this out
These tools also have decent editor integration and can be use hand in hand:
https://blog.jetbrains.com/go/2019/04/03/profiling-go-applic...
https://blog.jetbrains.com/go/2020/03/03/how-to-find-gorouti...
I mean, Visual Studio has some kind of hot reloading, but I'm not gonna VS, I want mainstream support for this fast iteration and deep runtime debugging.
e.g. "set var i=10" ?
Or are you talking about something else?
I've just seen that Goland (the IDE) has some nice integration:
https://blog.jetbrains.com/go/2020/03/03/how-to-find-gorouti...
I've used Tracy for C++, which does similar tracing on OS-level threads, but I don't know much about Python.
Java has historically been amazing in this area, it's great to see other languages stepping up as well.
Does anyone know of a similar visualizer for coroutines/threads for async Python?
Will I be able to use console or part of it with the rest of the rust async ecosystem? (for example, async-std or futures-rs), Or is is Tokio specific?
So much great stuff!
If folks use this console thing for perf reasons and not debug reasons, then yeah, maybe cool to have in Go.
Goland (the IDE) has some nice integration:
https://blog.jetbrains.com/go/2020/03/03/how-to-find-gorouti...
I hate these ambigious HN vaguebait headlines and you should too.
https://english.stackexchange.com/questions/207014/why-was-t...
this is not a problem.
The idea is great it's the way to consume the information that I think misses the point.
> It gives you a live, easy-to-navigate view into the program's tasks and resources, summarizing both their current status and their historical behavior.