Skip to content

Commit 903d9fa

Browse files
authored
feat(console): replace target column with kind column in tasks view (#478)
In the `tokio-console` tasks view, there are a fixed set of columns and any remaining fields are included in a "Fields" column at the end. One of the fields which is always present on a task, but doesn't receive a dedicated column is the kind field, which currently takes one of the following values: * `task` (a "normal" async task) * `blocking` * `block_on` * `local` Meanwhile, there is a dedicated column for the task span's target, which currently takes one of the following values: * `tokio::task` * `tokio::task::blocking` The target for tasks with kind `block_on` and `local` is also `tokio::task`. This change replaces the target column with a kind column as it provides more information in fewer characters. The target value is moved (somewhat artificially) to the fields which appear in the final column. The `target` is also left on the `state::Task` struct as we expect to want to filter by it in the future. Additionally, the `console-subscriber` examples have been updated so that there are options to visualize `blocking`, `block_on`, and `local` tasks. The `app` example has been updated to include an optional task which calls `tokio::spawn_blocking`. A new example `local` has been added which creates a `LocalSet` and spawns local tasks onto it.
1 parent 6dd661d commit 903d9fa

File tree

6 files changed

+116
-32
lines changed

6 files changed

+116
-32
lines changed

console-subscriber/examples/app.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
4545
.spawn(no_yield(20))
4646
.unwrap();
4747
}
48+
"blocking" => {
49+
tokio::task::Builder::new()
50+
.name("spawns_blocking")
51+
.spawn(spawn_blocking(5))
52+
.unwrap();
53+
}
4854
"help" | "-h" => {
4955
eprintln!("{}", HELP);
5056
return Ok(());
@@ -135,3 +141,14 @@ async fn no_yield(seconds: u64) {
135141
_ = handle.await;
136142
}
137143
}
144+
145+
#[tracing::instrument]
146+
async fn spawn_blocking(seconds: u64) {
147+
loop {
148+
let seconds = seconds;
149+
_ = tokio::task::spawn_blocking(move || {
150+
std::thread::sleep(Duration::from_secs(seconds));
151+
})
152+
.await;
153+
}
154+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Local tasks
2+
//!
3+
//! This example shows the instrumentation on local tasks. Tasks spawned onto a
4+
//! `LocalSet` with `spawn_local` have the kind `local` in `tokio-console`.
5+
//!
6+
//! Additionally, because the `console-subscriber` is initialized before the
7+
//! tokio runtime is created, we will also see the `block_on` kind task.
8+
use std::time::Duration;
9+
use tokio::{runtime, task};
10+
11+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
12+
console_subscriber::init();
13+
14+
let rt = runtime::Builder::new_current_thread()
15+
.enable_all()
16+
.build()
17+
.unwrap();
18+
let local = task::LocalSet::new();
19+
local.block_on(&rt, async {
20+
loop {
21+
let mut join_handles = Vec::new();
22+
for _ in 0..10 {
23+
let jh = task::spawn_local(async {
24+
tokio::time::sleep(Duration::from_millis(100)).await;
25+
26+
std::thread::sleep(Duration::from_millis(100));
27+
});
28+
join_handles.push(jh);
29+
}
30+
31+
for jh in join_handles {
32+
_ = jh.await;
33+
}
34+
}
35+
});
36+
37+
Ok(())
38+
}

tokio-console/src/intern.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,17 @@ pub(crate) struct Strings {
2121
pub(crate) struct InternedStr(Rc<String>);
2222

2323
impl Strings {
24-
// NOTE(elzia): currently, we never need to use this, but we can always
25-
// uncomment it if we do...
26-
27-
// pub(crate) fn string_ref<Q>(&mut self, string: &Q) -> InternedStr
28-
// where
29-
// InternedStr: Borrow<Q>,
30-
// Q: Hash + Eq + ToOwned<Owned = String>,
31-
// {
32-
// if let Some(s) = self.strings.get(string) {
33-
// return s.clone();
34-
// }
35-
36-
// self.insert(string.to_owned())
37-
// }
24+
pub(crate) fn string_ref<Q>(&mut self, string: &Q) -> InternedStr
25+
where
26+
InternedStr: Borrow<Q>,
27+
Q: Hash + Eq + ToOwned<Owned = String> + ?Sized,
28+
{
29+
if let Some(s) = self.strings.get(string) {
30+
return s.clone();
31+
}
32+
33+
self.insert(string.to_owned())
34+
}
3835

3936
pub(crate) fn string(&mut self, string: String) -> InternedStr {
4037
if let Some(s) = self.strings.get(&string) {

tokio-console/src/state/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,15 @@ impl Metadata {
274274

275275
impl Field {
276276
const SPAWN_LOCATION: &'static str = "spawn.location";
277+
const KIND: &'static str = "kind";
277278
const NAME: &'static str = "task.name";
278279
const TASK_ID: &'static str = "task.id";
279280

281+
/// Creates a new Field with a pre-interned `name` and a `FieldValue`.
282+
fn new(name: InternedStr, value: FieldValue) -> Self {
283+
Field { name, value }
284+
}
285+
280286
/// Converts a wire-format `Field` into an internal `Field` representation,
281287
/// using the provided `Metadata` for the task span that the field came
282288
/// from.

tokio-console/src/state/tasks.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,22 @@ pub(crate) struct Task {
8888
span_id: SpanId,
8989
/// A cached string representation of the Id for display purposes.
9090
id_str: String,
91+
/// A precomputed short description string used in the async ops table
9192
short_desc: InternedStr,
93+
/// Fields that don't have their own column, pre-formatted
9294
formatted_fields: Vec<Vec<Span<'static>>>,
95+
/// The task statistics that are updated over the lifetime of the task
9396
stats: TaskStats,
97+
/// The target of the span representing the task
9498
target: InternedStr,
99+
/// The name of the task (when `tokio::task::Builder` is used)
95100
name: Option<InternedStr>,
96101
/// Currently active warnings for this task.
97102
warnings: Vec<Linter<Task>>,
103+
/// The source file and line number the task was spawned from
98104
location: String,
105+
/// The kind of task, currently one of task, blocking, block_on, local
106+
kind: InternedStr,
99107
}
100108

101109
#[derive(Debug)]
@@ -171,25 +179,38 @@ impl TasksState {
171179
};
172180
let mut name = None;
173181
let mut task_id = None;
182+
let mut kind = strings.string(String::new());
183+
let target_field = Field::new(
184+
strings.string_ref("target"),
185+
FieldValue::Str(meta.target.to_string()),
186+
);
174187
let mut fields = task
175188
.fields
176189
.drain(..)
177190
.filter_map(|pb| {
178191
let field = Field::from_proto(pb, meta, strings)?;
179192
// the `task.name` field gets its own column, if it's present.
180-
if &*field.name == Field::NAME {
181-
name = Some(strings.string(field.value.to_string()));
182-
return None;
193+
match &*field.name {
194+
Field::NAME => {
195+
name = Some(strings.string(field.value.to_string()));
196+
None
197+
}
198+
Field::TASK_ID => {
199+
task_id = match field.value {
200+
FieldValue::U64(id) => Some(id as TaskId),
201+
_ => None,
202+
};
203+
None
204+
}
205+
Field::KIND => {
206+
kind = strings.string(field.value.to_string());
207+
None
208+
}
209+
_ => Some(field),
183210
}
184-
if &*field.name == Field::TASK_ID {
185-
task_id = match field.value {
186-
FieldValue::U64(id) => Some(id as TaskId),
187-
_ => None,
188-
};
189-
return None;
190-
}
191-
Some(field)
192211
})
212+
// We wish to include the target in the fields as we won't give it a dedicated column.
213+
.chain([target_field])
193214
.collect::<Vec<_>>();
194215

195216
let formatted_fields = Field::make_formatted(styles, &mut fields);
@@ -220,6 +241,7 @@ impl TasksState {
220241
target: meta.target.clone(),
221242
warnings: Vec::new(),
222243
location,
244+
kind,
223245
};
224246
if let TaskLintResult::RequiresRecheck = task.lint(linters) {
225247
next_pending_lint.insert(task.id);
@@ -307,6 +329,10 @@ impl Task {
307329
&self.target
308330
}
309331

332+
pub(crate) fn kind(&self) -> &str {
333+
&self.kind
334+
}
335+
310336
pub(crate) fn short_desc(&self) -> &str {
311337
&self.short_desc
312338
}

tokio-console/src/view/tasks.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl TableList<12> for TasksTable {
2626
type Context = ();
2727

2828
const HEADER: &'static [&'static str; 12] = &[
29-
"Warn", "ID", "State", "Name", "Total", "Busy", "Sched", "Idle", "Polls", "Target",
29+
"Warn", "ID", "State", "Name", "Total", "Busy", "Sched", "Idle", "Polls", "Kind",
3030
"Location", "Fields",
3131
];
3232

@@ -78,15 +78,15 @@ impl TableList<12> for TasksTable {
7878
let mut id_width = view::Width::new(Self::WIDTHS[1] as u16);
7979
let mut name_width = view::Width::new(Self::WIDTHS[3] as u16);
8080
let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16);
81-
let mut target_width = view::Width::new(Self::WIDTHS[8] as u16);
81+
let mut kind_width = view::Width::new(Self::WIDTHS[8] as u16);
8282
let mut location_width = view::Width::new(Self::WIDTHS[9] as u16);
8383

8484
let mut num_idle = 0;
8585
let mut num_running = 0;
8686

8787
let rows = {
8888
let id_width = &mut id_width;
89-
let target_width = &mut target_width;
89+
let kind_width = &mut kind_width;
9090
let location_width = &mut location_width;
9191
let name_width = &mut name_width;
9292
let polls_width = &mut polls_width;
@@ -134,7 +134,7 @@ impl TableList<12> for TasksTable {
134134
dur_cell(task.scheduled(now)),
135135
dur_cell(task.idle(now)),
136136
Cell::from(polls_width.update_str(task.total_polls().to_string())),
137-
Cell::from(target_width.update_str(task.target()).to_owned()),
137+
Cell::from(kind_width.update_str(task.kind()).to_owned()),
138138
Cell::from(location_width.update_str(task.location()).to_owned()),
139139
Cell::from(Spans::from(
140140
task.formatted_fields()
@@ -186,7 +186,7 @@ impl TableList<12> for TasksTable {
186186
Span::from(format!(" Idle ({})", num_idle)),
187187
]);
188188

189-
/* TODO: use this to adjust the max size of name and target columns...
189+
/* TODO: use this to adjust the max size of name and kind columns...
190190
// How many characters wide are the fixed-length non-field columns?
191191
let fixed_col_width = id_width.chars()
192192
+ STATE_LEN
@@ -195,7 +195,7 @@ impl TableList<12> for TasksTable {
195195
+ DUR_LEN as u16
196196
+ DUR_LEN as u16
197197
+ POLLS_LEN as u16
198-
+ target_width.chars();
198+
+ kind_width.chars();
199199
*/
200200
let warnings = state
201201
.tasks_state()
@@ -257,7 +257,7 @@ impl TableList<12> for TasksTable {
257257
layout::Constraint::Length(DUR_LEN as u16),
258258
layout::Constraint::Length(DUR_LEN as u16),
259259
polls_width.constraint(),
260-
target_width.constraint(),
260+
kind_width.constraint(),
261261
location_width.constraint(),
262262
fields_width,
263263
];

0 commit comments

Comments
 (0)