[cache_check (rust)] Check space map counts
- Support space map checking and auto-repair
This commit is contained in:
parent
636d50a38d
commit
cf4b937ade
@ -49,6 +49,11 @@ fn main() {
|
|||||||
.help("Only return a non-zero exit code if a fatal error is found.")
|
.help("Only return a non-zero exit code if a fatal error is found.")
|
||||||
.long("ignore-non-fatal-errors"),
|
.long("ignore-non-fatal-errors"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("AUTO_REPAIR")
|
||||||
|
.help("Auto repair trivial issues.")
|
||||||
|
.long("auto-repair"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("QUIET")
|
Arg::with_name("QUIET")
|
||||||
.help("Suppress output messages, return only exit code.")
|
.help("Suppress output messages, return only exit code.")
|
||||||
@ -76,6 +81,7 @@ fn main() {
|
|||||||
skip_hints: matches.is_present("SKIP_HINTS"),
|
skip_hints: matches.is_present("SKIP_HINTS"),
|
||||||
skip_discards: matches.is_present("SKIP_DISCARDS"),
|
skip_discards: matches.is_present("SKIP_DISCARDS"),
|
||||||
ignore_non_fatal: matches.is_present("IGNORE_NON_FATAL"),
|
ignore_non_fatal: matches.is_present("IGNORE_NON_FATAL"),
|
||||||
|
auto_repair: matches.is_present("AUTO_REPAIR"),
|
||||||
report,
|
report,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
45
src/cache/check.rs
vendored
45
src/cache/check.rs
vendored
@ -10,6 +10,9 @@ use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine};
|
|||||||
use crate::pdata::array::{self, ArrayBlock, ArrayError};
|
use crate::pdata::array::{self, ArrayBlock, ArrayError};
|
||||||
use crate::pdata::array_walker::*;
|
use crate::pdata::array_walker::*;
|
||||||
use crate::pdata::bitset::*;
|
use crate::pdata::bitset::*;
|
||||||
|
use crate::pdata::space_map::*;
|
||||||
|
use crate::pdata::space_map_checker::*;
|
||||||
|
use crate::pdata::unpack::unpack;
|
||||||
use crate::report::*;
|
use crate::report::*;
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@ -18,6 +21,14 @@ const MAX_CONCURRENT_IO: u32 = 1024;
|
|||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
|
fn inc_superblock(sm: &ASpaceMap) -> anyhow::Result<()> {
|
||||||
|
let mut sm = sm.lock().unwrap();
|
||||||
|
sm.inc(SUPERBLOCK_LOCATION, 1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
mod format1 {
|
mod format1 {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -204,6 +215,7 @@ pub struct CacheCheckOptions<'a> {
|
|||||||
pub skip_hints: bool,
|
pub skip_hints: bool,
|
||||||
pub skip_discards: bool,
|
pub skip_discards: bool,
|
||||||
pub ignore_non_fatal: bool,
|
pub ignore_non_fatal: bool,
|
||||||
|
pub auto_repair: bool,
|
||||||
pub report: Arc<Report>,
|
pub report: Arc<Report>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,6 +252,8 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> {
|
|||||||
let ctx = mk_context(&opts)?;
|
let ctx = mk_context(&opts)?;
|
||||||
|
|
||||||
let engine = &ctx.engine;
|
let engine = &ctx.engine;
|
||||||
|
let metadata_sm = core_sm(engine.get_nr_blocks(), u8::MAX as u32);
|
||||||
|
inc_superblock(&metadata_sm)?;
|
||||||
|
|
||||||
let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?;
|
let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?;
|
||||||
check_superblock(&sb)?;
|
check_superblock(&sb)?;
|
||||||
@ -258,7 +272,7 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// TODO: factor out into check_mappings()
|
// TODO: factor out into check_mappings()
|
||||||
if !opts.skip_mappings {
|
if !opts.skip_mappings {
|
||||||
let w = ArrayWalker::new(engine.clone(), opts.ignore_non_fatal);
|
let w = ArrayWalker::new_with_sm(engine.clone(), metadata_sm.clone(), opts.ignore_non_fatal)?;
|
||||||
match sb.version {
|
match sb.version {
|
||||||
1 => {
|
1 => {
|
||||||
let mut c = format1::MappingChecker::new(nr_origin_blocks);
|
let mut c = format1::MappingChecker::new(nr_origin_blocks);
|
||||||
@ -267,13 +281,13 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
// TODO: allow ignore_none_fatal
|
let (dirty_bits, err) = read_bitset_with_sm(
|
||||||
let (dirty_bits, err) = read_bitset(
|
|
||||||
engine.clone(),
|
engine.clone(),
|
||||||
sb.dirty_root.unwrap(),
|
sb.dirty_root.unwrap(),
|
||||||
sb.cache_blocks as usize,
|
sb.cache_blocks as usize,
|
||||||
|
metadata_sm.clone(),
|
||||||
opts.ignore_non_fatal,
|
opts.ignore_non_fatal,
|
||||||
);
|
)?;
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
ctx.report.fatal(&format!("{}", err.unwrap()));
|
ctx.report.fatal(&format!("{}", err.unwrap()));
|
||||||
}
|
}
|
||||||
@ -292,7 +306,7 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> {
|
|||||||
if sb.policy_hint_size != 4 {
|
if sb.policy_hint_size != 4 {
|
||||||
return Err(anyhow!("cache_check only supports policy hint size of 4"));
|
return Err(anyhow!("cache_check only supports policy hint size of 4"));
|
||||||
}
|
}
|
||||||
let w = ArrayWalker::new(engine.clone(), opts.ignore_non_fatal);
|
let w = ArrayWalker::new_with_sm(engine.clone(), metadata_sm.clone(), opts.ignore_non_fatal)?;
|
||||||
let mut c = HintChecker::new();
|
let mut c = HintChecker::new();
|
||||||
if let Err(e) = w.walk(&mut c, sb.hint_root) {
|
if let Err(e) = w.walk(&mut c, sb.hint_root) {
|
||||||
ctx.report.fatal(&format!("{}", e));
|
ctx.report.fatal(&format!("{}", e));
|
||||||
@ -302,17 +316,34 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> {
|
|||||||
// The discard bitset might not be available if the cache has never been suspended,
|
// The discard bitset might not be available if the cache has never been suspended,
|
||||||
// e.g., a crash of freshly created cache.
|
// e.g., a crash of freshly created cache.
|
||||||
if !opts.skip_discards && sb.discard_root != 0 {
|
if !opts.skip_discards && sb.discard_root != 0 {
|
||||||
let (discard_bits, err) = read_bitset(
|
let (_discard_bits, err) = read_bitset_with_sm(
|
||||||
engine.clone(),
|
engine.clone(),
|
||||||
sb.discard_root,
|
sb.discard_root,
|
||||||
sb.cache_blocks as usize,
|
sb.cache_blocks as usize,
|
||||||
|
metadata_sm.clone(),
|
||||||
opts.ignore_non_fatal,
|
opts.ignore_non_fatal,
|
||||||
);
|
)?;
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
ctx.report.fatal(&format!("{}", err.unwrap()));
|
ctx.report.fatal(&format!("{}", err.unwrap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let root = unpack::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
||||||
|
let metadata_leaks = check_metadata_space_map(
|
||||||
|
engine.clone(),
|
||||||
|
ctx.report.clone(),
|
||||||
|
root,
|
||||||
|
metadata_sm.clone(),
|
||||||
|
opts.ignore_non_fatal,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if opts.auto_repair {
|
||||||
|
if !metadata_leaks.is_empty() {
|
||||||
|
ctx.report.info("Repairing metadata leaks.");
|
||||||
|
repair_space_map(ctx.engine.clone(), metadata_leaks, metadata_sm.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,14 @@ use crate::io_engine::*;
|
|||||||
use crate::pdata::array::{self, *};
|
use crate::pdata::array::{self, *};
|
||||||
use crate::pdata::btree::{self, *};
|
use crate::pdata::btree::{self, *};
|
||||||
use crate::pdata::btree_walker::*;
|
use crate::pdata::btree_walker::*;
|
||||||
|
use crate::pdata::space_map::*;
|
||||||
use crate::pdata::unpack::*;
|
use crate::pdata::unpack::*;
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
pub struct ArrayWalker {
|
pub struct ArrayWalker {
|
||||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
ignore_non_fatal: bool,
|
ignore_non_fatal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,17 +25,20 @@ pub trait ArrayVisitor<V: Unpack> {
|
|||||||
struct BlockValueVisitor<'a, V> {
|
struct BlockValueVisitor<'a, V> {
|
||||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
array_visitor: &'a mut dyn ArrayVisitor<V>,
|
array_visitor: &'a mut dyn ArrayVisitor<V>,
|
||||||
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
array_errs: Mutex<Vec<ArrayError>>,
|
array_errs: Mutex<Vec<ArrayError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, V: Unpack + Copy> BlockValueVisitor<'a, V> {
|
impl<'a, V: Unpack + Copy> BlockValueVisitor<'a, V> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
e: Arc<dyn IoEngine + Send + Sync>,
|
e: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
v: &'a mut dyn ArrayVisitor<V>,
|
v: &'a mut dyn ArrayVisitor<V>,
|
||||||
) -> BlockValueVisitor<'a, V> {
|
) -> BlockValueVisitor<'a, V> {
|
||||||
BlockValueVisitor {
|
BlockValueVisitor {
|
||||||
engine: e,
|
engine: e,
|
||||||
array_visitor: v,
|
array_visitor: v,
|
||||||
|
sm: sm,
|
||||||
array_errs: Mutex::new(Vec::new()),
|
array_errs: Mutex::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,6 +90,8 @@ impl<'a, V: Unpack + Copy> NodeVisitor<u64> for BlockValueVisitor<'a, V> {
|
|||||||
if let Err(e) = self.array_visitor.visit(keys[i], array_block) {
|
if let Err(e) = self.array_visitor.visit(keys[i], array_block) {
|
||||||
self.array_errs.lock().unwrap().push(e);
|
self.array_errs.lock().unwrap().push(e);
|
||||||
}
|
}
|
||||||
|
let mut sm = self.sm.lock().unwrap();
|
||||||
|
sm.inc(b.loc, 1).unwrap();
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.array_errs.lock().unwrap().push(e);
|
self.array_errs.lock().unwrap().push(e);
|
||||||
@ -113,21 +120,44 @@ impl<'a, V: Unpack + Copy> NodeVisitor<u64> for BlockValueVisitor<'a, V> {
|
|||||||
|
|
||||||
impl ArrayWalker {
|
impl ArrayWalker {
|
||||||
pub fn new(engine: Arc<dyn IoEngine + Send + Sync>, ignore_non_fatal: bool) -> ArrayWalker {
|
pub fn new(engine: Arc<dyn IoEngine + Send + Sync>, ignore_non_fatal: bool) -> ArrayWalker {
|
||||||
|
let nr_blocks = engine.get_nr_blocks() as u64;
|
||||||
let r: ArrayWalker = ArrayWalker {
|
let r: ArrayWalker = ArrayWalker {
|
||||||
engine,
|
engine,
|
||||||
|
sm: Arc::new(Mutex::new(RestrictedSpaceMap::new(nr_blocks))),
|
||||||
ignore_non_fatal,
|
ignore_non_fatal,
|
||||||
};
|
};
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_with_sm(
|
||||||
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
|
ignore_non_fatal: bool,
|
||||||
|
) -> array::Result<ArrayWalker> {
|
||||||
|
{
|
||||||
|
let sm = sm.lock().unwrap();
|
||||||
|
assert_eq!(sm.get_nr_blocks().unwrap(), engine.get_nr_blocks());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ArrayWalker {
|
||||||
|
engine,
|
||||||
|
sm,
|
||||||
|
ignore_non_fatal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk<V>(&self, visitor: &mut dyn ArrayVisitor<V>, root: u64) -> array::Result<()>
|
pub fn walk<V>(&self, visitor: &mut dyn ArrayVisitor<V>, root: u64) -> array::Result<()>
|
||||||
where
|
where
|
||||||
V: Unpack + Copy,
|
V: Unpack + Copy,
|
||||||
{
|
{
|
||||||
let w = BTreeWalker::new(self.engine.clone(), self.ignore_non_fatal);
|
let w = BTreeWalker::new_with_sm(
|
||||||
|
self.engine.clone(),
|
||||||
|
self.sm.clone(),
|
||||||
|
self.ignore_non_fatal
|
||||||
|
)?;
|
||||||
let mut path = Vec::new();
|
let mut path = Vec::new();
|
||||||
path.push(0);
|
path.push(0);
|
||||||
let v = BlockValueVisitor::<V>::new(self.engine.clone(), visitor);
|
let v = BlockValueVisitor::<V>::new(self.engine.clone(), self.sm.clone(), visitor);
|
||||||
let btree_err = w.walk(&mut path, &v, root).map_err(|e| ArrayError::BTreeError(e));
|
let btree_err = w.walk(&mut path, &v, root).map_err(|e| ArrayError::BTreeError(e));
|
||||||
|
|
||||||
let mut array_errs = v.array_errs.into_inner().unwrap();
|
let mut array_errs = v.array_errs.into_inner().unwrap();
|
||||||
|
@ -4,6 +4,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use crate::io_engine::IoEngine;
|
use crate::io_engine::IoEngine;
|
||||||
use crate::pdata::array::{self, ArrayBlock};
|
use crate::pdata::array::{self, ArrayBlock};
|
||||||
use crate::pdata::array_walker::{ArrayVisitor, ArrayWalker};
|
use crate::pdata::array_walker::{ArrayVisitor, ArrayWalker};
|
||||||
|
use crate::pdata::space_map::*;
|
||||||
|
|
||||||
pub struct CheckedBitSet {
|
pub struct CheckedBitSet {
|
||||||
bits: FixedBitSet,
|
bits: FixedBitSet,
|
||||||
@ -77,7 +78,7 @@ pub fn read_bitset(
|
|||||||
nr_entries: usize,
|
nr_entries: usize,
|
||||||
ignore_none_fatal: bool,
|
ignore_none_fatal: bool,
|
||||||
)-> (CheckedBitSet, Option<array::ArrayError>) {
|
)-> (CheckedBitSet, Option<array::ArrayError>) {
|
||||||
let w = ArrayWalker::new(engine.clone(), ignore_none_fatal);
|
let w = ArrayWalker::new(engine, ignore_none_fatal);
|
||||||
let mut v = BitsetVisitor::new(nr_entries);
|
let mut v = BitsetVisitor::new(nr_entries);
|
||||||
let err = w.walk(&mut v, root);
|
let err = w.walk(&mut v, root);
|
||||||
let e = match err {
|
let e = match err {
|
||||||
@ -86,3 +87,21 @@ pub fn read_bitset(
|
|||||||
};
|
};
|
||||||
return (v.get_bitset(), e);
|
return (v.get_bitset(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: multi-threaded is possible
|
||||||
|
pub fn read_bitset_with_sm(
|
||||||
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
root: u64,
|
||||||
|
nr_entries: usize,
|
||||||
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
|
ignore_none_fatal: bool,
|
||||||
|
)-> array::Result<(CheckedBitSet, Option<array::ArrayError>)> {
|
||||||
|
let w = ArrayWalker::new_with_sm(engine, sm, ignore_none_fatal)?;
|
||||||
|
let mut v = BitsetVisitor::new(nr_entries);
|
||||||
|
let err = w.walk(&mut v, root);
|
||||||
|
let e = match err {
|
||||||
|
Ok(()) => None,
|
||||||
|
Err(e) => Some(e),
|
||||||
|
};
|
||||||
|
return Ok((v.get_bitset(), e));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user