Push zpwxxpnpktps #67

Merged
coissac merged 46 commits from push-zpwxxpnpktps into main 2026-08-17 09:41:42 +00:00
Showing only changes of commit d2548e8c33 - Show all commits
+72 -9
View File
@@ -5,18 +5,20 @@ use indicatif::{ProgressBar, ProgressStyle};
use tracing::{debug, info};
const BRAILLE: &[&str] = &["", "", "", "", "", "", "", "", "", ""];
const ETA_REFRESH_MS: u64 = 500;
const ETA_MIN_ELAPSED_MS: u64 = 1000;
const ETA_CUSTOM_HOLD_MS: u64 = 2000;
/// Wrapper around `ProgressBar` that emits `tracing` events when stderr is not
/// a TTY (e.g. HPC job logs): every 10% for bounded bars, every ~10 s for
/// spinners (throttled on `set_message`).
pub struct TracedBar {
pb: ProgressBar,
label: String,
unit: String,
total: u64, // 0 for spinners
start: Instant, // creation time, for spinner throttling
last_pct: AtomicU64, // last emitted 10%-bucket (1..=10), 0 = none yet
last_log_ms: AtomicU64, // ms since `start` at last spinner log
total: u64,
start: Instant,
last_pct: AtomicU64,
last_log_ms: AtomicU64,
last_eta_refresh: AtomicU64,
last_custom_msg_ms: AtomicU64,
}
impl TracedBar {
@@ -40,15 +42,60 @@ impl TracedBar {
);
}
}
if !self.pb.is_hidden() && self.total > 0 {
self.maybe_update_eta();
}
}
fn maybe_update_eta(&self) {
let now_ms = self.start.elapsed().as_millis() as u64;
if now_ms < ETA_MIN_ELAPSED_MS {
return;
}
let last = self.last_eta_refresh.load(Ordering::Relaxed);
if now_ms < last + ETA_REFRESH_MS {
return;
}
let last_custom = self.last_custom_msg_ms.load(Ordering::Relaxed);
if now_ms < last_custom + ETA_CUSTOM_HOLD_MS {
return;
}
let pos = self.pb.position();
let remaining = self.total - pos;
if pos == 0 || remaining == 0 {
return;
}
let elapsed_secs = self.start.elapsed().as_secs_f64();
let avg_rate = pos as f64 / elapsed_secs;
let eta_secs = remaining as f64 / avg_rate;
self.pb.set_message(format!("(eta: {})", fmt_secs(eta_secs)));
let _ = self.last_eta_refresh.compare_exchange(
last,
now_ms,
Ordering::Relaxed,
Ordering::Relaxed,
);
}
pub fn set_message(&self, msg: impl Into<String>) {
let msg = msg.into();
self.last_custom_msg_ms.store(
self.start.elapsed().as_millis() as u64,
Ordering::Relaxed,
);
if self.pb.is_hidden() {
if self.total > 0 {
debug!(stage = %self.label, "{msg}");
} else {
// spinner: throttle to ~10 s
let now_ms = self.start.elapsed().as_millis() as u64;
let last = self.last_log_ms.load(Ordering::Relaxed);
if now_ms >= last + 10_000
@@ -69,6 +116,18 @@ impl TracedBar {
}
}
fn fmt_secs(s: f64) -> String {
if s >= 3600.0 {
format!("{:.0}h", s / 3600.0)
} else if s >= 60.0 {
format!("{:.0}min", s / 60.0)
} else if s >= 1.0 {
format!("{:.1}s", s)
} else {
format!("{:.0}ms", s * 1000.0)
}
}
/// Spinner with the standard project look: `⠋ label — msg 0s`.
/// Caller updates the message with `pb.set_message(...)`.
pub fn spinner(label: &str) -> TracedBar {
@@ -87,6 +146,8 @@ pub fn spinner(label: &str) -> TracedBar {
start: Instant::now(),
last_pct: AtomicU64::new(0),
last_log_ms: AtomicU64::new(0),
last_eta_refresh: AtomicU64::new(0),
last_custom_msg_ms: AtomicU64::new(0),
}
}
@@ -96,7 +157,7 @@ pub fn progress_bar(label: &str, n: u64, unit: &str) -> TracedBar {
let pb = ProgressBar::new(n);
pb.set_style(
ProgressStyle::with_template(&format!(
"{{spinner}} {label}{{bar:40.cyan/blue}} {{pos}}/{{len}} {unit} {{elapsed}} (eta: {{eta}})"
"{{spinner}} {label}{{bar:40.cyan/blue}} {{pos}}/{{len}} {unit} {{elapsed}} {{msg}}"
))
.unwrap()
.tick_strings(BRAILLE),
@@ -110,5 +171,7 @@ pub fn progress_bar(label: &str, n: u64, unit: &str) -> TracedBar {
start: Instant::now(),
last_pct: AtomicU64::new(0),
last_log_ms: AtomicU64::new(0),
last_eta_refresh: AtomicU64::new(0),
last_custom_msg_ms: AtomicU64::new(0),
}
}