]> granicus.if.org Git - sysstat/blob - tapestat.c
Display a percent sign after the value when --human option used
[sysstat] / tapestat.c
1 /*
2  * tapestat: report tape statistics
3  * (C) 2015 Hewlett-Packard Development Company, L.P.
4  *
5  * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hpe.com)
6  * Modified for sysstat by Sebastien GODARD (sysstat <at> orange.fr)
7  *
8  ***************************************************************************
9  * This program is free software; you can redistribute it and/or modify it *
10  * under the terms of the GNU General Public License as published  by  the *
11  * Free Software Foundation; either version 2 of the License, or (at  your *
12  * option) any later version.                                              *
13  *                                                                         *
14  * This program is distributed in the hope that it  will  be  useful,  but *
15  * WITHOUT ANY WARRANTY; without the implied warranty  of  MERCHANTABILITY *
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17  * for more details.                                                       *
18  *                                                                         *
19  * You should have received a copy of the GNU General Public License along *
20  * with this program; if not, write to the Free Software Foundation, Inc., *
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA              *
22  ***************************************************************************
23  */
24
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <dirent.h>
31 #define __DO_NOT_DEFINE_COMPILE
32 #include <regex.h>
33 #include <inttypes.h>
34 #include <stdint.h>
35 #include <signal.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/utsname.h>
39
40 #ifdef HAVE_SYS_PARAM_H
41 #include <sys/param.h>
42 #undef HZ /* sys/param.h defines HZ but needed for MAXPATHLEN */
43 #endif
44 #ifndef MAXPATHLEN
45 #define MAXPATHLEN 256
46 #endif
47
48 #include "version.h"
49 #include "tapestat.h"
50 #include "common.h"
51 #include "count.h"
52
53 #ifdef USE_NLS
54 #include <locale.h>
55 #include <libintl.h>
56 #define _(string) gettext(string)
57 #else
58 #define _(string) (string)
59 #endif
60
61 #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
62 char *sccsid(void) { return (SCCSID); }
63
64 int cpu_nr = 0;         /* Nb of processors on the machine */
65 int flags = 0;          /* Flag for common options and system state */
66
67 long interval = 0;
68 char timestamp[TIMESTAMP_LEN];
69
70 struct sigaction alrm_act;
71
72 /*
73  * For tape stats - it would be extremely rare for there to be a very large
74  * number of tape drives attached to a system. I wouldn't expect to see more
75  * than 20-30 in a very large configuration and discontinguous ones should
76  * be even more rare. Because of this we keep the old and new data in a
77  * simple data structure with the tape index being the number after the tape
78  * drive, st0 at index 0, etc.
79  */
80 int max_tape_drives = 0;
81 struct tape_stats *tape_new_stats = { NULL };
82 struct tape_stats *tape_old_stats = { NULL };
83 regex_t tape_reg;
84
85 /*
86  ***************************************************************************
87  * Print usage and exit.
88  *
89  * IN:
90  * @progname    Name of sysstat command.
91  ***************************************************************************
92  */
93 void usage(char *progname)
94 {
95         fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
96                 progname);
97         fprintf(stderr, _("Options are:\n"
98                           "[ --human ] [ -k | -m ] [ -t ] [ -V ] [ -y ] [ -z ]\n"));
99         exit(1);
100 }
101
102 /*
103  ***************************************************************************
104  * SIGALRM signal handler. No need to reset the handler here.
105  *
106  * IN:
107  * @sig Signal number.
108  ***************************************************************************
109  */
110 void alarm_handler(int sig)
111 {
112         alarm(interval);
113 }
114
115 /*
116  ***************************************************************************
117  * Initialization.
118  ***************************************************************************
119  */
120 void tape_initialise(void)
121 {
122         /* How many processors on this machine? */
123         cpu_nr = get_cpu_nr(~0, FALSE);
124
125         /* Compile regular expression for tape names */
126         if (regcomp(&tape_reg, "^st[0-9]+$", REG_EXTENDED) != 0) {
127                 exit(1);
128         }
129 }
130
131 /*
132  ***************************************************************************
133  * Free structures.
134  ***************************************************************************
135  */
136 void tape_uninitialise(void)
137 {
138         regfree(&tape_reg);
139         if (tape_old_stats != NULL) {
140                 free(tape_old_stats);
141         }
142         if (tape_new_stats != NULL) {
143                 free(tape_new_stats);
144         }
145 }
146
147 /*
148  ***************************************************************************
149  * Get maximum number of tapes in the system.
150  *
151  * RETURNS:
152  * Number of tapes found.
153  ***************************************************************************
154  */
155 int get_max_tape_drives(void)
156 {
157         DIR *dir;
158         struct dirent *entry;
159         int new_max_tape_drives, tmp, num_stats_dir = 0;
160         regmatch_t match;
161         char stats_dir[MAXPATHLEN + 1];
162         struct stat stat_buf;
163
164         new_max_tape_drives = max_tape_drives;
165
166         /* Open sysfs tree */
167         dir = opendir(SYSFS_CLASS_TAPE_DIR);
168         if (dir == NULL)
169                 return 0;
170
171         while ((entry = readdir(dir)) != NULL) {
172                 if (regexec(&tape_reg, &entry->d_name[0], 1, &match, 0) == 0) {
173                         /* d_name[2] to skip the st at the front */
174                         tmp = atoi(&entry->d_name[2]) + 1;
175                         if (tmp > new_max_tape_drives) {
176                                 new_max_tape_drives = tmp;
177                         }
178                 }
179                 snprintf(stats_dir, MAXPATHLEN, "%s/%s/%s",
180                         SYSFS_CLASS_TAPE_DIR, &entry->d_name[0], "stats");
181                 if (stat(stats_dir, &stat_buf) == 0) {
182                         if (S_ISDIR(stat_buf.st_mode)) {
183                                 num_stats_dir++;
184                         }
185                 }
186         }
187         closedir(dir);
188
189         /* If there are no stats directories make the new number of tape drives 0 */
190         if (num_stats_dir == 0) {
191                 new_max_tape_drives = 0;
192         }
193
194         return new_max_tape_drives;
195 }
196
197 /*
198  ***************************************************************************
199  * Check if new tapes have been added and reallocate structures accordingly.
200  ***************************************************************************
201  */
202 void tape_check_tapes_and_realloc(void)
203 {
204         int new_max_tape_drives, i;
205
206         /* Count again number of tapes */
207         new_max_tape_drives = get_max_tape_drives();
208
209         if (new_max_tape_drives > max_tape_drives && new_max_tape_drives > 0) {
210                 /* New tapes found: Realloc structures */
211                 struct tape_stats *tape_old_stats_t = (struct tape_stats *)
212                         realloc(tape_old_stats, sizeof(struct tape_stats) * new_max_tape_drives);
213                 struct tape_stats *tape_new_stats_t = (struct tape_stats *)
214                         realloc(tape_new_stats, sizeof(struct tape_stats) * new_max_tape_drives);
215                 if ((tape_old_stats_t == NULL) || (tape_new_stats_t == NULL)) {
216                         if (tape_old_stats_t != NULL) {
217                                 free(tape_old_stats_t);
218                                 tape_old_stats_t = NULL;
219                         } else {
220                                 free(tape_old_stats);
221                                 tape_old_stats = NULL;
222                         }
223                         if (tape_new_stats_t != NULL) {
224                                 free(tape_new_stats_t);
225                                 tape_new_stats_t = NULL;
226                         } else {
227                                 free(tape_new_stats);
228                                 tape_new_stats = NULL;
229                         }
230
231                         perror("realloc");
232                         exit(4);
233                 }
234
235                 tape_old_stats = tape_old_stats_t;
236                 tape_new_stats = tape_new_stats_t;
237
238                 for (i = max_tape_drives; i < new_max_tape_drives; i++) {
239                         tape_old_stats[i].valid = TAPE_STATS_INVALID;
240                         tape_new_stats[i].valid = TAPE_STATS_INVALID;
241                 }
242                 max_tape_drives = new_max_tape_drives;
243         }
244 }
245
246 /*
247  ***************************************************************************
248  * Collect initial statistics for all existing tapes in the system.
249  * This function should be called only once.
250  ***************************************************************************
251  */
252 void tape_gather_initial_stats(void)
253 {
254         int new_max_tape_drives, i;
255         FILE *fp;
256         char filename[MAXPATHLEN + 1];
257
258         /* Get number of tapes in the system */
259         new_max_tape_drives = get_max_tape_drives();
260
261         if (new_max_tape_drives == 0) {
262                 /* No tapes found */
263                 fprintf(stderr, _("No tape drives with statistics found\n"));
264                 exit(1);
265         }
266         else {
267                 /* Allocate structures */
268                 if (tape_old_stats == NULL) {
269                         tape_old_stats = (struct tape_stats *)
270                                 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
271                         tape_new_stats = (struct tape_stats *)
272                                 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
273                         for (i = 0; i < new_max_tape_drives; i++) {
274                                 tape_old_stats[i].valid = TAPE_STATS_INVALID;
275                                 tape_new_stats[i].valid = TAPE_STATS_INVALID;
276                         }
277                         max_tape_drives = new_max_tape_drives;
278                 } else
279                         /* This should only be called once */
280                         return;
281         }
282
283         /* Read stats for each tape */
284         for (i = 0; i < max_tape_drives; i++) {
285                 /*
286                  * Everything starts out valid but failing to open
287                  * a file gets the tape drive marked invalid.
288                  */
289                 tape_new_stats[i].valid = TAPE_STATS_VALID;
290                 tape_old_stats[i].valid = TAPE_STATS_VALID;
291
292                 gettimeofday(&tape_old_stats[i].tv, NULL);
293
294                 tape_new_stats[i].tv.tv_sec = tape_old_stats[i].tv.tv_sec;
295                 tape_new_stats[i].tv.tv_usec = tape_old_stats[i].tv.tv_usec;
296
297                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
298                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
299                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
300                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
301                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
302                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
303                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
304                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
305                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
306
307                 tape_old_stats[i].read_time = 0;
308                 tape_old_stats[i].write_time = 0;
309                 tape_old_stats[i].other_time = 0;
310                 tape_old_stats[i].read_bytes = 0;
311                 tape_old_stats[i].write_bytes = 0;
312                 tape_old_stats[i].read_count = 0;
313                 tape_old_stats[i].write_count = 0;
314                 tape_old_stats[i].other_count = 0;
315                 tape_old_stats[i].resid_count = 0;
316         }
317 }
318
319 /*
320  ***************************************************************************
321  * Collect a new sample of statistics for all existing tapes in the system.
322  ***************************************************************************
323  */
324 void tape_get_updated_stats(void)
325 {
326         int i;
327         FILE *fp;
328         char filename[MAXPATHLEN + 1] = { 0 };
329
330         /* Check tapes and realloc structures if  needed */
331         tape_check_tapes_and_realloc();
332
333         for (i = 0; i < max_tape_drives; i++) {
334                 /*
335                  * Everything starts out valid but failing
336                  * to open a file gets the tape drive marked invalid.
337                  */
338                 tape_new_stats[i].valid = TAPE_STATS_VALID;
339                 gettimeofday(&tape_new_stats[i].tv, NULL);
340
341                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
342                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
343                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
344                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
345                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
346                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
347                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
348                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
349                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
350
351                 if ((tape_new_stats[i].read_time < tape_old_stats[i].read_time) ||
352                     (tape_new_stats[i].write_time < tape_old_stats[i].write_time) ||
353                     (tape_new_stats[i].other_time < tape_old_stats[i].other_time)) {
354                         tape_new_stats[i].valid = TAPE_STATS_INVALID;
355                 }
356         }
357 }
358
359 /*
360  ***************************************************************************
361  * Display tapes statistics headings.
362  ***************************************************************************
363  */
364 void tape_write_headings(void)
365 {
366         printf("Tape:     r/s     w/s   ");
367         if (DISPLAY_MEGABYTES(flags)) {
368                 printf("MB_read/s   MB_wrtn/s");
369         } else {
370                 printf("kB_read/s   kB_wrtn/s");
371         }
372         printf("  %%Rd  %%Wr  %%Oa    Rs/s    Ot/s\n");
373 }
374
375 /*
376  ***************************************************************************
377  * Calculate statistics for current tape.
378  *
379  * IN:
380  * @i           Index in array for current tape.
381  *
382  * OUT:
383  * @stats       Statistics for current tape.
384  ***************************************************************************
385  */
386 void tape_calc_one_stats(struct calc_stats *stats, int i)
387 {
388         uint64_t duration;
389         double temp;
390         FILE *fp;
391
392         /* Duration in ms done in ms to prevent rounding issues with using seconds */
393         duration = (tape_new_stats[i].tv.tv_sec -
394                 tape_old_stats[i].tv.tv_sec) * 1000;
395         duration -= tape_old_stats[i].tv.tv_usec / 1000;
396         duration += tape_new_stats[i].tv.tv_usec / 1000;
397
398         /* If duration is zero we need to calculate the ms since boot time */
399         if (duration == 0) {
400                 fp = fopen("/proc/uptime", "r");
401
402                 /*
403                  * Get uptime from /proc/uptime and if we can't then just set duration to
404                  * be 0 - it will mean that we don't calculate stats.
405                  */
406                 if (fp == NULL) {
407                         duration = 0;
408                 } else {
409                         if (fscanf(fp, "%lf", &temp) != 1) {
410                                 temp = 0;
411                         }
412                         duration = (uint64_t) (temp * 1000);
413                         fclose(fp);
414                 }
415         }
416
417         /* The second value passed into the macro is the thing being calculated */
418         CALC_STAT_CNT(read_count, reads_per_second)
419         CALC_STAT_CNT(write_count, writes_per_second)
420         CALC_STAT_CNT(other_count, other_per_second)
421         CALC_STAT_KB(read_bytes, kbytes_read_per_second)
422         CALC_STAT_KB(write_bytes, kbytes_written_per_second)
423         CALC_STAT_PCT(read_time, read_pct_wait)
424         CALC_STAT_PCT(write_time, write_pct_wait)
425         CALC_STAT_PCT(other_time, all_pct_wait)
426         CALC_STAT_CNT(resid_count, resids_per_second)
427 }
428
429 /*
430  ***************************************************************************
431  * Display statistics for current tape.
432  *
433  * IN:
434  * @tape        Statistics for current tape.
435  * @i           Index in array for current tape.
436  ***************************************************************************
437  */
438 void tape_write_stats(struct calc_stats *tape, int i)
439 {
440         char buffer[32];
441         uint64_t divisor = 1;
442
443         if (DISPLAY_MEGABYTES(flags))
444                 divisor = 1024;
445
446         sprintf(buffer, "st%i        ", i);
447         buffer[5] = 0;
448         cprintf_in(IS_STR, "%s", buffer, 0);
449         cprintf_u64(NO_UNIT, 2, 7,
450                     tape->reads_per_second,
451                     tape->writes_per_second);
452         cprintf_u64(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 2, 11,
453                     DISPLAY_UNIT(flags) ? tape->kbytes_read_per_second
454                                         : tape->kbytes_read_per_second / divisor,
455                     DISPLAY_UNIT(flags) ? tape->kbytes_written_per_second
456                                         : tape->kbytes_written_per_second / divisor);
457         cprintf_pc(DISPLAY_UNIT(flags), 3, 4, 0,
458                    (double) tape->read_pct_wait,
459                    (double) tape->write_pct_wait,
460                    (double) tape->all_pct_wait);
461         cprintf_u64(NO_UNIT, 2, 7,
462                     tape->resids_per_second,
463                     tape->other_per_second);
464         printf("\n");
465 }
466
467 /*
468  ***************************************************************************
469  * Print everything now (stats and uptime).
470  *
471  * IN:
472  * @rectime     Current date and time.
473  ***************************************************************************
474  */
475 void write_stats(struct tm *rectime)
476 {
477         int i;
478         struct calc_stats tape;
479         struct tape_stats *tmp;
480
481         /* Test stdout */
482         TEST_STDOUT(STDOUT_FILENO);
483
484         /* Print time stamp */
485         if (DISPLAY_TIMESTAMP(flags)) {
486                 if (DISPLAY_ISO(flags)) {
487                         strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
488                 }
489                 else {
490                         strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
491                 }
492                 printf("%s\n", timestamp);
493         }
494
495         /* Print the headings */
496         tape_write_headings();
497
498         /*
499          * If either new or old is invalid or the I/Os per second is 0 and
500          * zero omit is true then we print nothing.
501          */
502         if (max_tape_drives > 0) {
503
504                 for (i = 0; i < max_tape_drives; i++) {
505                         if ((tape_new_stats[i].valid == TAPE_STATS_VALID) &&
506                                 (tape_old_stats[i].valid == TAPE_STATS_VALID)) {
507                                 tape_calc_one_stats(&tape, i);
508                                 if (!(DISPLAY_ZERO_OMIT(flags)
509                                         && (tape.other_per_second == 0)
510                                         && (tape.reads_per_second == 0)
511                                         && (tape.writes_per_second == 0)
512                                         && (tape.kbytes_read_per_second == 0)
513                                         && (tape.kbytes_written_per_second == 0)
514                                         && (tape.read_pct_wait == 0)
515                                         && (tape.write_pct_wait == 0)
516                                         && (tape.all_pct_wait == 0)
517                                         && (tape.resids_per_second == 0))) {
518                                         tape_write_stats(&tape, i);
519                                 }
520                         }
521                 }
522                 /*
523                  * Swap new and old so next time we compare against the new old stats.
524                  * If a new tape drive appears it won't appear in the output until after
525                  * the second time we gather information about it.
526                  */
527                 tmp = tape_old_stats;
528                 tape_old_stats = tape_new_stats;
529                 tape_new_stats = tmp;
530         }
531         printf("\n");
532 }
533
534 /*
535  ***************************************************************************
536  * Main loop: Read tape stats from the relevant sources and display them.
537  *
538  * IN:
539  * @count       Number of lines of stats to print.
540  * @rectime     Current date and time.
541  ***************************************************************************
542  */
543 void rw_tape_stat_loop(long int count, struct tm *rectime)
544 {
545         struct tape_stats *tmp;
546         int skip = 0;
547
548         /* Should we skip first report? */
549         if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) {
550                 skip = 1;
551         }
552
553         /* Don't buffer data if redirected to a pipe */
554         setbuf(stdout, NULL);
555
556         do {
557
558                 if (tape_new_stats == NULL) {
559                         tape_gather_initial_stats();
560                 } else {
561                         tape_get_updated_stats();
562                 }
563
564                 /* Get time */
565                 get_localtime(rectime, 0);
566
567                 /* Check whether we should skip first report */
568                 if (!skip) {
569                         /* Print results */
570                         write_stats(rectime);
571
572                         if (count > 0) {
573                                 count--;
574                         }
575                 }
576                 else {
577                         skip = 0;
578                         tmp = tape_old_stats;
579                         tape_old_stats = tape_new_stats;
580                         tape_new_stats = tmp;
581                 }
582
583                 if (count) {
584                         pause();
585                 }
586         }
587         while (count);
588 }
589
590 /*
591  ***************************************************************************
592  * Main entry to the tapestat program.
593  ***************************************************************************
594  */
595 int main(int argc, char **argv)
596 {
597         int it = 0;
598         int opt = 1;
599         int i;
600         long count = 1;
601         struct utsname header;
602         struct tm rectime;
603
604 #ifdef USE_NLS
605         /* Init National Language Support */
606         init_nls();
607 #endif
608
609         /* Init color strings */
610         init_colors();
611
612         /* Get HZ */
613         get_HZ();
614
615         /* Process args... */
616         while (opt < argc) {
617
618                 if (!strcmp(argv[opt], "--human")) {
619                         flags |= T_D_UNIT;
620                         opt++;
621                 }
622
623                 else if (!strncmp(argv[opt], "-", 1)) {
624                         for (i = 1; *(argv[opt] + i); i++) {
625
626                                 switch (*(argv[opt] + i)) {
627
628                                 case 'k':
629                                         if (DISPLAY_MEGABYTES(flags)) {
630                                                 usage(argv[0]);
631                                         }
632                                         /* Display stats in kB/s */
633                                         flags |= T_D_KILOBYTES;
634                                         break;
635
636                                 case 'm':
637                                         if (DISPLAY_KILOBYTES(flags)) {
638                                                 usage(argv[0]);
639                                         }
640                                         /* Display stats in MB/s */
641                                         flags |= T_D_MEGABYTES;
642                                         break;
643
644                                 case 't':
645                                         /* Display timestamp */
646                                         flags |= T_D_TIMESTAMP;
647                                         break;
648
649                                 case 'y':
650                                         /* Don't display stats since system restart */
651                                         flags |= T_D_OMIT_SINCE_BOOT;
652                                         break;
653
654                                 case 'z':
655                                         /* Omit output for devices with no activity */
656                                         flags |= T_D_ZERO_OMIT;
657                                         break;
658
659                                 case 'V':
660                                         /* Print version number and exit */
661                                         print_version();
662                                         break;
663
664                                 default:
665                                         usage(argv[0]);
666                                 }
667                         }
668                         opt++;
669                 }
670
671                 else if (!it) {
672                         interval = atol(argv[opt++]);
673                         if (interval < 0) {
674                                 usage(argv[0]);
675                         }
676                         count = -1;
677                         it = 1;
678                 }
679
680                 else if (it > 0) {
681                         count = atol(argv[opt++]);
682                         if ((count < 1) || !interval) {
683                                 usage(argv[0]);
684                         }
685                         it = -1;
686                 }
687                 else {
688                         usage(argv[0]);
689                 }
690         }
691
692         if (!interval) {
693                 count = 1;
694         }
695
696         tape_initialise();
697
698         get_localtime(&rectime, 0);
699
700         /* Get system name, release number and hostname */
701         uname(&header);
702         if (print_gal_header(&rectime, header.sysname, header.release,
703                              header.nodename, header.machine, cpu_nr,
704                              PLAIN_OUTPUT)) {
705                 flags |= T_D_ISO;
706         }
707         printf("\n");
708
709         /* Set a handler for SIGALRM */
710         memset(&alrm_act, 0, sizeof(alrm_act));
711         alrm_act.sa_handler = alarm_handler;
712         sigaction(SIGALRM, &alrm_act, NULL);
713         alarm(interval);
714
715         /* Main loop */
716         rw_tape_stat_loop(count, &rectime);
717
718         /* Free structures */
719         tape_uninitialise();
720
721         return 0;
722 }