2 static char sccsid[] = "@(#)nhfsstone.c 1.22 90/05/08 Copyright (c) 1990, Legato Systems Inc";
6 * Copyright (c) 1990 Legato Systems Inc.
8 * See DISCLAIMER file for restrictions
10 * Ported to Linux by Olaf Kirch <okir@monad.swb.de>
21 #include <sys/param.h>
39 * Usage: nhfsstone [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs]
40 * [-m mixfile] [dir]...
42 * Generates an artifical NFS client load based on a given mix of
45 * Strategy: loop for some number of NFS calls doing a random sleep
46 * followed by a call to one of the op generator routines. The routines
47 * are called based on a weighting factor determined by the difference
48 * between the current ops percentages (derived from kernel NFS stats)
49 * and a set of default percentages or a mix supplied by the caller.
51 * The generator routines try very hard to guess how many NFS operations
52 * they are generating so that the calling routine can keep a running
53 * estimate of the number of calls and the mix to avoid having to get
54 * the NFS statistics from the kernel too often.
56 * The operations are done in a directory that has a set of file names
57 * that are long enough that they won't be cached by the name cache
58 * in the kernel. The "lookup" operation steps through the names and
59 * creates a file if that name does not exist, or closes and reopens it
60 * if it does. This generates a table of open file descriptors. Most of the
61 * other operations are done on random descriptors in the table. The "getattr"
62 * operation tries to avoid the kernel attribute cache by doing "fstat"
63 * system calls on random descriptors in the table. There must be enough
64 * files in the directory so that, on average, the getattr operation hits
65 * any file less often than once each 6 seconds (the default timeout for
66 * the attributes cache).
68 * The parent process starts children to do the real work of generating load.
69 * The parent coordinates them so that they all start at the same time, and
70 * collects statistics from them when they are done. To coordinate the
71 * start up, the parent waits for each child to write one byte into
72 * a common log file (opened in append mode to avoid overwriting).
73 * After they write a byte the children pause, and the parent send SIGUSR1
74 * when it has heard from all of the kids. The children write their statistics
75 * into the same common log file and the parent reads and accumulates the
76 * statics and prints them out.
78 * This code will only compile and run on 4.X BSD based systems.
81 #define DEFAULT_LOAD 30 /* default calls per sec */
82 #define DEFAULT_CALLS 5000 /* default number of calls */
83 #define NFILES 40 /* number of test files/dir */
84 #define BUFSIZE 8192 /* block size for read and write */
85 #define MAXFILESIZE 32 /* size, in blocks, of large file */
86 #define SAMPLETIME 5 /* secs between samples of NFS stats */
87 #define NPROCS 7 /* number of children to run */
91 * The names of NFS operations
94 "null", "getattr", "setattr", "root", "lookup", "readlink", "read",
95 "wrcache", "write", "create", "remove", "rename", "link", "symlink",
96 "mkdir", "rmdir", "readdir", "fsstat",
100 * NFS operation numbers
102 * Used to index the Opnames, Mix and statistics arrays.
104 #define NOPS 18 /* number of NFS ops */
133 * Software development mix for server with about 50/50 mix of
134 * diskless and diskful clients running SunOS 4.0.
157 /* Prototype decls */
158 int setmix(FILE *fp);
160 void init_logfile(void);
161 void init_counters(void);
162 void get_delta(struct count *start, struct count *cur);
163 void init_testdir(int dirnum, char *parentdir);
164 void do_op(int rpct);
167 int createfile(void);
170 void collect_counters(void);
171 int check_counters(void);
173 void msec_sleep(int msecs);
174 void get_opct(struct count *count);
175 int substr(char *sp, char *subsp);
176 int check_access(struct stat statb);
177 void error(char *str);
180 * NFS operations generator routines
202 * Operations generator vector
205 int (*funct)(); /* op */
230 #define DIRSTR "dir" /* directory */
231 #define SYMSTR "sym" /* symbolic link */
232 #define LINSTR "lin" /* hard link */
234 struct timeval Optime[NOPS+1]; /* cumulative running time for ops */
235 struct count Curct; /* total number ops called */
236 int Openfd[NFILES]; /* open file descriptors */
237 int Curnum; /* current file number */
238 int Symnum; /* current symlink file number */
239 int Linknum; /* current link file number */
240 int Dirnum; /* current directory number */
241 DIR *Testdir; /* my test directory */
242 char Testdirname[MAXNAMLEN*2]; /* my test directory name */
243 char Curname[MAXNAMLEN]; /* current file name */
244 char Dirname[MAXNAMLEN]; /* current directory name */
245 char Symname[MAXNAMLEN]; /* symlink file name */
246 char Linkname[MAXNAMLEN]; /* link file name */
247 char *Otherspec = "%s/%03d"; /* sprintf spec for other names */
248 char *Rename1 = "rename1"; /* first name of rename pair */
249 char *Rename2 = "rename2"; /* second name of rename pair */
250 char *Symlinkpath = "./symlinknamelongstuff";
251 /* symlink file data */
252 char *Myname; /* name program invoked under */
253 char Namebuf[MAXNAMLEN]; /* unique name for this program */
254 int Log; /* synchronization log */
255 char Logname[MAXNAMLEN]; /* synchronization log name */
256 int Kmem; /* /dev/kmem file descriptor */
257 off_t Statoffset; /* offset to op count in NFS stats */
258 int Nprocs; /* sub-processes started */
259 int Verbose; /* print more info */
260 int Testop = -1; /* operation to test */
261 int Saveerrno; /* place to save errno */
263 #define subtime(t1, t2) {if ((t1.tv_usec -= t2.tv_usec) >= 1000000) {\
264 t1.tv_sec += (t1.tv_usec / 1000000); \
265 t1.tv_usec %= 1000000; \
266 } else if (t1.tv_usec < 0) { \
267 t1.tv_usec += 1000000; \
270 t1.tv_sec -= t2.tv_sec; \
273 #define addtime(t1, t2) {if ((t1.tv_usec += t2.tv_usec) >= 1000000) {\
274 t1.tv_sec += (t1.tv_usec / 1000000); \
275 t1.tv_usec %= 1000000; \
276 } else if (t1.tv_usec < 0) { \
277 t1.tv_usec += 1000000; \
280 t1.tv_sec += t2.tv_sec; \
284 * Used to catch the parent's "start" signal
300 (void) unlink(Logname);
305 main(int argc, char **argv)
307 int runtime; /* length of run, in seconds */
308 int load; /* load factor, in client loads */
309 int ncalls; /* total number of calls to make */
310 int avgmspc; /* average millisec per call */
311 int mspc; /* millisec per call */
312 int wantcalls; /* ncalls that should have happend by now */
313 int pid; /* process id */
314 int delay; /* msecs since last checked current time */
315 int randnum; /* a random number */
317 sigset_t oldmask; /* saved signal mask */
319 int oldmask; /* saved signal mask */
321 int sampletime; /* secs between reading kernel stats */
322 char *opts; /* option parsing */
326 struct timeval curtime;
327 struct timeval starttime;
328 struct count startct;
330 char workdir[MAXPATHLEN];
349 while (argc && **argv == '-') {
356 * Set number of calls
358 if (!isdigit(argv[1][0])) {
359 (void) fprintf(stderr,
360 "%s: illegal calls value %s\n",
364 ncalls = atoi(argv[1]);
373 if (!isdigit(argv[1][0])) {
374 (void) fprintf(stderr,
375 "%s: illegal load value %s\n",
379 load = atoi(argv[1]);
386 * Set mix from a file
388 if ((fp = fopen(argv[1], "r")) == NULL) {
390 (void) fprintf(stderr,
391 "%s: bad mix file", Myname);
396 if (setmix(fp) < 0) {
406 * Set number of child processes
408 if (!isdigit(argv[1][0])) {
409 (void) fprintf(stderr,
410 "%s: illegal procs value %s\n",
414 Nprocs = atoi(argv[1]);
421 * Set test mode, number following is opnum
423 if (!isdigit(argv[1][0])) {
424 (void) fprintf(stderr,
425 "%s: illegal test value %s\n",
429 Testop = atoi(argv[1]);
430 if (Testop >= NOPS) {
431 (void) fprintf(stderr,
432 "%s: illegal test value %d\n",
444 if (!isdigit(argv[1][0])) {
445 (void) fprintf(stderr,
446 "%s: illegal time value %s\n",
450 runtime = atoi(argv[1]);
473 init_logfile(); /* Set up synchronizatin log file */
475 if (getcwd(workdir, sizeof(workdir)) == (char *) 0) {
477 (void) fprintf(stderr,
478 "%s: can't find current directory ", Myname);
484 (void) signal(SIGINT, cleanup);
485 (void) signal(SIGUSR1, startup);
490 sigaddset(&mask, SIGUSR1);
491 sigprocmask(SIG_BLOCK, &mask, &oldmask);
495 * sigblock() is marked deprecated in modern
496 * glibc and hence generates a warning.
498 oldmask = sigblock(sigmask(SIGUSR1));
503 ncalls = DEFAULT_CALLS;
505 ncalls = runtime * load;
508 avgmspc = Nprocs * 1000 / load;
513 for (procnum = 0; procnum < Nprocs; procnum++) {
514 if ((pid = fork()) == -1) {
516 (void) fprintf(stderr, "%s: can't fork ", Myname);
519 (void) kill(0, SIGINT);
531 * Parent: wait for kids to get ready, start them, wait for them to
532 * finish, read and accumulate results.
536 * wait for kids to initialize
540 if (fstat(Log, &statb) == -1) {
541 (void) fprintf(stderr, "%s: can't stat log %s",
543 (void) kill(0, SIGINT);
546 } while (statb.st_size != Nprocs);
548 if (ftruncate(Log, 0L) == -1) {
549 (void) fprintf(stderr, "%s: can't truncate log %s",
551 (void) kill(0, SIGINT);
559 * Be sure there isn't something else going on
563 get_delta(&startct, &Curct);
564 if (Curct.total > 20) {
565 (void) fprintf(stderr,
566 "%s: too much background activity (%d calls/sec)\n",
567 Myname, Curct.total);
568 (void) kill(0, SIGINT);
580 (void) kill(0, SIGUSR1);
583 * Kids started, wait for first one to finish, signal the
584 * rest and wait for them to finish.
586 if (wait((union wait *) 0) != -1) {
587 (void) kill(0, SIGUSR1);
588 while (wait((union wait *) 0) != -1)
593 * Initialize and sum up counters
596 get_delta(&startct, &Curct);
598 if (check_counters() == -1) {
604 (void) unlink(Logname);
610 * Children: initialize, then notify parent through log file,
611 * wait to get signal, beat the snot out of the server, write
612 * stats to the log file, and exit.
616 * Change my name for error logging
618 (void) sprintf(Namebuf, "%s%d", Myname, procnum);
622 * Initialize and cd to test directory
625 init_testdir(procnum, argv[procnum % argc]);
627 init_testdir(procnum, ".");
629 if ((Testdir = opendir(".")) == NULL) {
631 (void) fprintf(stderr,
632 "%s: can't open test directory ", Myname);
642 * Tell parent I'm ready then wait for go ahead
644 if (write(Log, " ", 1) != 1) {
645 (void) fprintf(stderr, "%s: can't write sync file %s",
647 (void) kill(0, SIGINT);
652 sigsuspend(&oldmask);
658 * Initialize counters
661 (void) gettimeofday(&starttime, (struct timezone *)NULL);
662 sampletime = starttime.tv_sec + ((int) random()) % (2 * SAMPLETIME);
666 * Do pseudo NFS operations and adapt to dynamic changes in load
667 * by adjusting the sleep time between operations based on the
668 * number of calls that should have occured since starttime and
669 * the number that have actually occured. A delay is used to avoid
670 * doing gettimeofday calls too often, and a sampletime is
671 * used to avoid reading kernel NFS stats too often.
672 * If parent interrupts, get out and clean up.
677 randnum = (int) random();
679 msec_sleep(randnum % (mspc << 1));
683 * Do the NFS operation
684 * We use a random number from 0-199 to avoid starvation
685 * of the operations at the end of the mix.
687 do_op(randnum % 200);
690 * Do a gettimeofday call only once per second
693 if (delay > 1000 || Curct.total >= ncalls) {
695 (void) gettimeofday(&curtime, (struct timezone *)NULL);
698 * If sample time is up, check the kernel stats
699 * and adjust our parameters to either catch up or
702 if (curtime.tv_sec > sampletime ||
703 Curct.total >= ncalls) {
704 sampletime = curtime.tv_sec + SAMPLETIME;
705 get_delta(&startct, &Curct);
706 if (Curct.total >= ncalls) {
710 ((curtime.tv_sec - starttime.tv_sec) * 1000
711 +(curtime.tv_usec-starttime.tv_usec) / 1000)
713 pct = 1000 * (Curct.total - wantcalls) / ncalls;
714 mspc = avgmspc + avgmspc * pct / 20;
717 * mspc must be positive or we will
718 * never advance time.
727 * Store total time in last slot of counts array
729 Optime[NOPS].tv_sec = curtime.tv_sec - starttime.tv_sec;
730 Optime[NOPS].tv_usec = curtime.tv_usec - starttime.tv_usec;
733 * write stats to log file (append mode)
735 if (write(Log, (char *)Optime, sizeof (Optime)) == -1) {
737 (void) fprintf(stderr, "%s: can't write log ", Myname);
740 (void) kill(0, SIGINT);
749 * Initialize test directory
751 * If the directory already exists, check to see that all of the
752 * files exist and we can write them. If directory doesn't exist
753 * create it and fill it using the LOOKUP and WRITE ops.
754 * Chdir to the directory.
757 init_testdir(int dirnum, char *parentdir)
764 (void) sprintf(Testdirname, "%s/testdir%d", parentdir, dirnum);
765 if (stat(Testdirname, &statb) == -1) {
766 if (mkdir(Testdirname, 0777) == -1) {
768 (void) fprintf(stderr,
769 "%s: can't create test directory ", Myname);
772 (void) kill(0, SIGINT);
775 if (chdir(Testdirname) == -1) {
777 (void) fprintf(stderr,
778 "%s: can't cd to test directory ", Myname);
781 (void) kill(0, SIGINT);
786 * create some files with long names and average size
788 for (i = 0; i < NFILES; i++) {
791 if (Openfd[Curnum] == 0 || writefile() == 0) {
793 (void) fprintf(stderr,
794 "%s: can't create test file '%s'\n",
798 (void) kill(0, SIGINT);
803 if (chdir(Testdirname) == -1) {
805 (void) fprintf(stderr,
806 "%s: can't cd to test directory ", Myname);
809 (void) kill(0, SIGINT);
814 * Verify that we can read and write the test dir
816 if (check_access(statb) == -1) {
817 (void) fprintf(stderr,
818 "%s: wrong permissions on test dir %s\n",
819 Myname, Testdirname);
820 (void) kill(0, SIGINT);
825 * Verify that we can read and write all the files
827 for (i = 0; i < NFILES; i++) {
829 if (stat(Curname, &statb) == -1 || statb.st_size == 0) {
831 * File doesn't exist or is 0 size
834 if (Openfd[Curnum] == 0 || writefile() == 0) {
835 (void) kill(0, SIGINT);
838 } else if (check_access(statb) == -1) {
840 * should try to remove and recreate it
842 (void) fprintf(stderr,
843 "%s: wrong permissions on testfile %s\n",
845 (void) kill(0, SIGINT);
847 } else if (Openfd[Curnum] == 0) {
849 if (Openfd[Curnum] == 0) {
850 (void) kill(0, SIGINT);
858 * Start with Rename1 and no Rename2 so the
859 * rename op can ping pong back and forth.
861 (void) unlink(Rename2);
862 if ((fd = open(Rename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) == -1) {
864 (void) fprintf(stderr, "%s: can't create rename file ", Myname);
867 (void) kill(0, SIGINT);
872 * Remove and recreate the test sub-directories
873 * for mkdir symlink and hard link.
875 (void) sprintf(cmd, "rm -rf %s %s %s", DIRSTR, SYMSTR, LINSTR);
876 if (system(cmd) != 0) {
877 (void) fprintf(stderr, "%s: can't %s\n", Myname, cmd);
878 (void) kill(0, SIGINT);
882 if (mkdir(DIRSTR, 0777) == -1) {
883 (void) fprintf(stderr,
884 "%s: can't create subdir %s\n", Myname, DIRSTR);
885 (void) kill(0, SIGINT);
889 if (mkdir(SYMSTR, 0777) == -1) {
890 (void) fprintf(stderr,
891 "%s: can't create subdir %s\n", Myname, SYMSTR);
892 (void) kill(0, SIGINT);
897 if (mkdir(LINSTR, 0777) == -1) {
898 (void) fprintf(stderr, "%s: can't create subdir %s\n", Myname,
900 (void) kill(0, SIGINT);
908 * The routines below attempt to do over-the-wire operations.
909 * Each op tries to cause one or more of a particular
910 * NFS operation to go over the wire. OPs return the number
911 * of OTW calls they think they have generated.
913 * An array of open file descriptors is kept for the files in each
914 * test directory. The open fd's are used to get access to the files
915 * without generating lookups. An fd value of 0 mean the corresponding
916 * file name is closed. Ops that need a name use Curname.
920 * Call an op based on a random number and the current
921 * op calling weights. Op weights are derived from the
922 * mix percentage and the current NFS stats mix percentage.
936 for (opnum = rpct % NOPS; rpct >= 0; opnum = (opnum + 1) % NOPS) {
938 oppct = (Curct.calls[opnum] * 100) / Curct.total;
943 * Weight is mix percent - (how far off we are * fudge)
944 * fudge factor is required because some ops (read, write)
945 * generate many NFS calls for a single op call
947 weight = Mix[opnum] - ((oppct - Mix[opnum]) << 4);
953 if (opnum == RMDIR && Dirnum == 0) {
955 } else if (opnum != CREATE && opnum != LOOKUP &&
960 if (Openfd[Curnum] == 0) {
972 * Call an op generator and keep track of its running time
977 struct timeval start;
981 (void) gettimeofday(&start, (struct timezone *)NULL);
982 nops = (*Op_vect[opnum].funct)();
983 (void) gettimeofday(&stop, (struct timezone *)NULL);
984 stop.tv_sec -= start.tv_sec;
985 stop.tv_usec -= start.tv_usec;
989 * SunOS 4.0 does a lookup and a getattr on each open
990 * so we have to account for that in the getattr op
992 if (opnum == GETATTR && nops == 2) {
996 Curct.total += Nprocs;
997 Curct.calls[LOOKUP] += Nprocs;
998 addtime(Optime[LOOKUP], stop);
1003 Curct.total += nops;
1004 Curct.calls[opnum] += nops;
1005 addtime(Optime[opnum], stop);
1009 * Advance file number (Curnum) and name (Curname)
1014 static char *numpart = NULL;
1017 Curnum = (Curnum + 1) % NFILES;
1018 if (numpart == NULL) {
1019 (void) sprintf(Curname, "%03dabcdefghijklmn", Curnum);
1023 numpart[0] = '0' + num / 100;
1025 numpart[1] = '0' + num / 10;
1027 numpart[2] = '0' + num;
1038 fd = Openfd[Curnum];
1040 if ((fd && close(fd) == -1) ||
1041 (fd = open(Curname, O_CREAT|O_RDWR|O_TRUNC, 0666)) == -1) {
1046 Openfd[Curnum] = fd;
1057 fd = Openfd[Curnum];
1058 if (fd == 0 && (fd = open(Curname, O_RDWR, 0666)) == -1) {
1063 Openfd[Curnum] = fd;
1077 fd = Openfd[Curnum];
1079 if (lseek(fd, 0L, 0) == (off_t) -1) {
1080 error("write: lseek");
1084 randnum = (int) random();
1085 bufs = randnum % 100; /* using this for distribution desired */
1087 * Attempt to create a distribution of file sizes
1088 * to reflect reality. Most files are small,
1089 * but there are a few files that are very large.
1091 * The sprite paper (USENIX 198?) claims :
1092 * 50% of all files are < 2.5K
1093 * 80% of all file accesses are to files < 10K
1094 * 40% of all file I/O is to files > 25K
1096 * static examination of the files in our file system
1097 * seems to support the claim that 50% of all files are
1101 bufs = (randnum % 3) + 1;
1103 } else if (bufs < 97) {
1104 bufs = (randnum % 6) + 1;
1111 for (wrote = 0; wrote < bufs; wrote++) {
1112 if (write(fd, buf, size) == -1) {
1130 * Generate a getattr call by fstat'ing the current file
1131 * or by closing and re-opening it. This helps to keep the
1132 * attribute cache cold.
1139 if ((random() % 2) == 0) {
1140 (void) close(Openfd[Curnum]);
1142 if (openfile() == -1) {
1147 if (fstat(Openfd[Curnum], &statb) == -1) {
1154 int op_setattr(void)
1157 if (fchmod(Openfd[Curnum], 0666) == -1) {
1173 * Generate a lookup by stat'ing the current name.
1179 if (stat(Curname, &statb) == -1) {
1194 fd = Openfd[Curnum];
1196 if (lseek(fd, 0L, 0) == (off_t) -1) {
1197 error("read: lseek");
1201 while ((got = read(fd, buf, sizeof (buf))) > 0) {
1208 bufs++; /* did one extra read to find EOF */
1214 int op_wrcache(void)
1229 (void) fsync(Openfd[Curnum]);
1238 if (createfile() == -1) {
1251 got = unlink(Linkname);
1253 (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
1254 } else if (Symnum > 1) {
1255 got = unlink(Symname);
1257 (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
1259 fd = Openfd[Curnum];
1261 if (fd && (close(fd) == -1)) {
1262 error("remove: close");
1265 got = unlink(Curname);
1280 if (toggle++ & 01) {
1281 got = rename(Rename2, Rename1);
1283 got = rename(Rename1, Rename2);
1296 (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
1297 if (link(Curname, Linkname) == -1) {
1304 int op_readlink(void)
1306 char buf[MAXPATHLEN];
1312 if (readlink(Symname, buf, sizeof (buf)) == -1) {
1319 int op_symlink(void)
1323 (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
1324 if (symlink(Symlinkpath, Symname) == -1) {
1335 (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
1336 if (mkdir(Dirname, 0777) == -1) {
1350 if (rmdir(Dirname) == -1) {
1354 (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
1359 int op_readdir(void)
1363 while (readdir(Testdir) != (struct dirent *)NULL)
1371 struct statfs statfsb;
1373 if (statfs(".", &statfsb) == -1) {
1385 * Read counter arrays out of log file and accumulate them in "Optime"
1388 collect_counters(void)
1393 (void) lseek(Log, 0L, 0);
1395 for (i = 0; i < Nprocs; i++) {
1396 struct timeval buf[NOPS+1];
1398 if (read(Log, (char *)buf, sizeof (buf)) == -1) {
1400 (void) fprintf(stderr, "%s: can't read log ", Myname);
1403 (void) kill(0, SIGINT);
1407 for (j = 0; j < NOPS+1; j++) {
1408 addtime(Optime[j], buf[j]);
1414 * Check consistance of results
1417 check_counters(void)
1425 for (i = 0; i < NOPS; i++) {
1426 got = Curct.calls[i] * 10000 / Curct.total;
1427 want = Mix[i] * 100;
1429 mixdiff += got - want;
1431 mixdiff += want - got;
1434 if (mixdiff > 1000) {
1435 (void) fprintf(stdout,
1436 "%s: INVALID RUN, mix generated is off by %d.%02d%%\n",
1437 Myname, mixdiff / 100, mixdiff % 100);
1455 for (i = 0; i < NOPS; i++) {
1456 totalmsec += Optime[i].tv_sec * 1000;
1457 totalmsec += Optime[i].tv_usec / 1000;
1461 const char *format = sizeof (Optime[0].tv_sec) == sizeof (long)
1462 ? "%-10s%3d%% %2d.%02d%% %6d %4ld.%02ld %4d.%02d %2d.%02d%%\n"
1463 : "%-10s%3d%% %2d.%02d%% %6d %4d.%02d %4d.%02d %2d.%02d%%\n";
1464 (void) fprintf(stdout,
1465 "op want got calls secs msec/call time %%\n");
1466 for (i = 0; i < NOPS; i++) {
1467 msec = Optime[i].tv_sec * 1000
1468 + Optime[i].tv_usec / 1000;
1469 (void) fprintf(stdout, format,
1471 Curct.calls[i] * 100 / Curct.total,
1472 (Curct.calls[i] * 100 % Curct.total)
1473 * 100 / Curct.total,
1475 Optime[i].tv_sec, Optime[i].tv_usec / 10000,
1477 ? msec / Curct.calls[i]
1480 ? (msec % Curct.calls[i]) * 100 / Curct.calls[i]
1482 msec * 100 / totalmsec,
1483 (msec * 100 % totalmsec) * 100 / totalmsec);
1487 runtime = Optime[NOPS].tv_sec / Nprocs;
1488 (void) fprintf(stdout,
1489 "%d sec %d calls %d.%02d calls/sec %d.%02d msec/call\n",
1490 runtime, Curct.total,
1491 Curct.total / runtime,
1492 ((Curct.total % runtime) * 100) / runtime,
1493 totalmsec / Curct.total,
1494 ((totalmsec % Curct.total) * 100) / Curct.total);
1498 * Use select to sleep for some number of milliseconds
1499 * granularity is 20 msec
1502 msec_sleep(int msecs)
1504 struct timeval sleeptime;
1509 sleeptime.tv_sec = msecs / 1000;
1510 sleeptime.tv_usec = (msecs % 1000) * 1000;
1512 if (select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &sleeptime) == -1){
1514 (void) fprintf(stderr, "%s: select failed ", Myname);
1517 (void) kill(0, SIGINT);
1523 * Open the synchronization file with append mode
1529 (void) sprintf(Logname, "/tmp/nhfsstone%d", getpid());
1530 if ((Log = open(Logname, O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0666)) == -1){
1532 (void) fprintf(stderr,
1533 "%s: can't open log file %s ", Myname, Logname);
1549 for (i = 0; i < NOPS; i++) {
1551 Optime[i].tv_sec = 0;
1552 Optime[i].tv_usec = 0;
1554 Optime[NOPS].tv_sec = 0;
1555 Optime[NOPS].tv_usec = 0;
1559 * Set cur = cur - start
1562 get_delta(struct count *start, struct count *cur)
1567 cur->total -= start->total;
1568 for (i = 0; i < NOPS; i++) {
1569 cur->calls[i] -= start->calls[i];
1577 get_opct(struct count *count)
1579 static FILE *fp = NULL;
1583 if (fp == NULL && !(fp = fopen("/proc/net/rpc/nfs", "r"))) {
1584 perror("/proc/net/rpc/nfs");
1585 (void) kill(0, SIGINT);
1592 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
1593 char *sp, *line = buffer;
1595 if ((sp = strchr(line, '\n')) != NULL)
1597 if (!(sp = strtok(line, " \t")) || strcmp(line, "proc2"))
1599 if (!(sp = strtok(NULL, " \t")))
1602 for (i = 0; i < 18; i++) {
1603 if (!(sp = strtok(NULL, " \t")))
1605 /* printf("call %d -> %s\n", i, sp); */
1606 count->calls[i] = atoi(sp);
1607 count->total += count->calls[i];
1609 /* printf("total calls %d\n", count->total); */
1616 fprintf(stderr, "parse error in /proc/net/rpc/nfs!\n");
1621 #define LINELEN 128 /* max bytes/line in mix file */
1623 #define MIX_DATALINE 1
1625 #define MIX_FIRSTLINE 3
1629 * Assumes that the input file is in the same format as
1630 * the output of the nfsstat(8) command.
1632 * Uses a simple state transition to keep track of what to expect.
1633 * Parsing is done a line at a time.
1635 * State Input action New state
1636 * MIX_START ".*nfs:.*" skip one line MIX_FIRSTLINE
1637 * MIX_FIRSTLINE ".*[0-9]*.*" get ncalls MIX_DATALINE
1638 * MIX_DATALINE "[0-9]* [0-9]*%"X6 get op counts MIX_DATALINE
1639 * MIX_DATALINE "[0-9]* [0-9]*%"X4 get op counts MIX_DONE
1640 * MIX_DONE EOF return
1655 while (state != MIX_DONE && fgets(line, LINELEN, fp)) {
1661 if (len >= 4 && substr(line, "nfs:")) {
1662 if (fgets(line, LINELEN, fp) == NULL) {
1663 (void) fprintf(stderr,
1664 "%s: bad mix format: unexpected EOF after 'nfs:'\n", Myname);
1667 state = MIX_FIRSTLINE;
1672 got = sscanf(line, "%d", &calls);
1674 (void) fprintf(stderr,
1675 "%s: bad mix format: can't find 'calls' value %d\n", Myname, got);
1678 if (fgets(line, LINELEN, fp) == NULL) {
1679 (void) fprintf(stderr,
1680 "%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
1683 state = MIX_DATALINE;
1688 "%d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%%",
1689 &Mix[opnum], &Mix[opnum+1], &Mix[opnum+2], &Mix[opnum+3],
1690 &Mix[opnum+4], &Mix[opnum+5], &Mix[opnum+6]);
1691 if (got == 4 && opnum == 14) {
1693 * looks like the last line
1696 } else if (got == 7) {
1698 if (fgets(line, LINELEN, fp) == NULL) {
1699 (void) fprintf(stderr,
1700 "%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
1704 (void) fprintf(stderr,
1705 "%s: bad mix format: can't find %d op values\n", Myname, got);
1710 (void) fprintf(stderr,
1711 "%s: unknown state %d\n", Myname, state);
1715 if (state != MIX_DONE) {
1716 (void) fprintf(stderr,
1717 "%s: bad mix format: unexpected EOF\n", Myname);
1720 for (opnum = 0; opnum < NOPS; opnum++) {
1721 Mix[opnum] = Mix[opnum] * 100 / calls
1722 + ((Mix[opnum] * 1000 / calls % 10) >= 5);
1728 * return true if sp contains the substring subsp, false otherwise
1731 substr(char *sp, char *subsp)
1737 if (sp == NULL || subsp == NULL) {
1741 want = strlen(subsp);
1743 while (*sp != '\0') {
1744 while (*sp != *subsp && *sp != '\0') {
1749 while (*sp == *s2) {
1754 if (found == want) {
1762 * check to make sure that we have
1763 * both read and write permissions
1764 * for this file or directory.
1767 check_access(struct stat statb)
1770 gid_t gidset[NGROUPS];
1773 if (statb.st_uid == getuid()) {
1774 if ((statb.st_mode & 0200) && (statb.st_mode & 0400)) {
1781 gidsetlen = NGROUPS;
1783 if (getgroups(gidsetlen, gidset) == -1) {
1784 perror("getgroups");
1788 for (i = 0; i < NGROUPS; i++) {
1789 if (statb.st_gid == gidset[i]) {
1790 if ((statb.st_mode & 020) && (statb.st_mode & 040)) {
1798 if ((statb.st_mode & 02) && (statb.st_mode & 04)) {
1809 (void) fprintf(stderr, "usage: %s [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs] [-m mixfile] [dir]...\n", Myname);
1817 (void) fprintf(stderr, "%s: op failed: %s ", Myname, str);