#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TimeUnit {
Microsecond,
Millisecond,
Second,
}
impl TimeUnit {
pub fn parse(s: &str) -> Option<Self> {
match s {
"us" | "µs" | "microsecond" => Some(TimeUnit::Microsecond),
"ms" | "millisecond" => Some(TimeUnit::Millisecond),
"s" | "second" => Some(TimeUnit::Second),
_ => None,
}
}
fn factor(self) -> f64 {
match self {
TimeUnit::Microsecond => 1e6,
TimeUnit::Millisecond => 1e3,
TimeUnit::Second => 1.0,
}
}
fn suffix(self) -> &'static str {
match self {
TimeUnit::Microsecond => "µs",
TimeUnit::Millisecond => "ms",
TimeUnit::Second => "s",
}
}
}
pub fn auto_unit(seconds: f64) -> TimeUnit {
if seconds < 1e-3 {
TimeUnit::Microsecond
} else if seconds < 1.0 {
TimeUnit::Millisecond
} else {
TimeUnit::Second
}
}
fn time_value(seconds: f64, unit: TimeUnit) -> String {
format!("{:.3}", seconds * unit.factor())
}
pub fn format_time(seconds: f64, unit: TimeUnit) -> String {
format!("{} {}", time_value(seconds, unit), unit.suffix())
}
const BYTE_UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
fn byte_unit(bytes: u64) -> usize {
let mut unit = 0;
let mut limit = 1u64 << 10;
while bytes >= limit && unit < BYTE_UNITS.len() - 1 {
limit <<= 10;
unit += 1;
}
unit
}
fn format_byte_value(bytes: u64, unit: usize) -> String {
if unit == 0 {
format!("{bytes}")
} else {
format!("{:.1}", bytes as f64 / (1u64 << (10 * unit)) as f64)
}
}
pub fn format_bytes(bytes: u64) -> String {
let unit = byte_unit(bytes);
format!("{} {}", format_byte_value(bytes, unit), BYTE_UNITS[unit])
}
pub fn truncate(s: &str, max: usize) -> String {
let chars: Vec<char> = s.chars().collect();
if chars.len() <= max {
return s.to_string();
}
if max <= 1 {
return chars[..max].iter().collect();
}
let budget = max - 1; let front = budget.div_ceil(2);
let back = budget - front;
let mut out: String = chars[..front].iter().collect();
out.push('…');
out.extend(&chars[chars.len() - back..]);
out
}
pub struct CounterRow<'a> {
pub label: &'a str,
pub count: u64,
pub mean: f64,
pub std: Option<f64>,
pub peak_rss: u64,
}
pub fn render_counters(
rows: &[CounterRow],
forced_unit: Option<TimeUnit>,
budget: usize,
) -> Vec<String> {
let present: Vec<&CounterRow> = rows.iter().filter(|r| r.count > 0).collect();
if present.is_empty() {
return rows
.iter()
.map(|r| {
let label = truncate(r.label, budget.saturating_sub(": pending".chars().count()));
format!("{label}: pending")
})
.collect();
}
let time_unit =
forced_unit.unwrap_or_else(|| present.iter().map(|r| auto_unit(r.mean)).min().unwrap());
let suffix = time_unit.suffix();
let rss_unit = present
.iter()
.filter(|r| r.peak_rss > 0)
.map(|r| byte_unit(r.peak_rss))
.min();
let label_w = rows
.iter()
.map(|r| r.label.chars().count())
.max()
.unwrap_or(0);
let count_w = present
.iter()
.map(|r| r.count.to_string().len())
.max()
.unwrap();
let mean_w = present
.iter()
.map(|r| time_value(r.mean, time_unit).len())
.max()
.unwrap();
let std_w = present
.iter()
.filter_map(|r| r.std.map(|s| time_value(s, time_unit).len()))
.max();
let rss_w = rss_unit.map(|u| {
present
.iter()
.filter(|r| r.peak_rss > 0)
.map(|r| format_byte_value(r.peak_rss, u).len())
.max()
.unwrap()
});
let suffix_w = suffix.chars().count();
let fixed_tail = 2 + count_w + 1 + 4 + 2 + mean_w + 1 + suffix_w;
let std_tail = std_w.map_or(0, |sw| 3 + sw + 1 + suffix_w);
let peak_tail = match (rss_unit, rss_w) {
(Some(u), Some(rw)) => 7 + rw + 1 + BYTE_UNITS[u].chars().count(),
_ => 0,
};
let label_w = label_w.min(budget.saturating_sub(fixed_tail + std_tail + peak_tail));
rows.iter()
.map(|x| {
let label = truncate(x.label, label_w);
if x.count == 0 {
return format!("{label:<label_w$} pending");
}
let runs = if x.count == 1 { "run" } else { "runs" };
let mut line = format!(
"{label:<label_w$} {:>count_w$} {runs:<4} {:>mean_w$} {suffix}",
x.count,
time_value(x.mean, time_unit),
);
if let Some(sw) = std_w {
match x.std {
Some(std) => {
line.push_str(&format!(" ± {:>sw$} {suffix}", time_value(std, time_unit)))
}
None => line.push_str(&" ".repeat(3 + sw + 1 + suffix.chars().count())),
}
}
if let (Some(u), Some(rw)) = (rss_unit, rss_w)
&& x.peak_rss > 0
{
line.push_str(&format!(
" peak {:>rw$} {}",
format_byte_value(x.peak_rss, u),
BYTE_UNITS[u],
));
}
line
})
.collect()
}