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