2 * tapestat: report tape statistics
3 * (C) 2015 Hewlett-Packard Development Company, L.P.
5 * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hpe.com)
6 * Modified for sysstat by Sebastien GODARD (sysstat <at> orange.fr)
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. *
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 *
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 ***************************************************************************
31 #define __DO_NOT_DEFINE_COMPILE
38 #include <sys/utsname.h>
40 #ifdef HAVE_SYS_PARAM_H
41 #include <sys/param.h>
42 #undef HZ /* sys/param.h defines HZ but needed for MAXPATHLEN */
45 #define MAXPATHLEN 256
56 #define _(string) gettext(string)
58 #define _(string) (string)
62 #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
63 char *sccsid(void) { return (SCCSID); }
67 void int_handler(int n) { return; }
70 int cpu_nr = 0; /* Nb of processors on the machine */
71 int flags = 0; /* Flag for common options and system state */
74 char timestamp[TIMESTAMP_LEN];
76 struct sigaction alrm_act;
78 /* Number of decimal places */
82 * For tape stats - it would be extremely rare for there to be a very large
83 * number of tape drives attached to a system. I wouldn't expect to see more
84 * than 20-30 in a very large configuration and discontinguous ones should
85 * be even more rare. Because of this we keep the old and new data in a
86 * simple data structure with the tape index being the number after the tape
87 * drive, st0 at index 0, etc.
89 int max_tape_drives = 0;
90 struct tape_stats *tape_new_stats = { NULL };
91 struct tape_stats *tape_old_stats = { NULL };
95 ***************************************************************************
96 * Print usage and exit.
99 * @progname Name of sysstat command.
100 ***************************************************************************
102 void usage(char *progname)
104 fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
106 fprintf(stderr, _("Options are:\n"
107 "[ --human ] [ -k | -m ] [ -t ] [ -V ] [ -y ] [ -z ]\n"));
112 ***************************************************************************
113 * SIGALRM signal handler. No need to reset the handler here.
116 * @sig Signal number.
117 ***************************************************************************
119 void alarm_handler(int sig)
125 ***************************************************************************
127 ***************************************************************************
129 void tape_initialise(void)
131 /* How many processors on this machine? */
132 cpu_nr = get_cpu_nr(~0, FALSE);
134 /* Compile regular expression for tape names */
135 if (regcomp(&tape_reg, "^st[0-9]+$", REG_EXTENDED) != 0) {
141 ***************************************************************************
143 ***************************************************************************
145 void tape_uninitialise(void)
148 if (tape_old_stats != NULL) {
149 free(tape_old_stats);
151 if (tape_new_stats != NULL) {
152 free(tape_new_stats);
157 ***************************************************************************
158 * Get maximum number of tapes in the system.
161 * Number of tapes found.
162 ***************************************************************************
164 int get_max_tape_drives(void)
167 struct dirent *entry;
168 int new_max_tape_drives, tmp, num_stats_dir = 0;
170 char stats_dir[MAXPATHLEN + 1];
171 struct stat stat_buf;
173 new_max_tape_drives = max_tape_drives;
175 /* Open sysfs tree */
176 dir = opendir(SYSFS_CLASS_TAPE_DIR);
180 while ((entry = readdir(dir)) != NULL) {
182 if (regexec(&tape_reg, &entry->d_name[0], 1, &match, 0) == 0) {
183 /* d_name[2] to skip the st at the front */
184 tmp = atoi(&entry->d_name[2]) + 1;
185 if (tmp > new_max_tape_drives) {
186 new_max_tape_drives = tmp;
189 snprintf(stats_dir, MAXPATHLEN, "%s/%s/%s",
190 SYSFS_CLASS_TAPE_DIR, &entry->d_name[0], "stats");
191 if (stat(stats_dir, &stat_buf) == 0) {
192 if (S_ISDIR(stat_buf.st_mode)) {
199 /* If there are no stats directories make the new number of tape drives 0 */
200 if (num_stats_dir == 0) {
201 new_max_tape_drives = 0;
204 return new_max_tape_drives;
208 ***************************************************************************
209 * Check if new tapes have been added and reallocate structures accordingly.
210 ***************************************************************************
212 void tape_check_tapes_and_realloc(void)
214 int new_max_tape_drives;
216 /* Count again number of tapes */
217 new_max_tape_drives = get_max_tape_drives();
219 if (new_max_tape_drives > max_tape_drives && new_max_tape_drives > 0) {
222 /* New tapes found: Realloc structures */
223 struct tape_stats *tape_old_stats_t = (struct tape_stats *)
224 realloc(tape_old_stats, sizeof(struct tape_stats) * new_max_tape_drives);
225 struct tape_stats *tape_new_stats_t = (struct tape_stats *)
226 realloc(tape_new_stats, sizeof(struct tape_stats) * new_max_tape_drives);
227 if ((tape_old_stats_t == NULL) || (tape_new_stats_t == NULL)) {
228 if (tape_old_stats_t != NULL) {
229 free(tape_old_stats_t);
230 tape_old_stats_t = NULL;
232 free(tape_old_stats);
233 tape_old_stats = NULL;
235 if (tape_new_stats_t != NULL) {
236 free(tape_new_stats_t);
237 tape_new_stats_t = NULL;
239 free(tape_new_stats);
240 tape_new_stats = NULL;
247 tape_old_stats = tape_old_stats_t;
248 tape_new_stats = tape_new_stats_t;
250 for (i = max_tape_drives; i < new_max_tape_drives; i++) {
251 tape_old_stats[i].valid = TAPE_STATS_INVALID;
252 tape_new_stats[i].valid = TAPE_STATS_INVALID;
254 max_tape_drives = new_max_tape_drives;
259 ***************************************************************************
260 * Collect initial statistics for all existing tapes in the system.
261 * This function should be called only once.
262 ***************************************************************************
264 void tape_gather_initial_stats(void)
266 int new_max_tape_drives, i;
268 char filename[MAXPATHLEN + 1];
270 /* Get number of tapes in the system */
271 new_max_tape_drives = get_max_tape_drives();
273 if (new_max_tape_drives == 0) {
275 fprintf(stderr, _("No tape drives with statistics found\n"));
279 /* Allocate structures */
280 if (tape_old_stats == NULL) {
281 tape_old_stats = (struct tape_stats *)
282 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
283 tape_new_stats = (struct tape_stats *)
284 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
285 for (i = 0; i < new_max_tape_drives; i++) {
286 tape_old_stats[i].valid = TAPE_STATS_INVALID;
287 tape_new_stats[i].valid = TAPE_STATS_INVALID;
289 max_tape_drives = new_max_tape_drives;
291 /* This should only be called once */
295 /* Read stats for each tape */
296 for (i = 0; i < max_tape_drives; i++) {
298 * Everything starts out valid but failing to open
299 * a file gets the tape drive marked invalid.
301 tape_new_stats[i].valid = TAPE_STATS_VALID;
302 tape_old_stats[i].valid = TAPE_STATS_VALID;
304 __gettimeofday(&tape_old_stats[i].tv, NULL);
306 tape_new_stats[i].tv.tv_sec = tape_old_stats[i].tv.tv_sec;
307 tape_new_stats[i].tv.tv_usec = tape_old_stats[i].tv.tv_usec;
309 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
310 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
311 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
312 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
313 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
314 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
315 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
316 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
317 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
319 tape_old_stats[i].read_time = 0;
320 tape_old_stats[i].write_time = 0;
321 tape_old_stats[i].other_time = 0;
322 tape_old_stats[i].read_bytes = 0;
323 tape_old_stats[i].write_bytes = 0;
324 tape_old_stats[i].read_count = 0;
325 tape_old_stats[i].write_count = 0;
326 tape_old_stats[i].other_count = 0;
327 tape_old_stats[i].resid_count = 0;
332 ***************************************************************************
333 * Collect a new sample of statistics for all existing tapes in the system.
334 ***************************************************************************
336 void tape_get_updated_stats(void)
340 char filename[MAXPATHLEN + 1] = { 0 };
342 /* Check tapes and realloc structures if needed */
343 tape_check_tapes_and_realloc();
345 for (i = 0; i < max_tape_drives; i++) {
347 * Everything starts out valid but failing
348 * to open a file gets the tape drive marked invalid.
350 tape_new_stats[i].valid = TAPE_STATS_VALID;
351 __gettimeofday(&tape_new_stats[i].tv, NULL);
353 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
354 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
355 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
356 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
357 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
358 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
359 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
360 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
361 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
363 if ((tape_new_stats[i].read_time < tape_old_stats[i].read_time) ||
364 (tape_new_stats[i].write_time < tape_old_stats[i].write_time) ||
365 (tape_new_stats[i].other_time < tape_old_stats[i].other_time)) {
366 tape_new_stats[i].valid = TAPE_STATS_INVALID;
372 ***************************************************************************
373 * Display tapes statistics headings.
374 ***************************************************************************
376 void tape_write_headings(void)
378 printf("Tape: r/s w/s ");
379 if (DISPLAY_MEGABYTES(flags)) {
380 printf("MB_read/s MB_wrtn/s");
382 printf("kB_read/s kB_wrtn/s");
384 printf(" %%Rd %%Wr %%Oa Rs/s Ot/s\n");
388 ***************************************************************************
389 * Calculate statistics for current tape.
392 * @i Index in array for current tape.
395 * @stats Statistics for current tape.
396 ***************************************************************************
398 void tape_calc_one_stats(struct calc_stats *stats, int i)
404 /* Duration in ms done in ms to prevent rounding issues with using seconds */
405 duration = (tape_new_stats[i].tv.tv_sec -
406 tape_old_stats[i].tv.tv_sec) * 1000;
407 duration -= tape_old_stats[i].tv.tv_usec / 1000;
408 duration += tape_new_stats[i].tv.tv_usec / 1000;
410 /* If duration is zero we need to calculate the ms since boot time */
412 fp = fopen(UPTIME, "r");
415 * Get uptime from /proc/uptime and if we can't then just set duration to
416 * be 0 - it will mean that we don't calculate stats.
421 if (fscanf(fp, "%lf", &temp) != 1) {
424 duration = (uint64_t) (temp * 1000);
429 /* The second value passed into the macro is the thing being calculated */
430 CALC_STAT_CNT(read_count, reads_per_second)
431 CALC_STAT_CNT(write_count, writes_per_second)
432 CALC_STAT_CNT(other_count, other_per_second)
433 CALC_STAT_KB(read_bytes, kbytes_read_per_second)
434 CALC_STAT_KB(write_bytes, kbytes_written_per_second)
435 CALC_STAT_PCT(read_time, read_pct_wait)
436 CALC_STAT_PCT(write_time, write_pct_wait)
437 CALC_STAT_PCT(other_time, all_pct_wait)
438 CALC_STAT_CNT(resid_count, resids_per_second)
442 ***************************************************************************
443 * Display statistics for current tape.
446 * @tape Statistics for current tape.
447 * @i Index in array for current tape.
448 ***************************************************************************
450 void tape_write_stats(struct calc_stats *tape, int i)
453 uint64_t divisor = 1;
455 if (DISPLAY_MEGABYTES(flags))
458 sprintf(buffer, "st%i ", i);
460 cprintf_in(IS_STR, "%s", buffer, 0);
461 cprintf_u64(NO_UNIT, 2, 7,
462 tape->reads_per_second,
463 tape->writes_per_second);
464 cprintf_u64(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 2, 11,
465 DISPLAY_UNIT(flags) ? tape->kbytes_read_per_second
466 : tape->kbytes_read_per_second / divisor,
467 DISPLAY_UNIT(flags) ? tape->kbytes_written_per_second
468 : tape->kbytes_written_per_second / divisor);
469 cprintf_xpc(DISPLAY_UNIT(flags), XHIGH, 3, 4, 0,
470 (double) tape->read_pct_wait,
471 (double) tape->write_pct_wait,
472 (double) tape->all_pct_wait);
473 cprintf_u64(NO_UNIT, 2, 7,
474 tape->resids_per_second,
475 tape->other_per_second);
480 ***************************************************************************
481 * Print everything now (stats and uptime).
484 * @rectime Current date and time.
485 ***************************************************************************
487 void write_stats(struct tm *rectime)
489 struct calc_stats tape;
490 struct tape_stats *tmp;
493 TEST_STDOUT(STDOUT_FILENO);
495 /* Print time stamp */
496 if (DISPLAY_TIMESTAMP(flags)) {
497 if (DISPLAY_ISO(flags)) {
498 strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
501 strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
503 printf("%s\n", timestamp);
506 /* Print the headings */
507 tape_write_headings();
510 * If either new or old is invalid or the I/Os per second is 0 and
511 * zero omit is true then we print nothing.
513 if (max_tape_drives > 0) {
516 for (i = 0; i < max_tape_drives; i++) {
517 if ((tape_new_stats[i].valid == TAPE_STATS_VALID) &&
518 (tape_old_stats[i].valid == TAPE_STATS_VALID)) {
519 tape_calc_one_stats(&tape, i);
520 if (!(DISPLAY_ZERO_OMIT(flags)
521 && (tape.other_per_second == 0)
522 && (tape.reads_per_second == 0)
523 && (tape.writes_per_second == 0)
524 && (tape.kbytes_read_per_second == 0)
525 && (tape.kbytes_written_per_second == 0)
526 && (tape.read_pct_wait == 0)
527 && (tape.write_pct_wait == 0)
528 && (tape.all_pct_wait == 0)
529 && (tape.resids_per_second == 0))) {
530 tape_write_stats(&tape, i);
535 * Swap new and old so next time we compare against the new old stats.
536 * If a new tape drive appears it won't appear in the output until after
537 * the second time we gather information about it.
539 tmp = tape_old_stats;
540 tape_old_stats = tape_new_stats;
541 tape_new_stats = tmp;
547 ***************************************************************************
548 * Main loop: Read tape stats from the relevant sources and display them.
551 * @count Number of lines of stats to print.
552 * @rectime Current date and time.
553 ***************************************************************************
555 void rw_tape_stat_loop(long int count, struct tm *rectime)
557 struct tape_stats *tmp;
560 /* Should we skip first report? */
561 if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) {
567 if (tape_new_stats == NULL) {
568 tape_gather_initial_stats();
570 tape_get_updated_stats();
574 get_xtime(rectime, 0, LOCAL_TIME);
576 /* Check whether we should skip first report */
579 write_stats(rectime);
587 tmp = tape_old_stats;
588 tape_old_stats = tape_new_stats;
589 tape_new_stats = tmp;
600 ***************************************************************************
601 * Main entry to the tapestat program.
602 ***************************************************************************
604 int main(int argc, char **argv)
610 struct utsname header;
614 /* Init National Language Support */
618 /* Init color strings */
621 /* Process args... */
624 if (!strcmp(argv[opt], "--human")) {
629 else if (!strncmp(argv[opt], "-", 1)) {
630 for (i = 1; *(argv[opt] + i); i++) {
632 switch (*(argv[opt] + i)) {
635 if (DISPLAY_MEGABYTES(flags)) {
638 /* Display stats in kB/s */
639 flags |= T_D_KILOBYTES;
643 if (DISPLAY_KILOBYTES(flags)) {
646 /* Display stats in MB/s */
647 flags |= T_D_MEGABYTES;
651 /* Display timestamp */
652 flags |= T_D_TIMESTAMP;
656 /* Don't display stats since system restart */
657 flags |= T_D_OMIT_SINCE_BOOT;
661 /* Omit output for devices with no activity */
662 flags |= T_D_ZERO_OMIT;
666 /* Print version number and exit */
678 interval = atol(argv[opt++]);
687 count = atol(argv[opt++]);
688 if ((count < 1) || !interval) {
704 get_xtime(&rectime, 0, LOCAL_TIME);
707 * Don't buffer data if redirected to a pipe.
708 * Note: With musl-c, the behavior of this function is undefined except
709 * when it is the first operation on the stream.
711 setbuf(stdout, NULL);
713 /* Get system name, release number and hostname */
715 if (print_gal_header(&rectime, header.sysname, header.release,
716 header.nodename, header.machine, cpu_nr,
722 /* Set a handler for SIGALRM */
723 memset(&alrm_act, 0, sizeof(alrm_act));
724 alrm_act.sa_handler = alarm_handler;
725 sigaction(SIGALRM, &alrm_act, NULL);
729 rw_tape_stat_loop(count, &rectime);
731 /* Free structures */