Blob Blame History Raw
diff -uprN sysstat-10.1.5.orig/Makefile.in sysstat-10.1.5/Makefile.in
--- sysstat-10.1.5.orig/Makefile.in	2016-06-01 12:46:28.193462305 +0200
+++ sysstat-10.1.5/Makefile.in	2016-06-01 12:46:52.027338556 +0200
@@ -160,7 +160,7 @@ NLSPOT= $(NLSPO:.po=.pot)
 % : %.o
 	$(CC) -o $@ $(CFLAGS) $^ $(LFLAGS)
 
-all: sadc sar sadf iostat mpstat pidstat nfsiostat-sysstat cifsiostat locales
+all: sadc sar sadf iostat tapestat mpstat pidstat nfsiostat-sysstat cifsiostat locales
 
 common.o: common.c version.h common.h ioconf.h sysconfig.h
 
@@ -221,6 +221,10 @@ iostat.o: iostat.c iostat.h version.h co
 
 iostat: iostat.o librdstats.a libsyscom.a
 
+tapestat.o: tapestat.c tapestat.h version.h common.h
+
+tapestat: tapestat.o librdstats.a libsyscom.a
+
 pidstat.o: pidstat.c pidstat.h version.h common.h rd_stats.h
 
 pidstat: pidstat.o librdstats.a libsyscom.a
@@ -269,6 +273,8 @@ ifeq ($(INSTALL_DOC),y)
 	$(INSTALL_DATA) $(MANGRPARG) man/sysstat.5 $(DESTDIR)$(MAN5_DIR)
 	rm -f $(DESTDIR)$(MAN1_DIR)/iostat.1*
 	$(INSTALL_DATA) $(MANGRPARG) man/iostat.1 $(DESTDIR)$(MAN1_DIR)
+	rm -f $(DESTDIR)$(MAN1_DIR)/tapestat.1*
+	$(INSTALL_DATA) $(MANGRPARG) man/tapestat.1 $(DESTDIR)$(MAN1_DIR)
 	rm -f $(DESTDIR)$(MAN1_DIR)/mpstat.1*
 	$(INSTALL_DATA) $(MANGRPARG) man/mpstat.1 $(DESTDIR)$(MAN1_DIR)
 	rm -f $(DESTDIR)$(MAN1_DIR)/pidstat.1*
@@ -288,6 +294,7 @@ ifeq ($(COMPRESS_MANPG),y)
 	$(ZIP) $(DESTDIR)$(MAN1_DIR)/sadf.1
 	$(ZIP) $(DESTDIR)$(MAN5_DIR)/sysstat.5
 	$(ZIP) $(DESTDIR)$(MAN1_DIR)/iostat.1
+	$(ZIP) $(DESTDIR)$(MAN1_DIR)/tapestat.1
 	$(ZIP) $(DESTDIR)$(MAN1_DIR)/mpstat.1
 	$(ZIP) $(DESTDIR)$(MAN1_DIR)/pidstat.1
 	$(ZIP) $(DESTDIR)$(MAN1_DIR)/nfsiostat-sysstat.1
@@ -328,6 +335,7 @@ endif
 	$(INSTALL_BIN) sar $(DESTDIR)$(BIN_DIR)
 	$(INSTALL_BIN) sadf $(DESTDIR)$(BIN_DIR)
 	$(INSTALL_BIN) iostat $(DESTDIR)$(BIN_DIR)
+	$(INSTALL_BIN) tapestat $(DESTDIR)$(BIN_DIR)
 	$(INSTALL_BIN) mpstat $(DESTDIR)$(BIN_DIR)
 	$(INSTALL_BIN) pidstat $(DESTDIR)$(BIN_DIR)
 	$(INSTALL_BIN) nfsiostat-sysstat $(DESTDIR)$(BIN_DIR)
@@ -395,6 +403,7 @@ ifeq ($(INSTALL_DOC),y)
 	rm -f $(DESTDIR)$(MAN1_DIR)/sadf.1*
 	rm -f $(DESTDIR)$(MAN5_DIR)/sysstat.5*
 	rm -f $(DESTDIR)$(MAN1_DIR)/iostat.1*
+	rm -f $(DESTDIR)$(MAN1_DIR)/tapestat.1*
 	rm -f $(DESTDIR)$(MAN1_DIR)/mpstat.1*
 	rm -f $(DESTDIR)$(MAN1_DIR)/pidstat.1*
 	rm -f $(DESTDIR)$(MAN1_DIR)/nfsiostat-sysstat.1*
@@ -423,6 +432,7 @@ uninstall_base: uninstall_man uninstall_
 	rm -f $(DESTDIR)$(BIN_DIR)/sar
 	rm -f $(DESTDIR)$(BIN_DIR)/sadf
 	rm -f $(DESTDIR)$(BIN_DIR)/iostat
+	rm -f $(DESTDIR)$(BIN_DIR)/tapestat
 	rm -f $(DESTDIR)$(BIN_DIR)/mpstat
 	rm -f $(DESTDIR)$(BIN_DIR)/pidstat
 	rm -f $(DESTDIR)$(BIN_DIR)/nfsiostat-sysstat
@@ -487,7 +497,7 @@ po-files:
 endif
 
 clean:
-	rm -f sadc sar sadf iostat mpstat pidstat nfsiostat-sysstat cifsiostat *.o *.a core TAGS
+	rm -f sadc sar sadf iostat tapestat mpstat pidstat nfsiostat-sysstat cifsiostat *.o *.a core TAGS
 	find nls -name "*.gmo" -exec rm -f {} \;
 
 almost-distclean: clean nls/sysstat.pot
diff -uprN sysstat-10.1.5.orig/man/tapestat.1 sysstat-10.1.5/man/tapestat.1
--- sysstat-10.1.5.orig/man/tapestat.1	1970-01-01 01:00:00.000000000 +0100
+++ sysstat-10.1.5/man/tapestat.1	2016-06-01 12:46:52.028338551 +0200
@@ -0,0 +1,216 @@
+.TH TAPESTAT 1 "MARCH 2016" Linux "Linux User's Manual" -*- nroff -*-
+.SH NAME
+tapestat \- Report tape statistics.
+.SH SYNOPSIS
+.B tapestat [ -k | -m ] [ -t ] [ -V ] [ -y ] [ -z ] [
+.I interval
+.B [
+.I count
+.B ] ]
+.SH DESCRIPTION
+The
+.B tapestat
+command is used for monitoring the activity of tape drives connected to a system.
+
+The first report generated by the
+.B tapestat
+command provides statistics
+concerning the time since the system was booted, unless the
+.B -y
+option is used, when this first report is omitted.
+Each subsequent report
+covers the time since the previous report.
+
+The
+.I interval
+parameter specifies the amount of time in seconds between
+each report.
+The
+.I count
+parameter can be specified in conjunction with the
+.I interval
+parameter. If the
+.I count
+parameter is specified, the value of
+.I count
+determines the number of reports generated at
+.I interval
+seconds apart. If the
+.I interval
+parameter is specified without the
+.I count
+parameter, the
+.B tapestat
+command generates reports continuously.
+
+.SH REPORT
+The
+.B tapestat
+report provides statistics for each tape drive connected to the system.
+The following data are displayed:
+
+.B r/s 
+.RS
+The number of reads issued expressed as the number per second averaged over the interval.
+
+.RE
+.B w/s
+.RS
+The number of writes issued expressed as the number per second averaged over the interval.
+
+.RE
+.B kB_read/s | MB_read/s
+.RS
+The amount of data read expressed in kilobytes (by default or if option -k used) or
+megabytes (if option -m used) per second averaged over the interval.
+
+.RE
+.B kB_wrtn/s | MB_wrtn/s
+.RS
+The amount of data written expressed in kilobytes (by default or if option -k used) or
+megabytes (if option -m used) per second averaged over the interval.
+
+.RE
+.B %Rd
+.RS
+Read percentage wait - The percentage of time over the interval spent waiting for read requests
+to complete.
+The time is measured from when the request is dispatched to the SCSI mid-layer until it signals
+that it completed.
+
+.RE
+.B %Wr
+.RS
+Write percentage wait - The percentage of time over the interval spent waiting for write requests
+to complete. The time is measured from when the request is dispatched to the SCSI mid-layer until
+it signals that it completed.
+
+.RE
+.B %Oa
+.RS
+Overall percentage wait - The percentage of time over the interval spent waiting for any
+I/O request to complete (read, write, and other).
+
+.RE
+.B Rs/s
+.RS
+The number of I/Os, expressed as the number per second averaged over the interval, where
+a non-zero residual value was encountered.
+
+.RE
+.B Ot/s
+.RS
+The number of I/Os, expressed as the number per second averaged over the interval, that
+were included as "other". Other I/O includes ioctl calls made to the tape driver and
+implicit operations performed by the tape driver such as rewind on close
+(for tape devices that implement rewind on close). It does not include any I/O performed
+using methods outside of the tape driver (e.g. via sg ioctls).
+.RE
+.RE
+.SH OPTIONS
+.IP -k
+Show the amount of data written or read in kilobytes per second instead of megabytes.
+This option is mutually exclusive with -m.
+.IP -m
+Show the amount of data written or read in megabytes per second instead of kilobytes.
+This option is mutually exclusive with -k.
+.IP -t
+Display time stamps. The time stamp format may depend
+on the value of the S_TIME_FORMAT environment variable (see below).
+.IP -V
+Print version and exit.
+.IP -y
+Omit the initial statistic showing values since boot.
+.IP -z
+Tell
+.B tapestat
+to omit output for any tapes for which there was no activity
+during the sample period.
+
+.SH CONSIDERATIONS
+It is possible for a percentage value (read, write, or other) to be greater than 100 percent
+(the
+.B tapestat
+command will never show a percentage value more than 999).
+If rewinding a tape takes 40 seconds where the interval time is 5 seconds the %Oa value
+would show as 0 in the intervals before the rewind completed and then show as approximately
+800 percent when the rewind completes. 
+
+Similar values will be observed for %Rd and %Wr if a tape drive stops reading or writing
+and then restarts (that is it stopped streaming). In such a case you may see the r/s or w/s drop to zero and the %Rd/%Wr value could be higher than 100 when reading or writing continues
+(depending on how long it takes to restart writing or reading).
+This is only an issue if it happens a lot as it may cause tape wear and will impact
+on the backup times.
+
+For fast tape drives you may see low percentage wait times.
+This does not indicate an issue with the tape drive. For a slower tape drive (e.g. an older
+generation DDS drive) the speed of the tape (and tape drive) is much slower than filesystem I/O,
+percent wait times are likely to be higher. For faster tape drives (e.g. LTO) the percentage
+wait times are likely to be lower as program writing to or reading from tape is going
+to be doing a lot more filesystem I/O because of the higher throughput.
+
+Although tape statistics are implemented in the kernel using atomic variables they cannot be
+read atomically as a group. All of the statistics values are read from different files under
+/sys, because of this there may be I/O completions while reading the different files for the
+one tape drive. This may result in a set of statistics for a device that contain some values
+before an I/O completed and some after.
+
+This command uses rounding down as the rounding method when calculating per second statistics.
+If, for example, you are using dd to copy one tape to another and running
+.B tapestat
+with an interval of 5 seconds and over the interval there were 3210 writes and 3209 reads
+then w/s would show 642 and r/s 641 (641.8 rounded down to 641). In such a case if it was
+a tar archive being copied (with a 10k block size) you would also see a difference between
+the kB_read/s and kB_wrtn/s of 2 (one I/O 10k in size divided by the interval period of 5
+seconds). If instead there were 3210 writes and 3211 reads both w/s and r/s would both show
+642 but you would still see a difference between the kB_read/s and kB_wrtn/s values of 2 kB/s.
+
+This command is provided with an interval in seconds. However internally the interval is
+tracked per device and can potentially have an effect on the per second statistics reported.
+The time each set of statistics is captured is kept with those statistics. The difference
+between the current and previous time is converted to milliseconds for use in calculations.
+We can look at how this can impact the statistics reported if we use an example of a tar
+archive being copied between two tape drives using dd. If both devices reported 28900 kilobytes
+transferred and the reading tape drive had an interval of 5001 milliseconds and the writing
+tape drive 5000 milliseconds that would calculate out as 5778 kB_read/s and 5780 kB_wrtn/s.
+
+The impact of some retrieving statistics during an I/O completion, rounding down, and small differences in the interval period on the statistics calculated should be minimal but may be non-zero.
+.SH ENVIRONMENT
+The
+.B tapestat
+command takes into account the following environment variable:
+
+.IP S_TIME_FORMAT
+If this variable exists and its value is
+.BR ISO
+then the current locale will be ignored when printing the date in the report
+header. The
+.B tapestat
+command will use the ISO 8601 format (YYYY-MM-DD) instead.
+The timestamp displayed with option -t will also be compliant with ISO 8601
+format.
+
+.SH BUGS
+.I /sys
+filesystem must be mounted for
+.B tapestat
+to work. It will not work on kernels that do not have sysfs support
+
+This command requires kernel version 4.2 or later
+(or tape statistics support backported for an earlier kernel version).
+
+.SH FILES
+.I /sys/class/scsi_tape/st<num>/stats/*
+Statistics files for tape devices.
+
+.I /proc/uptime
+contains system uptime.
+.SH AUTHOR
+Initial revision by Shane M. SEYMOUR (shane.seymour <at> hpe.com)
+.br
+Modified for sysstat by Sebastien Godard (sysstat <at> orange.fr)
+.SH SEE ALSO
+.BR iostat (1),
+.BR mpstat (1)
+
+.I http://pagesperso-orange.fr/sebastien.godard/
diff -uprN sysstat-10.1.5.orig/tapestat.c sysstat-10.1.5/tapestat.c
--- sysstat-10.1.5.orig/tapestat.c	1970-01-01 01:00:00.000000000 +0100
+++ sysstat-10.1.5/tapestat.c	2016-06-01 12:54:20.384015957 +0200
@@ -0,0 +1,697 @@
+/*
+ * tapestat: report tape statistics
+ * (C) 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hpe.com)
+ * Modified for sysstat by Sebastien GODARD (sysstat <at> orange.fr)
+ *
+ ***************************************************************************
+ * This program 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 (at  your *
+ * option) any later version.                                              *
+ *                                                                         *
+ * This program is distributed in the hope that it  will  be  useful,  but *
+ * WITHOUT ANY WARRANTY; without the implied warranty  of  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 this program; if not, write to the Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA              *
+ ***************************************************************************
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <dirent.h>
+#define __DO_NOT_DEFINE_COMPILE
+#include <regex.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+#include <sys/param.h>
+#undef HZ /* sys/param.h defines HZ but needed for MAXPATHLEN */
+
+#include "version.h"
+#include "tapestat.h"
+#include "common.h"
+
+#ifdef USE_NLS
+#include <locale.h>
+#include <libintl.h>
+#define _(string) gettext(string)
+#else
+#define _(string) (string)
+#endif
+
+#define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
+char *sccsid(void) { return (SCCSID); }
+
+int cpu_nr = 0;		/* Nb of processors on the machine */
+int flags = 0;		/* Flag for common options and system state */
+
+long interval = 0;
+char timestamp[64];
+
+struct sigaction alrm_act;
+
+/*
+ * For tape stats - it would be extremely rare for there to be a very large
+ * number of tape drives attached to a system. I wouldn't expect to see more
+ * than 20-30 in a very large configuration and discontinguous ones should
+ * be even more rare. Because of this we keep the old and new data in a
+ * simple data structure with the tape index being the number after the tape
+ * drive, st0 at index 0, etc.
+ */
+int max_tape_drives = 0;
+struct tape_stats *tape_new_stats = { NULL };
+struct tape_stats *tape_old_stats = { NULL };
+regex_t tape_reg;
+
+/*
+ ***************************************************************************
+ * Print usage and exit.
+ *
+ * IN:
+ * @progname	Name of sysstat command.
+ ***************************************************************************
+ */
+void usage(char *progname)
+{
+	fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
+		progname);
+	fprintf(stderr, _("Options are:\n"
+			  "[ -k | -m ] [ -t ] [ -V ] [ -y ] [ -z ]\n"));
+	exit(1);
+}
+
+/*
+ ***************************************************************************
+ * SIGALRM signal handler. No need to reset the handler here.
+ *
+ * IN:
+ * @sig	Signal number.
+ ***************************************************************************
+ */
+void alarm_handler(int sig)
+{
+	alarm(interval);
+}
+
+/*
+ ***************************************************************************
+ * Initialization.
+ ***************************************************************************
+ */
+void tape_initialise(void)
+{
+	/* How many processors on this machine? */
+	cpu_nr = get_cpu_nr(~0);
+
+	/* Compile regular expression for tape names */
+        if (regcomp(&tape_reg, "^st[0-9]+$", REG_EXTENDED) != 0) {
+		exit(1);
+        }
+}
+
+/*
+ ***************************************************************************
+ * Free structures.
+ ***************************************************************************
+ */
+void tape_uninitialise(void)
+{
+	regfree(&tape_reg);
+	if (tape_old_stats != NULL) {
+		free(tape_old_stats);
+	}
+	if (tape_new_stats != NULL) {
+		free(tape_new_stats);
+	}
+}
+
+/*
+ ***************************************************************************
+ * Get maximum number of tapes in the system.
+ *
+ * RETURNS:
+ * Number of tapes found.
+ ***************************************************************************
+ */
+int get_max_tape_drives(void)
+{
+	DIR *dir;
+	struct dirent *entry;
+	int new_max_tape_drives, tmp, num_stats_dir = 0;
+	regmatch_t match;
+	char stats_dir[MAXPATHLEN + 1];
+	struct stat stat_buf;
+
+	new_max_tape_drives = max_tape_drives;
+
+	/* Open sysfs tree */
+	dir = opendir(SYSFS_CLASS_TAPE_DIR);
+	if (dir == NULL)
+		return 0;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (regexec(&tape_reg, &entry->d_name[0], 1, &match, 0) == 0) {
+			/* d_name[2] to skip the st at the front */
+			tmp = atoi(&entry->d_name[2]) + 1;
+			if (tmp > new_max_tape_drives) {
+				new_max_tape_drives = tmp;
+			}
+		}
+		snprintf(stats_dir, MAXPATHLEN, "%s/%s/%s",
+			SYSFS_CLASS_TAPE_DIR, &entry->d_name[0], "stats");
+		if (stat(stats_dir, &stat_buf) == 0) {
+			if (S_ISDIR(stat_buf.st_mode)) {
+				num_stats_dir++;
+			}
+		}
+	}
+	closedir(dir);
+
+	/* If there are no stats directories make the new number of tape drives 0 */
+	if (num_stats_dir == 0) {
+		new_max_tape_drives = 0;
+	}
+
+	return new_max_tape_drives;
+}
+
+/*
+ ***************************************************************************
+ * Check if new tapes have been added and reallocate structures accordingly.
+ ***************************************************************************
+ */
+void tape_check_tapes_and_realloc(void)
+{
+	int new_max_tape_drives, i;
+
+	/* Count again number of tapes */
+	new_max_tape_drives = get_max_tape_drives();
+
+	if (new_max_tape_drives > max_tape_drives && new_max_tape_drives > 0) {
+		/* New tapes found: Realloc structures */
+		struct tape_stats *tape_old_stats_t = (struct tape_stats *)
+			realloc(tape_old_stats,	sizeof(struct tape_stats) * new_max_tape_drives);
+		struct tape_stats *tape_new_stats_t = (struct tape_stats *)
+			realloc(tape_new_stats,	sizeof(struct tape_stats) * new_max_tape_drives);
+		if ((tape_old_stats_t == NULL) || (tape_new_stats_t == NULL)) {
+			if (tape_old_stats_t != NULL) {
+				free(tape_old_stats_t);
+				tape_old_stats_t = NULL;
+			} else {
+				free(tape_old_stats);
+				tape_old_stats = NULL;
+			}
+			if (tape_new_stats_t != NULL) {
+				free(tape_new_stats_t);
+				tape_new_stats_t = NULL;
+			} else {
+				free(tape_new_stats);
+				tape_new_stats = NULL;
+			}
+
+			perror("realloc");
+			exit(4);
+		}
+
+		tape_old_stats = tape_old_stats_t;
+		tape_new_stats = tape_new_stats_t;
+
+		for (i = max_tape_drives; i < new_max_tape_drives; i++) {
+			tape_old_stats[i].valid = TAPE_STATS_INVALID;
+			tape_new_stats[i].valid = TAPE_STATS_INVALID;
+		}
+		max_tape_drives = new_max_tape_drives;
+	}
+}
+
+/*
+ ***************************************************************************
+ * Collect initial statistics for all existing tapes in the system.
+ * This function should be called only once.
+ ***************************************************************************
+ */
+void tape_gather_initial_stats(void)
+{
+	int new_max_tape_drives, i;
+	FILE *fp;
+	char filename[MAXPATHLEN + 1];
+
+	/* Get number of tapes in the system */
+	new_max_tape_drives = get_max_tape_drives();
+
+	if (new_max_tape_drives == 0) {
+		/* No tapes found */
+		fprintf(stderr, _("No tape drives with statistics found\n"));
+		exit(1);
+	}
+	else {
+		/* Allocate structures */
+		if (tape_old_stats == NULL) {
+			tape_old_stats = (struct tape_stats *)
+				malloc(sizeof(struct tape_stats) * new_max_tape_drives);
+			tape_new_stats = (struct tape_stats *)
+				malloc(sizeof(struct tape_stats) * new_max_tape_drives);
+			for (i = 0; i < new_max_tape_drives; i++) {
+				tape_old_stats[i].valid = TAPE_STATS_INVALID;
+				tape_new_stats[i].valid = TAPE_STATS_INVALID;
+			}
+			max_tape_drives = new_max_tape_drives;
+		} else
+			/* This should only be called once */
+			return;
+	}
+
+	/* Read stats for each tape */
+	for (i = 0; i < max_tape_drives; i++) {
+		/*
+		 * Everything starts out valid but failing to open
+		 * a file gets the tape drive marked invalid.
+		 */
+		tape_new_stats[i].valid = TAPE_STATS_VALID;
+		tape_old_stats[i].valid = TAPE_STATS_VALID;
+
+		gettimeofday(&tape_old_stats[i].tv, NULL);
+
+		tape_new_stats[i].tv.tv_sec = tape_old_stats[i].tv.tv_sec;
+		tape_new_stats[i].tv.tv_usec = tape_old_stats[i].tv.tv_usec;
+
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
+
+		tape_old_stats[i].read_time = 0;
+		tape_old_stats[i].write_time = 0;
+		tape_old_stats[i].other_time = 0;
+		tape_old_stats[i].read_bytes = 0;
+		tape_old_stats[i].write_bytes = 0;
+		tape_old_stats[i].read_count = 0;
+		tape_old_stats[i].write_count = 0;
+		tape_old_stats[i].other_count = 0;
+		tape_old_stats[i].resid_count = 0;
+	}
+}
+
+/*
+ ***************************************************************************
+ * Collect a new sample of statistics for all existing tapes in the system.
+ ***************************************************************************
+ */
+void tape_get_updated_stats(void)
+{
+	int i;
+	FILE *fp;
+	char filename[MAXPATHLEN + 1] = { 0 };
+
+	/* Check tapes and realloc structures if  needed */
+	tape_check_tapes_and_realloc();
+
+	for (i = 0; i < max_tape_drives; i++) {
+		/*
+		 * Everything starts out valid but failing
+		 * to open a file gets the tape drive marked invalid.
+		 */
+		tape_new_stats[i].valid = TAPE_STATS_VALID;
+		gettimeofday(&tape_new_stats[i].tv, NULL);
+
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
+		TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
+
+		if ((tape_new_stats[i].read_time < tape_old_stats[i].read_time) ||
+		    (tape_new_stats[i].write_time < tape_old_stats[i].write_time) ||
+		    (tape_new_stats[i].other_time < tape_old_stats[i].other_time)) {
+			tape_new_stats[i].valid = TAPE_STATS_INVALID;
+		}
+	}
+}
+
+/*
+ ***************************************************************************
+ * Display tapes statistics headings.
+ ***************************************************************************
+ */
+void tape_write_headings(void)
+{
+	printf("Tape:    r/s     w/s   ");
+	if (DISPLAY_MEGABYTES(flags)) {
+		printf("MB_read/s   MB_wrtn/s");
+	} else {
+		printf("kB_read/s   kB_wrtn/s");
+	}
+	printf(" %%Rd %%Wr %%Oa    Rs/s    Ot/s\n");
+}
+
+/*
+ ***************************************************************************
+ * Calculate statistics for current tape.
+ *
+ * IN:
+ * @i		Index in array for current tape.
+ *
+ * OUT:
+ * @stats	Statistics for current tape.
+ ***************************************************************************
+ */
+void tape_calc_one_stats(struct calc_stats *stats, int i)
+{
+	uint64_t duration;
+	double temp;
+	FILE *fp;
+
+	/* Duration in ms done in ms to prevent rounding issues with using seconds */
+	duration = (tape_new_stats[i].tv.tv_sec -
+		tape_old_stats[i].tv.tv_sec) * 1000;
+	duration -= tape_old_stats[i].tv.tv_usec / 1000;
+	duration += tape_new_stats[i].tv.tv_usec / 1000;
+
+	/* If duration is zero we need to calculate the ms since boot time */
+	if (duration == 0) {
+		fp = fopen("/proc/uptime", "r");
+
+		/*
+		 * Get uptime from /proc/uptime and if we can't then just set duration to
+		 * be 0 - it will mean that we don't calculate stats.
+		 */
+		if (fp == NULL) {
+			duration = 0;
+		} else {
+			if (fscanf(fp, "%lf", &temp) != 1) {
+				temp = 0;
+			}
+			duration = (uint64_t) (temp * 1000);
+			fclose(fp);
+		}
+	}
+
+	/* The second value passed into the macro is the thing being calculated */
+	CALC_STAT_CNT(read_count, reads_per_second)
+	CALC_STAT_CNT(write_count, writes_per_second)
+	CALC_STAT_CNT(other_count, other_per_second)
+	CALC_STAT_KB(read_bytes, kbytes_read_per_second)
+	CALC_STAT_KB(write_bytes, kbytes_written_per_second)
+	CALC_STAT_PCT(read_time, read_pct_wait)
+	CALC_STAT_PCT(write_time, write_pct_wait)
+	CALC_STAT_PCT(other_time, all_pct_wait)
+	CALC_STAT_CNT(resid_count, resids_per_second)
+}
+
+/*
+ ***************************************************************************
+ * Display statistics for current tape.
+ *
+ * IN:
+ * @tape	Statistics for current tape.
+ * @i		Index in array for current tape.
+ ***************************************************************************
+ */
+void tape_write_stats(struct calc_stats *tape, int i)
+{
+	char buffer[32];
+	uint64_t divisor = 1;
+
+	if (DISPLAY_MEGABYTES(flags))
+		divisor = 1024;
+
+	sprintf(buffer, "st%i        ", i);
+	buffer[5] = 0;
+	printf("%s%7"PRId64" %7"PRId64" %11"PRId64
+		" %11"PRId64" %3"PRId64" %3"PRId64" %3"PRId64
+		" %7"PRId64" %7"PRId64"\n", buffer,
+		tape->reads_per_second, tape->writes_per_second,
+		tape->kbytes_read_per_second / divisor,
+		tape->kbytes_written_per_second / divisor,
+		tape->read_pct_wait, tape->write_pct_wait,
+		tape->all_pct_wait, tape->resids_per_second,
+		tape->other_per_second);
+}
+
+/*
+ ***************************************************************************
+ * Print everything now (stats and uptime).
+ *
+ * IN:
+ * @rectime	Current date and time.
+ ***************************************************************************
+ */
+void write_stats(struct tm *rectime)
+{
+	int i;
+	struct calc_stats tape;
+	struct tape_stats *tmp;
+
+	/* Test stdout */
+	TEST_STDOUT(STDOUT_FILENO);
+
+	/* Print time stamp */
+	if (DISPLAY_TIMESTAMP(flags)) {
+		if (DISPLAY_ISO(flags)) {
+			strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
+		}
+		else {
+			strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
+		}
+		printf("%s\n", timestamp);
+	}
+
+	/* Print the headings */
+	tape_write_headings();
+
+	/*
+	 * If either new or old is invalid or the I/Os per second is 0 and
+	 * zero omit is true then we print nothing.
+	 */
+	if (max_tape_drives > 0) {
+
+		for (i = 0; i < max_tape_drives; i++) {
+			if ((tape_new_stats[i].valid == TAPE_STATS_VALID) &&
+				(tape_old_stats[i].valid == TAPE_STATS_VALID)) {
+				tape_calc_one_stats(&tape, i);
+				if (!(DISPLAY_ZERO_OMIT(flags)
+					&& (tape.other_per_second == 0)
+					&& (tape.reads_per_second == 0)
+					&& (tape.writes_per_second == 0)
+					&& (tape.kbytes_read_per_second == 0)
+					&& (tape.kbytes_written_per_second == 0)
+					&& (tape.read_pct_wait == 0)
+					&& (tape.write_pct_wait == 0)
+					&& (tape.all_pct_wait == 0)
+					&& (tape.resids_per_second == 0))) {
+					tape_write_stats(&tape, i);
+				}
+			}
+		}
+		/*
+		 * Swap new and old so next time we compare against the new old stats.
+		 * If a new tape drive appears it won't appear in the output until after
+		 * the second time we gather information about it.
+		 */
+		tmp = tape_old_stats;
+		tape_old_stats = tape_new_stats;
+		tape_new_stats = tmp;
+	}
+	printf("\n");
+}
+
+/*
+ ***************************************************************************
+ * Main loop: Read tape stats from the relevant sources and display them.
+ *
+ * IN:
+ * @count	Number of lines of stats to print.
+ * @rectime	Current date and time.
+ ***************************************************************************
+ */
+void rw_tape_stat_loop(long int count, struct tm *rectime)
+{
+	struct tape_stats *tmp;
+	int skip = 0;
+
+	/* Should we skip first report? */
+	if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) {
+		skip = 1;
+	}
+
+	/* Don't buffer data if redirected to a pipe */
+	setbuf(stdout, NULL);
+
+	do {
+
+		if (tape_new_stats == NULL) {
+			tape_gather_initial_stats();
+		} else {
+			tape_get_updated_stats();
+		}
+
+		/* Get time */
+		get_localtime(rectime, 0);
+
+		/* Check whether we should skip first report */
+		if (!skip) {
+			/* Print results */
+			write_stats(rectime);
+
+			if (count > 0) {
+				count--;
+			}
+		}
+		else {
+			skip = 0;
+			tmp = tape_old_stats;
+			tape_old_stats = tape_new_stats;
+			tape_new_stats = tmp;
+		}
+
+		if (count) {
+			pause();
+		}
+	}
+	while (count);
+}
+
+/*
+ ***************************************************************************
+ * Main entry to the tapestat program.
+ ***************************************************************************
+ */
+int main(int argc, char **argv)
+{
+	int it = 0;
+	int opt = 1;
+	int i;
+	long count = 1;
+	struct utsname header;
+	struct tm rectime;
+
+#ifdef USE_NLS
+	/* Init National Language Support */
+	init_nls();
+#endif
+
+	/* Get HZ */
+	get_HZ();
+
+	/* Process args... */
+	while (opt < argc) {
+			if (!strncmp(argv[opt], "-", 1)) {
+			for (i = 1; *(argv[opt] + i); i++) {
+
+				switch (*(argv[opt] + i)) {
+
+				case 'k':
+					if (DISPLAY_MEGABYTES(flags)) {
+						usage(argv[0]);
+					}
+					/* Display stats in kB/s */
+					flags |= T_D_KILOBYTES;
+					break;
+
+				case 'm':
+					if (DISPLAY_KILOBYTES(flags)) {
+						usage(argv[0]);
+					}
+					/* Display stats in MB/s */
+					flags |= T_D_MEGABYTES;
+					break;
+
+				case 't':
+					/* Display timestamp */
+					flags |= T_D_TIMESTAMP;
+					break;
+
+				case 'y':
+					/* Don't display stats since system restart */
+					flags |= T_D_OMIT_SINCE_BOOT;
+					break;
+
+				case 'z':
+					/* Omit output for devices with no activity */
+					flags |= T_D_ZERO_OMIT;
+					break;
+
+				case 'V':
+					/* Print version number and exit */
+					print_version();
+					break;
+
+				default:
+					usage(argv[0]);
+				}
+			}
+			opt++;
+		}
+
+		else if (!it) {
+			interval = atol(argv[opt++]);
+			if (interval < 0) {
+				usage(argv[0]);
+			}
+			count = -1;
+			it = 1;
+		}
+
+		else if (it > 0) {
+			count = atol(argv[opt++]);
+			if ((count < 1) || !interval) {
+				usage(argv[0]);
+			}
+			it = -1;
+		}
+		else {
+			usage(argv[0]);
+		}
+	}
+
+	if (!interval) {
+		count = 1;
+	}
+
+	tape_initialise();
+
+	get_localtime(&rectime, 0);
+
+	/* Get system name, release number and hostname */
+	uname(&header);
+	if (print_gal_header(&rectime, header.sysname, header.release,
+			     header.nodename, header.machine, cpu_nr)) {
+		flags |= T_D_ISO;
+	}
+	printf("\n");
+
+	/* Set a handler for SIGALRM */
+	memset(&alrm_act, 0, sizeof(alrm_act));
+	alrm_act.sa_handler = alarm_handler;
+	sigaction(SIGALRM, &alrm_act, NULL);
+	alarm(interval);
+
+	/* Main loop */
+	rw_tape_stat_loop(count, &rectime);
+
+	/* Free structures */
+	tape_uninitialise();
+
+	return 0;
+}
diff -uprN sysstat-10.1.5.orig/tapestat.h sysstat-10.1.5/tapestat.h
--- sysstat-10.1.5.orig/tapestat.h	1970-01-01 01:00:00.000000000 +0100
+++ sysstat-10.1.5/tapestat.h	2016-06-01 12:46:52.028338551 +0200
@@ -0,0 +1,126 @@
+/*
+ * tapestat: report tape statistics
+ * (C) 2015 Hewlett-Packard Development Company, L.P.
+ * 
+ * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hpe.com)
+ * Modified for sysstat by Sebastien GODARD (sysstat <at> orange.fr)
+ */
+
+#ifndef _TAPESTAT_H
+#define _TAPESTAT_H
+
+/* T_: tapestat - D_: Display - F_: Flag */
+#define T_D_TIMESTAMP		0x00001
+#define T_D_KILOBYTES		0x00002
+#define T_D_MEGABYTES		0x00004
+#define T_D_OMIT_SINCE_BOOT	0x00008
+#define T_D_ISO			0x00010
+#define T_D_ZERO_OMIT		0x00020
+
+#define DISPLAY_TIMESTAMP(m)		(((m) & T_D_TIMESTAMP)       == T_D_TIMESTAMP)
+#define DISPLAY_KILOBYTES(m)		(((m) & T_D_KILOBYTES)       == T_D_KILOBYTES)
+#define DISPLAY_MEGABYTES(m)		(((m) & T_D_MEGABYTES)       == T_D_MEGABYTES)
+#define DISPLAY_OMIT_SINCE_BOOT(m)	(((m) & T_D_OMIT_SINCE_BOOT) == T_D_OMIT_SINCE_BOOT)
+#define DISPLAY_ISO(m)			(((m) & T_D_ISO)             == T_D_ISO)
+#define DISPLAY_ZERO_OMIT(m)		(((m) & T_D_ZERO_OMIT)       == T_D_ZERO_OMIT)
+
+
+#define TAPE_STATS_VALID 1
+#define TAPE_STATS_INVALID 0
+
+#define SYSFS_CLASS_TAPE_DIR "/sys/class/scsi_tape"
+#define TAPE_STAT_PATH "/sys/class/scsi_tape/st%i/stats/"
+
+#define TAPE_STAT_FILE_VAL(A, B)					\
+	snprintf(filename, MAXPATHLEN, A, i);				\
+	if ((fp = fopen(filename, "r")) != NULL) {			\
+		if (fscanf(fp, "%"PRId64, &tape_new_stats[i].B) != 1) {	\
+			tape_new_stats[i].valid = TAPE_STATS_INVALID;	\
+		}							\
+		fclose(fp);						\
+	} else {							\
+		tape_new_stats[i].valid = TAPE_STATS_INVALID;		\
+		continue;						\
+	}
+
+
+/*
+ * A - tape_stats structure member name, e.g. read_count
+ * B - calc_stats structure member name, e.g. reads_per_second
+ *
+ * These macros are not selfcontained they depend on some other
+ * variables defined either as global or local to the function.
+ */
+
+#define CALC_STAT_CNT(A, B)					\
+	if ((tape_new_stats[i].A == tape_old_stats[i].A) ||	\
+		(duration <= 0)) {				\
+		stats->B = 0;					\
+	} else {						\
+                temp = (double) (tape_new_stats[i].A -		\
+			tape_old_stats[i].A)			\
+			/ (((double) duration) / 1000);		\
+		stats->B = (uint64_t) temp;			\
+	}
+#define CALC_STAT_KB(A, B)					\
+        if ((tape_new_stats[i].A == tape_old_stats[i].A) ||	\
+		(duration <= 0)) {				\
+		stats->B = 0;					\
+        } else {						\
+		temp = (double) (tape_new_stats[i].A -		\
+			tape_old_stats[i].A)			\
+			/ (((double) duration) / 1000.0);	\
+		stats->B = (uint64_t) (temp / 1024.0);		\
+	}
+
+#define TAPE_MAX_PCT 999
+
+#define CALC_STAT_PCT(A, B)						\
+	if ((tape_new_stats[i].A == tape_old_stats[i].A) ||		\
+		(duration <= 0)) {					\
+		stats->B = 0;						\
+	} else {							\
+		temp = (double) (tape_new_stats[i].A -			\
+			tape_old_stats[i].A)				\
+			/ (((double) duration));			\
+		stats->B = (uint64_t) (100.0 * temp / 1000000.0);	\
+		if (stats->B > TAPE_MAX_PCT)				\
+			stats->B = TAPE_MAX_PCT;				\
+	}
+
+struct tape_stats {
+        uint64_t read_time;
+        uint64_t write_time;
+        uint64_t other_time;
+        uint64_t read_bytes;
+        uint64_t write_bytes;
+        uint64_t read_count;
+        uint64_t write_count;
+        uint64_t other_count;
+        uint64_t resid_count;
+        char valid;
+        struct timeval tv;
+};
+struct calc_stats {
+        uint64_t reads_per_second;
+        uint64_t writes_per_second;
+        uint64_t other_per_second;
+        uint64_t kbytes_read_per_second;
+        uint64_t kbytes_written_per_second;
+        uint64_t read_pct_wait;
+        uint64_t write_pct_wait;
+        uint64_t all_pct_wait;
+        uint64_t resids_per_second;
+};
+
+void tape_get_updated_stats(void);
+void tape_gather_initial_stats(void);
+void tape_check_tapes_and_realloc(void);
+int get_max_tape_drives(void);
+void tape_uninitialise(void);
+void tape_initialise(void);
+void tape_calc_one_stats(struct calc_stats *, int);
+void tape_write_headings(void);
+void tape_write_stats(struct calc_stats *, int);
+
+#endif  /* _TAPESTAT_H */