ingester/src/progress.rs (109 lines of code) (raw):

use crash_ping_ingest::Status; use std::mem::ManuallyDrop; use std::sync::{ atomic::{AtomicBool, Ordering::Relaxed}, Arc, }; use std::thread; const RENDER_FREQUENCY: std::time::Duration = std::time::Duration::from_millis(100); pub struct Progress { cancel: Arc<AtomicBool>, thread: ManuallyDrop<thread::JoinHandle<()>>, } impl Progress { pub fn new(status: Arc<Status>) -> Option<Self> { let cancel = Arc::new(AtomicBool::new(false)); let mut renderer = Renderer::new(status)?; Some(Progress { cancel: cancel.clone(), thread: ManuallyDrop::new(thread::spawn(move || { while !cancel.load(Relaxed) { if let Err(e) = renderer.render() { log::warn!("failed to render to terminal: {e}"); } thread::sleep(RENDER_FREQUENCY); } })), }) } } impl Drop for Progress { fn drop(&mut self) { self.cancel.store(true, Relaxed); unsafe { ManuallyDrop::take(&mut self.thread) } .join() .unwrap(); } } struct Renderer { terminal: Box<term::StderrTerminal>, status: Arc<Status>, last_lines: usize, } impl Renderer { fn new(status: Arc<Status>) -> Option<Self> { Some(Renderer { terminal: term::stderr()?, status, last_lines: 0, }) } fn render(&mut self) -> term::Result<()> { // Reset from last render for i in 0..std::mem::replace(&mut self.last_lines, 0) { if i == 0 { self.terminal.carriage_return()?; } else { self.terminal.cursor_up()?; } self.terminal.delete_line()?; } if !self.status.pings.done() { if self.last_lines > 0 { writeln!(self.terminal)?; } let complete = self.status.pings.complete_count(); let total = self.status.pings.total_count(); write!( self.terminal, "Pings: {:.1}% ({}/{}), {} symbolicating", complete as f64 * 100. / total as f64, complete, total, self.status.pings.symbolicating_count() )?; self.last_lines += 1; } if let Some(cache) = self.status.cache.as_ref() { if self.last_lines > 0 { writeln!(self.terminal)?; } let current = cache.current(); let max = cache.max_size(); write!( self.terminal, "Cache usage: {:.1}% of {}", current as f64 * 100. / max as f64, friendly_byte_units(cache.max_size()), )?; self.last_lines += 1; } if self.status.is_cancelled() { if self.last_lines > 0 { writeln!(self.terminal)?; } write!(self.terminal, "Cancelling...")?; self.last_lines += 1; } Ok(()) } } fn friendly_byte_units(mut size: u64) -> String { const UNITS: &[&str] = &["B", "kB", "MB", "GB", "TB"]; const FACTOR: u64 = 1000; let mut ind = 0; while size > FACTOR { size /= FACTOR; ind += 1; } format!("{}{}", size, UNITS[ind]) }