/* * cifsiostat: Report I/O statistics for CIFS filesystems. * Copyright (C) 2010 Red Hat, Inc. All Rights Reserved * Written by Ivana Varekova * *************************************************************************** * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation; either version 2 of the License, or (at your * * option) any later version. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * *************************************************************************** */ #include #include #include #include #include #include #include #include "version.h" #include "cifsiostat.h" #include "rd_stats.h" #include "count.h" #ifdef USE_NLS #include #include #define _(string) gettext(string) #else #define _(string) (string) #endif #ifdef USE_SCCSID #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__ char *sccsid(void) { return (SCCSID); } #endif #ifdef TEST void int_handler(int n) { return; } #endif unsigned long long uptime_cs[2] = {0, 0}; struct io_cifs *cifs_list = NULL; int cpu_nr = 0; /* Nb of processors on the machine */ int flags = 0; /* Flag for common options and system state */ int dplaces_nr = -1; /* Number of decimal places */ long interval = 0; char timestamp[TIMESTAMP_LEN]; struct sigaction alrm_act; /* *************************************************************************** * Print usage and exit. * * IN: * @progname Name of sysstat command. *************************************************************************** */ void usage(char *progname) { fprintf(stderr, _("Usage: %s [ options ] [ [ ] ]\n"), progname); #ifdef DEBUG fprintf(stderr, _("Options are:\n" "[ --dec={ 0 | 1 | 2 } ] [ --human ] [ --pretty ]\n" "[ -h ] [ -k | -m ] [ -t ] [ -V ] [ --debuginfo ]\n")); #else fprintf(stderr, _("Options are:\n" "[ --dec={ 0 | 1 | 2 } ] [ --human ] [ --pretty ]\n" "[ -h ] [ -k | -m ] [ -t ] [ -V ]\n")); #endif exit(1); } /* *************************************************************************** * SIGALRM signal handler. * * IN: * @sig Signal number. *************************************************************************** */ void alarm_handler(int sig) { alarm(interval); } /* *************************************************************************** * Set every cifs entry to nonexistent status. * * IN: * @clist Pointer on the start of the linked list. *************************************************************************** */ void set_cifs_nonexistent(struct io_cifs *clist) { while (clist != NULL) { clist->exist = FALSE; clist = clist->next; } } /* *************************************************************************** * Check if a cifs filesystem is present in the list, and add it if requested. * * IN: * @clist Address of pointer on the start of the linked list. * @name cifs name. * * RETURNS: * Pointer on the io_cifs structure in the list where the cifs is located * (whether it was already in the list or if it has been added). * NULL if the cifs name is too long or if the cifs doesn't exist and we * don't want to add it. *************************************************************************** */ struct io_cifs *add_list_cifs(struct io_cifs **clist, char *name) { struct io_cifs *c, *cs; int i; if (strnlen(name, MAX_NAME_LEN) == MAX_NAME_LEN) /* cifs name is too long */ return NULL; while (*clist != NULL) { c = *clist; if ((i = strcmp(c->name, name)) == 0) { /* cifs found in list */ c->exist = TRUE; return c; } if (i > 0) /* * If no group defined and we don't use /proc/diskstats, * insert current device in alphabetical order. * NB: Using /proc/diskstats ("iostat -p ALL") is a bit better than * using alphabetical order because sda10 comes after sda9... */ break; clist = &(c->next); } /* cifs not found */ cs = *clist; /* Add cifs to the list */ if ((*clist = (struct io_cifs *) malloc(IO_CIFS_SIZE)) == NULL) { perror("malloc"); exit(4); } memset(*clist, 0, IO_CIFS_SIZE); c = *clist; for (i = 0; i < 2; i++) { if ((c->cifs_stats[i] = (struct cifs_st *) malloc(CIFS_ST_SIZE)) == NULL) { perror("malloc"); exit(4); } memset(c->cifs_stats[i], 0, CIFS_ST_SIZE); } strncpy(c->name, name, sizeof(c->name)); c->name[sizeof(c->name) - 1] = '\0'; c->exist = TRUE; c->next = cs; return c; } /* *************************************************************************** * Read CIFS-mount directories stats from /proc/fs/cifs/Stats. * * IN: * @curr Index in array for current sample statistics. *************************************************************************** */ void read_cifs_stat(int curr) { FILE *fp; char line[256]; char aux[32]; int start = 0; long long unsigned aux_open; long long unsigned all_open = 0; char cifs_name[MAX_NAME_LEN]; char name_tmp[MAX_NAME_LEN]; struct cifs_st scifs; struct io_cifs *ci; if ((fp = fopen(CIFSSTATS, "r")) == NULL) return; sprintf(aux, "%%*d) %%%ds", MAX_NAME_LEN < 200 ? MAX_NAME_LEN - 1 : 200); memset(&scifs, 0, CIFS_ST_SIZE); while (fgets(line, sizeof(line), fp) != NULL) { /* Read CIFS directory name */ if (isdigit((unsigned char) line[0]) && sscanf(line, aux , name_tmp) == 1) { if (start) { scifs.fopens = all_open; ci = add_list_cifs(&cifs_list, cifs_name); if (ci != NULL) { *ci->cifs_stats[curr] = scifs; } all_open = 0; } else { start = 1; } strncpy(cifs_name, name_tmp, sizeof(cifs_name)); cifs_name[sizeof(cifs_name) - 1] = '\0'; memset(&scifs, 0, CIFS_ST_SIZE); } else { if (!strncmp(line, "Reads:", 6)) { /* * SMB1 format: Reads: %llu Bytes: %llu * SMB2 format: Reads: %llu sent %llu failed * If this is SMB2 format then only the first variable (rd_ops) will be set. */ sscanf(line, "Reads: %llu Bytes: %llu", &scifs.rd_ops, &scifs.rd_bytes); } else if (!strncmp(line, "Bytes read:", 11)) { sscanf(line, "Bytes read: %llu Bytes written: %llu", &scifs.rd_bytes, &scifs.wr_bytes); } else if (!strncmp(line, "Writes:", 7)) { /* * SMB1 format: Writes: %llu Bytes: %llu * SMB2 format: Writes: %llu sent %llu failed * If this is SMB2 format then only the first variable (wr_ops) will be set. */ sscanf(line, "Writes: %llu Bytes: %llu", &scifs.wr_ops, &scifs.wr_bytes); } else if (!strncmp(line, "Opens:", 6)) { sscanf(line, "Opens: %llu Closes:%llu Deletes: %llu", &aux_open, &scifs.fcloses, &scifs.fdeletes); all_open += aux_open; } else if (!strncmp(line, "Posix Opens:", 12)) { sscanf(line, "Posix Opens: %llu", &aux_open); all_open += aux_open; } else if (!strncmp(line, "Open files:", 11)) { sscanf(line, "Open files: %llu total (local), %llu", &all_open, &aux_open); all_open += aux_open; } else if (!strncmp(line, "Closes:", 7)) { sscanf(line, "Closes: %llu", &scifs.fcloses); } } } if (start) { scifs.fopens = all_open; ci = add_list_cifs(&cifs_list, cifs_name); if (ci != NULL) { *ci->cifs_stats[curr] = scifs; } } fclose(fp); } /* *************************************************************************** * Display CIFS stats header. * * OUT: * @fctr Conversion factor. *************************************************************************** */ void write_cifs_stat_header(int *fctr) { if (!DISPLAY_PRETTY(flags)) { printf("Filesystem "); } if (DISPLAY_KILOBYTES(flags)) { printf(" rkB/s wkB/s"); *fctr = 1024; } else if (DISPLAY_MEGABYTES(flags)) { printf(" rMB/s wMB/s"); *fctr = 1024 * 1024; } else { printf(" rB/s wB/s"); *fctr = 1; } printf(" rops/s wops/s fo/s fc/s fd/s"); if (DISPLAY_PRETTY(flags)) { printf(" Filesystem"); } printf("\n"); } /* *************************************************************************** * Write CIFS stats read from /proc/fs/cifs/Stats. * * IN: * @curr Index in array for current sample statistics. * @itv Interval of time (in 1/100th of a second). * @fctr Conversion factor. * @clist Pointer on the linked list where the cifs is saved. * @ioi Current sample statistics. * @ioj Previous sample statistics. *************************************************************************** */ void write_cifs_stat(int curr, unsigned long long itv, int fctr, struct io_cifs *clist, struct cifs_st *ioni, struct cifs_st *ionj) { double rbytes, wbytes; if (!DISPLAY_PRETTY(flags)) { cprintf_in(IS_STR, "%-22s", clist->name, 0); } /* rB/s wB/s fo/s fc/s fd/s*/ rbytes = S_VALUE(ionj->rd_bytes, ioni->rd_bytes, itv); wbytes = S_VALUE(ionj->wr_bytes, ioni->wr_bytes, itv); if (!DISPLAY_UNIT(flags)) { rbytes /= fctr; wbytes /= fctr; } cprintf_f(DISPLAY_UNIT(flags) ? UNIT_BYTE : NO_UNIT, 2, 12, 2, rbytes, wbytes); cprintf_f(NO_UNIT, 2, 9, 2, S_VALUE(ionj->rd_ops, ioni->rd_ops, itv), S_VALUE(ionj->wr_ops, ioni->wr_ops, itv)); cprintf_f(NO_UNIT, 3, 12, 2, S_VALUE(ionj->fopens, ioni->fopens, itv), S_VALUE(ionj->fcloses, ioni->fcloses, itv), S_VALUE(ionj->fdeletes, ioni->fdeletes, itv)); if (DISPLAY_PRETTY(flags)) { cprintf_in(IS_STR, " %s", clist->name, 0); } printf("\n"); } /* *************************************************************************** * Print everything now (stats and uptime). * * IN: * @curr Index in array for current sample statistics. * @rectime Current date and time. *************************************************************************** */ void write_stats(int curr, struct tm *rectime) { int fctr = 1; unsigned long long itv; struct io_cifs *clist; struct cifs_st *ioni, *ionj; /* Test stdout */ TEST_STDOUT(STDOUT_FILENO); /* Print time stamp */ if (DISPLAY_TIMESTAMP(flags)) { if (DISPLAY_ISO(flags)) { strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime); } else { strftime(timestamp, sizeof(timestamp), "%x %X", rectime); } printf("%s\n", timestamp); #ifdef DEBUG if (DISPLAY_DEBUG(flags)) { fprintf(stderr, "%s\n", timestamp); } #endif } /* Interval of time, reduced to one processor */ itv = get_interval(uptime_cs[!curr], uptime_cs[curr]); /* Display CIFS stats header */ write_cifs_stat_header(&fctr); for (clist = cifs_list; clist != NULL; clist = clist->next) { if (!clist->exist) /* Current cifs non existent */ continue; ioni = clist->cifs_stats[curr]; ionj = clist->cifs_stats[!curr]; #ifdef DEBUG if (DISPLAY_DEBUG(flags)) { /* Debug output */ fprintf(stderr, "name=%s itv=%llu fctr=%d ioni{ rd_bytes=%llu " "wr_bytes=%llu rd_ops=%llu wr_ops=%llu fopens=%llu " "fcloses=%llu fdeletes=%llu}\n", clist->name, itv, fctr, ioni->rd_bytes, ioni->wr_bytes, ioni->rd_ops, ioni->wr_ops, ioni->fopens, ioni->fcloses, ioni->fdeletes); } #endif write_cifs_stat(curr, itv, fctr, clist, ioni, ionj); } printf("\n"); } /* *************************************************************************** * Main loop: Read stats from the relevant sources and display them. * * IN: * @count Number of lines of stats to print. * @rectime Current date and time. *************************************************************************** */ void rw_io_stat_loop(long int count, struct tm *rectime) { int curr = 1; /* Set a handler for SIGALRM */ memset(&alrm_act, 0, sizeof(alrm_act)); alrm_act.sa_handler = alarm_handler; sigaction(SIGALRM, &alrm_act, NULL); alarm(interval); do { /* Every device is potentially nonexistent */ set_cifs_nonexistent(cifs_list); /* Read system uptime in 1/100th of a second */ read_uptime(&(uptime_cs[curr])); /* Read CIFS stats */ read_cifs_stat(curr); /* Get time */ get_xtime(rectime, 0, LOCAL_TIME); /* Print results */ write_stats(curr, rectime); if (count > 0) { count--; } if (count) { curr ^= 1; __pause(); } } while (count); } /* *************************************************************************** * Main entry to the cifsiostat program. *************************************************************************** */ int main(int argc, char **argv) { int it = 0; int opt = 1; int i; long count = 1; struct utsname header; struct tm rectime; #ifdef USE_NLS /* Init National Language Support */ init_nls(); #endif /* Init color strings */ init_colors(); /* Process args... */ while (opt < argc) { #ifdef DEBUG if (!strcmp(argv[opt], "--debuginfo")) { flags |= I_D_DEBUG; opt++; } else #endif if (!strcmp(argv[opt], "--human")) { flags |= I_D_UNIT; opt++; } else if (!strcmp(argv[opt], "--pretty")) { /* Display an easy-to-read CIFS report */ flags |= I_D_PRETTY; opt++; } else if (!strncmp(argv[opt], "--dec=", 6) && (strlen(argv[opt]) == 7)) { /* Get number of decimal places */ dplaces_nr = atoi(argv[opt] + 6); if ((dplaces_nr < 0) || (dplaces_nr > 2)) { usage(argv[0]); } opt++; } else if (!strncmp(argv[opt], "-", 1)) { for (i = 1; *(argv[opt] + i); i++) { switch (*(argv[opt] + i)) { case 'h': /* Option -h is equivalent to --pretty --human */ flags |= I_D_PRETTY + I_D_UNIT; break; case 'k': if (DISPLAY_MEGABYTES(flags)) { usage(argv[0]); } /* Display stats in kB/s */ flags |= I_D_KILOBYTES; break; case 'm': if (DISPLAY_KILOBYTES(flags)) { usage(argv[0]); } /* Display stats in MB/s */ flags |= I_D_MEGABYTES; break; case 't': /* Display timestamp */ flags |= I_D_TIMESTAMP; break; case 'V': /* Print version number and exit */ print_version(); break; default: usage(argv[0]); } } opt++; } else if (!it) { interval = atol(argv[opt++]); if (interval < 0) { usage(argv[0]); } count = -1; it = 1; } else if (it > 0) { count = atol(argv[opt++]); if ((count < 1) || !interval) { usage(argv[0]); } it = -1; } else { usage(argv[0]); } } if (!interval) { count = 1; } /* How many processors on this machine? */ cpu_nr = get_cpu_nr(~0, FALSE); get_xtime(&rectime, 0, LOCAL_TIME); /* * Don't buffer data if redirected to a pipe. * Note: With musl-c, the behavior of this function is undefined except * when it is the first operation on the stream. */ setbuf(stdout, NULL); /* Get system name, release number and hostname */ __uname(&header); if (print_gal_header(&rectime, header.sysname, header.release, header.nodename, header.machine, cpu_nr, PLAIN_OUTPUT)) { flags |= I_D_ISO; } printf("\n"); /* Main loop */ rw_io_stat_loop(count, &rectime); return 0; }