]> Dogcows Code - chaz/tar/commitdiff
Implement statistics display in checkpoint actions.
authorSergey Poznyakoff <gray@gnu.org.ua>
Tue, 21 Jan 2014 11:05:16 +0000 (13:05 +0200)
committerSergey Poznyakoff <gray@gnu.org.ua>
Tue, 21 Jan 2014 15:57:46 +0000 (17:57 +0200)
* NEWS: Update.
* configure.ac: Version 1.27.90
* gnulib.modules: Add fprintftime.
* doc/tar.texi: Document the "totals" action and new format specifiers
for echo and ttyout checkpoint actions.
* src/buffer.c (compute_duration): Return computed value.
(print_stats): Don't print trailing newline.  Return number of
characters output.
(format_total_stats): New function.
(print_total_stats): Rewrite via format_total_stats.
* src/checkpoint.c (checkpoint_opcode) <cop_totals>: New opcode.
(checkpoint_compile_action): Handle cop_totals.
(expand_checkpoint_string): Remove.
(format_checkpoint_string): New function to be used instead of
expand_checkpoint_string.  All callers updated.
* src/common.h (TF_READ,TF_WRITE)
(TF_DELETED): New constants.
(format_total_stats,print_total_stats): New protos.

NEWS
configure.ac
doc/tar.texi
gnulib.modules
src/buffer.c
src/checkpoint.c
src/common.h

diff --git a/NEWS b/NEWS
index 1a264b050e216021eb2b9eadc8c0ab3674bf081d..ceda138c7a026b4c2f549c7c8461a7d4faaa4338 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,25 @@
-GNU tar NEWS - User visible changes. 2013-11-17
+GNU tar NEWS - User visible changes. 2014-01-21
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 
+\f
+version 1.27.90 (Git)
+
+* New checkpoint action: totals
+
+The --checkpoint-action=totals option instructs tar to output the
+total number of bytes transferred at each checkpoint.
+
+* Extended checkpoint format specification.
+
+New conversion specifiers are implemented:
+
+  %d  -  output number of seconds since tar started
+  %T  -  output I/O totals
+  %{FMT}t - output current local time using FMT as strftime(3) format
+            If {FMT} is omitted, use %c
+  %{N}*   - pad output with spaces to the Nth column, or to the 
+            current screen width, if {N} is not given.
+
 \f
 version 1.27.1 - Sergey Poznyakoff, 2013-11-17
 
index 1c0f77049295c6a49408f057d2ded956dddfc5cd..a6a23765b0108841fadfbec58fcb0ebcb7ae05ba 100644 (file)
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-AC_INIT([GNU tar], [1.27.1], [bug-tar@gnu.org])
+AC_INIT([GNU tar], [1.27.90], [bug-tar@gnu.org])
 AC_CONFIG_SRCDIR([src/tar.c])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
index 9fde5a0761f41b7b6aff3e76bb840a8926e8fdaa..979e2427954ef2d199774456a8325cf7608b9c4a 100644 (file)
@@ -3961,10 +3961,10 @@ e.g.:
 @end smallexample
 
 The @samp{%s} and @samp{%u} in the above example are
-@dfn{meta-characters}.  The @samp{%s} meta-character is replaced with
+@dfn{format specifiers}.  The @samp{%s} specifier is replaced with
 the @dfn{type} of the checkpoint: @samp{write} or
 @samp{read} (or a corresponding translated version in locales other
-than @acronym{POSIX}).  The @samp{%u} meta-character is replaced with
+than @acronym{POSIX}).  The @samp{%u} specifier is replaced with
 the ordinal number of the checkpoint.  Thus, the above example could
 produce the following output when used with the @option{--create}
 option:
@@ -3975,7 +3975,46 @@ tar: Hit write checkpoint #20
 tar: Hit write checkpoint #30
 @end smallexample
 
-Aside from meta-character expansion, the message string is subject to
+The complete list of available format specifiers follows:
+
+@table @samp
+@item %s
+Print type of the checkpoint (@samp{write} or @samp{read}).
+
+@item %u
+Print number of the checkpoint.
+
+@item %T
+Print number of bytes transferred so far and approximate transfer
+speed.  The number is preceded by @samp{W:}, when writing and by
+@samp{R:} when reading.  If @command{tar} is performing delete
+operation (@pxref{delete}), three numbers are printed: number of bytes
+read, written and deleted, each of them prefixed by @samp{R:},
+@samp{W:} and @samp{D:} correspondingy.  For example:
+
+@example
+$ @kbd{tar --delete -f f.tar --checkpoint-action=echo="#%u: %T" main.c}
+tar: #1: R: 0 (0B, ?/s),W: 0 (0B, ?/s),D: 0
+tar: #2: R: 10240 (10KiB, 19MiB/s),W: 0 (0B, 0B/s),D: 10240
+@end example
+
+@noindent
+See also the @samp{totals} action, described below.
+
+@item %@{@var{fmt}@}t
+Output current local time using @var{fmt} as format for @command{strftime}
+(@pxref{strftime, strftime,,strftime(3), strftime(3) man page}).  The
+@samp{@{@var{fmt}@}} part is optional.  If not present, the default
+format is @samp{%c}, i.e. the preferred date and time representation
+for the current locale.
+
+@item %@{@var{n}@}*
+Pad output with spaces to the @var{n}th column.  If the
+@samp{@{@var{n}@}} part is omitted, the current screen width
+is assumed.
+@end table
+
+Aside from format expansion, the message string is subject to
 @dfn{unquoting}, during which the backslash @dfn{escape sequences} are
 replaced with their corresponding @acronym{ASCII} characters
 (@pxref{escape sequences}).  E.g. the following action will produce an
@@ -4002,9 +4041,23 @@ following action will print the checkpoint message at the same screen
 line, overwriting any previous message:
 
 @smallexample
---checkpoint-action="ttyout=\rHit %s checkpoint #%u"
+--checkpoint-action="ttyout=Hit %s checkpoint #%u%*\r"
 @end smallexample
 
+@noindent
+Notice the use of @samp{%*} specifier to clear out any eventual
+remains of the prior output line.  As as more complex example,
+consider this:
+
+@smallexample
+--checkpoint-action=ttyout='%@{%Y-%m-%d %H:%M:%S@}t (%d sec): #%u, %T%*\r'
+@end smallexample
+
+@noindent
+This prints the current local time, number of seconds expired since
+tar was started, the checkpoint ordinal number, transferred bytes and
+average computed I/O speed.
+
 @cindex @code{dot}, checkpoint action
 Another available checkpoint action is @samp{dot} (or @samp{.}).  It
 instructs @command{tar} to print a single dot on the standard listing
@@ -4019,6 +4072,12 @@ For compatibility with previous @GNUTAR{} versions, this action can
 be abbreviated by placing a dot in front of the checkpoint frequency,
 as shown in the previous section.
 
+@cindex @code{totals}, checkpoint action
+The @samp{totals} action prints the total number of bytes transferred
+so far.  The format of the data is the same as for the
+@option{--totals} option (@pxref{totals}).  See also @samp{%T} format
+specifier of the @samp{echo} or @samp{ttyout} action.
+
 @cindex @code{sleep}, checkpoint action
 Yet another action, @samp{sleep}, pauses @command{tar} for a specified
 amount of seconds.  The following example will stop for 30 seconds at each
index 1dd7c207838cc1bdf9bfdaef98257e7ff7d0f7f7..bf01d5db961ef373f10b87e3afd2163a5103d7f0 100644 (file)
@@ -38,6 +38,7 @@ fdopendir
 fdutimensat
 fileblocks
 fnmatch-gnu
+fprintftime
 fseeko
 fstatat
 full-write
index 4b44eaf13b9844b39e7bc6b8c80adbb93c19e882..0f5c76e7c5238c068faa41fefcdeaf4abe7f6607 100644 (file)
@@ -247,7 +247,7 @@ set_volume_start_time (void)
   last_stat_time = volume_start_time;
 }
 
-void
+double
 compute_duration (void)
 {
   struct timespec now;
@@ -255,6 +255,7 @@ compute_duration (void)
   duration += ((now.tv_sec - last_stat_time.tv_sec)
                + (now.tv_nsec - last_stat_time.tv_nsec) / 1e9);
   gettime (&last_stat_time);
+  return duration;
 }
 
 \f
@@ -488,8 +489,7 @@ open_compressed_archive (void)
   return archive;
 }
 \f
-
-static void
+static int
 print_stats (FILE *fp, const char *text, tarlong numbytes)
 {
   char bytes[sizeof (tarlong) * CHAR_BIT];
@@ -500,52 +500,86 @@ print_stats (FILE *fp, const char *text, tarlong numbytes)
 
   sprintf (bytes, TARLONG_FORMAT, numbytes);
 
-  fprintf (fp, "%s: %s (%s, %s/s)\n",
-           text, bytes,
-           human_readable (numbytes, abbr, human_opts, 1, 1),
-           (0 < duration && numbytes / duration < (uintmax_t) -1
-            ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
-            : "?"));
+  return fprintf (fp, "%s: %s (%s, %s/s)",
+                 text, bytes,
+                 human_readable (numbytes, abbr, human_opts, 1, 1),
+                 (0 < duration && numbytes / duration < (uintmax_t) -1
+                  ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
+                  : "?"));
 }
 
-void
-print_total_stats (void)
+/* Format totals to file FP.  FORMATS is an array of strings to output
+   before each data item (bytes read, written, deleted, in that order).
+   EOR is a delimiter to output after each item (used only if deleting
+   from the archive), EOL is a delimiter to add at the end of the output
+   line. */ 
+int
+format_total_stats (FILE *fp, char **formats, int eor, int eol)
 {
+  int n;
+  
   switch (subcommand_option)
     {
     case CREATE_SUBCOMMAND:
     case CAT_SUBCOMMAND:
     case UPDATE_SUBCOMMAND:
     case APPEND_SUBCOMMAND:
-      /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*".  */
-      print_stats (stderr, _("Total bytes written"),
-                   prev_written + bytes_written);
+      n = print_stats (fp, _(formats[TF_WRITE]),
+                      prev_written + bytes_written);
       break;
 
     case DELETE_SUBCOMMAND:
       {
         char buf[UINTMAX_STRSIZE_BOUND];
-        print_stats (stderr, _("Total bytes read"),
-                     records_read * record_size);
-        print_stats (stderr, _("Total bytes written"),
-                     prev_written + bytes_written);
-        fprintf (stderr, _("Total bytes deleted: %s\n"),
-                 STRINGIFY_BIGINT ((records_read - records_skipped)
-                                    * record_size
-                                   - (prev_written + bytes_written), buf));
+        n = print_stats (fp, _(formats[TF_READ]),
+                        records_read * record_size);
+
+       fputc (eor, fp);
+       n++;
+       
+        n += print_stats (fp, _(formats[TF_WRITE]),
+                         prev_written + bytes_written);
+
+       fputc (eor, fp);
+       n++;
+       
+        n += fprintf (fp, "%s: %s",
+                     _(formats[TF_DELETED]),
+                     STRINGIFY_BIGINT ((records_read - records_skipped)
+                                       * record_size
+                                       - (prev_written + bytes_written), buf));
       }
       break;
 
     case EXTRACT_SUBCOMMAND:
     case LIST_SUBCOMMAND:
     case DIFF_SUBCOMMAND:
-      print_stats (stderr, _("Total bytes read"),
-                   records_read * record_size);
+      n = print_stats (fp, _(formats[TF_READ]),
+                      records_read * record_size);
       break;
 
     default:
       abort ();
     }
+  if (eol)
+    {
+      fputc (eol, fp);
+      n++;
+    }
+  return n;
+}
+
+char *default_total_format[] = {
+  N_("Total bytes read"),
+  /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*".  */
+  N_("Total bytes written"),
+  N_("Total bytes deleted")
+};
+
+void
+print_total_stats (void)
+{
+  format_total_stats (stderr, default_total_format, '\n', '\n');
 }
 
 /* Compute and return the block ordinal at current_block.  */
index 54c2cd6048956b16ef782643036c0063146a1e13..76374524a1f9d340cdc5fe2f73a907dadb9d43d9 100644 (file)
@@ -19,6 +19,9 @@
 
 #include <system.h>
 #include "common.h"
+#include "wordsplit.h"
+#include <sys/ioctl.h>
+#include "fprintftime.h"
 
 enum checkpoint_opcode
   {
@@ -27,7 +30,8 @@ enum checkpoint_opcode
     cop_echo,
     cop_ttyout,
     cop_sleep,
-    cop_exec
+    cop_exec,
+    cop_totals
   };
 
 struct checkpoint_action
@@ -110,6 +114,8 @@ checkpoint_compile_action (const char *str)
       act = alloc_action (cop_sleep);
       act->v.time = n;
     }
+  else if (strcmp (str, "totals") == 0)
+    alloc_action (cop_totals);
   else
     FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
 }
@@ -128,61 +134,160 @@ checkpoint_finish_compile (void)
     checkpoint_option = DEFAULT_CHECKPOINT;
 }
 
+static char *checkpoint_total_format[] = {
+  "R",
+  "W",
+  "D"
+};
+
+static int
+getwidth(FILE *fp)
+{
+  struct winsize ws;
+
+  ws.ws_col = ws.ws_row = 0;
+  if ((ioctl (fileno (fp), TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0)
+    {
+      const char *col = getenv ("COLUMNS");
+      if (col)
+       return strtol (col, NULL, 10);
+      else
+       return 80;
+    }
+  return ws.ws_col;
+}
+
 static char *
-expand_checkpoint_string (const char *input, bool do_write, unsigned cpn)
+getarg (const char *input, const char ** endp, char **argbuf, size_t *arglen)
+{
+  if (input[0] == '{')
+    {
+      char *p = strchr (input + 1, '}');
+      if (p)
+       {
+         size_t n = p - input;
+         if (n > *arglen)
+           {
+             *arglen = n;
+             *argbuf = xrealloc (*argbuf, *arglen);
+           }
+         n--;
+         memcpy (*argbuf, input + 1, n);
+         (*argbuf)[n] = 0;
+         *endp = p + 1;
+         return *argbuf;
+       }
+    }
+
+  *endp = input;
+  return NULL;
+}
+
+
+static void
+format_checkpoint_string (FILE *fp, const char *input, bool do_write,
+                         unsigned cpn)
 {
   const char *opstr = do_write ? gettext ("write") : gettext ("read");
-  size_t opstrlen = strlen (opstr);
   char uintbuf[UINTMAX_STRSIZE_BOUND];
   char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
-  size_t cpslen = strlen (cps);
   const char *ip;
-  char *op;
-  char *output;
-  size_t outlen = strlen (input); /* Initial guess */
+  size_t len = 0;
 
-  /* Fix the initial length guess */
-  for (ip = input; (ip = strchr (ip, '%')) != NULL; )
+  static char *argbuf = NULL;
+  static size_t arglen = 0;
+  char *arg = NULL;
+  
+  if (!input)
     {
-      switch (ip[1])
-       {
-       case 'u':
-         outlen += cpslen - 2;
-         break;
-
-       case 's':
-         outlen += opstrlen - 2;
-       }
-      ip++;
+      if (do_write)
+       /* TRANSLATORS: This is a "checkpoint of write operation",
+        *not* "Writing a checkpoint".
+        E.g. in Spanish "Punto de comprobaci@'on de escritura",
+        *not* "Escribiendo un punto de comprobaci@'on" */
+       input = gettext ("Write checkpoint %u");
+      else
+       /* TRANSLATORS: This is a "checkpoint of read operation",
+        *not* "Reading a checkpoint".
+        E.g. in Spanish "Punto de comprobaci@'on de lectura",
+        *not* "Leyendo un punto de comprobaci@'on" */
+       input = gettext ("Read checkpoint %u");
     }
-
-  output = xmalloc (outlen + 1);
-  for (ip = input, op = output; *ip; )
+  
+  for (ip = input; *ip; ip++)
     {
       if (*ip == '%')
        {
-         switch (*++ip)
+         if (*++ip == '{')
+           {
+             arg = getarg (ip, &ip, &argbuf, &arglen);
+             if (!arg)
+               {
+                 fputc ('%', fp);
+                 fputc (*ip, fp);
+                 len += 2;
+                 continue;
+               }
+           }
+         switch (*ip)
            {
            case 'u':
-             op = stpcpy (op, cps);
+             fputs (cps, fp);
+             len += strlen (cps);
              break;
 
            case 's':
-             op = stpcpy (op, opstr);
+             fputs (opstr, fp);
+             len += strlen (opstr);
              break;
 
+           case 'd':
+             len += fprintf (fp, "%.0f", compute_duration ());
+             break;
+             
+           case 'T':
+             compute_duration ();
+             len += format_total_stats (fp, checkpoint_total_format, ',', 0);
+             break;
+
+           case 't':
+             {
+               struct timeval tv;
+               struct tm *tm;
+               char *fmt = arg ? arg : "%c";
+
+               gettimeofday (&tv, NULL);
+               tm = localtime (&tv.tv_sec);
+               len += fprintftime (fp, fmt, tm, 0, tv.tv_usec * 1000);
+             }
+             break;
+             
+           case '*':
+             {
+               int w = arg ? strtoul (arg, NULL, 10) : getwidth (fp);
+               for (; w > len; len++)
+                 fputc (' ', fp);
+             }
+             break;
+             
            default:
-             *op++ = '%';
-             *op++ = *ip;
+             fputc ('%', fp);
+             fputc (*ip, fp);
+             len += 2;
              break;
            }
-         ip++;
+         arg = NULL;
        }
       else
-       *op++ = *ip++;
+       {
+         fputc (*ip, fp);
+         if (*ip == '\r')
+           len = 0;
+         else
+           len++;
+       }
     }
-  *op = 0;
-  return output;
+  fflush (fp);
 }
 
 static void
@@ -211,28 +316,9 @@ run_checkpoint_actions (bool do_write)
          break;
 
        case cop_echo:
-         {
-           char *tmp;
-           const char *str = p->v.command;
-           if (!str)
-             {
-               if (do_write)
-                 /* TRANSLATORS: This is a "checkpoint of write operation",
-                    *not* "Writing a checkpoint".
-                    E.g. in Spanish "Punto de comprobaci@'on de escritura",
-                    *not* "Escribiendo un punto de comprobaci@'on" */
-                 str = gettext ("Write checkpoint %u");
-               else
-                 /* TRANSLATORS: This is a "checkpoint of read operation",
-                    *not* "Reading a checkpoint".
-                    E.g. in Spanish "Punto de comprobaci@'on de lectura",
-                    *not* "Leyendo un punto de comprobaci@'on" */
-                 str = gettext ("Read checkpoint %u");
-             }
-           tmp = expand_checkpoint_string (str, do_write, checkpoint);
-           WARN ((0, 0, "%s", tmp));
-           free (tmp);
-         }
+         fprintf (stderr, "%s: ", program_name);
+         format_checkpoint_string (stderr, p->v.command, do_write, checkpoint);
+         fputc ('\n', stderr);
          break;
 
        case cop_ttyout:
@@ -240,11 +326,9 @@ run_checkpoint_actions (bool do_write)
            tty = fopen ("/dev/tty", "w");
          if (tty)
            {
-             char *tmp = expand_checkpoint_string (p->v.command, do_write,
-                                                   checkpoint);
-             fprintf (tty, "%s", tmp);
+             format_checkpoint_string (tty, p->v.command, do_write,
+                                       checkpoint);
              fflush (tty);
-             free (tmp);
            }
          break;
 
@@ -257,6 +341,10 @@ run_checkpoint_actions (bool do_write)
                                      archive_name_cursor[0],
                                      checkpoint);
          break;
+
+       case cop_totals:
+         compute_duration ();
+         print_total_stats ();
        }
     }
   if (tty)
index 42fd539085ed58676136ab4c03d77041a23a5bba..28d6d61924cc5d8ceaa8d17d3bb9d4445585b0ac 100644 (file)
@@ -427,7 +427,7 @@ size_t available_space_after (union block *pointer);
 off_t current_block_ordinal (void);
 void close_archive (void);
 void closeout_volume_number (void);
-void compute_duration (void);
+double compute_duration (void);
 union block *find_next_block (void);
 void flush_read (void);
 void flush_write (void);
@@ -444,6 +444,12 @@ void archive_read_error (void);
 off_t seek_archive (off_t size);
 void set_start_time (void);
 
+#define TF_READ    0
+#define TF_WRITE   1
+#define TF_DELETED 2
+int format_total_stats (FILE *fp, char **formats, int eor, int eol);
+void print_total_stats (void);
+
 void mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft);
 
 void mv_begin_read (struct tar_stat_info *st);
This page took 0.05265 seconds and 4 git commands to generate.