Skip to content

Commit c13085e

Browse files
hdshawkw
authored andcommitted
feat(console): reduce decimal digits in UI (#402)
The durations in the tokio-console UI are shown with a unit, so the digits after the decimal separator are generally not relevant. Since space is at a premium in a terminal UI, it makes sense to cut down where possible. This change removes all digits after the decimal separator in the tasks table view. In the task detail view, 2 decimal places are kept. Additionally, 4 new duration formats are added to represent minutes-with-secondsi, hours-with-minutes, days-with-hours and days. These are only applied once the leading unit is greater than zero. For example, 59 seconds will be shown as seconds: `99s` and then 60 seconds will be shown as minutes-with-seconds: `1m00s`. New colors have been chosen for each format. NOTE: I had to make a small unrelated change in `task::Details::make_percentiles_widget` to make clippy happy. Fixes: #224
1 parent bbb8f25 commit c13085e

File tree

6 files changed

+143
-63
lines changed

6 files changed

+143
-63
lines changed

tokio-console/src/view/async_ops.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
view::{
88
self, bold,
99
table::{self, TableList, TableListState},
10-
DUR_LEN, DUR_PRECISION,
10+
DUR_LEN, DUR_TABLE_PRECISION,
1111
},
1212
};
1313

@@ -106,12 +106,7 @@ impl TableList<9> for AsyncOpsTable {
106106
let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16);
107107

108108
let dur_cell = |dur: std::time::Duration| -> Cell<'static> {
109-
Cell::from(styles.time_units(format!(
110-
"{:>width$.prec$?}",
111-
dur,
112-
width = DUR_LEN,
113-
prec = DUR_PRECISION,
114-
)))
109+
Cell::from(styles.time_units(dur, DUR_TABLE_PRECISION, Some(DUR_LEN)))
115110
};
116111

117112
let rows = {

tokio-console/src/view/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ mod tasks;
1818
pub(crate) use self::styles::{Palette, Styles};
1919
pub(crate) use self::table::SortBy;
2020

21-
const DUR_LEN: usize = 10;
21+
const DUR_LEN: usize = 6;
2222
// This data is only updated every second, so it doesn't make a ton of
2323
// sense to have a lot of precision in timestamps (and this makes sure
2424
// there's room for the unit!)
25-
const DUR_PRECISION: usize = 4;
25+
const DUR_LIST_PRECISION: usize = 2;
26+
const DUR_TABLE_PRECISION: usize = 0;
2627
const TABLE_HIGHLIGHT_SYMBOL: &str = ">> ";
2728

2829
pub struct View {

tokio-console/src/view/resources.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
view::{
77
self, bold,
88
table::{self, TableList, TableListState},
9-
DUR_LEN, DUR_PRECISION,
9+
DUR_LEN, DUR_TABLE_PRECISION,
1010
},
1111
};
1212

@@ -104,12 +104,11 @@ impl TableList<9> for ResourcesTable {
104104
))),
105105
Cell::from(parent_width.update_str(resource.parent_id()).to_owned()),
106106
Cell::from(kind_width.update_str(resource.kind()).to_owned()),
107-
Cell::from(styles.time_units(format!(
108-
"{:>width$.prec$?}",
107+
Cell::from(styles.time_units(
109108
resource.total(now),
110-
width = DUR_LEN,
111-
prec = DUR_PRECISION,
112-
))),
109+
DUR_TABLE_PRECISION,
110+
Some(DUR_LEN),
111+
)),
113112
Cell::from(target_width.update_str(resource.target()).to_owned()),
114113
Cell::from(type_width.update_str(resource.concrete_type()).to_owned()),
115114
Cell::from(resource.type_visibility().render(styles)),

tokio-console/src/view/styles.rs

+116-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::config;
22
use serde::{Deserialize, Serialize};
3-
use std::{borrow::Cow, str::FromStr};
3+
use std::{str::FromStr, time::Duration};
44
use tui::{
55
style::{Color, Modifier, Style},
66
text::Span,
@@ -32,11 +32,41 @@ pub enum Palette {
3232
All,
3333
}
3434

35+
/// Represents formatted time spans.
36+
///
37+
/// Distinguishing between different units allows appropriate colouring.
38+
enum FormattedDuration {
39+
/// Days (and no minor unit), e.g. `102d`
40+
Days(String),
41+
/// Days with hours, e.g. `12d03h`
42+
DaysHours(String),
43+
/// Hours with minutes, e.g. `14h32m`
44+
HoursMinutes(String),
45+
/// Minutes with seconds, e.g. `43m02s`
46+
MinutesSeconds(String),
47+
/// The `time::Duration` debug string which uses units ranging from
48+
/// picoseconds (`ps`) to seconds (`s`). May contain decimal digits
49+
/// (e.g. `628.76ms`) or not (e.g. `32ns`)
50+
Debug(String),
51+
}
52+
53+
impl FormattedDuration {
54+
fn into_inner(self) -> String {
55+
match self {
56+
Self::Days(inner) => inner,
57+
Self::DaysHours(inner) => inner,
58+
Self::HoursMinutes(inner) => inner,
59+
Self::MinutesSeconds(inner) => inner,
60+
Self::Debug(inner) => inner,
61+
}
62+
}
63+
}
64+
3565
fn fg_style(color: Color) -> Style {
3666
Style::default().fg(color)
3767
}
3868

39-
// === impl Config ===
69+
// === impl Styles ===
4070

4171
impl Styles {
4272
pub fn from_config(config: config::ViewOptions) -> Self {
@@ -126,39 +156,100 @@ impl Styles {
126156
}
127157
}
128158

129-
pub fn time_units<'a>(&self, text: impl Into<Cow<'a, str>>) -> Span<'a> {
130-
let mut text = text.into();
131-
if !self.toggles.color_durations() {
132-
return Span::raw(text);
133-
}
159+
/// Creates a span with a formatted duration inside.
160+
///
161+
/// The formatted duration will be colored depending on the palette
162+
/// defined for this `Styles` object.
163+
///
164+
/// If the `width` parameter is `None` then no padding will be
165+
/// added. Otherwise the text in the span will be left-padded to
166+
/// the specified width (right aligned). Passing `Some(0)` is
167+
/// equivalent to `None`.
168+
pub fn time_units<'a>(&self, dur: Duration, prec: usize, width: Option<usize>) -> Span<'a> {
169+
let formatted = self.duration_text(dur, width.unwrap_or(0), prec);
134170

135-
if !self.utf8 {
136-
if let Some(mu_offset) = text.find("µs") {
137-
text.to_mut().replace_range(mu_offset.., "us");
138-
}
171+
if !self.toggles.color_durations() {
172+
return Span::raw(formatted.into_inner());
139173
}
140174

141175
let style = match self.palette {
142-
Palette::NoColors => return Span::raw(text),
143-
Palette::Ansi8 | Palette::Ansi16 => match text.as_ref() {
144-
s if s.ends_with("ps") => fg_style(Color::Blue),
145-
s if s.ends_with("ns") => fg_style(Color::Green),
146-
s if s.ends_with("µs") || s.ends_with("us") => fg_style(Color::Yellow),
147-
s if s.ends_with("ms") => fg_style(Color::Red),
148-
s if s.ends_with('s') => fg_style(Color::Magenta),
176+
Palette::NoColors => return Span::raw(formatted.into_inner()),
177+
Palette::Ansi8 | Palette::Ansi16 => match &formatted {
178+
FormattedDuration::Days(_) => fg_style(Color::Blue),
179+
FormattedDuration::DaysHours(_) => fg_style(Color::Blue),
180+
FormattedDuration::HoursMinutes(_) => fg_style(Color::Cyan),
181+
FormattedDuration::MinutesSeconds(_) => fg_style(Color::Green),
182+
FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Gray),
183+
FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Gray),
184+
FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => {
185+
fg_style(Color::Magenta)
186+
}
187+
FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Red),
188+
FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Yellow),
149189
_ => Style::default(),
150190
},
151-
Palette::Ansi256 | Palette::All => match text.as_ref() {
152-
s if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3
153-
s if s.ends_with("ns") => fg_style(Color::Indexed(41)), // spring green 3
154-
s if s.ends_with("µs") || s.ends_with("us") => fg_style(Color::Indexed(42)), // spring green 2
155-
s if s.ends_with("ms") => fg_style(Color::Indexed(43)), // cyan 3
156-
s if s.ends_with('s') => fg_style(Color::Indexed(44)), // dark turquoise,
191+
Palette::Ansi256 | Palette::All => match &formatted {
192+
FormattedDuration::Days(_) => fg_style(Color::Indexed(33)), // dodger blue 1
193+
FormattedDuration::DaysHours(_) => fg_style(Color::Indexed(33)), // dodger blue 1
194+
FormattedDuration::HoursMinutes(_) => fg_style(Color::Indexed(39)), // deep sky blue 1
195+
FormattedDuration::MinutesSeconds(_) => fg_style(Color::Indexed(45)), // turquoise 2
196+
FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3
197+
FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Indexed(41)), // spring green 3
198+
FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => {
199+
fg_style(Color::Indexed(42))
200+
} // spring green 2
201+
FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Indexed(43)), // cyan 3
202+
FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Indexed(44)), // dark turquoise,
157203
_ => Style::default(),
158204
},
159205
};
160206

161-
Span::styled(text, style)
207+
Span::styled(formatted.into_inner(), style)
208+
}
209+
210+
fn duration_text(&self, dur: Duration, width: usize, prec: usize) -> FormattedDuration {
211+
let secs = dur.as_secs();
212+
213+
if secs >= 60 * 60 * 24 * 100 {
214+
let days = secs / (60 * 60 * 24);
215+
FormattedDuration::Days(format!("{days:>width$}d", days = days, width = width))
216+
} else if secs >= 60 * 60 * 24 {
217+
let hours = secs / (60 * 60);
218+
FormattedDuration::DaysHours(format!(
219+
"{days:>leading_width$}d{hours:02.0}h",
220+
days = hours / 24,
221+
hours = hours % 24,
222+
// Subtract the known 4 characters that trail the days value.
223+
leading_width = width.saturating_sub(4),
224+
))
225+
} else if secs >= 60 * 60 {
226+
let mins = secs / 60;
227+
FormattedDuration::HoursMinutes(format!(
228+
"{hours:>leading_width$}h{minutes:02.0}m",
229+
hours = mins / 60,
230+
minutes = mins % 60,
231+
// Subtract the known 4 characters that trail the hours value.
232+
leading_width = width.saturating_sub(4),
233+
))
234+
} else if secs >= 60 {
235+
FormattedDuration::MinutesSeconds(format!(
236+
"{minutes:>leading_width$}m{seconds:02.0}s",
237+
minutes = secs / 60,
238+
seconds = secs % 60,
239+
// Subtract the known 4 characters that trail the minutes value.
240+
leading_width = width.saturating_sub(4),
241+
))
242+
} else {
243+
let mut text = format!("{:>width$.prec$?}", dur, width = width, prec = prec);
244+
245+
if !self.utf8 {
246+
if let Some(mu_offset) = text.find("µs") {
247+
text.replace_range(mu_offset.., "us");
248+
}
249+
}
250+
251+
FormattedDuration::Debug(text)
252+
}
162253
}
163254

164255
pub fn terminated(&self) -> Style {

tokio-console/src/view/task.rs

+15-16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
view::{
1010
self, bold,
1111
mini_histogram::{HistogramMetadata, MiniHistogram},
12+
DUR_LIST_PRECISION,
1213
},
1314
};
1415
use std::{
@@ -329,29 +330,27 @@ impl Details {
329330
fn make_percentiles_widget(&self, styles: &view::Styles) -> Text<'static> {
330331
let mut text = Text::default();
331332
let histogram = self.poll_times_histogram();
332-
let percentiles =
333-
histogram
334-
.iter()
335-
.flat_map(|&DurationHistogram { ref histogram, .. }| {
336-
let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64]
337-
.iter()
338-
.map(move |i| (*i, histogram.value_at_percentile(*i)));
339-
pairs.map(|pair| {
340-
Spans::from(vec![
341-
bold(format!("p{:>2}: ", pair.0)),
342-
dur(styles, Duration::from_nanos(pair.1)),
343-
])
344-
})
345-
});
333+
let percentiles = histogram
334+
.iter()
335+
.flat_map(|&DurationHistogram { histogram, .. }| {
336+
let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64]
337+
.iter()
338+
.map(move |i| (*i, histogram.value_at_percentile(*i)));
339+
pairs.map(|pair| {
340+
Spans::from(vec![
341+
bold(format!("p{:>2}: ", pair.0)),
342+
dur(styles, Duration::from_nanos(pair.1)),
343+
])
344+
})
345+
});
346346
text.extend(percentiles);
347347
text
348348
}
349349
}
350350

351351
fn dur(styles: &view::Styles, dur: std::time::Duration) -> Span<'static> {
352-
const DUR_PRECISION: usize = 4;
353352
// TODO(eliza): can we not have to use `format!` to make a string here? is
354353
// there a way to just give TUI a `fmt::Debug` implementation, or does it
355354
// have to be given a string in order to do layout stuff?
356-
styles.time_units(format!("{:.prec$?}", dur, prec = DUR_PRECISION))
355+
styles.time_units(dur, DUR_LIST_PRECISION, None)
357356
}

tokio-console/src/view/tasks.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
view::{
77
self, bold,
88
table::{self, TableList, TableListState},
9-
DUR_LEN, DUR_PRECISION,
9+
DUR_LEN, DUR_TABLE_PRECISION,
1010
},
1111
};
1212
use tui::{
@@ -68,12 +68,7 @@ impl TableList<11> for TasksTable {
6868
.sort(now, &mut table_list_state.sorted_items);
6969

7070
let dur_cell = |dur: std::time::Duration| -> Cell<'static> {
71-
Cell::from(styles.time_units(format!(
72-
"{:>width$.prec$?}",
73-
dur,
74-
width = DUR_LEN,
75-
prec = DUR_PRECISION,
76-
)))
71+
Cell::from(styles.time_units(dur, DUR_TABLE_PRECISION, Some(DUR_LEN)))
7772
};
7873

7974
// Start out wide enough to display the column headers...

0 commit comments

Comments
 (0)