[thin_check (rust)] Provide 3 different report types.

ProgressBar for interactive use.
Simple for when not a tty
and quiet for -q
This commit is contained in:
Joe Thornber 2020-08-17 13:08:29 +01:00
parent 7466cd7182
commit 9995751dde
3 changed files with 250 additions and 148 deletions

View File

@ -15,12 +15,13 @@ extern crate quickcheck;
#[cfg(test)] #[cfg(test)]
extern crate quickcheck_macros; extern crate quickcheck_macros;
pub mod io_engine;
pub mod cache; pub mod cache;
pub mod checksum; pub mod checksum;
pub mod file_utils; pub mod file_utils;
pub mod io_engine;
pub mod pack; pub mod pack;
pub mod pdata; pub mod pdata;
pub mod report;
pub mod shrink; pub mod shrink;
pub mod thin; pub mod thin;
pub mod version; pub mod version;

198
src/report.rs Normal file
View File

@ -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<ReportOutcome>,
inner: Mutex<Box<dyn ReportInner + Send>>,
}
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<dyn ReportInner + Send>) -> 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 {}))
}
//------------------------------------------

View File

@ -1,11 +1,8 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use indicatif::{ProgressBar, ProgressStyle};
use nom::{number::complete::*, IResult}; use nom::{number::complete::*, IResult};
use std::collections::BTreeMap;
use std::path::Path; use std::path::Path;
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{thread, time}; use std::thread;
use threadpool::ThreadPool; use threadpool::ThreadPool;
use crate::checksum; 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::btree::{btree_to_map, btree_to_map_with_sm, BTreeWalker, Node, NodeVisitor};
use crate::pdata::space_map::*; use crate::pdata::space_map::*;
use crate::pdata::unpack::*; use crate::pdata::unpack::*;
use crate::report::*;
use crate::thin::superblock::*; use crate::thin::superblock::*;
//------------------------------------------ //------------------------------------------
@ -156,143 +154,10 @@ impl<'a> NodeVisitor<u32> 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<ReportCmd>,
tid: thread::JoinHandle<()>,
}
impl Report {
fn new(
opts: ReportOptions,
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
total_allocated: u64,
) -> Result<Report> {
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<I: Into<String>>(&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<I: Into<String>>(&mut self, txt: I) -> Result<()> {
self.add_outcome(NonFatal);
self.tx.send(ReportCmd::Log(txt.into()))?;
Ok(())
}
fn fatal<I: Into<String>>(&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<Mutex<dyn SpaceMap + Send + Sync>>,
total_allocated: u64,
rx: Receiver<ReportCmd>,
) {
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( fn check_space_map(
kind: &str, kind: &str,
engine: Arc<dyn IoEngine + Send + Sync>, engine: Arc<dyn IoEngine + Send + Sync>,
bar: &mut Report, report: &Arc<Report>,
entries: Vec<IndexEntry>, entries: Vec<IndexEntry>,
metadata_sm: Option<Arc<Mutex<dyn SpaceMap + Send + Sync>>>, metadata_sm: Option<Arc<Mutex<dyn SpaceMap + Send + Sync>>>,
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>, sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
@ -344,7 +209,7 @@ fn check_space_map(
if actual == 1 && expected == 0 { if actual == 1 && expected == 0 {
leaks += 1; leaks += 1;
} else if actual != expected as u8 { } 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))?; kind, blocknr, expected, actual))?;
fail = true; fail = true;
} }
@ -352,7 +217,7 @@ fn check_space_map(
BitmapEntry::Overflow => { BitmapEntry::Overflow => {
let expected = sm.get(blocknr)?; let expected = sm.get(blocknr)?;
if expected < 3 { 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))?; kind, blocknr, expected))?;
fail = true; fail = true;
} }
@ -363,7 +228,7 @@ fn check_space_map(
} }
if leaks > 0 { if leaks > 0 {
bar.non_fatal(format!( report.non_fatal(&format!(
"{} {} blocks have leaked. Use --auto-repair to fix.", "{} {} blocks have leaked. Use --auto-repair to fix.",
leaks, kind leaks, kind
))?; ))?;
@ -421,8 +286,39 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
let devs = btree_to_map::<DeviceDetail>(engine.clone(), false, sb.details_root)?; let devs = btree_to_map::<DeviceDetail>(engine.clone(), false, sb.details_root)?;
let nr_devs = devs.len(); let nr_devs = devs.len();
let metadata_sm = core_sm(engine.get_nr_blocks(), nr_devs as u32); let metadata_sm = core_sm(engine.get_nr_blocks(), nr_devs as u32);
let opts = ReportOptions {}; //let report = Arc::new(mk_progress_bar_report());
let mut report = Report::new(opts, metadata_sm.clone(), nr_allocated_metadata)?; //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")?; report.set_title("device details tree")?;
let _devs = btree_to_map_with_sm::<DeviceDetail>( let _devs = btree_to_map_with_sm::<DeviceDetail>(
@ -439,7 +335,8 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
} }
// mapping top level // mapping top level
let roots = btree_to_map_with_sm::<u64>(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?; let roots =
btree_to_map_with_sm::<u64>(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?;
// Check the mappings filling in the data_sm as we go. // Check the mappings filling in the data_sm as we go.
report.set_title("mapping tree")?; report.set_title("mapping tree")?;
@ -489,7 +386,7 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
check_space_map( check_space_map(
"data", "data",
engine.clone(), engine.clone(),
&mut report, &report,
entries, entries,
Some(metadata_sm.clone()), Some(metadata_sm.clone()),
data_sm.clone(), data_sm.clone(),
@ -524,14 +421,20 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
check_space_map( check_space_map(
"metadata", "metadata",
engine.clone(), engine.clone(),
&mut report, &report,
entries, entries,
None, None,
metadata_sm.clone(), metadata_sm.clone(),
root, root,
)?; )?;
report.complete()?; // Completing consumes the report.
{
let mut stop_progress = stop_progress.lock().unwrap();
*stop_progress = true;
}
tid.join();
Ok(()) Ok(())
} }