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