Print ETA with dot progress

Hrvoje Niksic <[email protected]> Wed, 29 Jun 2005 00:03:00 +0200
Newsgroups gmane.comp.web.wget.patches
Message-ID <[email protected]>
This patch consolidates the entry from an older workspace, and is
partly inspired by the "wget-new-percentage" patch from
http://tinyurl.com/7ukde/.  This patch squeezes even more information
that wget-new-percentage and will hopefully be appreciated by the
authors.


2005-06-29  Hrvoje Niksic  <[email protected]>

	* main.c (secs_to_human_time): Use print_decimal when printing
	total download time in seconds.

	* progress.c (print_row_stats): Use it to print total download
	time at the end of the download.
	(create_image): Ditto.

	* utils.c (print_decimal): New function; print small decimal
	numbers with more precision than large ones.

	* progress.c (print_row_stats): New function.  Print ETA after the
	download rate at the end of each row.

Index: src/utils.h
===================================================================
--- src/utils.h	(revision 1832)
+++ src/utils.h	(working copy)
@@ -126,4 +126,6 @@
 
 void stable_sort (void *, size_t, size_t, int (*) (const void *, const void *));
 
+const char *print_decimal (double);
+
 #endif /* UTILS_H */
Index: src/ChangeLog
===================================================================
--- src/ChangeLog	(revision 1892)
+++ src/ChangeLog	(working copy)
@@ -1,5 +1,20 @@
Index: src/progress.c
===================================================================
--- src/progress.c	(revision 1887)
+++ src/progress.c	(working copy)
@@ -205,6 +205,7 @@
 
   int rows;			/* number of rows printed so far */
   int dots;			/* number of dots printed in this row */
+
   double last_timer_value;
 };
 
@@ -220,26 +221,28 @@
   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 = dp->initial_length % row_bytes;
+      int remainder = dp->initial_length % ROW_BYTES;
       wgint skipped = dp->initial_length - remainder;
 
       if (skipped)
 	{
-	  int skipped_k = 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)
@@ -250,28 +253,86 @@
       assert (dp->dots < opt.dots_in_line);
 
       dp->accumulated = remainder;
-      dp->rows = skipped / row_bytes;
+      dp->rows = skipped / ROW_BYTES;
     }
 
   return dp;
 }
 
+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_percentage (wgint bytes, wgint expected)
+print_row_stats (struct dot_progress *dp, double dltime, bool last)
 {
-  /* Round to the floor value in order to 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 / expected;
-  logprintf (LOG_VERBOSE, "%3d%%", percentage);
-}
+  const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
 
-static void
-print_download_speed (struct dot_progress *dp, wgint bytes, double dltime)
-{
-  logprintf (LOG_VERBOSE, " %7s",
-	     retr_rate (bytes, dltime - dp->last_timer_value));
-  dp->last_timer_value = dltime;
+  /* 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;
+    if (dp->rows == dp->initial_length / ROW_BYTES)
+      /* Don't count the portion of the row belonging to initial_length */
+      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 >= 100 ? 0 : rate >= 9.995 ? 1 : 2,
+	       rate, names[units]);
+    dp->last_timer_value = dltime;
+  }
+
+  if (!last)
+    {
+      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 secs_sofar = dltime / 1000;
+	  int eta = (int) (secs_sofar * bytes_remaining / bytes_sofar + 0.5);
+	  logprintf (LOG_VERBOSE, " %s", eta_to_human_short (eta, true));
+	}
+    }
+  else
+    {
+      /* When done, print the total download time */
+      double secs = dltime / 1000;
+      if (secs >= 10)
+	logprintf (LOG_VERBOSE, "=%s",
+		   eta_to_human_short ((int) (secs + 0.5), true));
+      else
+	logprintf (LOG_VERBOSE, "=%ss", print_decimal (secs));
+    }
 }
 
 /* Dot-progress backend for progress_update. */
@@ -281,7 +342,7 @@
 {
   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);
 
@@ -289,7 +350,8 @@
   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, " ");
@@ -298,16 +360,14 @@
       ++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;
+	  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);
 	}
     }
 
@@ -320,35 +380,22 @@
 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);
 
@@ -704,8 +751,6 @@
 #endif
 }
 
-static const char *eta_to_human_short (int);
-
 #define APPEND_LITERAL(s) do {			\
   memcpy (p, s, sizeof (s) - 1);		\
   p += sizeof (s) - 1;				\
@@ -884,7 +929,7 @@
 
 	  /* 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));
+	  sprintf (p, _("  eta %s"), eta_to_human_short (eta, false));
 	  move_to_end (p);
 	}
       else if (bp->total_length > 0)
@@ -901,15 +946,9 @@
       strcpy (p, _("   in "));
       move_to_end (p);		/* not p+=6, think translations! */
       if (secs >= 10)
-	strcpy (p, eta_to_human_short ((int) (secs + 0.5)));
+	strcpy (p, eta_to_human_short ((int) (secs + 0.5), false));
       else
-	/* For very quick downloads show more exact timing information. */
-	sprintf (p, "%.*fs",
-		 secs < 0.001 ? 0 : /* 0s instead of 0.000s */
-		 secs < 0.01 ? 3 :  /* 0.00x */
-		 secs < 0.1 ? 2 :   /* 0.0x */
-		 1,                 /* 0.x, 1.x, ..., 9.x */
-		 secs);
+	sprintf (p, "%ss", print_decimal (secs));
       move_to_end (p);
     }
 
@@ -984,18 +1023,20 @@
    and hours are shown.  This ensures brevity while still displaying
    as much as possible.
 
-   If SEP is false, 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.
+   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_short (int secs)
+eta_to_human_short (int secs, bool condensed)
 {
   static char buf[10];		/* 8 should be enough, but just in case */
   static int last = -1;
+  const char *space = condensed ? "" : " ";
 
   /* Trivial optimization.  create_image can call us every 200 msecs
      (see bar_update) for fast downloads, but ETA will only change
@@ -1007,11 +1048,11 @@
   if (secs < 100)
     sprintf (buf, "%ds", secs);
   else if (secs < 100 * 60)
-    sprintf (buf, "%dm %ds", secs / 60, secs % 60);
+    sprintf (buf, "%dm%s%ds", secs / 60, space, secs % 60);
   else if (secs < 100 * 3600)
-    sprintf (buf, "%dh %dm", secs / 3600, (secs / 60) % 60);
+    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
     /* even (2^31-1)/86400 doesn't overflow BUF. */
     sprintf (buf, "%dd", secs / 86400);
Index: src/convert.c
===================================================================
--- src/convert.c	(revision 1832)
+++ src/convert.c	(working copy)
@@ -168,8 +168,8 @@
 
   secs = ptimer_measure (timer) / 1000;
   ptimer_destroy (timer);
-  logprintf (LOG_VERBOSE, _("Converted %d files in %.*f seconds.\n"),
-	     file_count, secs < 10 ? 3 : 1, secs);
+  logprintf (LOG_VERBOSE, _("Converted %d files in %s seconds.\n"),
+	     file_count, print_decimal (secs));
 }
 
 static void write_backup_file (const char *, downloaded_file_t);
Index: src/main.c
===================================================================
--- src/main.c	(revision 1877)
+++ src/main.c	(working copy)
@@ -643,16 +643,8 @@
     sprintf (buf, "%dh %dm %ds", hours, mins, secs);
   else if (mins)
     sprintf (buf, "%dm %ds", mins, secs);
-  else if (interval >= 10)
-    sprintf (buf, "%ds", secs);
   else
-    /* For very quick downloads show more exact timing information. */
-    sprintf (buf, "%.*fs",
-	     interval < 0.001 ? 0 : /* 0s instead of 0.000s */
-	     interval < 0.01 ? 3 :  /* 0.00x */
-	     interval < 0.1 ? 2 :   /* 0.0x */
-	     1,                     /* 0.x, 1.x, ..., 9.x */
-	     interval);
+    sprintf (buf, "%ss", print_decimal (interval));
 
   return buf;
 }
Index: src/utils.c
===================================================================
--- src/utils.c	(revision 1893)
+++ src/utils.c	(working copy)
@@ -2039,3 +2039,38 @@
       mergesort_internal (base, temp, size, 0, nmemb - 1, cmpfun);
     }
 }
+
+/* Print a decimal number.  If it is equal to or larger than ten, the
+   number is rounded.  Otherwise it is printed with one significant
+   digit without trailing zeros and with no more than three fractional
+   digits total.  For example, 0.1 is printed as "0.1", 0.035 is
+   printed as "0.04", 0.0091 as "0.009", and 0.0003 as simply "0".
+
+   This is useful for displaying durations because it provides
+   order-of-magnitude information without unnecessary clutter --
+   long-running downloads are shown without the fractional part, and
+   short ones still retain one significant digit.  */
+
+const char *
+print_decimal (double number)
+{
+  static char buf[32];
+  double n = number >= 0 ? number : -number;
+
+  if (n >= 9.95)
+    /* Cut off at 9.95 because the below %.1f would round 9.96 to
+       "10.0" instead of "10".  OTOH 9.94 will print as "9.9".  */
+    snprintf (buf, sizeof buf, "%.0f", number);
+  else if (n >= 0.95)
+    snprintf (buf, sizeof buf, "%.1f", number);
+  else if (n >= 0.001)
+    snprintf (buf, sizeof buf, "%.1g", number);
+  else if (n >= 0.0005)
+    /* round [0.0005, 0.001) to 0.001 */
+    snprintf (buf, sizeof buf, "%.3f", number);
+  else
+    /* print numbers close to 0 as 0, not 0.000 */
+    strcpy (buf, "0");
+
+  return buf;
+}