-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clean up technical debt #20
Changes from all commits
e535be6
53c5e59
8087879
cbc8ce6
301e86f
9b4b21b
e68b95e
88b5552
a99c5c6
fa898c9
d41795c
5e89028
593f875
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
pub mod db; | ||
pub mod lmdb_utils; | ||
pub mod progress; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
use std::result::Result; | ||
|
||
use lmdb::{Database, Error, Transaction}; | ||
use lmdb_sys::{mdb_stat, MDB_stat}; | ||
|
||
/// Retrieves the number of entries in a database. | ||
pub fn entry_count<T: Transaction>(txn: &'_ T, database: Database) -> Result<usize, Error> { | ||
let mut stat = MDB_stat { | ||
ms_psize: 0, | ||
ms_depth: 0, | ||
ms_branch_pages: 0, | ||
ms_leaf_pages: 0, | ||
ms_overflow_pages: 0, | ||
ms_entries: 0, | ||
}; | ||
let result = unsafe { mdb_stat(txn.txn(), database.dbi(), &mut stat as *mut MDB_stat) }; | ||
if result != 0 { | ||
Err(Error::from_err_code(result)) | ||
} else { | ||
Ok(stat.ms_entries) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use lmdb::{Transaction, WriteFlags}; | ||
|
||
use crate::test_utils::LmdbTestFixture; | ||
|
||
use super::entry_count; | ||
|
||
#[test] | ||
fn db_entry_count() { | ||
let fixture = LmdbTestFixture::new(None, None); | ||
let env = &fixture.env; | ||
let db = fixture.db; | ||
|
||
if let Ok(txn) = env.begin_ro_txn() { | ||
assert_eq!(entry_count(&txn, db).unwrap(), 0); | ||
txn.commit().unwrap(); | ||
} | ||
|
||
let first_dummy_input = [0u8, 1u8]; | ||
let second_dummy_input = [1u8, 2u8]; | ||
// Insert the first entry into the database. | ||
if let Ok(mut txn) = env.begin_rw_txn() { | ||
txn.put( | ||
fixture.db, | ||
&first_dummy_input, | ||
&first_dummy_input, | ||
WriteFlags::empty(), | ||
) | ||
.unwrap(); | ||
txn.commit().unwrap(); | ||
}; | ||
|
||
if let Ok(txn) = env.begin_ro_txn() { | ||
assert_eq!(entry_count(&txn, db).unwrap(), 1); | ||
txn.commit().unwrap(); | ||
} | ||
|
||
// Insert the second entry into the database. | ||
if let Ok(mut txn) = env.begin_rw_txn() { | ||
txn.put( | ||
fixture.db, | ||
&second_dummy_input, | ||
&second_dummy_input, | ||
WriteFlags::empty(), | ||
) | ||
.unwrap(); | ||
txn.commit().unwrap(); | ||
}; | ||
|
||
if let Ok(txn) = env.begin_ro_txn() { | ||
assert_eq!(entry_count(&txn, db).unwrap(), 2); | ||
txn.commit().unwrap(); | ||
}; | ||
|
||
// Delete the first entry from the database. | ||
if let Ok(mut txn) = env.begin_rw_txn() { | ||
txn.del(fixture.db, &first_dummy_input, None).unwrap(); | ||
txn.commit().unwrap(); | ||
}; | ||
|
||
if let Ok(txn) = env.begin_ro_txn() { | ||
assert_eq!(entry_count(&txn, db).unwrap(), 1); | ||
txn.commit().unwrap(); | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
use std::result::Result; | ||
|
||
use log::warn; | ||
|
||
// The tracker will log 20 times during its operation. | ||
const STEPS: usize = 20; | ||
// The tracker will log progress every in 5% completion intervals. | ||
const PROGRESS_MULTIPLIER: u64 = 100 / STEPS as u64; | ||
// Error message for initialization of a progress tracker with nothing | ||
// to process. | ||
const NULL_TOTAL_TO_PROCESS_ERROR: &str = "Cannot initialize total to process with 0"; | ||
|
||
/// Tracks and logs progress of an operation in a human readable form. | ||
/// Whenever (1 / number of steps) of the total amount has been processed, | ||
/// this structure calls the `log_progress` function, which takes the | ||
/// percentage completed so far as a parameter. | ||
pub struct ProgressTracker { | ||
/// Total amount there is to process. | ||
total_to_process: usize, | ||
/// Amount processed so far. | ||
processed: usize, | ||
/// Internal counter to keep track of the number of steps completed | ||
/// so far relative to the maximum amount of steps this operation | ||
/// will do, defined in `STEPS`. | ||
progress_factor: u64, | ||
Fraser999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Function which takes the completion rate as a percentage as | ||
/// input. It is called zero or more times as progress is being made | ||
/// using `ProgressTracker::advance_by`. The purpose of this function | ||
/// is to allow users to create custom log messages for their specific | ||
/// operation. | ||
log_progress: Box<dyn Fn(u64)>, | ||
} | ||
|
||
impl ProgressTracker { | ||
/// Create a new progress tracker by initializing it with a non-zero | ||
/// amount to be processed and a log function. | ||
pub fn new( | ||
total_to_process: usize, | ||
log_progress: Box<dyn Fn(u64)>, | ||
) -> Result<Self, &'static str> { | ||
if total_to_process == 0 { | ||
Err(NULL_TOTAL_TO_PROCESS_ERROR) | ||
} else { | ||
Ok(Self { | ||
total_to_process, | ||
processed: 0, | ||
progress_factor: 1, | ||
log_progress, | ||
}) | ||
} | ||
} | ||
|
||
/// Advance the progress tracker by a specific amount. If it passes | ||
/// a milestone ((1 / STEP) of the total amount to process), | ||
/// `log_progress` will be called with the current completion rate | ||
/// as input. | ||
pub fn advance_by(&mut self, step: usize) { | ||
self.processed += step; | ||
while self.processed >= (self.total_to_process * self.progress_factor as usize) / STEPS { | ||
goral09 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(*self.log_progress)(self.progress_factor * PROGRESS_MULTIPLIER); | ||
self.progress_factor += 1; | ||
} | ||
if self.processed > self.total_to_process { | ||
warn!( | ||
"Exceeded total amount to process {} by {}", | ||
self.total_to_process, | ||
self.processed - self.total_to_process | ||
); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
use std::process; | ||
|
||
use clap::{ArgMatches, Command}; | ||
use thiserror::Error as ThisError; | ||
|
||
pub use create::Error as CreateError; | ||
pub use unpack::Error as UnpackError; | ||
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can get rid of this name aliasing, which can be confusing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I explicitly did it this way for 2 reasons:
As you can see, for me at least it's not confusing. I personally would like to keep it this way, but if people feel strongly about this, we can change it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm in favour of the approach taken in this PR personally. I dislike the stuttering effect of having e.g. I'm also ok with avoiding aliases if folks have an objection, but we generally end up with almost the same readability - e.g. |
||
|
||
use super::Error as SubcommandError; | ||
|
||
mod create; | ||
mod ring_buffer; | ||
|
@@ -15,6 +21,23 @@ enum DisplayOrder { | |
Unpack, | ||
} | ||
|
||
#[derive(ThisError, Debug)] | ||
pub enum Error { | ||
#[error("create: {0}")] | ||
Create(#[from] CreateError), | ||
#[error("unpack: {0}")] | ||
Unpack(#[from] UnpackError), | ||
} | ||
|
||
impl From<Error> for SubcommandError { | ||
fn from(err: Error) -> Self { | ||
match err { | ||
Error::Create(create_err) => SubcommandError::ArchiveCreate(create_err), | ||
Error::Unpack(unpack_err) => SubcommandError::ArchiveUnpack(unpack_err), | ||
} | ||
} | ||
} | ||
|
||
pub fn command(display_order: usize) -> Command<'static> { | ||
Command::new(COMMAND_NAME) | ||
.display_order(display_order) | ||
|
@@ -23,14 +46,14 @@ pub fn command(display_order: usize) -> Command<'static> { | |
.subcommand(unpack::command(DisplayOrder::Unpack as usize)) | ||
} | ||
|
||
pub fn run(matches: &ArgMatches) -> bool { | ||
pub fn run(matches: &ArgMatches) -> Result<(), Error> { | ||
let (subcommand_name, matches) = matches.subcommand().unwrap_or_else(|| { | ||
process::exit(1); | ||
}); | ||
|
||
match subcommand_name { | ||
create::COMMAND_NAME => create::run(matches), | ||
unpack::COMMAND_NAME => unpack::run(matches), | ||
create::COMMAND_NAME => create::run(matches).map_err(Error::Create), | ||
unpack::COMMAND_NAME => unpack::run(matches).map_err(Error::Unpack), | ||
_ => unreachable!("{} should be handled above", subcommand_name), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nit, but can you extend the test to
del
the previously added element and call theentry_count()
, just to prove how the system behaves?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.