mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
fish: Implement progress bars in guestfish.
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).
This commit is contained in:
@@ -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
|
||||
|
||||
22
fish/fish.c
22
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++;
|
||||
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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
|
||||
|
||||
239
fish/progress.c
Normal file
239
fish/progress.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/* 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 const char *
|
||||
spinner (int count)
|
||||
{
|
||||
/* Choice of unicode spinners.
|
||||
*
|
||||
* For basic dingbats, see:
|
||||
* http://www.fileformat.info/info/unicode/block/geometric_shapes/utf8test.htm
|
||||
* http://www.fileformat.info/info/unicode/block/dingbats/utf8test.htm
|
||||
*
|
||||
* Arrows are a mess in unicode. This page helps a lot:
|
||||
* http://xahlee.org/comp/unicode_arrows.html
|
||||
*
|
||||
* I prefer something which doesn't point, just spins.
|
||||
*/
|
||||
/* Black pointing triangle. */
|
||||
//static const char *us[] = { "\u25b2", "\u25b6", "\u25bc", "\u25c0" };
|
||||
/* White pointing triangle. */
|
||||
//static const char *us[] = { "\u25b3", "\u25b7", "\u25bd", "\u25c1" };
|
||||
/* Circle with half black. */
|
||||
static const char *us[] = { "\u25d0", "\u25d3", "\u25d1", "\u25d2" };
|
||||
/* White square white quadrant. */
|
||||
//static const char *us[] = { "\u25f0", "\u25f3", "\u25f2", "\u25f1" };
|
||||
/* White circle white quadrant. */
|
||||
//static const char *us[] = { "\u25f4", "\u25f7", "\u25f6", "\u25f5" };
|
||||
/* Black triangle. */
|
||||
//static const char *us[] = { "\u25e2", "\u25e3", "\u25e4", "\u25e5" };
|
||||
/* Spinning arrow in 8 directions. */
|
||||
//static const char *us[] = { "\u2190", "\u2196", "\u2191", "\u2197",
|
||||
// "\u2192", "\u2198", "\u2193", "\u2199" };
|
||||
|
||||
/* ASCII spinner. */
|
||||
static const char *as[] = { "/", "-", "\\", "|" };
|
||||
|
||||
const char **s;
|
||||
size_t n;
|
||||
|
||||
if (utf8_mode) {
|
||||
s = us;
|
||||
n = sizeof us / sizeof us[0];
|
||||
}
|
||||
else {
|
||||
s = as;
|
||||
n = sizeof as / sizeof as[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
|
||||
* 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 ("%s%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 = "\u2589"; 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
67
fish/rmsd.h
Normal file
67
fish/rmsd.h
Normal file
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user