-/* guestfish - the filesystem interactive shell
- * Copyright (C) 2010 Red Hat Inc.
+/* libguestfs - mini library for progress bars.
+ * Copyright (C) 2010-2011 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <inttypes.h>
#include <math.h>
+#include <sys/time.h>
+#include <langinfo.h>
-#include <guestfs.h>
-
-#include "fish.h"
-#include "rmsd.h"
+#include "progress.h"
/* Include these last since they redefine symbols such as 'lines'
* which seriously breaks other headers.
*/
extern const char *UP;
+#define STREQ(a,b) (strcmp((a),(b)) == 0)
+
+/* Compute the running mean and standard deviation from the
+ * series of estimated values.
+ *
+ * Method:
+ * http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
+ * Checked in a test program against answers given by Wolfram Alpha.
+ */
+struct rmsd {
+ double a; /* mean */
+ double i; /* number of samples */
+ double q;
+};
+
+static void
+rmsd_init (struct rmsd *r)
+{
+ r->a = 0;
+ r->i = 1;
+ r->q = 0;
+}
+
+static void
+rmsd_add_sample (struct rmsd *r, double x)
+{
+ double a_next, q_next;
+
+ a_next = r->a + (x - r->a) / r->i;
+ q_next = r->q + (x - r->a) * (x - a_next);
+ r->a = a_next;
+ r->q = q_next;
+ r->i += 1.0;
+}
+
+static double
+rmsd_get_mean (const struct rmsd *r)
+{
+ return r->a;
+}
+
+static double
+rmsd_get_standard_deviation (const struct rmsd *r)
+{
+ return sqrt (r->q / (r->i - 1.0));
+}
+
+struct progress_bar {
+ double start; /* start time of command */
+ int count; /* number of progress notifications per cmd */
+ struct rmsd rmsd; /* running mean and standard deviation */
+ int have_terminfo;
+ int utf8_mode;
+ int machine_readable;
+};
+
+struct progress_bar *
+progress_bar_init (unsigned flags)
+{
+ struct progress_bar *bar;
+ char *term;
+
+ bar = malloc (sizeof *bar);
+ if (bar == NULL)
+ return NULL;
+
+ if (flags & PROGRESS_BAR_MACHINE_READABLE) {
+ bar->machine_readable = 1;
+ bar->utf8_mode = 0;
+ bar->have_terminfo = 0;
+ } else {
+ bar->machine_readable = 0;
+
+ bar->utf8_mode = STREQ (nl_langinfo (CODESET), "UTF-8");
+
+ bar->have_terminfo = 0;
+
+ term = getenv ("TERM");
+ if (term) {
+ if (tgetent (NULL, term) == 1)
+ bar->have_terminfo = 1;
+ }
+ }
+
+ /* Call this to ensure the other fields are in a reasonable state.
+ * It is still the caller's responsibility to reset the progress bar
+ * before each command.
+ */
+ progress_bar_reset (bar);
+
+ return bar;
+}
+
+void
+progress_bar_free (struct progress_bar *bar)
+{
+ free (bar);
+}
+
+/* This function is called just before we issue any command. */
+void
+progress_bar_reset (struct progress_bar *bar)
+{
+ /* The time at which this command was issued. */
+ struct timeval start_t;
+ gettimeofday (&start_t, NULL);
+
+ bar->start = start_t.tv_sec + start_t.tv_usec / 1000000.;
+
+ bar->count = 0;
+
+ rmsd_init (&bar->rmsd);
+}
+
static const char *
-spinner (int count)
+spinner (struct progress_bar *bar, int count)
{
/* Choice of unicode spinners.
*
const char **s;
size_t n;
- if (utf8_mode) {
+ if (bar->utf8_mode) {
s = us;
n = sizeof us / sizeof us[0];
}
return s[count % n];
}
-static double start; /* start time of command */
-static int count; /* number of progress notifications per cmd */
-static struct rmsd rmsd; /* running mean and standard deviation */
-
-/* This function is called just before we issue any command. */
-void
-reset_progress_bar (void)
-{
- /* The time at which this command was issued. */
- struct timeval start_t;
- gettimeofday (&start_t, NULL);
-
- start = start_t.tv_sec + start_t.tv_usec / 1000000.;
-
- count = 0;
-
- rmsd_init (&rmsd);
-}
-
/* Return remaining time estimate (in seconds) for current call.
*
* This returns the running mean estimate of remaining time, but if
* when nothing should be printed).
*/
static double
-estimate_remaining_time (double ratio)
+estimate_remaining_time (struct progress_bar *bar, double ratio)
{
if (ratio <= 0.)
return -1.0;
double now = now_t.tv_sec + now_t.tv_usec / 1000000.;
/* We've done 'ratio' of the work in 'now - start' seconds. */
- double time_passed = now - start;
+ double time_passed = now - bar->start;
double total_time = time_passed / ratio;
/* Add total_time to running mean and s.d. and then see if our
* estimate of total time is meaningful.
*/
- rmsd_add_sample (&rmsd, total_time);
+ rmsd_add_sample (&bar->rmsd, total_time);
- double mean = rmsd_get_mean (&rmsd);
- double sd = rmsd_get_standard_deviation (&rmsd);
+ double mean = rmsd_get_mean (&bar->rmsd);
+ double sd = rmsd_get_standard_deviation (&bar->rmsd);
if (fabs (total_time - mean) >= 2.0*sd)
return -1.0;
*/
#define COLS_OVERHEAD 15
-/* Callback which displays a progress bar. */
void
-progress_callback (guestfs_h *g, void *data,
- int proc_nr, int serial,
- uint64_t position, uint64_t total)
+progress_bar_set (struct progress_bar *bar,
+ uint64_t position, uint64_t total)
{
- if (have_terminfo == 0) {
+ int i, cols, pulse_mode;
+ double ratio;
+ const char *s_open, *s_dot, *s_dash, *s_close;
+
+ if (bar->machine_readable || bar->have_terminfo == 0) {
dumb:
printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
} else {
- int cols = tgetnum ((char *) "co");
+ cols = tgetnum ((char *) "co");
if (cols < 32) goto dumb;
/* Update an existing progress bar just printed? */
- if (count > 0)
+ if (bar->count > 0)
tputs (UP, 2, putchar);
- count++;
+ bar->count++;
- double ratio = (double) position / total;
+ /* Find out if we're in "pulse mode". */
+ pulse_mode = position == 0 && total == 1;
+
+ ratio = (double) position / total;
if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1;
- if (ratio < 1) {
+ if (pulse_mode) {
+ printf ("%s --- ", spinner (bar, bar->count));
+ }
+ else if (ratio < 1) {
int percent = 100.0 * ratio;
- printf ("%s%3d%% ", spinner (count), percent);
- } else {
+ printf ("%s%3d%% ", spinner (bar, bar->count), percent);
+ }
+ else {
fputs (" 100% ", stdout);
}
- int dots = ratio * (double) (cols - COLS_OVERHEAD);
-
- const char *s_open, *s_dot, *s_dash, *s_close;
- if (utf8_mode) {
- s_open = "\u27e6"; s_dot = "\u2589"; s_dash = "\u2550"; s_close = "\u27e7";
+ if (bar->utf8_mode) {
+ s_open = "\u27e6";
+ s_dot = "\u2593";
+ s_dash = "\u2550";
+ s_close = "\u27e7";
} else {
s_open = "["; s_dot = "#"; s_dash = "-"; s_close = "]";
}
fputs (s_open, stdout);
- int i;
- for (i = 0; i < dots; ++i)
- fputs (s_dot, stdout);
- for (i = dots; i < cols - COLS_OVERHEAD; ++i)
- fputs (s_dash, stdout);
+
+ if (!pulse_mode) {
+ int dots = ratio * (double) (cols - COLS_OVERHEAD);
+
+ for (i = 0; i < dots; ++i)
+ fputs (s_dot, stdout);
+ for (i = dots; i < cols - COLS_OVERHEAD; ++i)
+ fputs (s_dash, stdout);
+ }
+ else { /* "Pulse mode": the progress bar just pulses. */
+ for (i = 0; i < cols - COLS_OVERHEAD; ++i) {
+ int cc = (bar->count * 3 - i) % (cols - COLS_OVERHEAD);
+ if (cc >= 0 && cc <= 3)
+ fputs (s_dot, stdout);
+ else
+ fputs (s_dash, stdout);
+ }
+ }
+
fputs (s_close, stdout);
fputc (' ', stdout);
/* Time estimate. */
- double estimate = estimate_remaining_time (ratio);
+ double estimate = estimate_remaining_time (bar, ratio);
if (estimate >= 100.0 * 60.0 * 60.0 /* >= 100 hours */) {
/* Display hours<h> */
estimate /= 60. * 60.;