summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2014-01-21 13:05:16 +0200
committerSergey Poznyakoff <gray@gnu.org.ua>2014-01-21 17:57:46 +0200
commitf0a1f78196f75678424712ac36f0a4a46e3e5658 (patch)
tree8f4139a4c4a6f0a6615bb8d593ec9cecd58563bc
parent7f21a4d3f55cf36cd6a5cb13bc51e88a68c2e097 (diff)
downloadtar-f0a1f78196f75678424712ac36f0a4a46e3e5658.tar.gz
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) <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.
-rw-r--r--NEWS21
-rw-r--r--configure.ac2
-rw-r--r--doc/tar.texi67
-rw-r--r--gnulib.modules1
-rw-r--r--src/buffer.c82
-rw-r--r--src/checkpoint.c202
-rw-r--r--src/common.h8
7 files changed, 295 insertions, 88 deletions
diff --git a/NEWS b/NEWS
index 1a264b05..ceda138c 100644
--- a/NEWS
+++ b/NEWS
@@ -1,7 +1,26 @@
-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>
+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
* Bug fixes
diff --git a/configure.ac b/configure.ac
index 1c0f7704..a6a23765 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 <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])
diff --git a/doc/tar.texi b/doc/tar.texi
index 9fde5a07..979e2427 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 1dd7c207..bf01d5db 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 4b44eaf1..0f5c76e7 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 54c2cd60..76374524 100644
--- a/src/checkpoint.c
+++ b/src/checkpoint.c
@@ -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)
diff --git a/src/common.h b/src/common.h
index 42fd5390..28d6d619 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);