1 /* libguestfs - mini library for progress bars.
2 * Copyright (C) 2010-2011 Red Hat Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 /* Include these last since they redefine symbols such as 'lines'
32 * which seriously breaks other headers.
37 /* Provided by termcap or terminfo emulation, but not defined
40 extern const char *UP;
42 #define STREQ(a,b) (strcmp((a),(b)) == 0)
44 /* Compute the running mean and standard deviation from the
45 * series of estimated values.
48 * http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
49 * Checked in a test program against answers given by Wolfram Alpha.
53 double i; /* number of samples */
58 rmsd_init (struct rmsd *r)
66 rmsd_add_sample (struct rmsd *r, double x)
68 double a_next, q_next;
70 a_next = r->a + (x - r->a) / r->i;
71 q_next = r->q + (x - r->a) * (x - a_next);
78 rmsd_get_mean (const struct rmsd *r)
84 rmsd_get_standard_deviation (const struct rmsd *r)
86 return sqrt (r->q / (r->i - 1.0));
90 double start; /* start time of command */
91 int count; /* number of progress notifications per cmd */
92 struct rmsd rmsd; /* running mean and standard deviation */
99 progress_bar_init (unsigned flags)
101 struct progress_bar *bar;
104 bar = malloc (sizeof *bar);
108 if (flags & PROGRESS_BAR_MACHINE_READABLE) {
109 bar->machine_readable = 1;
111 bar->have_terminfo = 0;
113 bar->machine_readable = 0;
115 bar->utf8_mode = STREQ (nl_langinfo (CODESET), "UTF-8");
117 bar->have_terminfo = 0;
119 term = getenv ("TERM");
121 if (tgetent (NULL, term) == 1)
122 bar->have_terminfo = 1;
126 /* Call this to ensure the other fields are in a reasonable state.
127 * It is still the caller's responsibility to reset the progress bar
128 * before each command.
130 progress_bar_reset (bar);
136 progress_bar_free (struct progress_bar *bar)
141 /* This function is called just before we issue any command. */
143 progress_bar_reset (struct progress_bar *bar)
145 /* The time at which this command was issued. */
146 struct timeval start_t;
147 gettimeofday (&start_t, NULL);
149 bar->start = start_t.tv_sec + start_t.tv_usec / 1000000.;
153 rmsd_init (&bar->rmsd);
157 spinner (struct progress_bar *bar, int count)
159 /* Choice of unicode spinners.
161 * For basic dingbats, see:
162 * http://www.fileformat.info/info/unicode/block/geometric_shapes/utf8test.htm
163 * http://www.fileformat.info/info/unicode/block/dingbats/utf8test.htm
165 * Arrows are a mess in unicode. This page helps a lot:
166 * http://xahlee.org/comp/unicode_arrows.html
168 * I prefer something which doesn't point, just spins.
170 /* Black pointing triangle. */
171 //static const char *us[] = { "\u25b2", "\u25b6", "\u25bc", "\u25c0" };
172 /* White pointing triangle. */
173 //static const char *us[] = { "\u25b3", "\u25b7", "\u25bd", "\u25c1" };
174 /* Circle with half black. */
175 static const char *us[] = { "\u25d0", "\u25d3", "\u25d1", "\u25d2" };
176 /* White square white quadrant. */
177 //static const char *us[] = { "\u25f0", "\u25f3", "\u25f2", "\u25f1" };
178 /* White circle white quadrant. */
179 //static const char *us[] = { "\u25f4", "\u25f7", "\u25f6", "\u25f5" };
180 /* Black triangle. */
181 //static const char *us[] = { "\u25e2", "\u25e3", "\u25e4", "\u25e5" };
182 /* Spinning arrow in 8 directions. */
183 //static const char *us[] = { "\u2190", "\u2196", "\u2191", "\u2197",
184 // "\u2192", "\u2198", "\u2193", "\u2199" };
187 static const char *as[] = { "/", "-", "\\", "|" };
192 if (bar->utf8_mode) {
194 n = sizeof us / sizeof us[0];
198 n = sizeof as / sizeof as[0];
204 /* Return remaining time estimate (in seconds) for current call.
206 * This returns the running mean estimate of remaining time, but if
207 * the latest estimate of total time is greater than two s.d.'s from
208 * the running mean then we don't print anything because we're not
209 * confident that the estimate is meaningful. (Returned value is <0.0
210 * when nothing should be printed).
213 estimate_remaining_time (struct progress_bar *bar, double ratio)
218 struct timeval now_t;
219 gettimeofday (&now_t, NULL);
221 double now = now_t.tv_sec + now_t.tv_usec / 1000000.;
222 /* We've done 'ratio' of the work in 'now - start' seconds. */
223 double time_passed = now - bar->start;
225 double total_time = time_passed / ratio;
227 /* Add total_time to running mean and s.d. and then see if our
228 * estimate of total time is meaningful.
230 rmsd_add_sample (&bar->rmsd, total_time);
232 double mean = rmsd_get_mean (&bar->rmsd);
233 double sd = rmsd_get_standard_deviation (&bar->rmsd);
234 if (fabs (total_time - mean) >= 2.0*sd)
237 /* Don't return early estimates. */
238 if (time_passed < 3.0)
241 return total_time - time_passed;
244 /* The overhead is how much we subtract before we get to the progress
247 * / 100% [########---------------] xx:xx
249 * | | | | time (5 cols)
251 * | | open paren + close paren + space (3 cols)
253 * | percentage and space (5 cols)
255 * spinner and space (2 cols)
257 * Total = 2 + 5 + 3 + 5 = 15
259 #define COLS_OVERHEAD 15
262 progress_bar_set (struct progress_bar *bar,
263 uint64_t position, uint64_t total)
265 int i, cols, pulse_mode;
267 const char *s_open, *s_dot, *s_dash, *s_close;
269 if (bar->machine_readable || bar->have_terminfo == 0) {
271 printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
273 cols = tgetnum ((char *) "co");
274 if (cols < 32) goto dumb;
276 /* Update an existing progress bar just printed? */
278 tputs (UP, 2, putchar);
281 /* Find out if we're in "pulse mode". */
282 pulse_mode = position == 0 && total == 1;
284 ratio = (double) position / total;
285 if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1;
288 printf ("%s --- ", spinner (bar, bar->count));
290 else if (ratio < 1) {
291 int percent = 100.0 * ratio;
292 printf ("%s%3d%% ", spinner (bar, bar->count), percent);
295 fputs (" 100% ", stdout);
298 if (bar->utf8_mode) {
304 s_open = "["; s_dot = "#"; s_dash = "-"; s_close = "]";
307 fputs (s_open, stdout);
310 int dots = ratio * (double) (cols - COLS_OVERHEAD);
312 for (i = 0; i < dots; ++i)
313 fputs (s_dot, stdout);
314 for (i = dots; i < cols - COLS_OVERHEAD; ++i)
315 fputs (s_dash, stdout);
317 else { /* "Pulse mode": the progress bar just pulses. */
318 for (i = 0; i < cols - COLS_OVERHEAD; ++i) {
319 int cc = (bar->count * 3 - i) % (cols - COLS_OVERHEAD);
320 if (cc >= 0 && cc <= 3)
321 fputs (s_dot, stdout);
323 fputs (s_dash, stdout);
327 fputs (s_close, stdout);
331 double estimate = estimate_remaining_time (bar, ratio);
332 if (estimate >= 100.0 * 60.0 * 60.0 /* >= 100 hours */) {
333 /* Display hours<h> */
334 estimate /= 60. * 60.;
335 int hh = floor (estimate);
337 } else if (estimate >= 100.0 * 60.0 /* >= 100 minutes */) {
338 /* Display hours<h>minutes */
339 estimate /= 60. * 60.;
340 int hh = floor (estimate);
342 int mm = floor (modf (estimate, &ignore) * 60.);
343 printf ("%02dh%02d", hh, mm);
344 } else if (estimate >= 0.0) {
345 /* Display minutes:seconds */
347 int mm = floor (estimate);
349 int ss = floor (modf (estimate, &ignore) * 60.);
350 printf ("%02d:%02d", mm, ss);
352 else /* < 0 means estimate was not meaningful */
353 fputs ("--:--", stdout);
355 fputc ('\n', stdout);