X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Fprogress.c;h=0e6267965047b0809618a53c3848050cbc65e9cc;hb=9228f0bf53d3b42459daeb28372196a007de3014;hp=4bf8541b3bdb7157a2fede69e7395687b51f1890;hpb=39b2248bdede16a64f2d748b757161913ddef682;p=wget diff --git a/src/progress.c b/src/progress.c index 4bf8541b..0e626796 100644 --- a/src/progress.c +++ b/src/progress.c @@ -1,5 +1,5 @@ /* Download progress. - Copyright (C) 2001 Free Software Foundation, Inc. + Copyright (C) 2001, 2002 Free Software Foundation, Inc. This file is part of GNU Wget. @@ -15,7 +15,17 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wget; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +In addition, as a special exception, the Free Software Foundation +gives permission to link the code of its release of Wget with the +OpenSSL project's "OpenSSL" library (or with modified versions of it +that use the same license as the "OpenSSL" library), and distribute +the linked executables. You must obey the GNU General Public License +in all respects for all of the code used other than "OpenSSL". If you +modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do +so, delete this exception statement from your version. */ #include @@ -30,6 +40,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_UNISTD_H # include #endif +#ifdef HAVE_SIGNAL_H +# include +#endif #include "wget.h" #include "progress.h" @@ -38,22 +51,22 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ struct progress_implementation { char *name; - void *(*create) (long, long); - void (*update) (void *, long, long); - void (*finish) (void *, long); - void (*set_params) (const char *); + void *(*create) PARAMS ((long, long)); + void (*update) PARAMS ((void *, long, double)); + void (*finish) PARAMS ((void *, double)); + void (*set_params) PARAMS ((const char *)); }; /* Necessary forward declarations. */ static void *dot_create PARAMS ((long, long)); -static void dot_update PARAMS ((void *, long, long)); -static void dot_finish PARAMS ((void *, long)); +static void dot_update PARAMS ((void *, long, double)); +static void dot_finish PARAMS ((void *, double)); static void dot_set_params PARAMS ((const char *)); static void *bar_create PARAMS ((long, long)); -static void bar_update PARAMS ((void *, long, long)); -static void bar_finish PARAMS ((void *, long)); +static void bar_update PARAMS ((void *, long, double)); +static void bar_finish PARAMS ((void *, double)); static void bar_set_params PARAMS ((const char *)); static struct progress_implementation implementations[] = { @@ -61,7 +74,7 @@ static struct progress_implementation implementations[] = { { "bar", bar_create, bar_update, bar_finish, bar_set_params } }; static struct progress_implementation *current_impl; -int current_impl_locked; +static int current_impl_locked; /* Progress implementation used by default. Can be overriden in wgetrc or by the fallback one. */ @@ -159,7 +172,7 @@ progress_create (long initial, long total) time in milliseconds since the beginning of the download. */ void -progress_update (void *progress, long howmuch, long dltime) +progress_update (void *progress, long howmuch, double dltime) { current_impl->update (progress, howmuch, dltime); } @@ -168,7 +181,7 @@ progress_update (void *progress, long howmuch, long dltime) PROGRESS object, the further use of which is not allowed. */ void -progress_finish (void *progress, long dltime) +progress_finish (void *progress, double dltime) { current_impl->finish (progress, dltime); } @@ -185,7 +198,7 @@ struct dot_progress { int rows; /* number of rows printed so far */ int dots; /* number of dots printed in this row */ - long last_timer_value; + double last_timer_value; }; /* Dot-progress backend for progress_create. */ @@ -247,7 +260,7 @@ print_percentage (long bytes, long expected) } static void -print_download_speed (struct dot_progress *dp, long bytes, long dltime) +print_download_speed (struct dot_progress *dp, long bytes, double dltime) { logprintf (LOG_VERBOSE, " %s", retr_rate (bytes, dltime - dp->last_timer_value, 1)); @@ -257,7 +270,7 @@ print_download_speed (struct dot_progress *dp, long bytes, long dltime) /* Dot-progress backend for progress_update. */ static void -dot_update (void *progress, long howmuch, long dltime) +dot_update (void *progress, long howmuch, double dltime) { struct dot_progress *dp = progress; int dot_bytes = opt.dot_bytes; @@ -297,7 +310,7 @@ dot_update (void *progress, long howmuch, long dltime) /* Dot-progress backend for progress_finish. */ static void -dot_finish (void *progress, long dltime) +dot_finish (void *progress, double dltime) { struct dot_progress *dp = progress; int dot_bytes = opt.dot_bytes; @@ -400,6 +413,15 @@ dot_set_params (const char *params) static int screen_width = DEFAULT_SCREEN_WIDTH; +/* Size of the download speed history ring. */ +#define DLSPEED_HISTORY_SIZE 30 + +/* The minimum time length of a history sample. By default, each + sample is at least 100ms long, which means that, over the course of + 30 samples, "current" download speed spans at least 3s into the + past. */ +#define DLSPEED_SAMPLE_MIN 100 + struct bar_progress { long initial_length; /* how many bytes have been downloaded previously. */ @@ -407,17 +429,49 @@ struct bar_progress { download finishes */ long count; /* bytes downloaded so far */ - long last_update; /* time of the last screen update. */ + double last_screen_update; /* time of the last screen update, + measured since the beginning of + download. */ - int width; /* screen width at the time the - progress gauge was created. */ + int width; /* screen width we're using at the + time the progress gauge was + created. this is different from + the screen_width global variable in + that the latter can be changed by a + signal. */ char *buffer; /* buffer where the bar "image" is stored. */ - - int tick; + int tick; /* counter used for drawing the + progress bar where the total size + is not known. */ + + /* The following variables (kept in a struct for namespace reasons) + keep track of recent download speeds. See bar_update() for + details. */ + struct bar_progress_hist { + int pos; + long times[DLSPEED_HISTORY_SIZE]; + long bytes[DLSPEED_HISTORY_SIZE]; + + /* The sum of times and bytes respectively, maintained for + efficiency. */ + long total_time; + long total_bytes; + } hist; + + double recent_start; /* timestamp of beginning of current + position. */ + long recent_bytes; /* bytes downloaded so far. */ + + /* create_image() uses these to make sure that ETA information + doesn't flash. */ + double last_eta_time; /* time of the last update to download + speed and ETA, measured since the + beginning of download. */ + long last_eta_value; }; -static void create_image PARAMS ((struct bar_progress *, long)); +static void create_image PARAMS ((struct bar_progress *, double)); static void display_image PARAMS ((char *)); static void * @@ -427,9 +481,17 @@ bar_create (long initial, long total) memset (bp, 0, sizeof (*bp)); + /* In theory, our callers should take care of this pathological + case, but it can sometimes happen. */ + if (initial > total) + total = initial; + bp->initial_length = initial; bp->total_length = total; - bp->width = screen_width; + + /* - 1 because we don't want to use the last screen column. */ + bp->width = screen_width - 1; + /* + 1 for the terminating zero. */ bp->buffer = xmalloc (bp->width + 1); logputs (LOG_VERBOSE, "\n"); @@ -440,11 +502,13 @@ bar_create (long initial, long total) return bp; } +static void update_speed_ring PARAMS ((struct bar_progress *, long, double)); + static void -bar_update (void *progress, long howmuch, long dltime) +bar_update (void *progress, long howmuch, double dltime) { struct bar_progress *bp = progress; - int force_update = 0; + int force_screen_update = 0; bp->count += howmuch; if (bp->total_length > 0 @@ -454,34 +518,35 @@ bar_update (void *progress, long howmuch, long dltime) adjust bp->total_length to the new reality, so that the code in create_image() that depends on total size being smaller or equal to the expected size doesn't abort. */ - bp->total_length = bp->count + bp->initial_length; + bp->total_length = bp->initial_length + bp->count; - if (screen_width != bp->width) + update_speed_ring (bp, howmuch, dltime); + + if (screen_width - 1 != bp->width) { - bp->width = screen_width; + bp->width = screen_width - 1; bp->buffer = xrealloc (bp->buffer, bp->width + 1); + force_screen_update = 1; } - if (dltime - bp->last_update < 200 && !force_update) - /* Don't update more often than every half a second. */ + if (dltime - bp->last_screen_update < 200 && !force_screen_update) + /* Don't update more often than five times per second. */ return; - bp->last_update = dltime; - create_image (bp, dltime); display_image (bp->buffer); + bp->last_screen_update = dltime; } static void -bar_finish (void *progress, long dltime) +bar_finish (void *progress, double dltime) { struct bar_progress *bp = progress; - if (dltime == 0) - /* If the download was faster than the granularity of the timer, - fake some output so that we don't get the ugly "----.--" rate - at the download finish. */ - dltime = 1; + if (bp->total_length > 0 + && bp->count + bp->initial_length > bp->total_length) + /* See bar_update() for explanation. */ + bp->total_length = bp->initial_length + bp->count; create_image (bp, dltime); display_image (bp->buffer); @@ -492,26 +557,117 @@ bar_finish (void *progress, long dltime) xfree (bp); } +/* This code attempts to maintain the notion of a "current" download + speed, over the course of no less than 3s. (Shorter intervals + produce very erratic results.) + + To do so, it samples the speed in 0.1s intervals and stores the + recorded samples in a FIFO history ring. The ring stores no more + than 30 intervals, hence the history covers the period of at least + three seconds and at most 30 reads into the past. This method + should produce good results for both very fast and very slow + downloads. + + The idea is that for fast downloads, we get the speed over exactly + the last three seconds. For slow downloads (where a network read + takes more than 0.1s to complete), we get the speed over a larger + time period, as large as it takes to complete thirty reads. This + is good because slow downloads tend to fluctuate more and a + 3-second average would be very erratic. */ + static void -create_image (struct bar_progress *bp, long dltime) +update_speed_ring (struct bar_progress *bp, long howmuch, double dltime) +{ + struct bar_progress_hist *hist = &bp->hist; + double recent_age = dltime - bp->recent_start; + + /* Update the download count. */ + bp->recent_bytes += howmuch; + + /* For very small time intervals, we return after having updated the + "recent" download count. When its age reaches or exceeds minimum + sample time, it will be recorded in the history ring. */ + if (recent_age < DLSPEED_SAMPLE_MIN) + return; + + /* Store "recent" bytes and download time to history ring at the + position POS. */ + + /* To correctly maintain the totals, first invalidate existing data + (least recent in time) at this position. */ + hist->total_time -= hist->times[hist->pos]; + hist->total_bytes -= hist->bytes[hist->pos]; + + /* Now store the new data and update the totals. */ + hist->times[hist->pos] = recent_age; + hist->bytes[hist->pos] = bp->recent_bytes; + hist->total_time += recent_age; + hist->total_bytes += bp->recent_bytes; + + /* Start a new "recent" period. */ + bp->recent_start = dltime; + bp->recent_bytes = 0; + + /* Advance the current ring position. */ + if (++hist->pos == DLSPEED_HISTORY_SIZE) + hist->pos = 0; + +#if 0 + /* Sledgehammer check to verify that the totals are accurate. */ + { + int i; + double sumt = 0, sumb = 0; + for (i = 0; i < DLSPEED_HISTORY_SIZE; i++) + { + sumt += hist->times[i]; + sumb += hist->bytes[i]; + } + assert (sumt == hist->total_time); + assert (sumb == hist->total_bytes); + } +#endif +} + +#define APPEND_LITERAL(s) do { \ + memcpy (p, s, sizeof (s) - 1); \ + p += sizeof (s) - 1; \ +} while (0) + +#ifndef MAX +# define MAX(a, b) ((a) >= (b) ? (a) : (b)) +#endif + +static void +create_image (struct bar_progress *bp, double dl_total_time) { char *p = bp->buffer; long size = bp->initial_length + bp->count; - /* The progress bar should look like this: - xx% [=======> ] nn.nnn rrK/s ETA 00:00 - - Calculate its geometry: + char *size_legible = legible (size); + int size_legible_len = strlen (size_legible); - "xx% " or "100%" - percentage - 4 chars exactly - "[]" - progress bar decorations - 2 chars exactly - " n,nnn,nnn,nnn" - downloaded bytes - 14 or less chars - " 1012.56K/s" - dl rate - 11 chars exactly - " ETA xx:xx:xx" - ETA - 13 or less chars + struct bar_progress_hist *hist = &bp->hist; - "=====>..." - progress bar content - the rest + /* The progress bar should look like this: + xx% [=======> ] nn,nnn 12.34K/s ETA 00:00 + + Calculate the geometry. The idea is to assign as much room as + possible to the progress bar. The other idea is to never let + things "jitter", i.e. pad elements that vary in size so that + their variance does not affect the placement of other elements. + It would be especially bad for the progress bar to be resized + randomly. + + "xx% " or "100%" - percentage - 4 chars + "[]" - progress bar decorations - 2 chars + " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more + " 1012.56K/s" - dl rate - 11 chars + " ETA xx:xx:xx" - ETA - 13 chars + + "=====>..." - progress bar - the rest */ - int progress_size = screen_width - (4 + 2 + 14 + 11 + 13); + int dlbytes_size = 1 + MAX (size_legible_len, 11); + int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13); if (progress_size < 5) progress_size = 0; @@ -530,12 +686,7 @@ create_image (struct bar_progress *bp, long dltime) p += 4; } else - { - *p++ = ' '; - *p++ = ' '; - *p++ = ' '; - *p++ = ' '; - } + APPEND_LITERAL (" "); /* The progress bar: "[====> ]" */ if (progress_size && bp->total_length > 0) @@ -589,69 +740,82 @@ create_image (struct bar_progress *bp, long dltime) ++bp->tick; } - /* " 1,234,567" */ - /* If there are 7 or less digits (9 because of "legible" comas), - print the number in constant space. This will prevent the rest - of the line jerking at the beginning of download, but without - assigning maximum width in all cases. */ - sprintf (p, " %9s", legible (size)); + /* " 234,567,890" */ + sprintf (p, " %-11s", legible (size)); p += strlen (p); /* " 1012.45K/s" */ - if (dltime && bp->count) + if (hist->total_time && hist->total_bytes) { static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" }; int units = 0; - double dlrate = calc_rate (bp->count, dltime, &units); + long bytes = hist->total_bytes + bp->recent_bytes; + double tm = hist->total_time + dl_total_time - bp->recent_start; + double dlrate = calc_rate (bytes, tm, &units); sprintf (p, " %7.2f%s", dlrate, short_units[units]); p += strlen (p); } else - { - strcpy (p, " --.--K/s"); - p += 11; - } + APPEND_LITERAL (" --.--K/s"); /* " ETA xx:xx:xx" */ - if (bp->total_length > 0 && bp->count > 0) + if (bp->total_length > 0 && dl_total_time > 3000) { - int eta, eta_hrs, eta_min, eta_sec; - double tm_sofar = (double)dltime / 1000; - long bytes_remaining = bp->total_length - size; - - eta = (int) (tm_sofar * bytes_remaining / bp->count); + long eta; + int eta_hrs, eta_min, eta_sec; + + /* Don't change the value of ETA more than approximately once + per second; doing so would cause flashing without providing + any value to the user. */ + if (dl_total_time - bp->last_eta_time < 900 + && bp->last_eta_value != 0) + eta = bp->last_eta_value; + else + { + /* Calculate ETA using the average download speed to predict + the future speed. If you want to use a speed averaged + over a more recent period, replace dl_total_time with + hist->total_time and bp->count with hist->total_bytes. + I found that doing that results in a very jerky and + ultimately unreliable ETA. */ + double time_sofar = (double)dl_total_time / 1000; + long bytes_remaining = bp->total_length - size; + eta = (long) (time_sofar * bytes_remaining / bp->count); + bp->last_eta_value = eta; + bp->last_eta_time = dl_total_time; + } eta_hrs = eta / 3600, eta %= 3600; eta_min = eta / 60, eta %= 60; eta_sec = eta; - /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/ - /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/ - - *p++ = ' '; - *p++ = 'E'; - *p++ = 'T'; - *p++ = 'A'; - *p++ = ' '; - if (eta_hrs > 99) - /* Bogus value, for whatever reason. We must avoid overflow. */ - sprintf (p, "--:--"); - else if (eta_hrs > 0) - sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec); + goto no_eta; + + if (eta_hrs == 0) + { + /* Hours not printed: pad with three spaces. */ + APPEND_LITERAL (" "); + sprintf (p, " ETA %02d:%02d", eta_min, eta_sec); + } else - sprintf (p, "%02d:%02d", eta_min, eta_sec); + { + if (eta_hrs < 10) + /* Hours printed with one digit: pad with one space. */ + *p++ = ' '; + sprintf (p, " ETA %d:%02d:%02d", eta_hrs, eta_min, eta_sec); + } p += strlen (p); } else if (bp->total_length > 0) { - strcpy (p, " ETA --:--"); - p += 10; + no_eta: + APPEND_LITERAL (" "); } - assert (p - bp->buffer <= screen_width); + assert (p - bp->buffer <= bp->width); - while (p < bp->buffer + screen_width) + while (p < bp->buffer + bp->width) *p++ = ' '; *p = '\0'; } @@ -662,14 +826,17 @@ create_image (struct bar_progress *bp, long dltime) static void display_image (char *buf) { + int old = log_set_save_context (0); logputs (LOG_VERBOSE, "\r"); logputs (LOG_VERBOSE, buf); + log_set_save_context (old); } static void bar_set_params (const char *params) { int sw; + char *term = getenv ("TERM"); if (params && 0 == strcmp (params, "force")) @@ -677,10 +844,19 @@ bar_set_params (const char *params) if ((opt.lfilename #ifdef HAVE_ISATTY + /* The progress bar doesn't make sense if the output is not a + TTY -- when logging to file, it is better to review the + dots. */ || !isatty (fileno (stderr)) #else 1 #endif + /* Normally we don't depend on terminal type because the + progress bar only uses ^M to move the cursor to the + beginning of line, which works even on dumb terminals. But + Jamie Zawinski reports that ^M and ^H tricks don't work in + Emacs shell buffers, and only make a mess. */ + || (term && 0 == strcmp (term, "emacs")) ) && !current_impl_locked) { @@ -698,10 +874,13 @@ bar_set_params (const char *params) screen_width = sw; } +#ifdef SIGWINCH RETSIGTYPE progress_handle_sigwinch (int sig) { int sw = determine_screen_width (); if (sw && sw >= MINIMUM_SCREEN_WIDTH) screen_width = sw; + signal (SIGWINCH, progress_handle_sigwinch); } +#endif