]> granicus.if.org Git - sysstat/commitdiff
Add new "tapestat" command
authorSebastien GODARD <sysstat@users.noreply.github.com>
Sun, 23 Aug 2015 06:31:11 +0000 (08:31 +0200)
committerSebastien GODARD <sysstat@users.noreply.github.com>
Sun, 23 Aug 2015 06:31:11 +0000 (08:31 +0200)
tapestat is a new command aimed at displaying statistics for tape drives
connected to the system.
Contributed by Shane SEYMOUR from HP..

Signed-off-by: Shane M. SEYMOUR <shane.seymour@hp.com>
Signed-off-by: Sebastien GODARD <sysstat@users.noreply.github.com>
tapestat.c [new file with mode: 0644]
tapestat.h [new file with mode: 0644]

diff --git a/tapestat.c b/tapestat.c
new file mode 100644 (file)
index 0000000..bdd7f00
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ * tapestat: report tape statistics
+ * (C) 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hp.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., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                   *
+ ***************************************************************************
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <dirent.h>
+#define __DO_NOT_DEFINE_COMPILE
+#include <regexp.h>
+#include <inttypes.h>
+#include <stdint.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"
+#include "count.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, FALSE);
+
+       /* 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 == 0) && (max_tape_drives == 0)) {
+               /* If there are no tape drives don't change anything */
+               return;
+        } else {
+               if (new_max_tape_drives > max_tape_drives) {
+                       /* New tapes found: Realloc structures */
+                       tape_old_stats = (struct tape_stats *)
+                               realloc(tape_old_stats, sizeof(struct tape_stats) * new_max_tape_drives);
+                       tape_new_stats=(struct tape_stats *)
+                               realloc(tape_new_stats, sizeof(struct tape_stats) * new_max_tape_drives);
+                       if ((tape_old_stats == NULL) || (tape_new_stats == NULL)) {
+                                if (tape_old_stats != NULL) {
+                                       free(tape_old_stats);
+                                       tape_old_stats = NULL;
+                               }
+                                if (tape_new_stats != NULL) {
+                                       free(tape_new_stats);
+                                       tape_new_stats = NULL;
+                               }
+                               return;
+                       }
+
+                       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 */
+               return;
+       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)
+       }
+}
+
+/*
+ ***************************************************************************
+ * 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 {
+                       fscanf(fp, "%lf", &temp);
+                       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)
+
+       return;
+}
+
+/*
+ ***************************************************************************
+ * 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:
+ * @curr       Index in array for current sample statistics.
+ * @rectime    Current date and time.
+ ***************************************************************************
+ */
+void write_stats(int curr, 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);
+       }
+
+       /*
+        * 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 there are no tape drives
+        * we do not print the headings either.
+        */
+       if (max_tape_drives > 0) {
+               tape_write_headings();
+
+               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 I/O stats from the relevant sources and display them.
+ *
+ * IN:
+ * @count      Number of lines of stats to print.
+ * @rectime    Current date and time.
+ ***************************************************************************
+ */
+void rw_io_stat_loop(long int count, struct tm *rectime)
+{
+       struct tape_stats *tmp;
+       int curr = 1;
+       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(curr, rectime);
+
+                       if (count > 0) {
+                               count--;
+                       }
+               }
+               else {
+                       skip = 0;
+                       tmp = tape_old_stats;
+                       tape_old_stats = tape_new_stats;
+                       tape_new_stats = tmp;
+               }
+
+               if (count) {
+                       curr ^= 1;
+                       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_io_stat_loop(count, &rectime);
+
+       /* Free structures */
+       tape_uninitialise();
+
+       return 0;
+}
diff --git a/tapestat.h b/tapestat.h
new file mode 100644 (file)
index 0000000..97a507b
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * tapestat: report tape statistics
+ * (C) 2015 Hewlett-Packard Development Company, L.P.
+ * 
+ * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hp.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) {              \
+               fscanf(fp, "%"PRId64, &tape_new_stats[i].B);    \
+               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 */