X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Fprogress.c;h=be841590abee1379139f7cd13e69918cbc68876f;hb=4d7c5e087b2bc82c9f503dff003916d1047903ce;hp=524aaf4e3bcc1410f7ca3831cb4bdbed7d1e66ce;hpb=e24bc5db1b2613cc669ec4d8b91477bdf0be5084;p=wget diff --git a/src/progress.c b/src/progress.c index 524aaf4e..be841590 100644 --- a/src/progress.c +++ b/src/progress.c @@ -1,11 +1,11 @@ /* Download progress. - Copyright (C) 2001, 2002 Free Software Foundation, Inc. + Copyright (C) 2001-2006 Free Software Foundation, Inc. This file is part of GNU Wget. GNU Wget 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 +the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Wget is distributed in the hope that it will be useful, @@ -14,8 +14,7 @@ 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 Wget; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +along with Wget. If not, see . In addition, as a special exception, the Free Software Foundation gives permission to link the code of its release of Wget with the @@ -76,7 +75,7 @@ static int current_impl_locked; #define DEFAULT_PROGRESS_IMPLEMENTATION "bar" -/* Fallnback progress implementation should be something that works +/* Fallback progress implementation should be something that works under all display types. If you put something other than "dot" here, remember that bar_set_params tries to switch to this if we're not running on a TTY. So changing this to "bar" could cause @@ -108,7 +107,7 @@ set_progress_implementation (const char *name) { int i, namelen; struct progress_implementation *pi = implementations; - char *colon; + const char *colon; if (!name) name = DEFAULT_PROGRESS_IMPLEMENTATION; @@ -175,7 +174,7 @@ progress_interactive_p (void *progress) } /* Inform the progress gauge of newly received bytes. DLTIME is the - time in milliseconds since the beginning of the download. */ + time since the beginning of the download. */ void progress_update (void *progress, wgint howmuch, double dltime) @@ -200,10 +199,12 @@ struct dot_progress { wgint total_length; /* expected total byte count when the download finishes */ - int accumulated; + int accumulated; /* number of bytes accumulated after + the last printed dot */ int rows; /* number of rows printed so far */ int dots; /* number of dots printed in this row */ + double last_timer_value; }; @@ -219,26 +220,28 @@ dot_create (wgint initial, wgint total) if (dp->initial_length) { int dot_bytes = opt.dot_bytes; - wgint row_bytes = opt.dot_bytes * opt.dots_in_line; + const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; - int remainder = (int) (dp->initial_length % row_bytes); + int remainder = dp->initial_length % ROW_BYTES; wgint skipped = dp->initial_length - remainder; if (skipped) { - int skipped_k = (int) (skipped / 1024); /* skipped amount in K */ + wgint skipped_k = skipped / 1024; /* skipped amount in K */ int skipped_k_len = numdigit (skipped_k); - if (skipped_k_len < 5) - skipped_k_len = 5; + if (skipped_k_len < 6) + skipped_k_len = 6; /* Align the [ skipping ... ] line with the dots. To do that, insert the number of spaces equal to the number of digits in the skipped amount in K. */ - logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"), - 2 + skipped_k_len, "", skipped_k); + logprintf (LOG_VERBOSE, _("\n%*s[ skipping %sK ]"), + 2 + skipped_k_len, "", + number_to_static_string (skipped_k)); } - logprintf (LOG_VERBOSE, "\n%5ldK", (long) (skipped / 1024)); + logprintf (LOG_VERBOSE, "\n%6sK", + number_to_static_string (skipped / 1024)); for (; remainder >= dot_bytes; remainder -= dot_bytes) { if (dp->dots % opt.dot_spacing == 0) @@ -249,25 +252,88 @@ dot_create (wgint initial, wgint total) assert (dp->dots < opt.dots_in_line); dp->accumulated = remainder; - dp->rows = skipped / row_bytes; + dp->rows = skipped / ROW_BYTES; } return dp; } -static void -print_percentage (wgint bytes, wgint expected) -{ - int percentage = (int)(100.0 * bytes / expected); - logprintf (LOG_VERBOSE, "%3d%%", percentage); -} +static const char *eta_to_human_short (int, bool); + +/* Prints the stats (percentage of completion, speed, ETA) for current + row. DLTIME is the time spent downloading the data in current + row. + + #### This function is somewhat uglified by the fact that current + row and last row have somewhat different stats requirements. It + might be worthwhile to split it to two different functions. */ static void -print_download_speed (struct dot_progress *dp, wgint bytes, double dltime) +print_row_stats (struct dot_progress *dp, double dltime, bool last) { - logprintf (LOG_VERBOSE, " %s", - retr_rate (bytes, dltime - dp->last_timer_value, 1)); - dp->last_timer_value = dltime; + const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; + + /* bytes_displayed is the number of bytes indicated to the user by + dots printed so far, includes the initially "skipped" amount */ + wgint bytes_displayed = dp->rows * ROW_BYTES + dp->dots * opt.dot_bytes; + + if (last) + /* For last row also count bytes accumulated after last dot */ + bytes_displayed += dp->accumulated; + + if (dp->total_length) + { + /* Round to floor value to provide gauge how much data *has* + been retrieved. 12.8% will round to 12% because the 13% mark + has not yet been reached. 100% is only shown when done. */ + int percentage = 100.0 * bytes_displayed / dp->total_length; + logprintf (LOG_VERBOSE, "%3d%%", percentage); + } + + { + static char names[] = {' ', 'K', 'M', 'G'}; + int units; + double rate; + wgint bytes_this_row; + if (!last) + bytes_this_row = ROW_BYTES; + else + /* For last row also include bytes accumulated after last dot. */ + bytes_this_row = dp->dots * opt.dot_bytes + dp->accumulated; + /* Don't count the portion of the row belonging to initial_length */ + if (dp->rows == dp->initial_length / ROW_BYTES) + bytes_this_row -= dp->initial_length % ROW_BYTES; + rate = calc_rate (bytes_this_row, dltime - dp->last_timer_value, &units); + logprintf (LOG_VERBOSE, " %4.*f%c", + rate >= 99.95 ? 0 : rate >= 9.995 ? 1 : 2, + rate, names[units]); + dp->last_timer_value = dltime; + } + + if (!last) + { + /* Display ETA based on average speed. Inspired by Vladi + Belperchinov-Shabanski's "wget-new-percentage" patch. */ + if (dp->total_length) + { + wgint bytes_remaining = dp->total_length - bytes_displayed; + /* The quantity downloaded in this download run. */ + wgint bytes_sofar = bytes_displayed - dp->initial_length; + double eta = dltime * bytes_remaining / bytes_sofar; + if (eta < INT_MAX - 1) + logprintf (LOG_VERBOSE, " %s", + eta_to_human_short ((int) (eta + 0.5), true)); + } + } + else + { + /* When done, print the total download time */ + if (dltime >= 10) + logprintf (LOG_VERBOSE, "=%s", + eta_to_human_short ((int) (dltime + 0.5), true)); + else + logprintf (LOG_VERBOSE, "=%ss", print_decimal (dltime)); + } } /* Dot-progress backend for progress_update. */ @@ -277,7 +343,7 @@ dot_update (void *progress, wgint howmuch, double dltime) { struct dot_progress *dp = progress; int dot_bytes = opt.dot_bytes; - wgint row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; log_set_flush (false); @@ -285,7 +351,8 @@ dot_update (void *progress, wgint howmuch, double dltime) for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes) { if (dp->dots == 0) - logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024)); + logprintf (LOG_VERBOSE, "\n%6sK", + number_to_static_string (dp->rows * ROW_BYTES / 1024)); if (dp->dots % opt.dot_spacing == 0) logputs (LOG_VERBOSE, " "); @@ -294,16 +361,10 @@ dot_update (void *progress, wgint howmuch, double dltime) ++dp->dots; if (dp->dots >= opt.dots_in_line) { - wgint row_qty = row_bytes; - if (dp->rows == dp->initial_length / row_bytes) - row_qty -= dp->initial_length % row_bytes; - ++dp->rows; dp->dots = 0; - if (dp->total_length) - print_percentage (dp->rows * row_bytes, dp->total_length); - print_download_speed (dp, row_qty, dltime); + print_row_stats (dp, dltime, false); } } @@ -316,35 +377,22 @@ static void dot_finish (void *progress, double dltime) { struct dot_progress *dp = progress; - int dot_bytes = opt.dot_bytes; - wgint row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; int i; log_set_flush (false); if (dp->dots == 0) - logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024)); + logprintf (LOG_VERBOSE, "\n%6sK", + number_to_static_string (dp->rows * ROW_BYTES / 1024)); for (i = dp->dots; i < opt.dots_in_line; i++) { if (i % opt.dot_spacing == 0) logputs (LOG_VERBOSE, " "); logputs (LOG_VERBOSE, " "); } - if (dp->total_length) - { - print_percentage (dp->rows * row_bytes - + dp->dots * dot_bytes - + dp->accumulated, - dp->total_length); - } - - { - wgint row_qty = dp->dots * dot_bytes + dp->accumulated; - if (dp->rows == dp->initial_length / row_bytes) - row_qty -= dp->initial_length % row_bytes; - print_download_speed (dp, row_qty, dltime); - } + print_row_stats (dp, dltime, true); logputs (LOG_VERBOSE, "\n\n"); log_set_flush (false); @@ -429,12 +477,20 @@ static volatile sig_atomic_t received_sigwinch; sample is at least 150ms long, which means that, over the course of 20 samples, "current" download speed spans at least 3s into the past. */ -#define DLSPEED_SAMPLE_MIN 150 +#define DLSPEED_SAMPLE_MIN 0.15 /* The time after which the download starts to be considered "stalled", i.e. the current bandwidth is not printed and the recent download speeds are scratched. */ -#define STALL_START_TIME 5000 +#define STALL_START_TIME 5 + +/* Time between screen refreshes will not be shorter than this, so + that Wget doesn't swamp the TTY with output. */ +#define REFRESH_INTERVAL 0.2 + +/* Don't refresh the ETA too often to avoid jerkiness in predictions. + This allows ETA to change approximately once per second. */ +#define ETA_REFRESH_INTERVAL 0.99 struct bar_progress { wgint initial_length; /* how many bytes have been downloaded @@ -464,12 +520,12 @@ struct bar_progress { details. */ struct bar_progress_hist { int pos; - wgint times[DLSPEED_HISTORY_SIZE]; + double times[DLSPEED_HISTORY_SIZE]; wgint bytes[DLSPEED_HISTORY_SIZE]; /* The sum of times and bytes respectively, maintained for efficiency. */ - wgint total_time; + double total_time; wgint total_bytes; } hist; @@ -489,7 +545,7 @@ struct bar_progress { int last_eta_value; }; -static void create_image (struct bar_progress *, double); +static void create_image (struct bar_progress *, double, bool); static void display_image (char *); static void * @@ -524,7 +580,7 @@ bar_create (wgint initial, wgint total) logputs (LOG_VERBOSE, "\n"); - create_image (bp, 0); + create_image (bp, 0, false); display_image (bp->buffer); return bp; @@ -569,11 +625,11 @@ bar_update (void *progress, wgint howmuch, double dltime) received_sigwinch = 0; } - if (dltime - bp->last_screen_update < 200 && !force_screen_update) + if (dltime - bp->last_screen_update < REFRESH_INTERVAL && !force_screen_update) /* Don't update more often than five times per second. */ return; - create_image (bp, dltime); + create_image (bp, dltime, false); display_image (bp->buffer); bp->last_screen_update = dltime; } @@ -588,7 +644,7 @@ bar_finish (void *progress, double dltime) /* See bar_update() for explanation. */ bp->total_length = bp->initial_length + bp->count; - create_image (bp, dltime); + create_image (bp, dltime, true); display_image (bp->buffer); logputs (LOG_VERBOSE, "\n\n"); @@ -659,7 +715,7 @@ update_speed_ring (struct bar_progress *bp, wgint howmuch, double dltime) value because the current bandwidth would start too small. Start with an arbitrary (but more reasonable) time value and let it level out. */ - recent_age = 1000; + recent_age = 1; } /* Store "recent" bytes and download time to history ring at the @@ -694,25 +750,34 @@ update_speed_ring (struct bar_progress *bp, wgint howmuch, double dltime) sumt += hist->times[i]; sumb += hist->bytes[i]; } - assert (sumt == hist->total_time); assert (sumb == hist->total_bytes); + /* We can't use assert(sumt==hist->total_time) because some + precision is lost by adding and subtracting floating-point + numbers. But during a download this precision should not be + detectable, i.e. no larger than 1ns. */ + double diff = sumt - hist->total_time; + if (diff < 0) diff = -diff; + assert (diff < 1e-9); } #endif } -static const char *eta_to_human (int); - #define APPEND_LITERAL(s) do { \ memcpy (p, s, sizeof (s) - 1); \ p += sizeof (s) - 1; \ } while (0) +/* Use move_to_end (s) to get S to point the end of the string (the + terminating \0). This is faster than s+=strlen(s), but some people + are confused when they see strchr (s, '\0') in the code. */ +#define move_to_end(s) s = strchr (s, '\0'); + #ifndef MAX # define MAX(a, b) ((a) >= (b) ? (a) : (b)) #endif static void -create_image (struct bar_progress *bp, double dl_total_time) +create_image (struct bar_progress *bp, double dl_total_time, bool done) { char *p = bp->buffer; wgint size = bp->initial_length + bp->count; @@ -735,13 +800,13 @@ create_image (struct bar_progress *bp, double dl_total_time) "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 + " 12.5K/s" - download rate - 8 chars " eta 36m 51s" - ETA - 13 chars "=====>..." - progress bar - the rest */ int dlbytes_size = 1 + MAX (size_grouped_len, 11); - int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13); + int progress_size = bp->width - (4 + 2 + dlbytes_size + 8 + 13); if (progress_size < 5) progress_size = 0; @@ -749,7 +814,7 @@ create_image (struct bar_progress *bp, double dl_total_time) /* "xx% " */ if (bp->total_length > 0) { - int percentage = (int)(100.0 * size / bp->total_length); + int percentage = 100.0 * size / bp->total_length; assert (percentage <= 100); if (percentage < 100) @@ -824,10 +889,10 @@ create_image (struct bar_progress *bp, double dl_total_time) /* " 234,567,890" */ sprintf (p, " %-11s", size_grouped); - p += strlen (p); + move_to_end (p); - /* " 1012.45K/s" */ - if (hist->total_time && hist->total_bytes) + /* " 12.52K/s" */ + if (hist->total_time > 0 && hist->total_bytes) { static const char *short_units[] = { "B/s", "K/s", "M/s", "G/s" }; int units = 0; @@ -836,47 +901,70 @@ create_image (struct bar_progress *bp, double dl_total_time) wgint dlquant = hist->total_bytes + bp->recent_bytes; double dltime = hist->total_time + (dl_total_time - bp->recent_start); double dlspeed = calc_rate (dlquant, dltime, &units); - sprintf (p, " %7.2f%s", dlspeed, short_units[units]); - p += strlen (p); + sprintf (p, " %4.*f%s", dlspeed >= 99.95 ? 0 : dlspeed >= 9.995 ? 1 : 2, + dlspeed, short_units[units]); + move_to_end (p); } else - APPEND_LITERAL (" --.--K/s"); + APPEND_LITERAL (" --.-K/s"); - /* " ETA ..m ..s"; wait for three seconds before displaying the ETA. - That's because the ETA value needs a while to become - reliable. */ - if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3000) + if (!done) { - int eta; - - /* 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 (bp->total_length != size - && bp->last_eta_value != 0 - && dl_total_time - bp->last_eta_time < 900) - eta = bp->last_eta_value; - else + /* " eta ..m ..s"; wait for three seconds before displaying the ETA. + That's because the ETA value needs a while to become + reliable. */ + if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3) { - /* 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; - wgint bytes_remaining = bp->total_length - size; - eta = (int) (time_sofar * bytes_remaining / bp->count + 0.5); - bp->last_eta_value = eta; - bp->last_eta_time = dl_total_time; + int eta; + + /* 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 (bp->total_length != size + && bp->last_eta_value != 0 + && dl_total_time - bp->last_eta_time < ETA_REFRESH_INTERVAL) + 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. */ + wgint bytes_remaining = bp->total_length - size; + double eta_ = dl_total_time * bytes_remaining / bp->count; + if (eta_ >= INT_MAX - 1) + goto skip_eta; + eta = (int) (eta_ + 0.5); + bp->last_eta_value = eta; + bp->last_eta_time = dl_total_time; + } + + /* Translation note: "ETA" is English-centric, but this must + be short, ideally 3 chars. Abbreviate if necessary. */ + sprintf (p, _(" eta %s"), eta_to_human_short (eta, false)); + move_to_end (p); + } + else if (bp->total_length > 0) + { + skip_eta: + APPEND_LITERAL (" "); } - - sprintf (p, " eta %s", eta_to_human (eta)); - p += strlen (p); } - else if (bp->total_length > 0) + else { - APPEND_LITERAL (" "); + /* When the download is done, print the elapsed time. */ + + /* Note to translators: this should not take up more room than + available here. Abbreviate if necessary. */ + strcpy (p, _(" in ")); + move_to_end (p); /* not p+=6, think translations! */ + if (dl_total_time >= 10) + strcpy (p, eta_to_human_short ((int) (dl_total_time + 0.5), false)); + else + sprintf (p, "%ss", print_decimal (dl_total_time)); + move_to_end (p); } assert (p - bp->buffer <= bp->width); @@ -942,18 +1030,32 @@ progress_handle_sigwinch (int sig) } #endif -/* Provide a human-readable rendition of the ETA. It never occupies - more than 7 characters of screen space. */ +/* Provide a short human-readable rendition of the ETA. This is like + secs_to_human_time in main.c, except the output doesn't include + fractions (which would look silly in by nature imprecise ETA) and + takes less room. If the time is measured in hours, hours and + minutes (but not seconds) are shown; if measured in days, then days + and hours are shown. This ensures brevity while still displaying + as much as possible. + + If CONDENSED is true, the separator between minutes and seconds + (and hours and minutes, etc.) is not included, shortening the + display by one additional character. This is used for dot + progress. + + The display never occupies more than 7 characters of screen + space. */ static const char * -eta_to_human (int secs) +eta_to_human_short (int secs, bool condensed) { - static char buf[10]; /* 8 is enough, but just in case */ + static char buf[10]; /* 8 should be enough, but just in case */ static int last = -1; + const char *space = condensed ? "" : " "; - /* Trivial optimization. This function can be called every 200 - msecs (see bar_update) for fast downloads, but ETA will only - change once per 900 msecs (see create_image). */ + /* Trivial optimization. create_image can call us every 200 msecs + (see bar_update) for fast downloads, but ETA will only change + once per 900 msecs. */ if (secs == last) return buf; last = secs; @@ -961,13 +1063,13 @@ eta_to_human (int secs) if (secs < 100) sprintf (buf, "%ds", secs); else if (secs < 100 * 60) - sprintf (buf, "%dm %ds", secs / 60, secs % 60); - else if (secs < 100 * 3600) - sprintf (buf, "%dh %dm", secs / 3600, (secs / 60) % 60); + sprintf (buf, "%dm%s%ds", secs / 60, space, secs % 60); + else if (secs < 48 * 3600) + sprintf (buf, "%dh%s%dm", secs / 3600, space, (secs / 60) % 60); else if (secs < 100 * 86400) - sprintf (buf, "%dd %dh", secs / 86400, (secs / 3600) % 60); + sprintf (buf, "%dd%s%dh", secs / 86400, space, (secs / 3600) % 60); else - /* (2^31-1)/86400 doesn't overflow BUF. */ + /* even (2^31-1)/86400 doesn't overflow BUF. */ sprintf (buf, "%dd", secs / 86400); return buf;