mtlog_progress/
lib.rs

1
2//! # mtlog-progress
3//! A progress bar implementation working gracefully with mtlog's logger.
4//!
5//! ## Usage with std threads
6//! ```toml
7//! // Cargo.toml
8//! ...
9//! [dependencies]
10//! mtlog-progress = "0.1.0"
11//! mtlog = "0.1.4"
12//! ```
13//! 
14//! ```rust
15//! use mtlog::logger_config;
16//! use mtlog_progress::LogProgressBar;
17//! 
18//! logger_config()
19//!     .init_global();
20//! 
21//! let h = std::thread::spawn(|| {
22//!     let pb = LogProgressBar::new(100, "My Progress Bar");
23//!     for i in 0..100 {
24//!         pb.inc(1);
25//!         if i == 50 {
26//!             log::info!("Halfway there!");
27//!         }
28//!     }
29//!     pb.finish();
30//! });
31//! log::info!("This log goes below the progress bar");
32//! h.join().unwrap(); // the progress bar continue to work at it's line position
33//! 
34//! ```
35//! ## Usage with tokio tasks
36//! 
37//! ## Usage
38//! ```toml
39//! // Cargo.toml
40//! ...
41//! [dependencies]
42//! mtlog-progress = "0.1.0"
43//! mtlog-tokio = "0.1.0"
44//! tokio = { version = "1.40.0", features = ["full"] }
45//! ```
46//! 
47//! ```rust
48//! use mtlog_tokio::logger_config;
49//! use mtlog_progress::LogProgressBar;
50//! 
51//! #[tokio::main]
52//! async fn main() {
53//!     logger_config()
54//!         .scope_global(async move {
55//!             let h = tokio::spawn(async move {
56//!                 logger_config()
57//!                     .scope_local(async move {
58//!                         let pb = LogProgressBar::new(100, "My Progress Bar");
59//!                         for i in 0..100 {
60//!                             pb.inc(1);
61//!                             if i == 50 {
62//!                                 log::info!("Halfway there!");
63//!                             }
64//!                         }
65//!                         pb.finish();
66//!                     }).await;    
67//!             });
68//!             log::info!("This log goes below the progress bar");
69//!             h.await.unwrap(); // the progress bar continue to work at it's line position
70//!         }).await;
71//! }
72//! ```
73
74
75use std::{sync::{Arc, Mutex},time::{Duration, Instant}};
76use colored::Colorize;
77use uuid::Uuid;
78
79
80#[derive(Clone)]
81pub struct LogProgressBar {
82    n_iter: Arc<usize>,
83    name: Arc<str>,
84    current_iter: Arc<Mutex<usize>>,
85    id: Arc<Uuid>,
86    finished: Arc<Mutex<bool>>,
87    min_duration: Arc<Duration>,
88    last_iter: Arc<Mutex<Instant>>
89}
90
91impl LogProgressBar {
92    pub fn new(n_iter: usize, name: &str) -> Self {
93        let pb = Self {
94            n_iter: Arc::new(n_iter.max(1)),
95            name: name.into(),
96            current_iter: Arc::new(Mutex::new(0usize)),
97            id: Arc::new(Uuid::new_v4()),
98            finished: Arc::new(Mutex::new(false)),
99            min_duration: Arc::new(Duration::from_millis(100)),
100            last_iter: Arc::new(Mutex::new(Instant::now()-Duration::from_millis(100))),
101        };
102        pb.send();
103        pb
104    }
105
106    pub fn with_min_timestep_ms(mut self, min_duration_ms: f64) -> Self {
107        self.min_duration = Arc::new(Duration::from_micros((min_duration_ms*1000.0).round() as u64));
108        self
109    }
110
111    pub fn send(&self) {
112        if !*self.finished.lock().unwrap() && self.last_iter.lock().unwrap().elapsed() > *self.min_duration {
113            log::info!("___PROGRESS___{}___{}",self.id,self.format());
114            *self.last_iter.lock().unwrap() = Instant::now();
115        }
116    }
117
118    pub fn set_progress(&self, n: usize) {
119        *self.current_iter.lock().unwrap() = n;
120        self.send();
121    }
122
123    pub fn inc(&self, n: usize) {
124        *self.current_iter.lock().unwrap() += n;
125        self.send();
126    }
127
128    fn format(&self) -> String {
129        let current_iter = *self.current_iter.lock().unwrap();
130        let percentage = (current_iter as f64 / *self.n_iter as f64 * 100.0) as usize;
131        let bar_length = 20; // Length of the progress bar
132        let filled_length = (bar_length * current_iter / *self.n_iter).min(bar_length);
133        let bar = "#".repeat(filled_length) + &".".repeat(bar_length - filled_length);
134        let n_iter_str = self.n_iter.to_string();
135        format!(
136            "Progress {name}: [{bar}] {current:>len$}/{n_iter_str} {percentage:>3}%",
137            name=self.name.cyan(), 
138            bar=bar.cyan(),
139            current=current_iter,
140            len=n_iter_str.len(),
141        )
142    }
143    
144    pub fn finish(&self) { 
145        if *self.finished.lock().unwrap() {
146            return
147        }
148        self.set_progress(*self.n_iter);    
149        *self.finished.lock().unwrap() = true;
150        log::info!("___PROGRESS___{}___FINISHED",self.id)
151    }
152}
153
154impl Drop for LogProgressBar {
155    fn drop(&mut self) {
156        if *self.finished.lock().unwrap() {
157            return
158        }
159        log::info!("___PROGRESS___{}___FINISHED",self.id);
160    }
161}
162
163
164#[test]
165fn test_progress_bar() {
166    use mtlog::logger_config;
167    logger_config()
168        .init_global();
169    let pb = LogProgressBar::new(100, "Test");
170    for _ in 0..50 {
171        pb.inc(1);
172    }
173    pb.finish();
174    std::thread::sleep(std::time::Duration::from_millis(1));
175}