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 */