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/stats/* +Statistics files for tape devices. + +.I /proc/uptime +contains system uptime. +.SH AUTHOR +Initial revision by Shane M. SEYMOUR (shane.seymour hpe.com) +.br +Modified for sysstat by Sebastien Godard (sysstat 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 hpe.com) + * Modified for sysstat by Sebastien GODARD (sysstat 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 +#include +#include +#include +#include +#include +#define __DO_NOT_DEFINE_COMPILE +#include +#include +#include +#include +#include +#include +#include +#include +#undef HZ /* sys/param.h defines HZ but needed for MAXPATHLEN */ + +#include "version.h" +#include "tapestat.h" +#include "common.h" + +#ifdef USE_NLS +#include +#include +#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 ] [ [ ] ]\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 hpe.com) + * Modified for sysstat by Sebastien GODARD (sysstat 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 */