From f5b06d2854c0a638aac7ce48a7a2eeaef615e9b9 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 28 Mar 2023 18:27:53 +0200 Subject: [PATCH] feat(console): use tokio task ids in task views (#403) Tokio Console generates its own sequential Id for internal tracking and indexing of objects (tasks, resources, etc.). However, this Id will be recreated if Console is restarted. In order to provide more useful information to the user, the task Id generated by Tokio can be used in the task list and task details screens instead. If used in this way, the ID field in the task list and task detail views will be stable across restarts of Console (assuming the monitored application is not restarted). This also frees up horizontal space, as the `task.id` attribute doesn't need to be displayed separately. The disadvantage of using Tokio's task Id is that it is not guaranteed to be present by the type system. To avoid problems with missing task Ids, the Tokio task Id is store in addition to the `span::Id` (used to communicate with the `console-subscriber`) and the sequential console `Id` (used in the `store`). If a task is missing the `task.id` field for whatever reason it will still appear, but with an empty ID. If multiple runtimes are active, then duplicate ID values will appear. Fixes: #385 Co-authored-by: Eliza Weisman --- console-api/src/common.rs | 2 +- tokio-console/src/state/mod.rs | 1 + tokio-console/src/state/tasks.rs | 43 ++++++++++++++++++++++++++++---- tokio-console/src/view/tasks.rs | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/console-api/src/common.rs b/console-api/src/common.rs index cb6edf73c..732479f49 100644 --- a/console-api/src/common.rs +++ b/console-api/src/common.rs @@ -210,7 +210,7 @@ impl From<&dyn std::fmt::Debug> for field::Value { // or vice versa. However, this is unavoidable here, because `prost` generates // a struct with `#[derive(PartialEq)]`, but we cannot add`#[derive(Hash)]` to the // generated code. -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] impl Hash for field::Name { fn hash(&self, state: &mut H) { match self { diff --git a/tokio-console/src/state/mod.rs b/tokio-console/src/state/mod.rs index eb96606af..1458a1065 100644 --- a/tokio-console/src/state/mod.rs +++ b/tokio-console/src/state/mod.rs @@ -271,6 +271,7 @@ impl Metadata { impl Field { const SPAWN_LOCATION: &'static str = "spawn.location"; const NAME: &'static str = "task.name"; + const TASK_ID: &'static str = "task.id"; /// Converts a wire-format `Field` into an internal `Field` representation, /// using the provided `Metadata` for the task span that the field came diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 8be120d8f..1c75453ca 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -5,7 +5,7 @@ use crate::{ histogram::DurationHistogram, pb_duration, store::{self, Id, SpanId, Store}, - Field, Metadata, Visibility, + Field, FieldValue, Metadata, Visibility, }, util::Percentage, view, @@ -58,6 +58,17 @@ pub(crate) enum TaskState { pub(crate) type TaskRef = store::Ref; +/// The Id for a Tokio task. +/// +/// This should be equivalent to [`tokio::task::Id`], which can't be +/// used because it's not possible to construct outside the `tokio` +/// crate. +/// +/// Within the context of `tokio-console`, we don't depend on it +/// being the same as Tokio's own type, as the task id is recorded +/// as a `u64` in tracing and then sent via the wire protocol as such. +pub(crate) type TaskId = u64; + #[derive(Debug)] pub(crate) struct Task { /// The task's pretty (console-generated, sequential) task ID. @@ -65,10 +76,14 @@ pub(crate) struct Task { /// This is NOT the `tracing::span::Id` for the task's tracing span on the /// remote. id: Id, + /// The `tokio::task::Id` in the remote tokio runtime. + task_id: Option, /// The `tracing::span::Id` on the remote process for this task's span. /// /// This is used when requesting a task details stream. span_id: SpanId, + /// A cached string representation of the Id for display purposes. + id_str: String, short_desc: InternedStr, formatted_fields: Vec>>, stats: TaskStats, @@ -147,6 +162,7 @@ impl TasksState { } }; let mut name = None; + let mut task_id = None; let mut fields = task .fields .drain(..) @@ -157,6 +173,13 @@ impl TasksState { name = Some(strings.string(field.value.to_string())); return None; } + if &*field.name == Field::TASK_ID { + task_id = match field.value { + FieldValue::U64(id) => Some(id as TaskId), + _ => None, + }; + return None; + } Some(field) }) .collect::>(); @@ -170,15 +193,19 @@ impl TasksState { // remap the server's ID to a pretty, sequential task ID let id = ids.id_for(span_id); - let short_desc = strings.string(match name.as_ref() { - Some(name) => format!("{} ({})", id, name), - None => format!("{}", id), + let short_desc = strings.string(match (task_id, name.as_ref()) { + (Some(task_id), Some(name)) => format!("{task_id} ({name})"), + (Some(task_id), None) => task_id.to_string(), + (None, Some(name)) => name.as_ref().to_owned(), + (None, None) => "".to_owned(), }); let mut task = Task { name, id, + task_id, span_id, + id_str: task_id.map(|id| id.to_string()).unwrap_or_default(), short_desc, formatted_fields, stats, @@ -245,6 +272,10 @@ impl Task { self.span_id } + pub(crate) fn id_str(&self) -> &str { + &self.id_str + } + pub(crate) fn target(&self) -> &str { &self.target } @@ -426,7 +457,9 @@ impl Default for SortBy { impl SortBy { pub fn sort(&self, now: SystemTime, tasks: &mut [Weak>]) { match self { - Self::Tid => tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().id)), + Self::Tid => { + tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().task_id)) + } Self::Name => { tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().name.clone())) } diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index 780f4775c..d103779ab 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -122,7 +122,7 @@ impl TableList<11> for TasksTable { warnings, Cell::from(id_width.update_str(format!( "{:>width$}", - task.id(), + task.id_str(), width = id_width.chars() as usize ))), Cell::from(task.state().render(styles)),