From db7554a018c414d79ca7b21a979a573919b50c55 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 13 Mar 2023 22:50:03 +0100 Subject: [PATCH 1/7] feat(console): use tokio task ids in task views 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 --- tokio-console/src/state/mod.rs | 1 + tokio-console/src/state/tasks.rs | 35 ++++++++++++++++++++++++++++++-- tokio-console/src/view/tasks.rs | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) 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..0d798e27b 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,6 +76,11 @@ 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. + /// + /// This Id may not be unique if there are multiple runtimes in the + /// remote process. + 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. @@ -147,6 +163,7 @@ impl TasksState { } }; let mut name = None; + let mut task_id = None; let mut fields = task .fields .drain(..) @@ -157,6 +174,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::>(); @@ -178,6 +202,7 @@ impl TasksState { let mut task = Task { name, id, + task_id, span_id, short_desc, formatted_fields, @@ -241,6 +266,10 @@ impl Task { self.id } + pub(crate) fn task_id(&self) -> Option { + self.task_id + } + pub(crate) fn span_id(&self) -> SpanId { self.span_id } @@ -426,7 +455,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.map(|id| 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..27f812358 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.task_id().map(|id| id.to_string()).unwrap_or_default(), width = id_width.chars() as usize ))), Cell::from(task.state().render(styles)), From e96be291632ca41e36323367bca3ebb01ea9ced8 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 13 Mar 2023 23:27:01 +0100 Subject: [PATCH 2/7] update clippy lint name --- console-api/src/common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From c5e9d69bf975b6b1fb6feac7283fea197c11556d Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 20 Mar 2023 10:41:14 +0100 Subject: [PATCH 3/7] Remove identity map --- tokio-console/src/state/tasks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 0d798e27b..215dfac24 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -456,7 +456,7 @@ 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().task_id.map(|id| id)) + task.upgrade().map(|t| t.borrow().task_id) }), Self::Name => { tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().name.clone())) From 5e52bc520724e9e74ddbd0eca81748de78bec154 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 20 Mar 2023 10:48:24 +0100 Subject: [PATCH 4/7] And the formatting for that last change --- tokio-console/src/state/tasks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 215dfac24..528d45b3c 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -455,9 +455,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().task_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())) } From 2f2332e8ae861b42528b783b98898af84b2bf611 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Wed, 22 Mar 2023 22:46:51 +0100 Subject: [PATCH 5/7] Updated the task-id used in the async op table --- tokio-console/src/state/tasks.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 528d45b3c..be1eb2e3a 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -194,9 +194,11 @@ 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) => format!("{}", task_id), + (None, Some(name)) => format!("({})", name), + (None, None) => "".to_owned(), }); let mut task = Task { From a14fb752bd23b74a3f86c5e7833f143657c1be0f Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 27 Mar 2023 23:23:21 +0200 Subject: [PATCH 6/7] Apply Eliza's suggestions from code review Co-authored-by: Eliza Weisman --- tokio-console/src/state/tasks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index be1eb2e3a..5d5c4ab01 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -195,9 +195,9 @@ impl TasksState { let id = ids.id_for(span_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) => format!("{}", task_id), - (None, Some(name)) => format!("({})", name), + (Some(task_id), Some(name)) => format!("{task_id} ({name})"), + (Some(task_id), None) => task_id.to_string(), + (None, Some(name)) => name.to_owned() (None, None) => "".to_owned(), }); From d2e00c1c0a66c9f98934c35bf5ebcf13be88cc2a Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 27 Mar 2023 23:52:23 +0200 Subject: [PATCH 7/7] Keep cached version of task_id string for use in display --- tokio-console/src/state/tasks.rs | 16 ++++++++-------- tokio-console/src/view/tasks.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 5d5c4ab01..1c75453ca 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -77,14 +77,13 @@ pub(crate) struct Task { /// remote. id: Id, /// The `tokio::task::Id` in the remote tokio runtime. - /// - /// This Id may not be unique if there are multiple runtimes in the - /// remote process. 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, @@ -197,7 +196,7 @@ impl TasksState { 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.to_owned() + (None, Some(name)) => name.as_ref().to_owned(), (None, None) => "".to_owned(), }); @@ -206,6 +205,7 @@ impl TasksState { id, task_id, span_id, + id_str: task_id.map(|id| id.to_string()).unwrap_or_default(), short_desc, formatted_fields, stats, @@ -268,14 +268,14 @@ impl Task { self.id } - pub(crate) fn task_id(&self) -> Option { - self.task_id - } - pub(crate) fn span_id(&self) -> SpanId { self.span_id } + pub(crate) fn id_str(&self) -> &str { + &self.id_str + } + pub(crate) fn target(&self) -> &str { &self.target } diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index 27f812358..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.task_id().map(|id| id.to_string()).unwrap_or_default(), + task.id_str(), width = id_width.chars() as usize ))), Cell::from(task.state().render(styles)),