]> granicus.if.org Git - sysstat/blob - tapestat.c
Merge branch 'increase_procname_size' of https://github.com/dnarc/sysstat into dnarc...
[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                           "[ -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(2, 7,
450                     tape->reads_per_second,
451                     tape->writes_per_second);
452         cprintf_u64(2, 11,
453                     tape->kbytes_read_per_second / divisor,
454                     tape->kbytes_written_per_second / divisor);
455         cprintf_pc(3, 3, 0,
456                    (double) tape->read_pct_wait,
457                    (double) tape->write_pct_wait,
458                    (double) tape->all_pct_wait);
459         cprintf_u64(2, 7,
460                     tape->resids_per_second,
461                     tape->other_per_second);
462         printf("\n");
463 }
464
465 /*
466  ***************************************************************************
467  * Print everything now (stats and uptime).
468  *
469  * IN:
470  * @rectime     Current date and time.
471  ***************************************************************************
472  */
473 void write_stats(struct tm *rectime)
474 {
475         int i;
476         struct calc_stats tape;
477         struct tape_stats *tmp;
478
479         /* Test stdout */
480         TEST_STDOUT(STDOUT_FILENO);
481
482         /* Print time stamp */
483         if (DISPLAY_TIMESTAMP(flags)) {
484                 if (DISPLAY_ISO(flags)) {
485                         strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
486                 }
487                 else {
488                         strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
489                 }
490                 printf("%s\n", timestamp);
491         }
492
493         /* Print the headings */
494         tape_write_headings();
495
496         /*
497          * If either new or old is invalid or the I/Os per second is 0 and
498          * zero omit is true then we print nothing.
499          */
500         if (max_tape_drives > 0) {
501
502                 for (i = 0; i < max_tape_drives; i++) {
503                         if ((tape_new_stats[i].valid == TAPE_STATS_VALID) &&
504                                 (tape_old_stats[i].valid == TAPE_STATS_VALID)) {
505                                 tape_calc_one_stats(&tape, i);
506                                 if (!(DISPLAY_ZERO_OMIT(flags)
507                                         && (tape.other_per_second == 0)
508                                         && (tape.reads_per_second == 0)
509                                         && (tape.writes_per_second == 0)
510                                         && (tape.kbytes_read_per_second == 0)
511                                         && (tape.kbytes_written_per_second == 0)
512                                         && (tape.read_pct_wait == 0)
513                                         && (tape.write_pct_wait == 0)
514                                         && (tape.all_pct_wait == 0)
515                                         && (tape.resids_per_second == 0))) {
516                                         tape_write_stats(&tape, i);
517                                 }
518                         }
519                 }
520                 /*
521                  * Swap new and old so next time we compare against the new old stats.
522                  * If a new tape drive appears it won't appear in the output until after
523                  * the second time we gather information about it.
524                  */
525                 tmp = tape_old_stats;
526                 tape_old_stats = tape_new_stats;
527                 tape_new_stats = tmp;
528         }
529         printf("\n");
530 }
531
532 /*
533  ***************************************************************************
534  * Main loop: Read tape stats from the relevant sources and display them.
535  *
536  * IN:
537  * @count       Number of lines of stats to print.
538  * @rectime     Current date and time.
539  ***************************************************************************
540  */
541 void rw_tape_stat_loop(long int count, struct tm *rectime)
542 {
543         struct tape_stats *tmp;
544         int skip = 0;
545
546         /* Should we skip first report? */
547         if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) {
548                 skip = 1;
549         }
550
551         /* Don't buffer data if redirected to a pipe */
552         setbuf(stdout, NULL);
553
554         do {
555
556                 if (tape_new_stats == NULL) {
557                         tape_gather_initial_stats();
558                 } else {
559                         tape_get_updated_stats();
560                 }
561
562                 /* Get time */
563                 get_localtime(rectime, 0);
564
565                 /* Check whether we should skip first report */
566                 if (!skip) {
567                         /* Print results */
568                         write_stats(rectime);
569
570                         if (count > 0) {
571                                 count--;
572                         }
573                 }
574                 else {
575                         skip = 0;
576                         tmp = tape_old_stats;
577                         tape_old_stats = tape_new_stats;
578                         tape_new_stats = tmp;
579                 }
580
581                 if (count) {
582                         pause();
583                 }
584         }
585         while (count);
586 }
587
588 /*
589  ***************************************************************************
590  * Main entry to the tapestat program.
591  ***************************************************************************
592  */
593 int main(int argc, char **argv)
594 {
595         int it = 0;
596         int opt = 1;
597         int i;
598         long count = 1;
599         struct utsname header;
600         struct tm rectime;
601
602 #ifdef USE_NLS
603         /* Init National Language Support */
604         init_nls();
605 #endif
606
607         /* Init color strings */
608         init_colors();
609
610         /* Get HZ */
611         get_HZ();
612
613         /* Process args... */
614         while (opt < argc) {
615                         if (!strncmp(argv[opt], "-", 1)) {
616                         for (i = 1; *(argv[opt] + i); i++) {
617
618                                 switch (*(argv[opt] + i)) {
619
620                                 case 'k':
621                                         if (DISPLAY_MEGABYTES(flags)) {
622                                                 usage(argv[0]);
623                                         }
624                                         /* Display stats in kB/s */
625                                         flags |= T_D_KILOBYTES;
626                                         break;
627
628                                 case 'm':
629                                         if (DISPLAY_KILOBYTES(flags)) {
630                                                 usage(argv[0]);
631                                         }
632                                         /* Display stats in MB/s */
633                                         flags |= T_D_MEGABYTES;
634                                         break;
635
636                                 case 't':
637                                         /* Display timestamp */
638                                         flags |= T_D_TIMESTAMP;
639                                         break;
640
641                                 case 'y':
642                                         /* Don't display stats since system restart */
643                                         flags |= T_D_OMIT_SINCE_BOOT;
644                                         break;
645
646                                 case 'z':
647                                         /* Omit output for devices with no activity */
648                                         flags |= T_D_ZERO_OMIT;
649                                         break;
650
651                                 case 'V':
652                                         /* Print version number and exit */
653                                         print_version();
654                                         break;
655
656                                 default:
657                                         usage(argv[0]);
658                                 }
659                         }
660                         opt++;
661                 }
662
663                 else if (!it) {
664                         interval = atol(argv[opt++]);
665                         if (interval < 0) {
666                                 usage(argv[0]);
667                         }
668                         count = -1;
669                         it = 1;
670                 }
671
672                 else if (it > 0) {
673                         count = atol(argv[opt++]);
674                         if ((count < 1) || !interval) {
675                                 usage(argv[0]);
676                         }
677                         it = -1;
678                 }
679                 else {
680                         usage(argv[0]);
681                 }
682         }
683
684         if (!interval) {
685                 count = 1;
686         }
687
688         tape_initialise();
689
690         get_localtime(&rectime, 0);
691
692         /* Get system name, release number and hostname */
693         uname(&header);
694         if (print_gal_header(&rectime, header.sysname, header.release,
695                              header.nodename, header.machine, cpu_nr,
696                              PLAIN_OUTPUT)) {
697                 flags |= T_D_ISO;
698         }
699         printf("\n");
700
701         /* Set a handler for SIGALRM */
702         memset(&alrm_act, 0, sizeof(alrm_act));
703         alrm_act.sa_handler = alarm_handler;
704         sigaction(SIGALRM, &alrm_act, NULL);
705         alarm(interval);
706
707         /* Main loop */
708         rw_tape_stat_loop(count, &rectime);
709
710         /* Free structures */
711         tape_uninitialise();
712
713         return 0;
714 }