1mod iter;
99
100pub use iter::{LogProgressIterator, ProgressIteratorExt};
101
102use colored::Colorize;
103use std::{
104 sync::{Arc, Mutex},
105 time::{Duration, Instant},
106};
107use uuid::Uuid;
108
109#[derive(Clone)]
110pub struct LogProgressBar {
111 n_iter: Arc<usize>,
112 name: Arc<str>,
113 current_iter: Arc<Mutex<usize>>,
114 id: Arc<Uuid>,
115 finished: Arc<Mutex<bool>>,
116 min_duration: Arc<Duration>,
117 last_iter: Arc<Mutex<Instant>>,
118 last_percentage: Arc<Mutex<f64>>,
119 min_percentage_change: Arc<f64>,
120}
121
122impl LogProgressBar {
123 pub fn new(n_iter: usize, name: &str) -> Self {
124 let pb = Self {
125 n_iter: Arc::new(n_iter.max(1)),
126 name: name.into(),
127 current_iter: Arc::new(Mutex::new(0usize)),
128 id: Arc::new(Uuid::new_v4()),
129 finished: Arc::new(Mutex::new(false)),
130 min_duration: Arc::new(Duration::from_millis(100)),
131 last_iter: Arc::new(Mutex::new(Instant::now() - Duration::from_millis(100))),
132 last_percentage: Arc::new(Mutex::new(0.0)),
133 min_percentage_change: Arc::new(0.1),
134 };
135 pb.send();
136 pb
137 }
138
139 pub fn with_min_timestep_ms(mut self, min_duration_ms: f64) -> Self {
140 self.min_duration = Arc::new(Duration::from_micros(
141 (min_duration_ms * 1000.0).round() as u64
142 ));
143 self
144 }
145
146 pub fn with_min_percentage_change(mut self, min_percentage: f64) -> Self {
147 self.min_percentage_change = Arc::new(min_percentage);
148 self
149 }
150
151 pub fn send(&self) {
152 if *self.finished.lock().unwrap() {
153 return;
154 }
155
156 let current_iter = *self.current_iter.lock().unwrap();
157 let current_percentage = (current_iter as f64 / *self.n_iter as f64) * 100.0;
158 let last_percentage = *self.last_percentage.lock().unwrap();
159 let time_elapsed = self.last_iter.lock().unwrap().elapsed() > *self.min_duration;
160 let percentage_changed =
161 (current_percentage - last_percentage).abs() >= *self.min_percentage_change;
162
163 if time_elapsed || percentage_changed {
164 log::info!("___PROGRESS___{}___{}", self.id, self.format());
165 *self.last_iter.lock().unwrap() = Instant::now();
166 *self.last_percentage.lock().unwrap() = current_percentage;
167 }
168 }
169
170 pub fn set_progress(&self, n: usize) {
171 *self.current_iter.lock().unwrap() = n;
172 self.send();
173 }
174
175 pub fn inc(&self, n: usize) {
176 *self.current_iter.lock().unwrap() += n;
177 self.send();
178 }
179
180 fn format(&self) -> String {
181 let current_iter = *self.current_iter.lock().unwrap();
182 let percentage = (current_iter as f64 / *self.n_iter as f64 * 100.0) as usize;
183 let bar_length = 20; let filled_length = (bar_length * current_iter / *self.n_iter).min(bar_length);
185 let bar = "#".repeat(filled_length) + &".".repeat(bar_length - filled_length);
186 let n_iter_str = self.n_iter.to_string();
187 format!(
188 "Progress {name}: [{bar}] {current:>len$}/{n_iter_str} {percentage:>3}%",
189 name = self.name.cyan(),
190 bar = bar.cyan(),
191 current = current_iter,
192 len = n_iter_str.len(),
193 )
194 }
195
196 pub fn finish(&self) {
197 if *self.finished.lock().unwrap() {
198 return;
199 }
200 self.set_progress(*self.n_iter);
201 *self.finished.lock().unwrap() = true;
202 log::info!("___PROGRESS___{}___FINISHED", self.id)
203 }
204}
205
206impl Drop for LogProgressBar {
207 fn drop(&mut self) {
208 if *self.finished.lock().unwrap() {
209 return;
210 }
211 log::info!("___PROGRESS___{}___FINISHED", self.id);
212 }
213}
214
215#[test]
216fn test_progress_bar() {
217 use mtlog::logger_config;
218 let _guard = logger_config().init_global();
219 let n = 5000000;
220 let handle = std::thread::spawn(move || {
221 let pb = LogProgressBar::new(n, "Background Task");
222 for _ in 0..n / 3 {
223 pb.inc(1);
224 }
225 pb.set_progress(0);
226 for _ in 0..n / 3 {
227 pb.inc(1);
228 }
229 pb.finish();
230 });
231 std::thread::sleep(Duration::from_millis(200));
232 let pb = LogProgressBar::new(n, "Main Task");
233 log::info!("Starting main task");
234 for i in 0..n {
235 if i == 10 {
236 log::info!("Main task is at 10 iterations");
237 }
238 pb.inc(1);
239 }
240 pb.finish();
241 handle.join().unwrap();
242 std::thread::sleep(Duration::from_millis(200));
243 let pb_outer = LogProgressBar::new(10, "Outer loop");
244 for _ in 0..10 {
245 let pb_inner = LogProgressBar::new(n / 10, "Inner loop");
246 for _ in 0..n / 10 {
247 pb_inner.inc(1);
248 }
249 pb_inner.finish();
250 pb_outer.inc(1);
251 }
252 pb_outer.finish();
253}