/* * Copyright (c) 1999-2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1997 Apple Computer, Inc. All Rights Reserved * * Copyright (c) 1983, 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * @(#)nfsstat.c 8.2 (Berkeley) 3/31/95 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SHOW_SERVER 0x01 #define SHOW_CLIENT 0x02 #define SHOW_ALL (SHOW_SERVER | SHOW_CLIENT) #define CLIENT_SERVER_MODE 0 #define EXPORTS_MODE 1 #define ACTIVE_USER_MODE 2 #define NUMERIC_NET 1 #define NUMERIC_USER 2 #define DEFAULT_EXPORT_STATS_BUFLEN 32768 #define DEFAULT_ACTIVE_USER_STATS_BUFLEN 131072 LIST_HEAD(nfs_active_user_node_head, nfs_active_user_node); LIST_HEAD(nfs_export_node_head, nfs_export_node); struct nfs_active_user_node { LIST_ENTRY(nfs_active_user_node) user_next; struct nfs_user_stat_user_rec *rec; }; struct nfs_export_node { LIST_ENTRY(nfs_export_node) export_next; struct nfs_active_user_node_head nodes; struct nfs_user_stat_path_rec *rec; }; struct nfs_active_user_list { char *buf; struct nfs_export_node_head export_list; }; void intpr(u_int); void printhdr(void); void sidewaysintpr(u_int, u_int); void usage(void); void do_exports_interval(u_int interval); void do_exports_normal(void); void do_active_users_interval(u_int interval, u_int flags); void do_active_users_normal(u_int flags); int read_export_stats(char **buf, uint *buflen); int read_active_user_stats(char **buf, uint *buflen); void display_export_diffs(char *newb, char *oldb); void display_active_user_diffs(char *newb, char *oldb, u_int flags); void displayActiveUserRec(struct nfs_user_stat_user_rec *rec, u_int flags); struct nfs_export_stat_rec *findExport(char *path, char *buf); struct nfs_user_stat_user_rec *findActiveUser(char *path, struct nfs_user_stat_user_rec *rec, char *buf); int cmp_active_user(struct nfs_user_stat_user_rec *rec1, struct nfs_user_stat_user_rec *rec2); uint64_t serialdiff_64(uint64_t new, uint64_t old); struct nfs_export_node_head *get_sorted_active_user_list(char *buf); void free_nfs_export_list(struct nfs_export_node_head *export_list); void catchalarm(int); int main(int argc, char *argv[]) { extern int optind; extern char *optarg; u_int interval; u_int display = SHOW_ALL; u_int usermode_flags = 0; u_int mode = CLIENT_SERVER_MODE; int ch; interval = 0; while ((ch = getopt(argc, argv, "w:sceun:")) != EOF) switch(ch) { case 'w': interval = atoi(optarg); break; case 's': mode = CLIENT_SERVER_MODE; display = SHOW_SERVER; break; case 'c': mode = CLIENT_SERVER_MODE; display = SHOW_CLIENT; break; case 'e': mode = EXPORTS_MODE; break; case 'u': mode = ACTIVE_USER_MODE; break; case 'n': if (!strcmp(optarg, "net")) usermode_flags |= NUMERIC_NET; else if (!strcmp(optarg, "user")) usermode_flags |= NUMERIC_USER; else printf("unsupported 'n' argument\n"); break; case '?': default: usage(); } argc -= optind; argv += optind; /* Determine the mode and display the stats */ if (mode == EXPORTS_MODE) { if (interval) do_exports_interval(interval); else do_exports_normal(); } else if (mode == ACTIVE_USER_MODE) { if (interval) do_active_users_interval(interval, usermode_flags); else do_active_users_normal(usermode_flags); } else { if (interval) sidewaysintpr(interval, display); else intpr(display); } exit(0); } /* * Read the nfs stats using sysctl(3) */ void readstats(struct nfsstats *stp) { int name[3]; size_t buflen = sizeof(*stp); struct vfsconf vfc; if (getvfsbyname("nfs", &vfc) < 0) err(1, "getvfsbyname: NFS not compiled into kernel"); name[0] = CTL_VFS; name[1] = vfc.vfc_typenum; name[2] = NFS_NFSSTATS; if (sysctl(name, 3, stp, &buflen, NULL, 0) < 0) err(1, "sysctl"); } /* * Read the nfs export stats from the kernel. * * If a valid buffer (non-NULL) is passed in parameter buf, then * the parameter buflen must also be valid. In this case an attempt will * be made to read the export stat records from the kernel using the passed buffer. * However if the passed buffer is of insufficient size to hold all available * export stat records, then the passed buffer will be freed and a new one * will be allocated. Thus the returned buffer may not be the same as * the buffer originally passed in the function call. * * If a NULL buffer is passed in parameter buf, a new buffer will * be allocated. * * Returns: * 0 if successful, valid buffer containing export stat records returned in buf, and buflen is valid. * 1 if not enough memory was available. NULL buffer returned in buf, buflen is undefined. */ int read_export_stats(char **buf, uint *buflen) { struct nfs_export_stat_desc *hdr; int name[3]; uint statlen; struct vfsconf vfc; /* check if we need to allocate a buffer */ if (*buf == NULL) { *buflen = DEFAULT_EXPORT_STATS_BUFLEN; *buf = malloc(*buflen); if (*buf == NULL) { warnx("No memory for reading export stat data"); return 1; } } if (getvfsbyname("nfs", &vfc) < 0) { free(*buf); err(1, "getvfsbyname: NFS not compiled into kernel"); } name[0] = CTL_VFS; name[1] = vfc.vfc_typenum; name[2] = NFS_EXPORTSTATS; /* fetch the export stats */ statlen = *buflen; if (sysctl(name, 3, *buf, (size_t *)&statlen, NULL, 0) < 0) { /* sysctl failed */ free(*buf); err(1, "sysctl failed"); } /* check if buffer was large enough */ if (statlen > *buflen) { /* Didn't get all the stat records, try again with a larger buffer */ /* Alloc a larger buffer than the sysctl indicated, in case more exports */ /* get added before we make the next sysctl */ free(*buf); *buflen = statlen + (4 * sizeof(struct nfs_export_stat_rec)); *buf = malloc(*buflen); if (*buf == NULL) { warnx("No mem for reading export statistics"); return 1; } /* fetch export stats one more time */ statlen = *buflen; if (sysctl(name, 3, *buf, (size_t *)&statlen, NULL, 0) < 0) { free(*buf); err(1, "sysctl failed"); } } /* Check export stat record version */ hdr = (struct nfs_export_stat_desc *)*buf; if (hdr->rec_vers != NFS_EXPORT_STAT_REC_VERSION) { free(*buf); errx(1, "NFS export stat version mismatch"); } return 0; } /* * Read nfs active user stats from the kernel. * * If a valid buffer (non-NULL) is passed in parameter buf, then * the parameter buflen must also be valid. In this case an attempt will * be made to read the user stat records from the kernel using the passed buffer. * However if the passed buffer is of insufficient size to hold all available * user stat records, then the passed buffer will be freed and a new one * will be allocated. Thus the returned buffer may not be the same as * the buffer originally passed in the function call. * * If a NULL buffer is passed in parameter buf, a new buffer will * be allocated. * * Returns: * 0 if successful, valid buffer containing export active user stat records returned in buf, and buflen is valid. * 1 if not enough memory was available. NULL buffer returned in buf, buflen is undefined. */ int read_active_user_stats(char **buf, uint *buflen) { struct nfs_user_stat_desc *hdr; int name[3]; uint statlen; struct vfsconf vfc; /* check if we need to allocate a buffer */ if (*buf == NULL) { *buflen = DEFAULT_ACTIVE_USER_STATS_BUFLEN; *buf = malloc(*buflen); if (*buf == NULL) { warnx("Non mem for reading active user statistics"); return 1; } } if (getvfsbyname("nfs", &vfc) < 0) { free(*buf); err(1, "getvfsbyname: NFS not compiled into kernel"); } name[0] = CTL_VFS; name[1] = vfc.vfc_typenum; name[2] = NFS_USERSTATS; /* fetch the user stats */ statlen = *buflen; if (sysctl(name, 3, *buf, (size_t *)&statlen, NULL, 0) < 0) { /* sysctl failed */ free(*buf); err(1, "sysctl failed"); } /* check if buffer was large enough */ if (statlen > *buflen) { /* Didn't get all the stat records, try again with a larger buffer. */ free(*buf); /* Allocate a little extra than indicated in case more exports and/or users */ /* show up before the next sysctl */ *buflen = statlen + sizeof(struct nfs_user_stat_path_rec); *buflen += (4 * sizeof(struct nfs_user_stat_user_rec)); *buf = malloc(*buflen); if (*buf == NULL) { warnx("No mem for reading active user statistics\n"); return 1; } /* fetch user stats one more time */ statlen = *buflen; if (sysctl(name, 3, *buf, (size_t *)&statlen, NULL, 0) < 0) { free(*buf); err(1, "sysctl failed"); } } /* Check record version */ hdr = (struct nfs_user_stat_desc *)*buf; if (hdr->rec_vers != NFS_USER_STAT_REC_VERSION) { free(*buf); errx(1, "NFS user stat version mismatch"); } return 0; } /* * Print a description of the nfs stats. */ void intpr(u_int display) { struct nfsstats nfsstats; readstats(&nfsstats); if (display & SHOW_CLIENT) { printf("Client Info:\n"); printf("RPC Counts:\n"); printf("%9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s\n", "Getattr", "Setattr", "Lookup", "Readlink", "Read", "Write", "Create", "Remove"); printf("%9d %9d %9d %9d %9d %9d %9d %9d\n", nfsstats.rpccnt[NFSPROC_GETATTR], nfsstats.rpccnt[NFSPROC_SETATTR], nfsstats.rpccnt[NFSPROC_LOOKUP], nfsstats.rpccnt[NFSPROC_READLINK], nfsstats.rpccnt[NFSPROC_READ], nfsstats.rpccnt[NFSPROC_WRITE], nfsstats.rpccnt[NFSPROC_CREATE], nfsstats.rpccnt[NFSPROC_REMOVE]); printf("%9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s\n", "Rename", "Link", "Symlink", "Mkdir", "Rmdir", "Readdir", "RdirPlus", "Access"); printf("%9d %9d %9d %9d %9d %9d %9d %9d\n", nfsstats.rpccnt[NFSPROC_RENAME], nfsstats.rpccnt[NFSPROC_LINK], nfsstats.rpccnt[NFSPROC_SYMLINK], nfsstats.rpccnt[NFSPROC_MKDIR], nfsstats.rpccnt[NFSPROC_RMDIR], nfsstats.rpccnt[NFSPROC_READDIR], nfsstats.rpccnt[NFSPROC_READDIRPLUS], nfsstats.rpccnt[NFSPROC_ACCESS]); printf("%9.9s %9.9s %9.9s %9.9s %9.9s\n", "Mknod", "Fsstat", "Fsinfo", "PathConf", "Commit"); printf("%9d %9d %9d %9d %9d\n", nfsstats.rpccnt[NFSPROC_MKNOD], nfsstats.rpccnt[NFSPROC_FSSTAT], nfsstats.rpccnt[NFSPROC_FSINFO], nfsstats.rpccnt[NFSPROC_PATHCONF], nfsstats.rpccnt[NFSPROC_COMMIT]); printf("RPC Info:\n"); printf("%9.9s %9.9s %9.9s %9.9s %9.9s\n", "TimedOut", "Invalid", "X Replies", "Retries", "Requests"); printf("%9d %9d %9d %9d %9d\n", nfsstats.rpctimeouts, nfsstats.rpcinvalid, nfsstats.rpcunexpected, nfsstats.rpcretries, nfsstats.rpcrequests); printf("Cache Info:\n"); printf("%9.9s %9.9s %9.9s %9.9s", "Attr Hits", "Misses", "Lkup Hits", "Misses"); printf(" %9.9s %9.9s %9.9s %9.9s\n", "BioR Hits", "Misses", "BioW Hits", "Misses"); printf("%9d %9d %9d %9d", nfsstats.attrcache_hits, nfsstats.attrcache_misses, nfsstats.lookupcache_hits, nfsstats.lookupcache_misses); printf(" %9d %9d %9d %9d\n", nfsstats.biocache_reads-nfsstats.read_bios, nfsstats.read_bios, nfsstats.biocache_writes-nfsstats.write_bios, nfsstats.write_bios); printf("%9.9s %9.9s %9.9s %9.9s", "BioRLHits", "Misses", "BioD Hits", "Misses"); printf(" %9.9s %9.9s\n", "DirE Hits", "Misses"); printf("%9d %9d %9d %9d", nfsstats.biocache_readlinks-nfsstats.readlink_bios, nfsstats.readlink_bios, nfsstats.biocache_readdirs-nfsstats.readdir_bios, nfsstats.readdir_bios); printf(" %9d %9d\n", nfsstats.direofcache_hits, nfsstats.direofcache_misses); } if (display & SHOW_SERVER) { printf("\nServer Info:\n"); printf("RPC Counts:\n"); printf("%9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s\n", "Getattr", "Setattr", "Lookup", "Readlink", "Read", "Write", "Create", "Remove"); printf("%9d %9d %9d %9d %9d %9d %9d %9d\n", nfsstats.srvrpccnt[NFSPROC_GETATTR], nfsstats.srvrpccnt[NFSPROC_SETATTR], nfsstats.srvrpccnt[NFSPROC_LOOKUP], nfsstats.srvrpccnt[NFSPROC_READLINK], nfsstats.srvrpccnt[NFSPROC_READ], nfsstats.srvrpccnt[NFSPROC_WRITE], nfsstats.srvrpccnt[NFSPROC_CREATE], nfsstats.srvrpccnt[NFSPROC_REMOVE]); printf("%9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s\n", "Rename", "Link", "Symlink", "Mkdir", "Rmdir", "Readdir", "RdirPlus", "Access"); printf("%9d %9d %9d %9d %9d %9d %9d %9d\n", nfsstats.srvrpccnt[NFSPROC_RENAME], nfsstats.srvrpccnt[NFSPROC_LINK], nfsstats.srvrpccnt[NFSPROC_SYMLINK], nfsstats.srvrpccnt[NFSPROC_MKDIR], nfsstats.srvrpccnt[NFSPROC_RMDIR], nfsstats.srvrpccnt[NFSPROC_READDIR], nfsstats.srvrpccnt[NFSPROC_READDIRPLUS], nfsstats.srvrpccnt[NFSPROC_ACCESS]); printf("%9.9s %9.9s %9.9s %9.9s %9.9s\n", "Mknod", "Fsstat", "Fsinfo", "PathConf", "Commit"); printf("%9d %9d %9d %9d %9d\n", nfsstats.srvrpccnt[NFSPROC_MKNOD], nfsstats.srvrpccnt[NFSPROC_FSSTAT], nfsstats.srvrpccnt[NFSPROC_FSINFO], nfsstats.srvrpccnt[NFSPROC_PATHCONF], nfsstats.srvrpccnt[NFSPROC_COMMIT]); printf("Server Ret-Failed\n"); printf("%17d\n", nfsstats.srvrpc_errs); printf("Server Faults\n"); printf("%13d\n", nfsstats.srv_errs); printf("Server Cache Stats:\n"); printf("%9.9s %9.9s %9.9s %9.9s\n", "Inprog", "Idem", "Non-idem", "Misses"); printf("%9d %9d %9d %9d\n", nfsstats.srvcache_inproghits, nfsstats.srvcache_idemdonehits, nfsstats.srvcache_nonidemdonehits, nfsstats.srvcache_misses); printf("Server Write Gathering:\n"); printf("%9.9s %9.9s %9.9s\n", "WriteOps", "WriteRPC", "Opsaved"); printf("%9d %9d %9d\n", nfsstats.srvvop_writes, nfsstats.srvrpccnt[NFSPROC_WRITE], nfsstats.srvrpccnt[NFSPROC_WRITE] - nfsstats.srvvop_writes); } } u_char signalled; /* set if alarm goes off "early" */ /* * Print a running summary of nfs statistics. * Repeat display every interval seconds, showing statistics * collected over that interval. Assumes that interval is non-zero. * First line printed at top of screen is always cumulative. */ void sidewaysintpr(u_int interval, u_int display) { struct nfsstats nfsstats, lastst; int hdrcnt; sigset_t sigset, oldsigset; signal(SIGALRM, catchalarm); signalled = 0; alarm(interval); bzero((caddr_t)&lastst, sizeof(lastst)); for (hdrcnt = 1;;) { if (!--hdrcnt) { printhdr(); hdrcnt = 20; } readstats(&nfsstats); if (display & SHOW_CLIENT) printf("Client: %8d %8d %8d %8d %8d %8d %8d %8d\n", nfsstats.rpccnt[NFSPROC_GETATTR]-lastst.rpccnt[NFSPROC_GETATTR], nfsstats.rpccnt[NFSPROC_LOOKUP]-lastst.rpccnt[NFSPROC_LOOKUP], nfsstats.rpccnt[NFSPROC_READLINK]-lastst.rpccnt[NFSPROC_READLINK], nfsstats.rpccnt[NFSPROC_READ]-lastst.rpccnt[NFSPROC_READ], nfsstats.rpccnt[NFSPROC_WRITE]-lastst.rpccnt[NFSPROC_WRITE], nfsstats.rpccnt[NFSPROC_RENAME]-lastst.rpccnt[NFSPROC_RENAME], nfsstats.rpccnt[NFSPROC_ACCESS]-lastst.rpccnt[NFSPROC_ACCESS], (nfsstats.rpccnt[NFSPROC_READDIR]-lastst.rpccnt[NFSPROC_READDIR]) +(nfsstats.rpccnt[NFSPROC_READDIRPLUS]-lastst.rpccnt[NFSPROC_READDIRPLUS])); if (display & SHOW_SERVER) printf("Server: %8d %8d %8d %8d %8d %8d %8d %8d\n", nfsstats.srvrpccnt[NFSPROC_GETATTR]-lastst.srvrpccnt[NFSPROC_GETATTR], nfsstats.srvrpccnt[NFSPROC_LOOKUP]-lastst.srvrpccnt[NFSPROC_LOOKUP], nfsstats.srvrpccnt[NFSPROC_READLINK]-lastst.srvrpccnt[NFSPROC_READLINK], nfsstats.srvrpccnt[NFSPROC_READ]-lastst.srvrpccnt[NFSPROC_READ], nfsstats.srvrpccnt[NFSPROC_WRITE]-lastst.srvrpccnt[NFSPROC_WRITE], nfsstats.srvrpccnt[NFSPROC_RENAME]-lastst.srvrpccnt[NFSPROC_RENAME], nfsstats.srvrpccnt[NFSPROC_ACCESS]-lastst.srvrpccnt[NFSPROC_ACCESS], (nfsstats.srvrpccnt[NFSPROC_READDIR]-lastst.srvrpccnt[NFSPROC_READDIR]) +(nfsstats.srvrpccnt[NFSPROC_READDIRPLUS]-lastst.srvrpccnt[NFSPROC_READDIRPLUS])); lastst = nfsstats; fflush(stdout); sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); if (sigprocmask(SIG_BLOCK, &sigset, &oldsigset) == -1) err(1, "sigprocmask failed"); if (!signalled) { sigemptyset(&sigset); sigsuspend(&sigset); } if (sigprocmask(SIG_SETMASK, &oldsigset, NULL) == -1) err(1, "sigprocmask failed"); signalled = 0; alarm(interval); } /*NOTREACHED*/ } /* ************************ */ /* *** Per-export Stats *** */ /* ************************ */ /* * Print the stats of all nfs exported directories */ void do_exports_normal(void) { struct nfs_export_stat_desc *stat_desc; struct nfs_export_stat_rec *rec; char *buf; uint bufLen, i, recs; /* Read in export stats from the kernel */ buf = NULL; /* check for failure */ if (read_export_stats(&buf, &bufLen)) return; /* check for empty export table */ stat_desc = (struct nfs_export_stat_desc *)buf; recs = stat_desc->rec_count; if (!recs) { printf("No export stat data found\n"); free(buf); return; } /* init record pointer to position following stat descriptor */ rec = (struct nfs_export_stat_rec *)(buf + sizeof(struct nfs_export_stat_desc)); /* print out a header */ printf("Exported Directory Info:\n"); printf("%12s %12s %12s\n", "Requests", "Read Bytes", "Write Bytes"); /* loop through, printing out stats of each export */ for(i = 0; i < recs; i++) printf("%12llu %12llu %12llu %s\n", rec[i].ops, rec[i].bytes_read, rec[i].bytes_written, rec[i].path); /* clean up */ free(buf); } /* * Print a running summary of nfs export statistics. * Repeat display every interval seconds, showing statistics * collected over that interval. Assumes that interval is non-zero. * First line printed at top of screen is always cumulative. */ void do_exports_interval(u_int interval) { char *oldExportBuf, *newExportBuf, *tmpBuf; uint oldLen, newLen, tmpLen; int hdrcnt; sigset_t sigset, oldsigset; oldExportBuf = newExportBuf = NULL; oldLen = newLen = 0; signal(SIGALRM, catchalarm); signalled = 0; alarm(interval); for (hdrcnt = 1;;) { if (!--hdrcnt) { printf("%12s %12s %12s\n", "Requests", "Read Bytes", "Write Bytes"); fflush(stdout); hdrcnt = 20; } if (read_export_stats(&newExportBuf, &newLen) == 0) { display_export_diffs(newExportBuf, oldExportBuf); tmpBuf = oldExportBuf; tmpLen = oldLen; oldExportBuf = newExportBuf; oldLen = newLen; newExportBuf = tmpBuf; newLen = tmpLen; } fflush(stdout); sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); if (sigprocmask(SIG_BLOCK, &sigset, &oldsigset) == -1) err(1, "sigprocmask failed"); if (!signalled) { sigemptyset(&sigset); sigsuspend(&sigset); } if (sigprocmask(SIG_SETMASK, &oldsigset, NULL) == -1) err(1, "sigprocmask failed"); signalled = 0; alarm(interval); } } void display_export_diffs(char *newb, char *oldb) { struct nfs_export_stat_desc *hdr; struct nfs_export_stat_rec *rec, *oldRec; uint i, recs; if (newb == NULL) return; /* Determine how many records in newb */ hdr = (struct nfs_export_stat_desc *)newb; recs = hdr->rec_count; /* check for empty export table */ if (!recs) { printf("No exported directories found\n"); return; } /* initialize rec pointer */ rec = (struct nfs_export_stat_rec *)(newb + sizeof(struct nfs_export_stat_desc)); for(i = 0; i < recs; i++) { /* find old export record for this path */ oldRec = findExport(rec[i].path, oldb); if (oldRec != NULL) { printf("%12llu %12llu %12llu %s\n", (rec[i].ops >= oldRec->ops) ? rec[i].ops - oldRec->ops : oldRec->ops - rec[i].ops, (rec[i].bytes_read >= oldRec->bytes_read) ? rec[i].bytes_read - oldRec->bytes_read : oldRec->bytes_read - rec[i].bytes_read, (rec[i].bytes_written >= oldRec->bytes_written) ? rec[i].bytes_written - oldRec->bytes_written : oldRec->bytes_written - rec[i].bytes_written, rec[i].path); } else printf("%12llu %12llu %12llu %s\n", rec[i].ops, rec[i].bytes_read, rec[i].bytes_written, rec[i].path); } } struct nfs_export_stat_rec * findExport(char *path, char *buf) { struct nfs_export_stat_desc *hdr; struct nfs_export_stat_rec *rec, *retRec; uint i, recs; retRec = NULL; if (buf == NULL) return retRec; /* determine how many records in buf */ hdr = (struct nfs_export_stat_desc *)buf; recs = hdr->rec_count; /* check if no records to compare */ if (!recs) return retRec; /* initialize our rec pointer */ rec = (struct nfs_export_stat_rec *)(buf + sizeof(struct nfs_export_stat_desc)); for(i = 0; i < recs; i++) { if (strcmp(path, rec[i].path) == 0) { /* found a match */ retRec = &rec[i]; break; } } return retRec; } /* ****************** */ /* *** User Stats *** */ /* ****************** */ /* * Print active user stats for each nfs exported directory */ void do_active_users_normal(u_int flags) { struct nfs_user_stat_desc *stat_desc; struct nfs_export_node_head *export_list; struct nfs_export_node *export_node; struct nfs_active_user_node *unode; char *buf; uint bufLen, recs; /* Read in user stats from the kernel */ buf = NULL; if (read_active_user_stats(&buf, &bufLen)) return; /* check for empty user list */ stat_desc = (struct nfs_user_stat_desc *)buf; recs = stat_desc->rec_count; if (!recs) { printf("No NFS active user statistics found.\n"); free(buf); return; } /* get a sorted list */ export_list = get_sorted_active_user_list(buf); if (!export_list) { printf("Not enough memory for displaying active user statistics\n"); free(buf); return; } /* print out a header */ printf("NFS Active User Info:\n"); LIST_FOREACH(export_node, export_list, export_next) { printf("%s\n", export_node->rec->path); printf("%12s %12s %12s %-7s %-8s %s\n", "Requests", "Read Bytes", "Write Bytes", "Idle", "User", "IP Address"); LIST_FOREACH(unode, &export_node->nodes, user_next) displayActiveUserRec(unode->rec, flags); } /* clean up */ free_nfs_export_list(export_list); free(export_list); free(buf); } /* * Print a running summary of nfs active user statistics. * Repeat display every interval seconds, showing statistics * collected over that interval. Assumes that interval is non-zero. * First line printed at top of screen is always cumulative. */ void do_active_users_interval(u_int interval, u_int flags) { char *oldBuf, *newBuf, *tmpBuf; uint oldLen, newLen, tmpLen; int hdrcnt; sigset_t sigset, oldsigset; oldBuf = newBuf = NULL; oldLen = newLen = 0; signal(SIGALRM, catchalarm); signalled = 0; alarm(interval); for (hdrcnt = 1;;) { if (!--hdrcnt) { printf("%12s %12s %12s %-7s %-8s %s\n", "Requests", "Read Bytes", "Write Bytes", "Idle", "User", "IP Address"); fflush(stdout); hdrcnt = 20; } if (read_active_user_stats(&newBuf, &newLen) == 0) { display_active_user_diffs(newBuf, oldBuf, flags); tmpBuf = oldBuf; tmpLen = oldLen; oldBuf = newBuf; oldLen = newLen; newBuf = tmpBuf; newLen = tmpLen; } fflush(stdout); sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); if (sigprocmask(SIG_BLOCK, &sigset, &oldsigset) == -1) err(1, "sigprocmask failed"); if (!signalled) { sigemptyset(&sigset); sigsuspend(&sigset); } if (sigprocmask(SIG_SETMASK, &oldsigset, NULL) == -1) err(1, "sigprocmask failed"); signalled = 0; alarm(interval); } } void display_active_user_diffs(char *newb, char *oldb, u_int flags) { struct nfs_user_stat_desc *stat_desc; struct nfs_export_node_head *export_list; struct nfs_export_node *export_node; struct nfs_active_user_node *unode; struct nfs_user_stat_user_rec *rec, *oldrec, diffrec; struct nfs_user_stat_path_rec *pathrec; if (newb == NULL) return; /* check for empty export table */ stat_desc = (struct nfs_user_stat_desc *)newb; if (!stat_desc->rec_count) { printf("No NFS active user statistics found.\n"); return; } /* get a sorted list from newb */ export_list = get_sorted_active_user_list(newb); if (!export_list) { printf("Not enough memory for displaying active user statistics\n"); return; } LIST_FOREACH(export_node, export_list, export_next) { pathrec = export_node->rec; printf("%s\n", export_node->rec->path); LIST_FOREACH(unode, &export_node->nodes, user_next) { rec = unode->rec; /* check for old record */ oldrec = findActiveUser(pathrec->path, rec, oldb); if (oldrec != NULL) { /* setup diff rec */ diffrec.uid = rec->uid; diffrec.tm_start = rec->tm_start; diffrec.tm_last = rec->tm_last; diffrec.ops = serialdiff_64(rec->ops, oldrec->ops); diffrec.bytes_read = serialdiff_64(rec->bytes_read, oldrec->bytes_read); diffrec.bytes_written = serialdiff_64(rec->bytes_written, oldrec->bytes_written); bcopy(&rec->sock, &diffrec.sock, rec->sock.ss_len); /* display differential record */ displayActiveUserRec(&diffrec, flags); } else displayActiveUserRec(rec, flags); } } /* clean up */ free_nfs_export_list(export_list); free(export_list); } struct nfs_user_stat_user_rec * findActiveUser(char *path, struct nfs_user_stat_user_rec *rec, char *buf) { struct nfs_user_stat_desc *stat_desc; struct nfs_user_stat_user_rec *tmpRec, *retRec; struct nfs_user_stat_path_rec *pathrec; char *bufp; uint i, recs, scan_state; #define FIND_EXPORT 0 #define FIND_USER 1 scan_state = FIND_EXPORT; retRec = NULL; if (buf == NULL) return retRec; /* determine how many records in buf */ stat_desc = (struct nfs_user_stat_desc *)buf; recs = stat_desc->rec_count; /* check if no records to compare */ if (!recs) return retRec; /* initialize buf pointer */ bufp = buf + sizeof(struct nfs_user_stat_desc); for(i = 0; i < recs; i++) { switch(*bufp) { case NFS_USER_STAT_PATH_REC: if(scan_state == FIND_EXPORT) { pathrec = (struct nfs_user_stat_path_rec *)bufp; if(!strcmp(path, pathrec->path)) scan_state = FIND_USER; } else { /* encountered the next export, didn't find user. */ goto done; } bufp += sizeof(struct nfs_user_stat_path_rec); break; case NFS_USER_STAT_USER_REC: if(scan_state == FIND_USER) { tmpRec = (struct nfs_user_stat_user_rec *)bufp; if (!cmp_active_user(rec, tmpRec)) { /* found a match */ retRec = tmpRec; goto done; } } bufp += sizeof(struct nfs_user_stat_user_rec); break; default: goto done; } } done: return retRec; } void displayActiveUserRec(struct nfs_user_stat_user_rec *rec, u_int flags) { struct sockaddr_in *in; struct sockaddr_in6 *in6; struct hostent *hp; struct passwd *pw; struct timeval now; struct timezone tz; uint32_t now32, hr, min, sec; char addrbuf[NI_MAXHOST]; /* get current time for calculating idle time */ gettimeofday(&now, &tz); /* calculate idle hour, min sec */ now32 = (uint32_t)now.tv_sec; if (now32 >= rec->tm_last) sec = now32 - rec->tm_last; else sec = ~(rec->tm_last - now32) + 1; hr = sec / 3600; sec %= 3600; min = sec / 60; sec %= 60; /* setup ip address string */ if (rec->sock.ss_family == AF_INET) { /* ipv4 */ in = (struct sockaddr_in *)&rec->sock; if (flags & NUMERIC_NET) { if (inet_ntop(AF_INET, &in->sin_addr, addrbuf, sizeof(addrbuf)) == NULL) sprintf(addrbuf, "* * * *"); } else { hp = gethostbyaddr((char *)&in->sin_addr, sizeof(in->sin_addr), AF_INET); if (hp) sprintf(addrbuf, "%s", hp->h_name); else sprintf(addrbuf, "* * * *"); } } else if (rec->sock.ss_family == AF_INET6) { /* ipv6 */ in6 = (struct sockaddr_in6 *)&rec->sock; if (flags & NUMERIC_NET) { if (inet_ntop(AF_INET6, &in6->sin6_addr, addrbuf, sizeof(addrbuf)) == NULL) sprintf(addrbuf, "* * * *"); } else { hp = gethostbyaddr((char *)&in6->sin6_addr, sizeof(in6->sin6_addr), AF_INET6); if (hp) sprintf(addrbuf, "%s", hp->h_name); else sprintf(addrbuf, "* * * *"); } } else sprintf(addrbuf, "* * * *"); if (flags & NUMERIC_USER) { /* print uid */ printf("%12llu %12llu %12llu %1u:%02u:%02u %-8u %s\n", rec->ops, rec->bytes_read, rec->bytes_written, hr, min, sec, rec->uid, addrbuf); } else { /* get user name from uid */ pw = getpwuid(rec->uid); if (pw) printf("%12llu %12llu %12llu %1u:%02u:%02u %-8.8s %s\n", rec->ops, rec->bytes_read, rec->bytes_written, hr, min, sec, pw->pw_name, addrbuf); else printf("%12llu %12llu %12llu %1u:%02u:%02u %-8u %s\n", rec->ops, rec->bytes_read, rec->bytes_written, hr, min, sec, rec->uid, addrbuf); } } /* Returns zero if both uid and IP address fields match */ int cmp_active_user(struct nfs_user_stat_user_rec *rec1, struct nfs_user_stat_user_rec *rec2) { struct sockaddr_in *ipv4_sock1, *ipv4_sock2; struct sockaddr_in6 *ipv6_sock1, *ipv6_sock2; int retVal = 1; /* check uid */ if (rec1->uid != rec2->uid) return retVal; /* check address length */ if (rec1->sock.ss_len != rec2->sock.ss_len) return retVal; /* Check address family */ if (rec1->sock.ss_family != rec2->sock.ss_family) return retVal; if (rec1->sock.ss_family == AF_INET) { /* IPv4 */ ipv4_sock1 = (struct sockaddr_in *)&rec1->sock; ipv4_sock2 = (struct sockaddr_in *)&rec2->sock; if (!bcmp(&ipv4_sock1->sin_addr, &ipv4_sock2->sin_addr, sizeof(struct in_addr))) retVal = 0; } else { /* IPv6 */ ipv6_sock1 = (struct sockaddr_in6 *)&rec1->sock; ipv6_sock2 = (struct sockaddr_in6 *)&rec2->sock; if (!bcmp(&ipv6_sock1->sin6_addr, &ipv6_sock2->sin6_addr, sizeof(struct in6_addr))) retVal = 0; } return retVal; } struct nfs_export_node_head * get_sorted_active_user_list(char *buf) { struct nfs_user_stat_desc *stat_desc; struct nfs_export_node_head *export_list; struct nfs_export_node *export_node; struct nfs_active_user_node *unode, *unode_before, *unode_after; char *bufp; uint i, recs, err; /* first check for empty user list */ stat_desc = (struct nfs_user_stat_desc *)buf; recs = stat_desc->rec_count; if (!recs) return NULL; export_list = (struct nfs_export_node_head *)malloc(sizeof(struct nfs_export_node_head)); if (export_list == NULL) return NULL; LIST_INIT(export_list); export_node = NULL; err = 0; /* init record pointer to position following the stat descriptor */ bufp = buf + sizeof(struct nfs_user_stat_desc); /* loop through, printing out each record */ for(i = 0; i < recs; i++) { switch(*bufp) { case NFS_USER_STAT_PATH_REC: /* create a new export node */ export_node = (struct nfs_export_node *)malloc(sizeof(struct nfs_export_node)); if (export_node == NULL) { err = 1; goto done_err; } LIST_INIT(&export_node->nodes); export_node->rec = (struct nfs_user_stat_path_rec *)bufp; LIST_INSERT_HEAD(export_list, export_node, export_next); bufp += sizeof(struct nfs_user_stat_path_rec); break; case NFS_USER_STAT_USER_REC: if (export_node == NULL) { err = 1; goto done_err; } /* create a new user node */ unode = (struct nfs_active_user_node *)malloc(sizeof(struct nfs_active_user_node)); if (unode == NULL) { err = 1; goto done_err; } unode->rec = (struct nfs_user_stat_user_rec *)bufp; /* insert in decending order */ unode_before = NULL; LIST_FOREACH(unode_after, &export_node->nodes, user_next) { if (unode->rec->tm_last > unode_after->rec->tm_last) break; unode_before = unode_after; } if (unode_after) LIST_INSERT_BEFORE(unode_after, unode, user_next); else if (unode_before) LIST_INSERT_AFTER(unode_before, unode, user_next); else LIST_INSERT_HEAD(&export_node->nodes, unode, user_next); bufp += sizeof(struct nfs_user_stat_user_rec); break; default: printf("nfsstat: unexpected record type 0x%02x in active user data stream\n", *bufp); err = 1; goto done_err; } } done_err: if (err) { free_nfs_export_list(export_list); free(export_list); export_list = NULL; } return(export_list); } void free_nfs_export_list(struct nfs_export_node_head *export_list) { struct nfs_export_node *exp_node; struct nfs_active_user_node *unode; while ((exp_node = LIST_FIRST(export_list))) { LIST_REMOVE(exp_node, export_next); while((unode = LIST_FIRST(&exp_node->nodes))) { LIST_REMOVE(unode, user_next); free(unode); } free(exp_node); } } uint64_t serialdiff_64(uint64_t new, uint64_t old) { if(new > old) return (new - old); else return ( ~(old - new) + 1); } void printhdr(void) { printf(" %8.8s %8.8s %8.8s %8.8s %8.8s %8.8s %8.8s %8.8s\n", "Getattr", "Lookup", "Readlink", "Read", "Write", "Rename", "Access", "Readdir"); fflush(stdout); } /* * Called if an interval expires before sidewaysintpr has completed a loop. * Sets a flag to not wait for the alarm. */ void catchalarm(int dummy) { signalled = 1; } void usage(void) { fprintf(stderr, "usage: nfsstat [-cseu] [-w interval] [-n user|net]\n"); exit(1); }