diff --git a/src/lib.rs b/src/lib.rs index 529b14c..0dbdfab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,12 +15,13 @@ extern crate quickcheck; #[cfg(test)] extern crate quickcheck_macros; -pub mod io_engine; pub mod cache; pub mod checksum; pub mod file_utils; +pub mod io_engine; pub mod pack; pub mod pdata; +pub mod report; pub mod shrink; pub mod thin; pub mod version; diff --git a/src/report.rs b/src/report.rs new file mode 100644 index 0000000..94a76da --- /dev/null +++ b/src/report.rs @@ -0,0 +1,198 @@ +use anyhow::Result; +use indicatif::{ProgressBar, ProgressStyle}; +use std::sync::Mutex; + +//------------------------------------------ + +#[derive(Clone)] +pub enum ReportOutcome { + Success, + NonFatal, + Fatal, +} + +use ReportOutcome::*; + +impl ReportOutcome { + pub fn combine(lhs: &ReportOutcome, rhs: &ReportOutcome) -> ReportOutcome { + match (lhs, rhs) { + (Success, rhs) => rhs.clone(), + (lhs, Success) => lhs.clone(), + (Fatal, _) => Fatal, + (_, Fatal) => Fatal, + (_, _) => NonFatal, + } + } +} + +pub struct Report { + outcome: Mutex, + inner: Mutex>, +} + +trait ReportInner { + fn set_title(&mut self, txt: &str) -> Result<()>; + fn progress(&mut self, percent: u8) -> Result<()>; + fn log(&mut self, txt: &str) -> Result<()>; + fn complete(&mut self) -> Result<()>; +} + +impl Report { + fn new(inner: Box) -> Report { + Report { + outcome: Mutex::new(Success), + inner: Mutex::new(inner), + } + } + + fn update_outcome(&self, rhs: ReportOutcome) { + let mut lhs = self.outcome.lock().unwrap(); + *lhs = ReportOutcome::combine(&lhs, &rhs); + } + + pub fn set_title(&self, txt: &str) -> Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.set_title(txt) + } + + pub fn progress(&self, percent: u8) -> Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.progress(percent) + } + + pub fn info(&self, txt: &str) -> Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.log(txt) + } + + pub fn non_fatal(&self, txt: &str) -> Result<()> { + self.update_outcome(NonFatal); + let mut inner = self.inner.lock().unwrap(); + inner.log(txt) + } + + pub fn fatal(&self, txt: &str) -> Result<()> { + self.update_outcome(Fatal); + let mut inner = self.inner.lock().unwrap(); + inner.log(txt) + } + + pub fn complete(&mut self) -> Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner.complete()?; + Ok(()) + } +} + +//------------------------------------------ + +struct PBInner { + bar: ProgressBar, +} + +impl ReportInner for PBInner { + fn set_title(&mut self, txt: &str) -> Result<()> { + let mut fmt = "Checking thin metadata [{bar:40}] Remaining {eta}, ".to_string(); + fmt.push_str(&txt); + self.bar.set_style( + ProgressStyle::default_bar() + .template(&fmt) + .progress_chars("=> "), + ); + Ok(()) + } + + fn progress(&mut self, percent: u8) -> Result<()> { + self.bar.set_position(percent as u64); + self.bar.tick(); + Ok(()) + } + + fn log(&mut self, txt: &str) -> Result<()> { + self.bar.println(txt); + Ok(()) + } + + fn complete(&mut self) -> Result<()> { + self.bar.finish(); + Ok(()) + } +} + +pub fn mk_progress_bar_report() -> Report { + Report::new(Box::new(PBInner { + bar: ProgressBar::new(100), + })) +} + +//------------------------------------------ + +struct SimpleInner { + last_progress: std::time::SystemTime, +} + +impl SimpleInner { + fn new() -> SimpleInner { + SimpleInner { + last_progress: std::time::SystemTime::now(), + } + } +} + +impl ReportInner for SimpleInner { + fn set_title(&mut self, txt: &str) -> Result<()> { + println!("{}", txt); + Ok(()) + } + + fn progress(&mut self, percent: u8) -> Result<()> { + let elapsed = self.last_progress.elapsed().unwrap(); + if elapsed > std::time::Duration::from_secs(5) { + println!("Progress: {}%", percent); + self.last_progress = std::time::SystemTime::now(); + } + Ok(()) + } + + fn log(&mut self, txt: &str) -> Result<()> { + eprintln!("{}", txt); + Ok(()) + } + + fn complete(&mut self) -> Result<()> { + Ok(()) + } +} + +pub fn mk_simple_report() -> Report { + Report::new(Box::new(SimpleInner::new())) +} + +//------------------------------------------ + +struct QuietInner { +} + +impl ReportInner for QuietInner { + fn set_title(&mut self, _txt: &str) -> Result<()> { + Ok(()) + } + + fn progress(&mut self, _percent: u8) -> Result<()> { + Ok(()) + } + + fn log(&mut self, _txt: &str) -> Result<()> { + Ok(()) + } + + fn complete(&mut self) -> Result<()> { + Ok(()) + } +} + +pub fn mk_quiet_report() -> Report { + Report::new(Box::new(QuietInner {})) +} + +//------------------------------------------ diff --git a/src/thin/check.rs b/src/thin/check.rs index 017c4e4..b24f666 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -1,11 +1,8 @@ use anyhow::{anyhow, Result}; -use indicatif::{ProgressBar, ProgressStyle}; use nom::{number::complete::*, IResult}; -use std::collections::BTreeMap; use std::path::Path; -use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; use std::sync::{Arc, Mutex}; -use std::{thread, time}; +use std::thread; use threadpool::ThreadPool; use crate::checksum; @@ -13,6 +10,7 @@ use crate::io_engine::{AsyncIoEngine, Block, IoEngine, SyncIoEngine}; use crate::pdata::btree::{btree_to_map, btree_to_map_with_sm, BTreeWalker, Node, NodeVisitor}; use crate::pdata::space_map::*; use crate::pdata::unpack::*; +use crate::report::*; use crate::thin::superblock::*; //------------------------------------------ @@ -156,143 +154,10 @@ impl<'a> NodeVisitor for OverflowChecker<'a> { //------------------------------------------ -struct ReportOptions {} - -#[derive(Clone)] -enum ReportOutcome { - Success, - NonFatal, - Fatal, -} - -use ReportOutcome::*; - -impl ReportOutcome { - fn combine(lhs: &ReportOutcome, rhs: &ReportOutcome) -> ReportOutcome { - match (lhs, rhs) { - (Success, rhs) => rhs.clone(), - (lhs, Success) => lhs.clone(), - (Fatal, _) => Fatal, - (_, Fatal) => Fatal, - (_, _) => NonFatal, - } - } -} - -enum ReportCmd { - Log(String), - Complete, - Title(String), -} - -struct Report { - opts: ReportOptions, - outcome: ReportOutcome, - tx: Sender, - tid: thread::JoinHandle<()>, -} - -impl Report { - fn new( - opts: ReportOptions, - sm: Arc>, - total_allocated: u64, - ) -> Result { - let (tx, rx) = channel(); - let tid = thread::spawn(move || report_thread(sm, total_allocated, rx)); - Ok(Report { - opts, - outcome: ReportOutcome::Success, - tx, - tid, - }) - } - - fn info>(&mut self, txt: I) -> Result<()> { - self.tx.send(ReportCmd::Log(txt.into()))?; - Ok(()) - } - - fn add_outcome(&mut self, rhs: ReportOutcome) { - self.outcome = ReportOutcome::combine(&self.outcome, &rhs); - } - - fn non_fatal>(&mut self, txt: I) -> Result<()> { - self.add_outcome(NonFatal); - self.tx.send(ReportCmd::Log(txt.into()))?; - Ok(()) - } - - fn fatal>(&mut self, txt: I) -> Result<()> { - self.add_outcome(Fatal); - self.tx.send(ReportCmd::Log(txt.into()))?; - Ok(()) - } - - fn complete(self) -> Result<()> { - self.tx.send(ReportCmd::Complete)?; - self.tid.join(); - Ok(()) - } - - fn set_title(&mut self, txt: &str) -> Result<()> { - self.tx.send(ReportCmd::Title(txt.to_string()))?; - Ok(()) - } -} - -fn report_thread( - sm: Arc>, - total_allocated: u64, - rx: Receiver, -) { - let interval = time::Duration::from_millis(250); - let bar = ProgressBar::new(total_allocated); - loop { - loop { - match rx.try_recv() { - Ok(ReportCmd::Log(txt)) => { - bar.println(txt); - } - Ok(ReportCmd::Complete) => { - bar.finish(); - return; - } - Ok(ReportCmd::Title(txt)) => { - let mut fmt = "Checking thin metadata [{bar:40}] Remaining {eta}, ".to_string(); - fmt.push_str(&txt); - bar.set_style( - ProgressStyle::default_bar() - .template(&fmt) - .progress_chars("=> "), - ); - } - Err(TryRecvError::Disconnected) => { - return; - } - Err(TryRecvError::Empty) => { - break; - } - } - } - - let sm = sm.lock().unwrap(); - let nr_allocated = sm.get_nr_allocated().unwrap(); - drop(sm); - - bar.set_position(nr_allocated); - bar.tick(); - - thread::sleep(interval); - } -} - -//------------------------------------------ - fn check_space_map( kind: &str, engine: Arc, - bar: &mut Report, + report: &Arc, entries: Vec, metadata_sm: Option>>, sm: Arc>, @@ -344,7 +209,7 @@ fn check_space_map( if actual == 1 && expected == 0 { leaks += 1; } else if actual != expected as u8 { - bar.fatal(format!("Bad reference count for {} block {}. Expected {}, but space map contains {}.", + report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map contains {}.", kind, blocknr, expected, actual))?; fail = true; } @@ -352,7 +217,7 @@ fn check_space_map( BitmapEntry::Overflow => { let expected = sm.get(blocknr)?; if expected < 3 { - bar.fatal(format!("Bad reference count for {} block {}. Expected {}, but space map says it's >= 3.", + report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map says it's >= 3.", kind, blocknr, expected))?; fail = true; } @@ -363,7 +228,7 @@ fn check_space_map( } if leaks > 0 { - bar.non_fatal(format!( + report.non_fatal(&format!( "{} {} blocks have leaked. Use --auto-repair to fix.", leaks, kind ))?; @@ -421,8 +286,39 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> { let devs = btree_to_map::(engine.clone(), false, sb.details_root)?; let nr_devs = devs.len(); let metadata_sm = core_sm(engine.get_nr_blocks(), nr_devs as u32); - let opts = ReportOptions {}; - let mut report = Report::new(opts, metadata_sm.clone(), nr_allocated_metadata)?; + //let report = Arc::new(mk_progress_bar_report()); + //let report = Arc::new(mk_simple_report()); + let report = Arc::new(mk_quiet_report()); + + let tid; + let stop_progress = Arc::new(Mutex::new(false)); + + { + let report = report.clone(); + let sm = metadata_sm.clone(); + let stop_progress = stop_progress.clone(); + tid = thread::spawn(move || { + let interval = std::time::Duration::from_millis(250); + loop { + { + let stop_progress = stop_progress.lock().unwrap(); + if *stop_progress { + break; + } + } + + let sm = sm.lock().unwrap(); + + let mut n = sm.get_nr_allocated().unwrap(); + drop(sm); + n *= 100; + n /= nr_allocated_metadata; + + let _r = report.progress(n as u8); + thread::sleep(interval); + } + }); + } report.set_title("device details tree")?; let _devs = btree_to_map_with_sm::( @@ -439,7 +335,8 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> { } // mapping top level - let roots = btree_to_map_with_sm::(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?; + let roots = + btree_to_map_with_sm::(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?; // Check the mappings filling in the data_sm as we go. report.set_title("mapping tree")?; @@ -489,7 +386,7 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> { check_space_map( "data", engine.clone(), - &mut report, + &report, entries, Some(metadata_sm.clone()), data_sm.clone(), @@ -524,14 +421,20 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> { check_space_map( "metadata", engine.clone(), - &mut report, + &report, entries, None, metadata_sm.clone(), root, )?; - report.complete()?; + // Completing consumes the report. + { + let mut stop_progress = stop_progress.lock().unwrap(); + *stop_progress = true; + } + + tid.join(); Ok(()) }