Skip to content

Commit dcefb03

Browse files
committed
added --comments arg
1 parent 1e07f97 commit dcefb03

File tree

5 files changed

+76
-6
lines changed

5 files changed

+76
-6
lines changed

src/cli.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ pub struct Cli {
8888
/// Automatically start a test when the app launches
8989
#[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
9090
pub test_on_launch: bool,
91+
92+
/// Attach custom comments to this run (saved/exported and shown in TUI status)
93+
#[arg(long)]
94+
pub comments: Option<String>,
9195
}
9296

9397
pub async fn run(args: Cli) -> Result<()> {
@@ -122,6 +126,7 @@ pub fn build_config(args: &Cli) -> RunConfig {
122126
RunConfig {
123127
base_url: args.base_url.clone(),
124128
meas_id: gen_meas_id(),
129+
comments: args.comments.clone(),
125130
download_bytes_per_req: args.download_bytes_per_req,
126131
upload_bytes_per_req: args.upload_bytes_per_req,
127132
concurrency: args.concurrency,
@@ -271,6 +276,11 @@ async fn run_text(args: Cli) -> Result<()> {
271276
if let Some(server) = enriched.server.as_deref() {
272277
println!("Server: {server}");
273278
}
279+
if let Some(comments) = enriched.comments.as_deref() {
280+
if !comments.trim().is_empty() {
281+
println!("Comments: {}", comments);
282+
}
283+
}
274284

275285
// Compute and display throughput metrics (mean, median, p25, p75)
276286
let dl_values: Vec<f64> = dl_points.iter().map(|(_, y)| *y).collect();

src/engine/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ impl TestEngine {
166166
.unwrap_or_else(|_| "now".into()),
167167
base_url: self.cfg.base_url.clone(),
168168
meas_id: self.cfg.meas_id.clone(),
169+
comments: self.cfg.comments.clone(),
169170
meta,
170171
server,
171172
idle_latency,

src/model.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::time::Duration;
55
pub struct RunConfig {
66
pub base_url: String,
77
pub meas_id: String,
8+
#[serde(default)]
9+
pub comments: Option<String>,
810
pub download_bytes_per_req: u64,
911
pub upload_bytes_per_req: u64,
1012
pub concurrency: usize,
@@ -99,6 +101,8 @@ pub struct RunResult {
99101
pub timestamp_utc: String,
100102
pub base_url: String,
101103
pub meas_id: String,
104+
#[serde(default)]
105+
pub comments: Option<String>,
102106
pub meta: Option<serde_json::Value>,
103107
#[serde(default)]
104108
pub server: Option<String>,

src/storage.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ pub fn export_csv(path: &Path, result: &RunResult) -> Result<()> {
5858
std::fs::create_dir_all(parent).context("create export directory")?;
5959
}
6060
let mut out = String::new();
61-
out.push_str("timestamp_utc,base_url,meas_id,server,download_mbps,upload_mbps,idle_mean_ms,idle_median_ms,idle_p25_ms,idle_p75_ms,idle_loss,dl_loaded_mean_ms,dl_loaded_median_ms,dl_loaded_p25_ms,dl_loaded_p75_ms,dl_loaded_loss,ul_loaded_mean_ms,ul_loaded_median_ms,ul_loaded_p25_ms,ul_loaded_p75_ms,ul_loaded_loss,ip,colo,asn,as_org,interface_name,network_name,is_wireless,interface_mac,link_speed_mbps\n");
61+
out.push_str("timestamp_utc,base_url,meas_id,comments,server,download_mbps,upload_mbps,idle_mean_ms,idle_median_ms,idle_p25_ms,idle_p75_ms,idle_loss,dl_loaded_mean_ms,dl_loaded_median_ms,dl_loaded_p25_ms,dl_loaded_p75_ms,dl_loaded_loss,ul_loaded_mean_ms,ul_loaded_median_ms,ul_loaded_p25_ms,ul_loaded_p75_ms,ul_loaded_loss,ip,colo,asn,as_org,interface_name,network_name,is_wireless,interface_mac,link_speed_mbps\n");
6262
out.push_str(&format!(
63-
"{},{},{},{},{:.3},{:.3},{:.3},{:.3},{:.3},{:.3},{:.6},{:.3},{:.3},{:.3},{:.3},{:.6},{:.3},{:.3},{:.3},{:.3},{:.6},{},{},{},{},{},{},{},{},{}\n",
63+
"{},{},{},{},{},{:.3},{:.3},{:.3},{:.3},{:.3},{:.3},{:.6},{:.3},{:.3},{:.3},{:.3},{:.6},{:.3},{:.3},{:.3},{:.3},{:.6},{},{},{},{},{},{},{},{},{}\n",
6464
csv_escape(&result.timestamp_utc),
6565
csv_escape(&result.base_url),
6666
csv_escape(&result.meas_id),
67+
csv_escape(result.comments.as_deref().unwrap_or("")),
6768
csv_escape(result.server.as_deref().unwrap_or("")),
6869
result.download.mbps,
6970
result.upload.mbps,

src/tui/mod.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ struct UiState {
2828
paused: bool,
2929
phase: Phase,
3030
info: String,
31+
comments: Option<String>,
3132

3233
dl_series: Vec<u64>,
3334
ul_series: Vec<u64>,
@@ -92,6 +93,7 @@ impl Default for UiState {
9293
paused: false,
9394
phase: Phase::IdleLatency,
9495
info: String::new(),
96+
comments: None,
9597
dl_series: Vec::new(),
9698
ul_series: Vec::new(),
9799
idle_lat_series: Vec::new(),
@@ -143,6 +145,52 @@ impl Default for UiState {
143145
}
144146
}
145147

148+
fn push_wrapped_status_kv(
149+
out: &mut Vec<Line<'static>>,
150+
label: &str,
151+
value: &str,
152+
status_area_width: u16,
153+
) {
154+
let value = value.trim();
155+
if value.is_empty() {
156+
return;
157+
}
158+
159+
// Account for borders (2 chars on each side)
160+
let usable_width = status_area_width.saturating_sub(4).max(1);
161+
let label_text = format!("{label}:");
162+
let label_width = label_text.chars().count() as u16;
163+
164+
let value_chars: Vec<char> = value.chars().collect();
165+
let mut remaining = value_chars.as_slice();
166+
let mut first = true;
167+
168+
while !remaining.is_empty() {
169+
let line_width = if first {
170+
usable_width.saturating_sub(label_width + 1).max(1)
171+
} else {
172+
usable_width.saturating_sub(2).max(1)
173+
};
174+
175+
let chars_to_take = (remaining.len() as u16).min(line_width) as usize;
176+
let (line_chars, rest) = remaining.split_at(chars_to_take);
177+
let line_text: String = line_chars.iter().collect();
178+
179+
if first {
180+
out.push(Line::from(vec![
181+
Span::styled(label_text.clone(), Style::default().fg(Color::Gray)),
182+
Span::raw(" "),
183+
Span::raw(line_text),
184+
]));
185+
first = false;
186+
} else {
187+
out.push(Line::from(vec![Span::raw(" "), Span::raw(line_text)]));
188+
}
189+
190+
remaining = rest;
191+
}
192+
}
193+
146194
impl UiState {
147195
fn push_series(series: &mut Vec<u64>, v: u64) {
148196
const MAX: usize = 120;
@@ -250,6 +298,7 @@ pub async fn run(args: Cli) -> Result<()> {
250298
let mut state = UiState {
251299
phase: Phase::IdleLatency,
252300
auto_save: args.auto_save,
301+
comments: args.comments.clone(),
253302
..Default::default()
254303
};
255304
state.initial_history_load_size = initial_load;
@@ -1280,12 +1329,17 @@ fn draw_dashboard(area: Rect, f: &mut ratatui::Frame, state: &UiState) {
12801329
),
12811330
])];
12821331

1332+
// Custom comments (wrapping to fit status area)
1333+
if let Some(comments) = state.comments.as_deref() {
1334+
push_wrapped_status_kv(&mut status_lines, "Comments", comments, main[3].width);
1335+
}
1336+
12831337
// Info line - split into two lines if it contains a saved path, with wrapping
12841338
if state.info.starts_with("Saved:") || state.info.starts_with("Saved (verifying):") {
12851339
// Split into label and path
12861340
if let Some(colon_pos) = state.info.find(':') {
12871341
let (label, path) = state.info.split_at(colon_pos + 1);
1288-
let label_text = label.trim();
1342+
let label_text = label.trim().to_string();
12891343
let path_str = path.trim();
12901344

12911345
// Wrap the path to fit within available width
@@ -1313,7 +1367,7 @@ fn draw_dashboard(area: Rect, f: &mut ratatui::Frame, state: &UiState) {
13131367
if is_first_path_line {
13141368
// First line - include label and first part of path
13151369
status_lines.push(Line::from(vec![
1316-
Span::styled(label_text, Style::default().fg(Color::Gray)),
1370+
Span::styled(label_text.clone(), Style::default().fg(Color::Gray)),
13171371
Span::raw(" "),
13181372
Span::raw(line_text),
13191373
]));
@@ -1328,13 +1382,13 @@ fn draw_dashboard(area: Rect, f: &mut ratatui::Frame, state: &UiState) {
13281382
} else {
13291383
status_lines.push(Line::from(vec![
13301384
Span::styled("Info: ", Style::default().fg(Color::Gray)),
1331-
Span::raw(&state.info),
1385+
Span::raw(state.info.clone()),
13321386
]));
13331387
}
13341388
} else {
13351389
status_lines.push(Line::from(vec![
13361390
Span::styled("Info: ", Style::default().fg(Color::Gray)),
1337-
Span::raw(&state.info),
1391+
Span::raw(state.info.clone()),
13381392
]));
13391393
}
13401394

0 commit comments

Comments
 (0)