From f0a1f78196f75678424712ac36f0a4a46e3e5658 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Tue, 21 Jan 2014 13:05:16 +0200 Subject: [PATCH] Implement statistics display in checkpoint actions. * 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) : 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 | 21 ++++- configure.ac | 2 +- doc/tar.texi | 67 +++++++++++++++- gnulib.modules | 1 + src/buffer.c | 82 +++++++++++++------ src/checkpoint.c | 202 ++++++++++++++++++++++++++++++++++------------- src/common.h | 8 +- 7 files changed, 295 insertions(+), 88 deletions(-) diff --git a/NEWS b/NEWS index 1a264b0..ceda138 100644 --- 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 + +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. + version 1.27.1 - Sergey Poznyakoff, 2013-11-17 diff --git a/configure.ac b/configure.ac index 1c0f770..a6a2376 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -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]) diff --git a/doc/tar.texi b/doc/tar.texi index 9fde5a0..979e242 100644 --- a/doc/tar.texi +++ b/doc/tar.texi @@ -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 diff --git a/gnulib.modules b/gnulib.modules index 1dd7c20..bf01d5d 100644 --- a/gnulib.modules +++ b/gnulib.modules @@ -38,6 +38,7 @@ fdopendir fdutimensat fileblocks fnmatch-gnu +fprintftime fseeko fstatat full-write diff --git a/src/buffer.c b/src/buffer.c index 4b44eaf..0f5c76e 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -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; } @@ -488,8 +489,7 @@ open_compressed_archive (void) return archive; } - -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. */ diff --git a/src/checkpoint.c b/src/checkpoint.c index 54c2cd6..7637452 100644 --- a/src/checkpoint.c +++ b/src/checkpoint.c @@ -19,6 +19,9 @@ #include #include "common.h" +#include "wordsplit.h" +#include +#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) diff --git a/src/common.h b/src/common.h index 42fd539..28d6d61 100644 --- a/src/common.h +++ b/src/common.h @@ -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); -- 2.45.2