1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// SPDX-License-Identifier: MIT OR Apache-2.0
//! 2026-09-26: How the dashboard prints byte counts, rates, percentages, the MTP gate state and wrapped text.
//!
//! Owner: server tui.
//! Invariants:
//! - [`bytes`] and [`rate`] scale by powers of 1024, so their `GB` is 1024³ bytes.
use metrale_speculative::snapshot::MtpModeSnap;
const GIB: f64 = 1024.0 * 1024.0 * 1024.0;
const MIB: u64 = 1024 * 1024;
const KIB: f64 = 1024.0;
/// 2026-09-26: A byte count for a human: `18.6 GB` at or above 1024³ bytes, whole `MB` below.
///
/// Below a gibibyte the value is truncated, not rounded, so one byte short of
/// a gibibyte reads `1023 MB`. `GB` here means 1024³, the same divisor as the
/// memory figures in `data/metrics_poll.rs`.
pub fn bytes(n: u64) -> String {
let g = n as f64 / GIB;
if g >= 1.0 {
format!("{g:.1} GB")
} else {
format!("{} MB", n / MIB)
}
}
/// 2026-09-26: A completion fraction as a percentage: one decimal below 10%, none from 10% up.
///
/// The decimal lets a download below 1% show progress (`0.4%`) instead of
/// `0%`. The input is clamped to 0..=1, and the result is unpadded.
pub fn percent(frac: f64) -> String {
let pct = frac.clamp(0.0, 1.0) * 100.0;
if pct < 10.0 {
format!("{pct:.1}%")
} else {
format!("{pct:.0}%")
}
}
/// 2026-09-26: A byte-per-second rate for a human: `92 MB/s`, `3.0 MB/s`, `2.0 KB/s`, `0 B/s`.
///
/// 1024-based like [`bytes`], so a rate divides into the size printed beside
/// it. One decimal below ten (never for `B/s`), none above; negative or NaN
/// input prints `0 B/s`.
pub fn rate(bytes_per_sec: f64) -> String {
let (n, unit) = match bytes_per_sec {
b if b >= GIB => (b / GIB, "GB"),
b if b >= MIB as f64 => (b / MIB as f64, "MB"),
b if b >= KIB => (b / KIB, "KB"),
b => (b.max(0.0), "B"),
};
if n < 10.0 && unit != "B" {
format!("{n:.1} {unit}/s")
} else {
format!("{n:.0} {unit}/s")
}
}
/// 2026-09-26: What the scheduler's MTP gate is doing, in words.
///
/// Not `{:?}`: a variant rename would silently change what the dashboard says.
pub fn mtp_mode_label(mode: MtpModeSnap) -> &'static str {
match mode {
MtpModeSnap::Mtp => "speculative",
MtpModeSnap::Serial => "serial",
MtpModeSnap::Probing => "probing",
MtpModeSnap::Off => "off",
}
}
/// 2026-09-26: Word-wrap one paragraph to `width` columns, as owned lines; empty for width 0.
///
/// `render::wrap` and [`wrap_help`] both wrap through this loop. Width is
/// measured in bytes, so non-ASCII text wraps early rather than late. A word
/// wider than `width` is split at character boundaries.
pub fn wrap_words(text: &str, width: usize) -> Vec<String> {
if width == 0 {
return Vec::new();
}
let mut lines = Vec::new();
let mut current = String::new();
for word in text.split_whitespace() {
if !current.is_empty() && current.len() + 1 + word.len() > width {
lines.push(std::mem::take(&mut current));
}
if !current.is_empty() {
current.push(' ');
}
if word.len() > width {
for ch in word.chars() {
if !current.is_empty() && current.len() + ch.len_utf8() > width {
lines.push(std::mem::take(&mut current));
}
current.push(ch);
}
} else {
current.push_str(word);
}
}
if !current.is_empty() {
lines.push(current);
}
lines
}
/// 2026-09-26: Wrap multi-paragraph help text to `width`, keeping one blank line between paragraphs.
///
/// [`wrap_words`] collapses all whitespace, which would merge the paragraphs
/// of a multi-paragraph clap doc such as `--kv-cache-dtype`'s. Leading and
/// trailing blank lines are dropped.
pub fn wrap_help(text: &str, width: usize) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut paragraph = String::new();
let flush = |paragraph: &mut String, out: &mut Vec<String>| {
if !paragraph.is_empty() {
out.extend(wrap_words(paragraph, width));
paragraph.clear();
}
};
for line in text.lines() {
if line.trim().is_empty() {
flush(&mut paragraph, &mut out);
if out.last().is_some_and(|l| !l.is_empty()) {
out.push(String::new());
}
} else {
if !paragraph.is_empty() {
paragraph.push(' ');
}
paragraph.push_str(line.trim());
}
}
flush(&mut paragraph, &mut out);
while out.last().is_some_and(|l| l.is_empty()) {
out.pop();
}
out
}
#[cfg(test)]
#[path = "format_tests.rs"]
mod tests;