From 59953761286f9a077b97e1b2801b2d0fd4436d36 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 2 Mar 2023 23:30:37 +0100 Subject: [PATCH 1/5] fix(console): reduce decimal digits in UI 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 --- tokio-console/src/view/async_ops.rs | 9 +- tokio-console/src/view/mod.rs | 5 +- tokio-console/src/view/resources.rs | 11 ++- tokio-console/src/view/styles.rs | 126 ++++++++++++++++++++++------ tokio-console/src/view/task.rs | 31 ++++--- tokio-console/src/view/tasks.rs | 9 +- 6 files changed, 129 insertions(+), 62 deletions(-) diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 7e804cfeb..0cdf02162 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -7,7 +7,7 @@ use crate::{ view::{ self, bold, table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -106,12 +106,7 @@ impl TableList<9> for AsyncOpsTable { let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16); let dur_cell = |dur: std::time::Duration| -> Cell<'static> { - Cell::from(styles.time_units(format!( - "{:>width$.prec$?}", - dur, - width = DUR_LEN, - prec = DUR_PRECISION, - ))) + Cell::from(styles.time_units(dur, DUR_TABLE_PRECISION, Some(DUR_LEN))) }; let rows = { diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index 8f1d5429d..9f50d5da7 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -18,11 +18,12 @@ mod tasks; pub(crate) use self::styles::{Palette, Styles}; pub(crate) use self::table::SortBy; -const DUR_LEN: usize = 10; +const DUR_LEN: usize = 6; // This data is only updated every second, so it doesn't make a ton of // sense to have a lot of precision in timestamps (and this makes sure // there's room for the unit!) -const DUR_PRECISION: usize = 4; +const DUR_LIST_PRECISION: usize = 2; +const DUR_TABLE_PRECISION: usize = 0; const TABLE_HIGHLIGHT_SYMBOL: &str = ">> "; pub struct View { diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index 4b65b00e8..afe9fc1f6 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -6,7 +6,7 @@ use crate::{ view::{ self, bold, table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -104,12 +104,11 @@ impl TableList<9> for ResourcesTable { ))), Cell::from(parent_width.update_str(resource.parent_id()).to_owned()), Cell::from(kind_width.update_str(resource.kind()).to_owned()), - Cell::from(styles.time_units(format!( - "{:>width$.prec$?}", + Cell::from(styles.time_units( resource.total(now), - width = DUR_LEN, - prec = DUR_PRECISION, - ))), + DUR_TABLE_PRECISION, + Some(DUR_LEN), + )), Cell::from(target_width.update_str(resource.target()).to_owned()), Cell::from(type_width.update_str(resource.concrete_type()).to_owned()), Cell::from(resource.type_visibility().render(styles)), diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index b71587c1d..06d35f7d0 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -1,6 +1,6 @@ use crate::config; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, str::FromStr}; +use std::{str::FromStr, time::Duration}; use tui::{ style::{Color, Modifier, Style}, text::Span, @@ -32,11 +32,31 @@ pub enum Palette { All, } +enum FormattedDuration { + Days(String), + DaysHours(String), + HoursMinutes(String), + MinutesSeconds(String), + Debug(String), +} + +impl FormattedDuration { + fn into_inner(self) -> String { + match self { + Self::Days(inner) => inner, + Self::DaysHours(inner) => inner, + Self::HoursMinutes(inner) => inner, + Self::MinutesSeconds(inner) => inner, + Self::Debug(inner) => inner, + } + } +} + fn fg_style(color: Color) -> Style { Style::default().fg(color) } -// === impl Config === +// === impl Styles === impl Styles { pub fn from_config(config: config::ViewOptions) -> Self { @@ -126,39 +146,97 @@ impl Styles { } } - pub fn time_units<'a>(&self, text: impl Into>) -> Span<'a> { - let mut text = text.into(); - if !self.toggles.color_durations() { - return Span::raw(text); - } + fn duration_text(&self, dur: Duration, width: usize, prec: usize) -> FormattedDuration { + let secs = dur.as_secs(); + + if secs >= 60 * 60 * 24 * 100 { + let days = secs / (60 * 60 * 24); + FormattedDuration::Days(format!("{days:>width$}d", days = days, width = width)) + } else if secs >= 60 * 60 * 24 { + let hours = secs / (60 * 60); + FormattedDuration::DaysHours(format!( + "{:>width$}", + format!( + "{days}d{hours:02.0}h", + days = hours / 24, + hours = hours % 24, + ), + width = width + )) + } else if secs >= 60 * 60 { + let mins = secs / 60; + FormattedDuration::HoursMinutes(format!( + "{:>width$}", + format!( + "{hours}h{minutes:02.0}m", + hours = mins / 60, + minutes = mins % 60, + ), + width = width + )) + } else if secs >= 60 { + FormattedDuration::MinutesSeconds(format!( + "{:>width$}", + format!( + "{minutes}m{seconds:02.0}s", + minutes = secs / 60, + seconds = secs % 60, + ), + width = width, + )) + } else { + let mut text = format!("{:>width$.prec$?}", dur, width = width, prec = prec); - if !self.utf8 { - if let Some(mu_offset) = text.find("µs") { - text.to_mut().replace_range(mu_offset.., "us"); + if !self.utf8 { + if let Some(mu_offset) = text.find("µs") { + text.replace_range(mu_offset.., "us"); + } } + + FormattedDuration::Debug(text) + } + } + + pub fn time_units<'a>(&self, dur: Duration, prec: usize, width: Option) -> Span<'a> { + let formatted = self.duration_text(dur, width.unwrap_or(0), prec); + + if !self.toggles.color_durations() { + return Span::raw(formatted.into_inner()); } let style = match self.palette { - Palette::NoColors => return Span::raw(text), - Palette::Ansi8 | Palette::Ansi16 => match text.as_ref() { - s if s.ends_with("ps") => fg_style(Color::Blue), - s if s.ends_with("ns") => fg_style(Color::Green), - s if s.ends_with("µs") || s.ends_with("us") => fg_style(Color::Yellow), - s if s.ends_with("ms") => fg_style(Color::Red), - s if s.ends_with('s') => fg_style(Color::Magenta), + Palette::NoColors => return Span::raw(formatted.into_inner()), + Palette::Ansi8 | Palette::Ansi16 => match &formatted { + FormattedDuration::Days(_) => fg_style(Color::Green), + FormattedDuration::DaysHours(_) => fg_style(Color::Blue), + FormattedDuration::HoursMinutes(_) => fg_style(Color::Gray), + FormattedDuration::MinutesSeconds(_) => fg_style(Color::Cyan), + FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Blue), + FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Green), + FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => { + fg_style(Color::Yellow) + } + FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Red), + FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Magenta), _ => Style::default(), }, - Palette::Ansi256 | Palette::All => match text.as_ref() { - s if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3 - s if s.ends_with("ns") => fg_style(Color::Indexed(41)), // spring green 3 - s if s.ends_with("µs") || s.ends_with("us") => fg_style(Color::Indexed(42)), // spring green 2 - s if s.ends_with("ms") => fg_style(Color::Indexed(43)), // cyan 3 - s if s.ends_with('s') => fg_style(Color::Indexed(44)), // dark turquoise, + Palette::Ansi256 | Palette::All => match &formatted { + FormattedDuration::Days(_) => fg_style(Color::Indexed(37)), // light sea green + FormattedDuration::DaysHours(_) => fg_style(Color::Indexed(38)), // deep sky blue 2 + FormattedDuration::HoursMinutes(_) => fg_style(Color::Indexed(39)), // deep sky blue 1 + FormattedDuration::MinutesSeconds(_) => fg_style(Color::Indexed(45)), // turquoise 2 + FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3 + FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Indexed(41)), // spring green 3 + FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => { + fg_style(Color::Indexed(42)) + } // spring green 2 + FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Indexed(43)), // cyan 3 + FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Indexed(44)), // dark turquoise, _ => Style::default(), }, }; - Span::styled(text, style) + Span::styled(formatted.into_inner(), style) } pub fn terminated(&self) -> Style { diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index 2708bfb92..b8febbcab 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -9,6 +9,7 @@ use crate::{ view::{ self, bold, mini_histogram::{HistogramMetadata, MiniHistogram}, + DUR_LIST_PRECISION, }, }; use std::{ @@ -329,29 +330,27 @@ impl Details { fn make_percentiles_widget(&self, styles: &view::Styles) -> Text<'static> { let mut text = Text::default(); let histogram = self.poll_times_histogram(); - let percentiles = - histogram - .iter() - .flat_map(|&DurationHistogram { ref histogram, .. }| { - let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64] - .iter() - .map(move |i| (*i, histogram.value_at_percentile(*i))); - pairs.map(|pair| { - Spans::from(vec![ - bold(format!("p{:>2}: ", pair.0)), - dur(styles, Duration::from_nanos(pair.1)), - ]) - }) - }); + let percentiles = histogram + .iter() + .flat_map(|&DurationHistogram { histogram, .. }| { + let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64] + .iter() + .map(move |i| (*i, histogram.value_at_percentile(*i))); + pairs.map(|pair| { + Spans::from(vec![ + bold(format!("p{:>2}: ", pair.0)), + dur(styles, Duration::from_nanos(pair.1)), + ]) + }) + }); text.extend(percentiles); text } } fn dur(styles: &view::Styles, dur: std::time::Duration) -> Span<'static> { - const DUR_PRECISION: usize = 4; // TODO(eliza): can we not have to use `format!` to make a string here? is // there a way to just give TUI a `fmt::Debug` implementation, or does it // have to be given a string in order to do layout stuff? - styles.time_units(format!("{:.prec$?}", dur, prec = DUR_PRECISION)) + styles.time_units(dur, DUR_LIST_PRECISION, None) } diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index eb6b2365a..780f4775c 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -6,7 +6,7 @@ use crate::{ view::{ self, bold, table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + DUR_LEN, DUR_TABLE_PRECISION, }, }; use tui::{ @@ -68,12 +68,7 @@ impl TableList<11> for TasksTable { .sort(now, &mut table_list_state.sorted_items); let dur_cell = |dur: std::time::Duration| -> Cell<'static> { - Cell::from(styles.time_units(format!( - "{:>width$.prec$?}", - dur, - width = DUR_LEN, - prec = DUR_PRECISION, - ))) + Cell::from(styles.time_units(dur, DUR_TABLE_PRECISION, Some(DUR_LEN))) }; // Start out wide enough to display the column headers... From d5a59ad65a87ad246b0190ea9ff3c968dab7b5ac Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 6 Mar 2023 23:00:29 +0100 Subject: [PATCH 2/5] Update tokio-console/src/view/styles.rs Co-authored-by: Eliza Weisman --- tokio-console/src/view/styles.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index 06d35f7d0..437b06b0c 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -156,7 +156,7 @@ impl Styles { let hours = secs / (60 * 60); FormattedDuration::DaysHours(format!( "{:>width$}", - format!( + format_args!( "{days}d{hours:02.0}h", days = hours / 24, hours = hours % 24, @@ -167,7 +167,7 @@ impl Styles { let mins = secs / 60; FormattedDuration::HoursMinutes(format!( "{:>width$}", - format!( + format_args!( "{hours}h{minutes:02.0}m", hours = mins / 60, minutes = mins % 60, @@ -177,7 +177,7 @@ impl Styles { } else if secs >= 60 { FormattedDuration::MinutesSeconds(format!( "{:>width$}", - format!( + format_args!( "{minutes}m{seconds:02.0}s", minutes = secs / 60, seconds = secs % 60, From d6f934afe907423ac2b57c8d1442c765e5015ab6 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 7 Mar 2023 18:56:50 +0100 Subject: [PATCH 3/5] fix the double-allocation when formatting durations Also changed the API for `time_units` to take an `Option`, although I'm not super happy with this change. --- tokio-console/src/view/async_ops.rs | 6 +- tokio-console/src/view/mod.rs | 4 +- tokio-console/src/view/resources.rs | 2 +- tokio-console/src/view/styles.rs | 143 ++++++++++++++++------------ tokio-console/src/view/tasks.rs | 12 +-- 5 files changed, 93 insertions(+), 74 deletions(-) diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 0cdf02162..374820187 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -217,9 +217,9 @@ impl TableList<9> for AsyncOpsTable { parent_width.constraint(), task_width.constraint(), source_width.constraint(), - layout::Constraint::Length(DUR_LEN as u16), - layout::Constraint::Length(DUR_LEN as u16), - layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), polls_width.constraint(), attributes_width, ]; diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index 9f50d5da7..74cff6d6a 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -1,5 +1,6 @@ use crate::view::{resources::ResourcesTable, table::TableListState, tasks::TasksTable}; use crate::{input, state::State}; +use std::num::NonZeroUsize; use std::{borrow::Cow, cmp}; use tui::{ layout, @@ -18,7 +19,8 @@ mod tasks; pub(crate) use self::styles::{Palette, Styles}; pub(crate) use self::table::SortBy; -const DUR_LEN: usize = 6; +// SAFETY: 6 is non-zero. +const DUR_LEN: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(6) }; // This data is only updated every second, so it doesn't make a ton of // sense to have a lot of precision in timestamps (and this makes sure // there's room for the unit!) diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index afe9fc1f6..eeccd29d8 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -186,7 +186,7 @@ impl TableList<9> for ResourcesTable { id_width.constraint(), parent_width.constraint(), kind_width.constraint(), - layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), target_width.constraint(), type_width.constraint(), layout::Constraint::Length(viz_len), diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index 437b06b0c..a7f8fc6fb 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -1,6 +1,6 @@ use crate::config; use serde::{Deserialize, Serialize}; -use std::{str::FromStr, time::Duration}; +use std::{num::NonZeroUsize, str::FromStr, time::Duration}; use tui::{ style::{Color, Modifier, Style}, text::Span, @@ -32,11 +32,21 @@ pub enum Palette { All, } +/// Represents formatted time spans. +/// +/// Distinguishing between different units allows appropriate colouring. enum FormattedDuration { + /// Days (and no minor unit), e.g. `102d` Days(String), + /// Days with hours, e.g. `12d03h` DaysHours(String), + /// Hours with minutes, e.g. `14h32m` HoursMinutes(String), + /// Minutes with seconds, e.g. `43m02s` MinutesSeconds(String), + /// The `time::Duration` debug string which uses units ranging from + /// picoseconds (`ps`) to seconds (`s`). May contain decimal digits + /// (e.g. `628.76ms`) or not (e.g. `32ns`) Debug(String), } @@ -146,59 +156,21 @@ impl Styles { } } - fn duration_text(&self, dur: Duration, width: usize, prec: usize) -> FormattedDuration { - let secs = dur.as_secs(); - - if secs >= 60 * 60 * 24 * 100 { - let days = secs / (60 * 60 * 24); - FormattedDuration::Days(format!("{days:>width$}d", days = days, width = width)) - } else if secs >= 60 * 60 * 24 { - let hours = secs / (60 * 60); - FormattedDuration::DaysHours(format!( - "{:>width$}", - format_args!( - "{days}d{hours:02.0}h", - days = hours / 24, - hours = hours % 24, - ), - width = width - )) - } else if secs >= 60 * 60 { - let mins = secs / 60; - FormattedDuration::HoursMinutes(format!( - "{:>width$}", - format_args!( - "{hours}h{minutes:02.0}m", - hours = mins / 60, - minutes = mins % 60, - ), - width = width - )) - } else if secs >= 60 { - FormattedDuration::MinutesSeconds(format!( - "{:>width$}", - format_args!( - "{minutes}m{seconds:02.0}s", - minutes = secs / 60, - seconds = secs % 60, - ), - width = width, - )) - } else { - let mut text = format!("{:>width$.prec$?}", dur, width = width, prec = prec); - - if !self.utf8 { - if let Some(mu_offset) = text.find("µs") { - text.replace_range(mu_offset.., "us"); - } - } - - FormattedDuration::Debug(text) - } - } - - pub fn time_units<'a>(&self, dur: Duration, prec: usize, width: Option) -> Span<'a> { - let formatted = self.duration_text(dur, width.unwrap_or(0), prec); + /// Creates a span with a formatted duration inside. + /// + /// The formatted duration will be colored depending on the palette + /// defined for this `Styles` object. + /// + /// If the `width` parameter is `None` then no padding will be + /// added. Otherwise the text in the span will be left-padded to + /// the specified width (right aligned). + pub fn time_units<'a>( + &self, + dur: Duration, + prec: usize, + width: Option, + ) -> Span<'a> { + let formatted = self.duration_text(dur, width.map_or(0, |w| w.get()), prec); if !self.toggles.color_durations() { return Span::raw(formatted.into_inner()); @@ -207,22 +179,22 @@ impl Styles { let style = match self.palette { Palette::NoColors => return Span::raw(formatted.into_inner()), Palette::Ansi8 | Palette::Ansi16 => match &formatted { - FormattedDuration::Days(_) => fg_style(Color::Green), + FormattedDuration::Days(_) => fg_style(Color::Blue), FormattedDuration::DaysHours(_) => fg_style(Color::Blue), - FormattedDuration::HoursMinutes(_) => fg_style(Color::Gray), - FormattedDuration::MinutesSeconds(_) => fg_style(Color::Cyan), - FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Blue), - FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Green), + FormattedDuration::HoursMinutes(_) => fg_style(Color::Cyan), + FormattedDuration::MinutesSeconds(_) => fg_style(Color::Green), + FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Gray), + FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Gray), FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => { - fg_style(Color::Yellow) + fg_style(Color::Magenta) } FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Red), - FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Magenta), + FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Yellow), _ => Style::default(), }, Palette::Ansi256 | Palette::All => match &formatted { - FormattedDuration::Days(_) => fg_style(Color::Indexed(37)), // light sea green - FormattedDuration::DaysHours(_) => fg_style(Color::Indexed(38)), // deep sky blue 2 + FormattedDuration::Days(_) => fg_style(Color::Indexed(33)), // dodger blue 1 + FormattedDuration::DaysHours(_) => fg_style(Color::Indexed(33)), // dodger blue 1 FormattedDuration::HoursMinutes(_) => fg_style(Color::Indexed(39)), // deep sky blue 1 FormattedDuration::MinutesSeconds(_) => fg_style(Color::Indexed(45)), // turquoise 2 FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3 @@ -239,6 +211,51 @@ impl Styles { Span::styled(formatted.into_inner(), style) } + fn duration_text(&self, dur: Duration, width: usize, prec: usize) -> FormattedDuration { + let secs = dur.as_secs(); + + if secs >= 60 * 60 * 24 * 100 { + let days = secs / (60 * 60 * 24); + FormattedDuration::Days(format!("{days:>width$}d", days = days, width = width)) + } else if secs >= 60 * 60 * 24 { + let hours = secs / (60 * 60); + FormattedDuration::DaysHours(format!( + "{days:>leading_width$}d{hours:02.0}h", + days = hours / 24, + hours = hours % 24, + // Subtract the known 4 characters that trail the days value. + leading_width = if width > 4 { width - 4 } else { 0 } + )) + } else if secs >= 60 * 60 { + let mins = secs / 60; + FormattedDuration::HoursMinutes(format!( + "{hours:>leading_width$}h{minutes:02.0}m", + hours = mins / 60, + minutes = mins % 60, + // Subtract the known 4 characters that trail the hours value. + leading_width = if width > 4 { width - 4 } else { 0 } + )) + } else if secs >= 60 { + FormattedDuration::MinutesSeconds(format!( + "{minutes:>leading_width$}m{seconds:02.0}s", + minutes = secs / 60, + seconds = secs % 60, + // Subtract the known 4 characters that trail the minutes value. + leading_width = if width > 4 { width - 4 } else { 0 }, + )) + } else { + let mut text = format!("{:>width$.prec$?}", dur, width = width, prec = prec); + + if !self.utf8 { + if let Some(mu_offset) = text.find("µs") { + text.replace_range(mu_offset.., "us"); + } + } + + FormattedDuration::Debug(text) + } + } + pub fn terminated(&self) -> Style { if !self.toggles.color_terminated() { return Style::default(); diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index 780f4775c..50ef152af 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -188,9 +188,9 @@ impl TableList<11> for TasksTable { let fixed_col_width = id_width.chars() + STATE_LEN + name_width.chars() - + DUR_LEN as u16 - + DUR_LEN as u16 - + DUR_LEN as u16 + + DUR_LEN.get() as u16 + + DUR_LEN.get() as u16 + + DUR_LEN.get() as u16 + POLLS_LEN as u16 + target_width.chars(); */ @@ -249,9 +249,9 @@ impl TableList<11> for TasksTable { id_width.constraint(), layout::Constraint::Length(state_len), name_width.constraint(), - layout::Constraint::Length(DUR_LEN as u16), - layout::Constraint::Length(DUR_LEN as u16), - layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN.get() as u16), polls_width.constraint(), target_width.constraint(), location_width.constraint(), From 8a382f3b032c6bbc9757727ca2aed8a040d29cb4 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 7 Mar 2023 19:30:36 +0100 Subject: [PATCH 4/5] convert time_units param width back to `Option` --- tokio-console/src/view/async_ops.rs | 6 +++--- tokio-console/src/view/mod.rs | 4 +--- tokio-console/src/view/resources.rs | 2 +- tokio-console/src/view/styles.rs | 14 +++++--------- tokio-console/src/view/tasks.rs | 12 ++++++------ 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 374820187..0cdf02162 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -217,9 +217,9 @@ impl TableList<9> for AsyncOpsTable { parent_width.constraint(), task_width.constraint(), source_width.constraint(), - layout::Constraint::Length(DUR_LEN.get() as u16), - layout::Constraint::Length(DUR_LEN.get() as u16), - layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN as u16), polls_width.constraint(), attributes_width, ]; diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index 74cff6d6a..9f50d5da7 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -1,6 +1,5 @@ use crate::view::{resources::ResourcesTable, table::TableListState, tasks::TasksTable}; use crate::{input, state::State}; -use std::num::NonZeroUsize; use std::{borrow::Cow, cmp}; use tui::{ layout, @@ -19,8 +18,7 @@ mod tasks; pub(crate) use self::styles::{Palette, Styles}; pub(crate) use self::table::SortBy; -// SAFETY: 6 is non-zero. -const DUR_LEN: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(6) }; +const DUR_LEN: usize = 6; // This data is only updated every second, so it doesn't make a ton of // sense to have a lot of precision in timestamps (and this makes sure // there's room for the unit!) diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index eeccd29d8..afe9fc1f6 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -186,7 +186,7 @@ impl TableList<9> for ResourcesTable { id_width.constraint(), parent_width.constraint(), kind_width.constraint(), - layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN as u16), target_width.constraint(), type_width.constraint(), layout::Constraint::Length(viz_len), diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index a7f8fc6fb..c2dd0d367 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -1,6 +1,6 @@ use crate::config; use serde::{Deserialize, Serialize}; -use std::{num::NonZeroUsize, str::FromStr, time::Duration}; +use std::{str::FromStr, time::Duration}; use tui::{ style::{Color, Modifier, Style}, text::Span, @@ -163,14 +163,10 @@ impl Styles { /// /// If the `width` parameter is `None` then no padding will be /// added. Otherwise the text in the span will be left-padded to - /// the specified width (right aligned). - pub fn time_units<'a>( - &self, - dur: Duration, - prec: usize, - width: Option, - ) -> Span<'a> { - let formatted = self.duration_text(dur, width.map_or(0, |w| w.get()), prec); + /// the specified width (right aligned). Passing `Some(0)` is + /// equivalent to `None`. + pub fn time_units<'a>(&self, dur: Duration, prec: usize, width: Option) -> Span<'a> { + let formatted = self.duration_text(dur, width.unwrap_or(0), prec); if !self.toggles.color_durations() { return Span::raw(formatted.into_inner()); diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index 50ef152af..780f4775c 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -188,9 +188,9 @@ impl TableList<11> for TasksTable { let fixed_col_width = id_width.chars() + STATE_LEN + name_width.chars() - + DUR_LEN.get() as u16 - + DUR_LEN.get() as u16 - + DUR_LEN.get() as u16 + + DUR_LEN as u16 + + DUR_LEN as u16 + + DUR_LEN as u16 + POLLS_LEN as u16 + target_width.chars(); */ @@ -249,9 +249,9 @@ impl TableList<11> for TasksTable { id_width.constraint(), layout::Constraint::Length(state_len), name_width.constraint(), - layout::Constraint::Length(DUR_LEN.get() as u16), - layout::Constraint::Length(DUR_LEN.get() as u16), - layout::Constraint::Length(DUR_LEN.get() as u16), + layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN as u16), polls_width.constraint(), target_width.constraint(), location_width.constraint(), From 4c62307fefd9029d2459f5e1e57d773484e2df0c Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 7 Mar 2023 19:34:32 +0100 Subject: [PATCH 5/5] used width.saturating_sub(4) instead of my if-else mess --- tokio-console/src/view/styles.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index c2dd0d367..7834cca2b 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -220,7 +220,7 @@ impl Styles { days = hours / 24, hours = hours % 24, // Subtract the known 4 characters that trail the days value. - leading_width = if width > 4 { width - 4 } else { 0 } + leading_width = width.saturating_sub(4), )) } else if secs >= 60 * 60 { let mins = secs / 60; @@ -229,7 +229,7 @@ impl Styles { hours = mins / 60, minutes = mins % 60, // Subtract the known 4 characters that trail the hours value. - leading_width = if width > 4 { width - 4 } else { 0 } + leading_width = width.saturating_sub(4), )) } else if secs >= 60 { FormattedDuration::MinutesSeconds(format!( @@ -237,7 +237,7 @@ impl Styles { minutes = secs / 60, seconds = secs % 60, // Subtract the known 4 characters that trail the minutes value. - leading_width = if width > 4 { width - 4 } else { 0 }, + leading_width = width.saturating_sub(4), )) } else { let mut text = format!("{:>width$.prec$?}", dur, width = width, prec = prec);