use log::info;
use std::{sync::Arc, time::Instant};
pub type LogFn = dyn Fn(&str) + Send + Sync;
pub enum Progress<'a> {
Log(LogProgress<'a>),
None,
}
impl<'a> Progress<'a> {
pub fn log(
message: &'a str,
final_message: &'a str,
use_thread_id: bool,
total: Option<u64>,
log_interval: u64,
log_fn: Arc<dyn Fn(&str) + Send + Sync>,
) -> Self {
Self::Log(LogProgress::new(
message,
final_message,
use_thread_id,
total,
log_interval,
log_fn,
))
}
pub fn none() -> Self {
Self::None
}
pub fn from_style(
style: &ProgressStyle,
message: &'a str,
final_message: &'a str,
total: Option<u64>,
log_interval: u64,
) -> Self {
match &style.log_fn {
None => Self::none(),
Some(log_fn) => Self::log(
message,
final_message,
style.use_thread_id,
total,
log_interval,
log_fn.clone(),
),
}
}
pub fn inc(&mut self, amount: u64) {
match self {
Self::Log(x) => x.inc(amount),
Self::None => {}
}
}
pub fn finish(&self) {
match self {
Self::Log(x) => x.finish(),
Self::None => {}
}
}
}
pub struct LogProgress<'a> {
message: &'a str,
final_message: &'a str,
thread_id: String,
total: Option<u64>,
current: u64,
log_interval: u64,
start_time: Instant,
last_log_time: Instant,
last_log_count: u64,
log_fn: Arc<dyn Fn(&str) + Send + Sync>,
}
impl<'a> LogProgress<'a> {
pub fn new(
message: &'a str,
final_message: &'a str,
use_thread_id: bool,
total: Option<u64>,
log_interval: u64,
log_fn: Arc<dyn Fn(&str) + Send + Sync>,
) -> Self {
assert_ne!(log_interval, 0);
let thread_id = if use_thread_id {
format!(" [{:?}]", std::thread::current().id())
} else {
"".to_string()
};
let now = Instant::now();
Self {
message,
final_message,
thread_id,
total,
current: 0,
start_time: now,
last_log_time: now,
last_log_count: 0,
log_interval,
log_fn,
}
}
pub fn inc(&mut self, amount: u64) {
self.current += amount;
if self.current % self.log_interval == 0 {
self.log_progress();
self.last_log_time = Instant::now();
self.last_log_count = self.current;
}
}
pub fn finish(&self) {
let elapsed = self.start_time.elapsed();
let avg_rate = self.current as f64 / elapsed.as_secs_f64();
match self.total {
None => {
(self.log_fn)(&format!(
"{} {} in {:.2?} | avg. rate: {:.2} items/s{}",
self.final_message, self.current, elapsed, avg_rate, self.thread_id
));
}
Some(total) => {
(self.log_fn)(&format!(
"{} {}/{} 100% in {:.2?} | avg. rate: {:.2} items/s{}",
self.final_message, self.current, total, elapsed, avg_rate, self.thread_id
));
}
}
}
fn log_progress(&self) {
let elapsed = self.start_time.elapsed();
let since_last = self.last_log_time.elapsed();
let current_rate = if since_last.as_secs_f64() > 0.0 {
(self.current - self.last_log_count) as f64 / since_last.as_secs_f64()
} else {
0.0
};
let avg_rate = if elapsed.as_secs_f64() > 0.0 {
self.current as f64 / elapsed.as_secs_f64()
} else {
0.0
};
match self.total {
None => {
(self.log_fn)(&format!(
"{} {} in {:.2?} | current rate: {:.2} items/s | avg. rate: {:.2} items/s{}",
self.message, self.current, elapsed, current_rate, avg_rate, self.thread_id
));
}
Some(total) => {
let percent: f64 = (self.current as f64) / (total as f64) * 100.0;
let remaining: f64 = ((total - self.current) as f64) / avg_rate;
(self.log_fn)(&format!(
"{} {}/{:.1} {:.0}% in {:.2?} | current rate: {:.2} items/s | avg. rate: {:.2} items/s | est {:.0}s remaining{}",
self.message,
self.current,
total,
percent,
elapsed,
current_rate,
avg_rate,
remaining,
self.thread_id
));
}
}
}
}
#[derive(Clone)]
pub struct ProgressStyle {
log_fn: Option<Arc<LogFn>>,
pub use_thread_id: bool,
}
impl ProgressStyle {
pub fn new(log_fn: Option<Arc<LogFn>>, use_thread_id: bool) -> Self {
Self {
log_fn,
use_thread_id,
}
}
}
impl Default for ProgressStyle {
fn default() -> Self {
ProgressStyle::new(Some(Arc::new(|msg| info!("{}", msg))), false)
}
}