1#[macro_use]
2extern crate log;
3
4use std::time::{Duration, Instant};
5use sysinfo::{System, SystemExt};
6
7pub struct ProgressLogger {
73 start: Instant,
74 count: u64,
75 expected_updates: Option<u64>,
76 items: String,
77 last_logged: Instant,
78 ettc: Option<f64>,
80 throughput: Option<f64>,
81 frequency: Duration,
82 system: System,
83}
84
85impl ProgressLogger {
86 pub fn builder() -> ProgressLoggerBuilder {
88 ProgressLoggerBuilder {
89 expected_updates: None,
90 items: None,
91 frequency: None,
92 }
93 }
94
95 fn log(&mut self) {
96 let elapsed = Instant::now() - self.start;
97 let throughput = self.count as f64 / elapsed.as_secs_f64();
98 self.throughput.replace(throughput);
99 self.system.refresh_memory();
100 let used_kb = PrettyNumber::from(self.system.get_used_memory());
101 let used_swap_kb = PrettyNumber::from(self.system.get_used_swap());
102 if let Some(expected_updates) = self.expected_updates {
103 let prediction = (expected_updates - self.count) as f64 / throughput;
104 self.ettc.replace(prediction);
105 info!(
106 "[mem: {} kB, swap: {} kB] {:.2?} {} {}, {:.2} s left ({:.2} {}/s)",
107 used_kb,
108 used_swap_kb,
109 elapsed,
110 PrettyNumber::from(self.count),
111 self.items,
112 prediction,
113 PrettyNumber::from(throughput),
114 self.items
115 );
116 } else {
117 info!(
118 "[mem: {} kB, swap: {} kB] {:.2?} {} {} ({:.2} {}/s)",
119 used_kb,
120 used_swap_kb,
121 elapsed,
122 PrettyNumber::from(self.count),
123 self.items,
124 PrettyNumber::from(throughput),
125 self.items
126 );
127 }
128 }
129
130 pub fn time_to_completion(&self) -> Option<Duration> {
132 self.ettc.map(Duration::from_secs_f64)
133 }
134
135 pub fn throughput(&self) -> Option<f64> {
136 self.throughput
137 }
138
139 #[inline]
141 pub fn update_light<N: Into<u64>>(&mut self, cnt: N) {
142 self.count += cnt.into();
143 if self.count % 1_000_000 == 0 {
144 let now = Instant::now();
145 if (now - self.last_logged) > self.frequency {
146 self.log();
147 self.last_logged = now;
148 }
149 }
150 }
151
152 #[inline]
155 pub fn update<N: Into<u64>>(&mut self, cnt: N) {
156 let cnt: u64 = cnt.into();
157 self.count += cnt;
158 let now = Instant::now();
159 if (now - self.last_logged) > self.frequency {
160 self.log();
161 self.last_logged = now;
162 }
163 }
164
165 pub fn stop(self) {
167 let elapsed = Instant::now() - self.start;
168 let throughput = self.count as f64 / elapsed.as_secs_f64();
169 info!(
170 "Done in {:.2?}. {} {} ({:.2} {}/s)",
171 elapsed,
172 PrettyNumber::from(self.count),
173 self.items,
174 PrettyNumber::from(throughput),
175 self.items
176 );
177 }
178}
179
180pub struct ProgressLoggerBuilder {
183 expected_updates: Option<u64>,
184 items: Option<String>,
185 frequency: Option<Duration>,
186}
187
188impl ProgressLoggerBuilder {
189 pub fn with_expected_updates<N: Into<u64>>(mut self, updates: N) -> Self {
191 self.expected_updates = Some(updates.into());
192 self
193 }
194 pub fn with_items_name<S: Into<String>>(mut self, name: S) -> Self {
196 self.items = Some(name.into());
197 self
198 }
199 pub fn with_frequency(mut self, freq: Duration) -> Self {
201 self.frequency = Some(freq);
202 self
203 }
204 pub fn start(self) -> ProgressLogger {
206 let now = Instant::now();
207 ProgressLogger {
208 start: now,
209 count: 0,
210 expected_updates: self.expected_updates,
211 items: self.items.unwrap_or_else(|| "updates".to_owned()),
212 last_logged: now,
213 ettc: None,
214 throughput: None,
215 frequency: self.frequency.unwrap_or_else(|| Duration::from_secs(10)),
216 system: System::default(),
217 }
218 }
219}
220
221struct PrettyNumber {
222 rendered: String,
223}
224
225impl std::fmt::Display for PrettyNumber {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 write!(f, "{}", self.rendered)
228 }
229}
230
231impl std::fmt::Debug for PrettyNumber {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 write!(f, "{}", self.rendered)
234 }
235}
236
237impl From<u64> for PrettyNumber {
238 fn from(n: u64) -> PrettyNumber {
239 let s = format!("{}", n);
240 let tmp: Vec<char> = s.chars().rev().collect();
241 let mut chunks: Vec<&[char]> = tmp.chunks(3).collect();
242
243 let mut rendered = String::new();
244 let mut ul = chunks.len() % 2 == 1;
245 while let Some(chunk) = chunks.pop() {
246 let mut chunk = Vec::from(chunk);
247 if ul {
248 rendered.push_str("\x1B[0m");
249 } else {
250 rendered.push_str("\x1B[4m");
251 }
252 ul = !ul;
253 while let Some(c) = chunk.pop() {
254 rendered.push(c);
255 }
256 }
257 if ul {
258 rendered.push_str("\x1B[0m");
259 }
260
261 PrettyNumber { rendered }
262 }
263}
264
265impl From<f64> for PrettyNumber {
266 fn from(x: f64) -> PrettyNumber {
267 assert!(x >= 0.0, "only positive number are supported for now");
268 let s = format!("{:.2}", x);
269 let mut parts = s.split(".");
270 let s = parts.next().expect("missing integer part");
271 let decimal = parts.next();
272 let tmp: Vec<char> = s.chars().rev().collect();
273 let mut chunks: Vec<&[char]> = tmp.chunks(3).collect();
274
275 let mut rendered = String::new();
276 let mut ul = chunks.len() % 2 == 1;
277 while let Some(chunk) = chunks.pop() {
278 let mut chunk = Vec::from(chunk);
279 if ul {
280 rendered.push_str("\x1B[0m");
281 } else {
282 rendered.push_str("\x1B[4m");
283 }
284 ul = !ul;
285 while let Some(c) = chunk.pop() {
286 rendered.push(c);
287 }
288 }
289 if ul {
290 rendered.push_str("\x1B[0m");
291 }
292 if let Some(decimal) = decimal {
293 rendered.push('.');
294 rendered.push_str(decimal);
295 }
296
297 PrettyNumber { rendered }
298 }
299}