The output you get will look something like this:
<fs> copy-size /large /large2 2G
50%
⟦▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░⟧ 00:30
The progress bar is updated 3 times per second, and is not displayed
at all for operations which take less than two seconds.
You can disable progress bars by using the flag --no-progress-bars,
and you can enable progress bars in non-interactive sessions with
the flag --progress-bars.
A good way to test this is to use the following command:
guestfish --progress-bars \
-N disk:10G \
zero-device /dev/sda
(adjust "10G" to get different lengths of time).
---
fish/Makefile.am | 3 +-
fish/fish.c | 22 ++++++
fish/fish.h | 5 ++
fish/guestfish.pod | 36 +++++++++
fish/progress.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++
fish/reopen.c | 3 +
fish/rmsd.h | 67 +++++++++++++++++
po/POTFILES.in | 1 +
8 files changed, 339 insertions(+), 1 deletions(-)
create mode 100644 fish/progress.c
create mode 100644 fish/rmsd.h
diff --git a/fish/Makefile.am b/fish/Makefile.am
index 4060f1f..e1e8833 100644
--- a/fish/Makefile.am
+++ b/fish/Makefile.am
@@ -49,6 +49,7 @@ guestfish_SOURCES = \
man.c \
more.c \
prep.c \
+ progress.c \
rc.c \
reopen.c \
supported.c \
@@ -72,7 +73,7 @@ guestfish_CFLAGS = \
guestfish_LDADD = \
$(LIBVIRT_LIBS) $(LIBXML2_LIBS) \
- $(top_builddir)/src/libguestfs.la $(LIBREADLINE)
+ $(top_builddir)/src/libguestfs.la $(LIBREADLINE) -lm
# Make libguestfs use the convenience library.
noinst_LTLIBRARIES = librc_protocol.la
diff --git a/fish/fish.c b/fish/fish.c
index c4ade8c..9696545 100644
--- a/fish/fish.c
+++ b/fish/fish.c
@@ -85,6 +85,8 @@ static void cleanup_readline (void);
static void add_history_line (const char *);
#endif
+static int override_progress_bars = -1;
+
/* Currently open libguestfs handle. */
guestfs_h *g;
@@ -100,6 +102,7 @@ const char *libvirt_uri = NULL;
int inspector = 0;
int utf8_mode = 0;
int have_terminfo = 0;
+int progress_bars = 0;
static void __attribute__((noreturn))
usage (int status)
@@ -137,6 +140,8 @@ usage (int status)
" -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
" -n|--no-sync Don't autosync\n"
" -N|--new type Create prepared disk (test1.img, ...)\n"
+ " --progress-bars Enable progress bars even when not
interactive\n"
+ " --no-progress-bars Disable progress bars\n"
" --remote[=pid] Send commands to remote %s\n"
" -r|--ro Mount read-only\n"
" --selinux Enable SELinux support\n"
@@ -182,6 +187,8 @@ main (int argc, char *argv[])
{ "new", 1, 0, 'N' },
{ "no-dest-paths", 0, 0, 'D' },
{ "no-sync", 0, 0, 'n' },
+ { "progress-bars", 0, 0, 0 },
+ { "no-progress-bars", 0, 0, 0 },
{ "remote", 2, 0, 0 },
{ "ro", 0, 0, 'r' },
{ "selinux", 0, 0, 0 },
@@ -267,6 +274,10 @@ main (int argc, char *argv[])
guestfs_set_selinux (g, 1);
} else if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
keys_from_stdin = 1;
+ } else if (STREQ (long_options[option_index].name, "progress-bars")) {
+ override_progress_bars = 1;
+ } else if (STREQ (long_options[option_index].name, "no-progress-bars"))
{
+ override_progress_bars = 0;
} else {
fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
program_name, long_options[option_index].name, option_index);
@@ -500,6 +511,15 @@ main (int argc, char *argv[])
}
}
+ /* Decide if we display progress bars. */
+ progress_bars =
+ override_progress_bars >= 0
+ ? override_progress_bars
+ : (optind >= argc && isatty (0));
+
+ if (progress_bars)
+ guestfs_set_progress_callback (g, progress_callback, NULL);
+
/* Interactive, shell script, or command(s) on the command line? */
if (optind >= argc) {
if (isatty (0))
@@ -963,6 +983,8 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
int pid = 0;
int i, r;
+ reset_progress_bar ();
+
/* This counts the commands issued, starting at 1. */
command_num++;
diff --git a/fish/fish.h b/fish/fish.h
index 8106610..be357f5 100644
--- a/fish/fish.h
+++ b/fish/fish.h
@@ -55,6 +55,7 @@ extern int verbose;
extern int command_num;
extern int utf8_mode;
extern int have_terminfo;
+extern int progress_bars;
extern const char *libvirt_uri;
extern int issue_command (const char *cmd, char *argv[], const char *pipe);
extern void pod2text (const char *name, const char *shortdesc, const char *body);
@@ -122,6 +123,10 @@ extern prep_data *create_prepared_file (const char *type_string,
extern void prepare_drive (const char *filename, prep_data *data,
const char *device);
+/* in progress.c */
+extern void reset_progress_bar (void);
+extern void progress_callback (guestfs_h *g, void *data, int proc_nr, int serial,
uint64_t position, uint64_t total);
+
/* in rc.c (remote control) */
extern void rc_listen (void) __attribute__((noreturn));
extern int rc_remote (int pid, const char *cmd, int argc, char *argv[],
diff --git a/fish/guestfish.pod b/fish/guestfish.pod
index cf1140a..3b9aa20 100644
--- a/fish/guestfish.pod
+++ b/fish/guestfish.pod
@@ -232,6 +232,17 @@ alternative to the I<-a> option: whereas I<-a> adds an
existing disk,
I<-N> creates a preformatted disk with a filesystem and adds it.
See L</PREPARED DISK IMAGES> below.
+=item B<--progress-bars>
+
+Enable progress bars, even when guestfish is used non-interactively.
+
+Progress bars are enabled by default when guestfish is used as an
+interactive shell.
+
+=item B<--no-progress-bars>
+
+Disable progress bars.
+
=item B<--remote[=pid]>
Send remote commands to C<$GUESTFISH_PID> or C<pid>. See section
@@ -729,6 +740,31 @@ Create a blank 200MB disk:
guestfish -N disk:200M
+=head1 PROGRESS BARS
+
+Some (not all) long-running commands send progress notification
+messages as they are running. Guestfish turns these messages into
+progress bars.
+
+When a command that supports progress bars takes longer than two
+seconds to run, and if progress bars are enabled, then you will see
+one appearing below the command:
+
+ ><fs> copy-size /large-file /another-file 2048M
+ / 10% [#####-----------------------------------------] 00:30
+
+The spinner on the left hand side moves round once for every progress
+notification received from the backend. This is a (reasonably) golden
+assurance that the command is "doing something" even if the progress
+bar is not moving, because the command is able to send the progress
+notifications. When the bar reaches 100% and the command finishes,
+the spinner disappears.
+
+Progress bars are enabled by default when guestfish is used
+interactively. You can enable them even for non-interactive modes
+using I<--progress-bars>, and you can disable them completely using
+I<--no-progress-bars>.
+
=head1 GUESTFISH COMMANDS
The commands in this section are guestfish convenience commands, in
diff --git a/fish/progress.c b/fish/progress.c
new file mode 100644
index 0000000..1468989
--- /dev/null
+++ b/fish/progress.c
@@ -0,0 +1,203 @@
+/* guestfish - the filesystem interactive shell
+ * Copyright (C) 2010 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <math.h>
+
+#include <guestfs.h>
+
+#include "fish.h"
+#include "rmsd.h"
+
+/* Include these last since they redefine symbols such as 'lines'
+ * which seriously breaks other headers.
+ */
+#include <term.h>
+#include <curses.h>
+
+/* Provided by termcap or terminfo emulation, but not defined
+ * in any header file.
+ */
+extern const char *UP;
+
+static char
+spinner (int count)
+{
+ return "/-\\|"[count&3];
+}
+
+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
+ * the latest estimate of total time is greater than two s.d.'s from
+ * the running mean then we don't print anything because we're not
+ * confident that the estimate is meaningful. (Returned value is <0.0
+ * when nothing should be printed).
+ */
+static double
+estimate_remaining_time (double ratio)
+{
+ if (ratio <= 0.)
+ return -1.0;
+
+ struct timeval now_t;
+ gettimeofday (&now_t, NULL);
+
+ 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 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);
+
+ double mean = rmsd_get_mean (&rmsd);
+ double sd = rmsd_get_standard_deviation (&rmsd);
+ if (fabs (total_time - mean) >= 2.0*sd)
+ return -1.0;
+
+ /* Don't return early estimates. */
+ if (time_passed < 3.0)
+ return -1.0;
+
+ return total_time - time_passed;
+}
+
+/* The overhead is how much we subtract before we get to the progress
+ * bar itself.
+ *
+ * / 100% [########---------------] xx:xx
+ * | | | | |
+ * | | | | time (5 cols)
+ * | | | |
+ * | | open paren + close paren + space (3 cols)
+ * | |
+ * | percentage and space (5 cols)
+ * |
+ * spinner and space (2 cols)
+ *
+ * Total = 2 + 5 + 3 + 5 = 15
+ */
+#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)
+{
+ if (have_terminfo == 0) {
+ dumb:
+ printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
+ } else {
+ int cols = tgetnum ((char *) "co");
+ if (cols < 32) goto dumb;
+
+ /* Update an existing progress bar just printed? */
+ if (count > 0)
+ tputs (UP, 2, putchar);
+ count++;
+
+ double ratio = (double) position / total;
+ if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1;
+
+ if (ratio < 1) {
+ int percent = 100.0 * ratio;
+ printf ("%c%3d%% ", spinner (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 = "\u2593";
+ s_dash = "\u2591";
+ 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);
+ fputs (s_close, stdout);
+ fputc (' ', stdout);
+
+ /* Time estimate. */
+ double estimate = estimate_remaining_time (ratio);
+ if (estimate >= 100.0 * 60.0 * 60.0 /* >= 100 hours */) {
+ /* Display hours<h> */
+ estimate /= 60. * 60.;
+ int hh = floor (estimate);
+ printf (">%dh", hh);
+ } else if (estimate >= 100.0 * 60.0 /* >= 100 minutes */) {
+ /* Display hours<h>minutes */
+ estimate /= 60. * 60.;
+ int hh = floor (estimate);
+ double ignore;
+ int mm = floor (modf (estimate, &ignore) * 60.);
+ printf ("%02dh%02d", hh, mm);
+ } else if (estimate >= 0.0) {
+ /* Display minutes:seconds */
+ estimate /= 60.;
+ int mm = floor (estimate);
+ double ignore;
+ int ss = floor (modf (estimate, &ignore) * 60.);
+ printf ("%02d:%02d", mm, ss);
+ }
+ else /* < 0 means estimate was not meaningful */
+ fputs ("--:--", stdout);
+
+ fputc ('\n', stdout);
+ }
+}
diff --git a/fish/reopen.c b/fish/reopen.c
index 2dfc8db..9e19018 100644
--- a/fish/reopen.c
+++ b/fish/reopen.c
@@ -66,6 +66,9 @@ do_reopen (const char *cmd, int argc, char *argv[])
if (p)
guestfs_set_path (g2, p);
+ if (progress_bars)
+ guestfs_set_progress_callback (g2, progress_callback, NULL);
+
/* Close the original handle. */
guestfs_close (g);
g = g2;
diff --git a/fish/rmsd.h b/fish/rmsd.h
new file mode 100644
index 0000000..d4335bd
--- /dev/null
+++ b/fish/rmsd.h
@@ -0,0 +1,67 @@
+/* libguestfs - guestfish shell
+ * Copyright (C) 2010 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FISH_RMSD_H
+#define FISH_RMSD_H
+
+/* 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));
+}
+
+#endif /* FISH_RMSD_H */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e5fb857..3faa1fb 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -81,6 +81,7 @@ fish/lcd.c
fish/man.c
fish/more.c
fish/prep.c
+fish/progress.c
fish/rc.c
fish/reopen.c
fish/supported.c
--
1.7.1