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 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
22 ***************************************************************************
31 #define __DO_NOT_DEFINE_COMPILE
38 #include <sys/utsname.h>
39 #include <sys/param.h>
40 #undef HZ /* sys/param.h defines HZ but needed for MAXPATHLEN */
50 #define _(string) gettext(string)
52 #define _(string) (string)
55 #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
56 char *sccsid(void) { return (SCCSID); }
58 int cpu_nr = 0; /* Nb of processors on the machine */
59 int flags = 0; /* Flag for common options and system state */
64 struct sigaction alrm_act;
67 * For tape stats - it would be extremely rare for there to be a very large
68 * number of tape drives attached to a system. I wouldn't expect to see more
69 * than 20-30 in a very large configuration and discontinguous ones should
70 * be even more rare. Because of this we keep the old and new data in a
71 * simple data structure with the tape index being the number after the tape
72 * drive, st0 at index 0, etc.
74 int max_tape_drives = 0;
75 struct tape_stats *tape_new_stats = { NULL };
76 struct tape_stats *tape_old_stats = { NULL };
80 ***************************************************************************
81 * Print usage and exit.
84 * @progname Name of sysstat command.
85 ***************************************************************************
87 void usage(char *progname)
89 fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
91 fprintf(stderr, _("Options are:\n"
92 "[ -k | -m ] [ -t ] [ -V ] [ -y ] [ -z ]\n"));
97 ***************************************************************************
98 * SIGALRM signal handler. No need to reset the handler here.
101 * @sig Signal number.
102 ***************************************************************************
104 void alarm_handler(int sig)
110 ***************************************************************************
112 ***************************************************************************
114 void tape_initialise(void)
116 /* How many processors on this machine? */
117 cpu_nr = get_cpu_nr(~0, FALSE);
119 /* Compile regular expression for tape names */
120 if (regcomp(&tape_reg, "^st[0-9]+$", REG_EXTENDED) != 0) {
126 ***************************************************************************
128 ***************************************************************************
130 void tape_uninitialise(void)
133 if (tape_old_stats != NULL) {
134 free(tape_old_stats);
136 if (tape_new_stats != NULL) {
137 free(tape_new_stats);
142 ***************************************************************************
143 * Get maximum number of tapes in the system.
146 * Number of tapes found.
147 ***************************************************************************
149 int get_max_tape_drives(void)
152 struct dirent *entry;
153 int new_max_tape_drives, tmp, num_stats_dir = 0;
155 char stats_dir[MAXPATHLEN + 1];
156 struct stat stat_buf;
158 new_max_tape_drives = max_tape_drives;
160 /* Open sysfs tree */
161 dir = opendir(SYSFS_CLASS_TAPE_DIR);
165 while ((entry = readdir(dir)) != NULL) {
166 if (regexec(&tape_reg, &entry->d_name[0], 1, &match, 0) == 0) {
167 /* d_name[2] to skip the st at the front */
168 tmp = atoi(&entry->d_name[2]) + 1;
169 if (tmp > new_max_tape_drives) {
170 new_max_tape_drives = tmp;
173 snprintf(stats_dir, MAXPATHLEN, "%s/%s/%s",
174 SYSFS_CLASS_TAPE_DIR, &entry->d_name[0], "stats");
175 if (stat(stats_dir, &stat_buf) == 0) {
176 if (S_ISDIR(stat_buf.st_mode)) {
183 /* If there are no stats directories make the new number of tape drives 0 */
184 if (num_stats_dir == 0) {
185 new_max_tape_drives = 0;
188 return new_max_tape_drives;
192 ***************************************************************************
193 * Check if new tapes have been added and reallocate structures accordingly.
194 ***************************************************************************
196 void tape_check_tapes_and_realloc(void)
198 int new_max_tape_drives, i;
200 /* Count again number of tapes */
201 new_max_tape_drives = get_max_tape_drives();
203 if ((new_max_tape_drives == 0) && (max_tape_drives == 0))
204 /* If there are no tape drives don't change anything */
207 if (new_max_tape_drives > max_tape_drives) {
208 /* New tapes found: Realloc structures */
209 tape_old_stats = (struct tape_stats *)
210 realloc(tape_old_stats, sizeof(struct tape_stats) * new_max_tape_drives);
211 tape_new_stats=(struct tape_stats *)
212 realloc(tape_new_stats, sizeof(struct tape_stats) * new_max_tape_drives);
213 if ((tape_old_stats == NULL) || (tape_new_stats == NULL)) {
214 if (tape_old_stats != NULL) {
215 free(tape_old_stats);
216 tape_old_stats = NULL;
218 if (tape_new_stats != NULL) {
219 free(tape_new_stats);
220 tape_new_stats = NULL;
225 for (i = max_tape_drives; i < new_max_tape_drives; i++) {
226 tape_old_stats[i].valid = TAPE_STATS_INVALID;
227 tape_new_stats[i].valid = TAPE_STATS_INVALID;
229 max_tape_drives = new_max_tape_drives;
235 ***************************************************************************
236 * Collect initial statistics for all existing tapes in the system.
237 * This function should be called only once.
238 ***************************************************************************
240 void tape_gather_initial_stats(void)
242 int new_max_tape_drives, i;
244 char filename[MAXPATHLEN + 1];
246 /* Get number of tapes in the system */
247 new_max_tape_drives = get_max_tape_drives();
249 if (new_max_tape_drives == 0) {
251 fprintf(stderr, _("No tape drives with statistics found\n"));
255 /* Allocate structures */
256 if (tape_old_stats == NULL) {
257 tape_old_stats = (struct tape_stats *)
258 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
259 tape_new_stats = (struct tape_stats *)
260 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
261 for (i = 0; i < new_max_tape_drives; i++) {
262 tape_old_stats[i].valid = TAPE_STATS_INVALID;
263 tape_new_stats[i].valid = TAPE_STATS_INVALID;
265 max_tape_drives = new_max_tape_drives;
267 /* This should only be called once */
271 /* Read stats for each tape */
272 for (i = 0; i < max_tape_drives; i++) {
274 * Everything starts out valid but failing to open
275 * a file gets the tape drive marked invalid.
277 tape_new_stats[i].valid = TAPE_STATS_VALID;
278 tape_old_stats[i].valid = TAPE_STATS_VALID;
280 gettimeofday(&tape_old_stats[i].tv, NULL);
282 tape_new_stats[i].tv.tv_sec = tape_old_stats[i].tv.tv_sec;
283 tape_new_stats[i].tv.tv_usec = tape_old_stats[i].tv.tv_usec;
285 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
286 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
287 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
288 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
289 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
290 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
291 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
292 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
293 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
295 tape_old_stats[i].read_time = 0;
296 tape_old_stats[i].write_time = 0;
297 tape_old_stats[i].other_time = 0;
298 tape_old_stats[i].read_bytes = 0;
299 tape_old_stats[i].write_bytes = 0;
300 tape_old_stats[i].read_count = 0;
301 tape_old_stats[i].write_count = 0;
302 tape_old_stats[i].other_count = 0;
303 tape_old_stats[i].resid_count = 0;
308 ***************************************************************************
309 * Collect a new sample of statistics for all existing tapes in the system.
310 ***************************************************************************
312 void tape_get_updated_stats(void)
316 char filename[MAXPATHLEN + 1] = { 0 };
318 /* Check tapes and realloc structures if needed */
319 tape_check_tapes_and_realloc();
321 for (i = 0; i < max_tape_drives; i++) {
323 * Everything starts out valid but failing
324 * to open a file gets the tape drive marked invalid.
326 tape_new_stats[i].valid = TAPE_STATS_VALID;
327 gettimeofday(&tape_new_stats[i].tv, NULL);
329 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
330 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
331 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
332 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
333 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
334 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
335 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
336 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
337 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
339 if ((tape_new_stats[i].read_time < tape_old_stats[i].read_time) ||
340 (tape_new_stats[i].write_time < tape_old_stats[i].write_time) ||
341 (tape_new_stats[i].other_time < tape_old_stats[i].other_time)) {
342 tape_new_stats[i].valid = TAPE_STATS_INVALID;
348 ***************************************************************************
349 * Display tapes statistics headings.
350 ***************************************************************************
352 void tape_write_headings(void)
354 printf("Tape: r/s w/s ");
355 if (DISPLAY_MEGABYTES(flags)) {
356 printf("MB_read/s MB_wrtn/s");
358 printf("kB_read/s kB_wrtn/s");
360 printf(" %%Rd %%Wr %%Oa Rs/s Ot/s\n");
364 ***************************************************************************
365 * Calculate statistics for current tape.
368 * @i Index in array for current tape.
371 * @stats Statistics for current tape.
372 ***************************************************************************
374 void tape_calc_one_stats(struct calc_stats *stats, int i)
380 /* Duration in ms done in ms to prevent rounding issues with using seconds */
381 duration = (tape_new_stats[i].tv.tv_sec -
382 tape_old_stats[i].tv.tv_sec) * 1000;
383 duration -= tape_old_stats[i].tv.tv_usec / 1000;
384 duration += tape_new_stats[i].tv.tv_usec / 1000;
386 /* If duration is zero we need to calculate the ms since boot time */
388 fp = fopen("/proc/uptime", "r");
391 * Get uptime from /proc/uptime and if we can't then just set duration to
392 * be 0 - it will mean that we don't calculate stats.
397 if (fscanf(fp, "%lf", &temp) != 1) {
400 duration = (uint64_t) (temp * 1000);
405 /* The second value passed into the macro is the thing being calculated */
406 CALC_STAT_CNT(read_count, reads_per_second)
407 CALC_STAT_CNT(write_count, writes_per_second)
408 CALC_STAT_CNT(other_count, other_per_second)
409 CALC_STAT_KB(read_bytes, kbytes_read_per_second)
410 CALC_STAT_KB(write_bytes, kbytes_written_per_second)
411 CALC_STAT_PCT(read_time, read_pct_wait)
412 CALC_STAT_PCT(write_time, write_pct_wait)
413 CALC_STAT_PCT(other_time, all_pct_wait)
414 CALC_STAT_CNT(resid_count, resids_per_second)
418 ***************************************************************************
419 * Display statistics for current tape.
422 * @tape Statistics for current tape.
423 * @i Index in array for current tape.
424 ***************************************************************************
426 void tape_write_stats(struct calc_stats *tape, int i)
429 uint64_t divisor = 1;
431 if (DISPLAY_MEGABYTES(flags))
434 sprintf(buffer, "st%i ", i);
436 cprintf_in(IS_STR, "%s", buffer, 0);
438 tape->reads_per_second,
439 tape->writes_per_second);
441 tape->kbytes_read_per_second / divisor,
442 tape->kbytes_written_per_second / divisor);
444 (double) tape->read_pct_wait,
445 (double) tape->write_pct_wait,
446 (double) tape->all_pct_wait);
448 tape->resids_per_second,
449 tape->other_per_second);
454 ***************************************************************************
455 * Print everything now (stats and uptime).
458 * @rectime Current date and time.
459 ***************************************************************************
461 void write_stats(struct tm *rectime)
464 struct calc_stats tape;
465 struct tape_stats *tmp;
468 TEST_STDOUT(STDOUT_FILENO);
470 /* Print time stamp */
471 if (DISPLAY_TIMESTAMP(flags)) {
472 if (DISPLAY_ISO(flags)) {
473 strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
476 strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
478 printf("%s\n", timestamp);
481 /* Print the headings */
482 tape_write_headings();
485 * If either new or old is invalid or the I/Os per second is 0 and
486 * zero omit is true then we print nothing.
488 if (max_tape_drives > 0) {
490 for (i = 0; i < max_tape_drives; i++) {
491 if ((tape_new_stats[i].valid == TAPE_STATS_VALID) &&
492 (tape_old_stats[i].valid == TAPE_STATS_VALID)) {
493 tape_calc_one_stats(&tape, i);
494 if (!(DISPLAY_ZERO_OMIT(flags)
495 && (tape.other_per_second == 0)
496 && (tape.reads_per_second == 0)
497 && (tape.writes_per_second == 0)
498 && (tape.kbytes_read_per_second == 0)
499 && (tape.kbytes_written_per_second == 0)
500 && (tape.read_pct_wait == 0)
501 && (tape.write_pct_wait == 0)
502 && (tape.all_pct_wait == 0)
503 && (tape.resids_per_second == 0))) {
504 tape_write_stats(&tape, i);
509 * Swap new and old so next time we compare against the new old stats.
510 * If a new tape drive appears it won't appear in the output until after
511 * the second time we gather information about it.
513 tmp = tape_old_stats;
514 tape_old_stats = tape_new_stats;
515 tape_new_stats = tmp;
521 ***************************************************************************
522 * Main loop: Read tape stats from the relevant sources and display them.
525 * @count Number of lines of stats to print.
526 * @rectime Current date and time.
527 ***************************************************************************
529 void rw_tape_stat_loop(long int count, struct tm *rectime)
531 struct tape_stats *tmp;
534 /* Should we skip first report? */
535 if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) {
539 /* Don't buffer data if redirected to a pipe */
540 setbuf(stdout, NULL);
544 if (tape_new_stats == NULL) {
545 tape_gather_initial_stats();
547 tape_get_updated_stats();
551 get_localtime(rectime, 0);
553 /* Check whether we should skip first report */
556 write_stats(rectime);
564 tmp = tape_old_stats;
565 tape_old_stats = tape_new_stats;
566 tape_new_stats = tmp;
577 ***************************************************************************
578 * Main entry to the tapestat program.
579 ***************************************************************************
581 int main(int argc, char **argv)
587 struct utsname header;
591 /* Init National Language Support */
595 /* Init color strings */
601 /* Process args... */
603 if (!strncmp(argv[opt], "-", 1)) {
604 for (i = 1; *(argv[opt] + i); i++) {
606 switch (*(argv[opt] + i)) {
609 if (DISPLAY_MEGABYTES(flags)) {
612 /* Display stats in kB/s */
613 flags |= T_D_KILOBYTES;
617 if (DISPLAY_KILOBYTES(flags)) {
620 /* Display stats in MB/s */
621 flags |= T_D_MEGABYTES;
625 /* Display timestamp */
626 flags |= T_D_TIMESTAMP;
630 /* Don't display stats since system restart */
631 flags |= T_D_OMIT_SINCE_BOOT;
635 /* Omit output for devices with no activity */
636 flags |= T_D_ZERO_OMIT;
640 /* Print version number and exit */
652 interval = atol(argv[opt++]);
661 count = atol(argv[opt++]);
662 if ((count < 1) || !interval) {
678 get_localtime(&rectime, 0);
680 /* Get system name, release number and hostname */
682 if (print_gal_header(&rectime, header.sysname, header.release,
683 header.nodename, header.machine, cpu_nr)) {
688 /* Set a handler for SIGALRM */
689 memset(&alrm_act, 0, sizeof(alrm_act));
690 alrm_act.sa_handler = alarm_handler;
691 sigaction(SIGALRM, &alrm_act, NULL);
695 rw_tape_stat_loop(count, &rectime);
697 /* Free structures */