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>
20 #include <sys/param.h>
38 * Usage: nhfsstone [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs]
39 * [-m mixfile] [dir]...
41 * Generates an artifical NFS client load based on a given mix of
44 * Strategy: loop for some number of NFS calls doing a random sleep
45 * followed by a call to one of the op generator routines. The routines
46 * are called based on a weighting factor determined by the difference
47 * between the current ops percentages (derived from kernel NFS stats)
48 * and a set of default percentages or a mix supplied by the caller.
50 * The generator routines try very hard to guess how many NFS operations
51 * they are generating so that the calling routine can keep a running
52 * estimate of the number of calls and the mix to avoid having to get
53 * the NFS statistics from the kernel too often.
55 * The operations are done in a directory that has a set of file names
56 * that are long enough that they won't be cached by the name cache
57 * in the kernel. The "lookup" operation steps through the names and
58 * creates a file if that name does not exist, or closes and reopens it
59 * if it does. This generates a table of open file descriptors. Most of the
60 * other operations are done on random descriptors in the table. The "getattr"
61 * operation tries to avoid the kernel attribute cache by doing "fstat"
62 * system calls on random descriptors in the table. There must be enough
63 * files in the directory so that, on average, the getattr operation hits
64 * any file less often than once each 6 seconds (the default timeout for
65 * the attributes cache).
67 * The parent process starts children to do the real work of generating load.
68 * The parent coordinates them so that they all start at the same time, and
69 * collects statistics from them when they are done. To coordinate the
70 * start up, the parent waits for each child to write one byte into
71 * a common log file (opened in append mode to avoid overwriting).
72 * After they write a byte the children pause, and the parent send SIGUSR1
73 * when it has heard from all of the kids. The children write their statistics
74 * into the same common log file and the parent reads and accumulates the
75 * statics and prints them out.
77 * This code will only compile and run on 4.X BSD based systems.
80 #define DEFAULT_LOAD 30 /* default calls per sec */
81 #define DEFAULT_CALLS 5000 /* default number of calls */
82 #define NFILES 40 /* number of test files/dir */
83 #define BUFSIZE 8192 /* block size for read and write */
84 #define MAXFILESIZE 32 /* size, in blocks, of large file */
85 #define SAMPLETIME 5 /* secs between samples of NFS stats */
86 #define NPROCS 7 /* number of children to run */
90 * The names of NFS operations
93 "null", "getattr", "setattr", "root", "lookup", "readlink", "read",
94 "wrcache", "write", "create", "remove", "rename", "link", "symlink",
95 "mkdir", "rmdir", "readdir", "fsstat",
99 * NFS operation numbers
101 * Used to index the Opnames, Mix and statistics arrays.
103 #define NOPS 18 /* number of NFS ops */
132 * Software development mix for server with about 50/50 mix of
133 * diskless and diskful clients running SunOS 4.0.
156 /* Prototype decls */
157 int setmix(FILE *fp);
159 void init_logfile(void);
160 void init_counters(void);
161 void get_delta(struct count *start, struct count *cur);
162 void init_testdir(int dirnum, char *parentdir);
163 void do_op(int rpct);
166 int createfile(void);
169 void collect_counters(void);
170 int check_counters(void);
172 void msec_sleep(int msecs);
173 void get_opct(struct count *count);
174 int substr(char *sp, char *subsp);
175 int check_access(struct stat statb);
176 void error(char *str);
179 * NFS operations generator routines
201 * Operations generator vector
204 int (*funct)(); /* op */
229 #define DIRSTR "dir" /* directory */
230 #define SYMSTR "sym" /* symbolic link */
231 #define LINSTR "lin" /* hard link */
233 struct timeval Optime[NOPS+1]; /* cumulative running time for ops */
234 struct count Curct; /* total number ops called */
235 int Openfd[NFILES]; /* open file descriptors */
236 int Curnum; /* current file number */
237 int Symnum; /* current symlink file number */
238 int Linknum; /* current link file number */
239 int Dirnum; /* current directory number */
240 DIR *Testdir; /* my test directory */
241 char Testdirname[MAXNAMLEN*2]; /* my test directory name */
242 char Curname[MAXNAMLEN]; /* current file name */
243 char Dirname[MAXNAMLEN]; /* current directory name */
244 char Symname[MAXNAMLEN]; /* symlink file name */
245 char Linkname[MAXNAMLEN]; /* link file name */
246 char *Otherspec = "%s/%03d"; /* sprintf spec for other names */
247 char *Rename1 = "rename1"; /* first name of rename pair */
248 char *Rename2 = "rename2"; /* second name of rename pair */
249 char *Symlinkpath = "./symlinknamelongstuff";
250 /* symlink file data */
251 char *Myname; /* name program invoked under */
252 char Namebuf[MAXNAMLEN]; /* unique name for this program */
253 int Log; /* synchronization log */
254 char Logname[MAXNAMLEN]; /* synchronization log name */
255 int Kmem; /* /dev/kmem file descriptor */
256 off_t Statoffset; /* offset to op count in NFS stats */
257 int Nprocs; /* sub-processes started */
258 int Verbose; /* print more info */
259 int Testop = -1; /* operation to test */
260 int Saveerrno; /* place to save errno */
262 #define subtime(t1, t2) {if ((t1.tv_usec -= t2.tv_usec) >= 1000000) {\
263 t1.tv_sec += (t1.tv_usec / 1000000); \
264 t1.tv_usec %= 1000000; \
265 } else if (t1.tv_usec < 0) { \
266 t1.tv_usec += 1000000; \
269 t1.tv_sec -= t2.tv_sec; \
272 #define addtime(t1, t2) {if ((t1.tv_usec += t2.tv_usec) >= 1000000) {\
273 t1.tv_sec += (t1.tv_usec / 1000000); \
274 t1.tv_usec %= 1000000; \
275 } else if (t1.tv_usec < 0) { \
276 t1.tv_usec += 1000000; \
279 t1.tv_sec += t2.tv_sec; \
283 * Used to catch the parent's "start" signal
299 (void) unlink(Logname);
304 main(int argc, char **argv)
306 int runtime; /* length of run, in seconds */
307 int load; /* load factor, in client loads */
308 int ncalls; /* total number of calls to make */
309 int avgmspc; /* average millisec per call */
310 int mspc; /* millisec per call */
311 int wantcalls; /* ncalls that should have happend by now */
312 int pid; /* process id */
313 int delay; /* msecs since last checked current time */
314 int randnum; /* a random number */
315 int oldmask; /* saved signal mask */
316 int sampletime; /* secs between reading kernel stats */
317 char *opts; /* option parsing */
321 struct timeval curtime;
322 struct timeval starttime;
323 struct count startct;
325 char workdir[MAXPATHLEN];
344 while (argc && **argv == '-') {
351 * Set number of calls
353 if (!isdigit(argv[1][0])) {
354 (void) fprintf(stderr,
355 "%s: illegal calls value %s\n",
359 ncalls = atoi(argv[1]);
368 if (!isdigit(argv[1][0])) {
369 (void) fprintf(stderr,
370 "%s: illegal load value %s\n",
374 load = atoi(argv[1]);
381 * Set mix from a file
383 if ((fp = fopen(argv[1], "r")) == NULL) {
385 (void) fprintf(stderr,
386 "%s: bad mix file", Myname);
391 if (setmix(fp) < 0) {
401 * Set number of child processes
403 if (!isdigit(argv[1][0])) {
404 (void) fprintf(stderr,
405 "%s: illegal procs value %s\n",
409 Nprocs = atoi(argv[1]);
416 * Set test mode, number following is opnum
418 if (!isdigit(argv[1][0])) {
419 (void) fprintf(stderr,
420 "%s: illegal test value %s\n",
424 Testop = atoi(argv[1]);
425 if (Testop >= NOPS) {
426 (void) fprintf(stderr,
427 "%s: illegal test value %d\n",
439 if (!isdigit(argv[1][0])) {
440 (void) fprintf(stderr,
441 "%s: illegal time value %s\n",
445 runtime = atoi(argv[1]);
468 init_logfile(); /* Set up synchronizatin log file */
470 if (getcwd(workdir, sizeof(workdir)) == (char *) 0) {
472 (void) fprintf(stderr,
473 "%s: can't find current directory ", Myname);
479 (void) signal(SIGINT, cleanup);
480 (void) signal(SIGUSR1, startup);
481 oldmask = sigblock(sigmask(SIGUSR1));
485 ncalls = DEFAULT_CALLS;
487 ncalls = runtime * load;
490 avgmspc = Nprocs * 1000 / load;
495 for (procnum = 0; procnum < Nprocs; procnum++) {
496 if ((pid = fork()) == -1) {
498 (void) fprintf(stderr, "%s: can't fork ", Myname);
501 (void) kill(0, SIGINT);
513 * Parent: wait for kids to get ready, start them, wait for them to
514 * finish, read and accumulate results.
518 * wait for kids to initialize
522 if (fstat(Log, &statb) == -1) {
523 (void) fprintf(stderr, "%s: can't stat log %s",
525 (void) kill(0, SIGINT);
528 } while (statb.st_size != Nprocs);
530 if (ftruncate(Log, 0L) == -1) {
531 (void) fprintf(stderr, "%s: can't truncate log %s",
533 (void) kill(0, SIGINT);
541 * Be sure there isn't something else going on
545 get_delta(&startct, &Curct);
546 if (Curct.total > 20) {
547 (void) fprintf(stderr,
548 "%s: too much background activity (%d calls/sec)\n",
549 Myname, Curct.total);
550 (void) kill(0, SIGINT);
562 (void) kill(0, SIGUSR1);
565 * Kids started, wait for first one to finish, signal the
566 * rest and wait for them to finish.
568 if (wait((union wait *) 0) != -1) {
569 (void) kill(0, SIGUSR1);
570 while (wait((union wait *) 0) != -1)
575 * Initialize and sum up counters
578 get_delta(&startct, &Curct);
580 if (check_counters() == -1) {
586 (void) unlink(Logname);
592 * Children: initialize, then notify parent through log file,
593 * wait to get signal, beat the snot out of the server, write
594 * stats to the log file, and exit.
598 * Change my name for error logging
600 (void) sprintf(Namebuf, "%s%d", Myname, procnum);
604 * Initialize and cd to test directory
607 init_testdir(procnum, argv[procnum % argc]);
609 init_testdir(procnum, ".");
611 if ((Testdir = opendir(".")) == NULL) {
613 (void) fprintf(stderr,
614 "%s: can't open test directory ", Myname);
624 * Tell parent I'm ready then wait for go ahead
626 if (write(Log, " ", 1) != 1) {
627 (void) fprintf(stderr, "%s: can't write sync file %s",
629 (void) kill(0, SIGINT);
636 * Initialize counters
639 (void) gettimeofday(&starttime, (struct timezone *)NULL);
640 sampletime = starttime.tv_sec + ((int) random()) % (2 * SAMPLETIME);
644 * Do pseudo NFS operations and adapt to dynamic changes in load
645 * by adjusting the sleep time between operations based on the
646 * number of calls that should have occured since starttime and
647 * the number that have actually occured. A delay is used to avoid
648 * doing gettimeofday calls too often, and a sampletime is
649 * used to avoid reading kernel NFS stats too often.
650 * If parent interrupts, get out and clean up.
655 randnum = (int) random();
657 msec_sleep(randnum % (mspc << 1));
661 * Do the NFS operation
662 * We use a random number from 0-199 to avoid starvation
663 * of the operations at the end of the mix.
665 do_op(randnum % 200);
668 * Do a gettimeofday call only once per second
671 if (delay > 1000 || Curct.total >= ncalls) {
673 (void) gettimeofday(&curtime, (struct timezone *)NULL);
676 * If sample time is up, check the kernel stats
677 * and adjust our parameters to either catch up or
680 if (curtime.tv_sec > sampletime ||
681 Curct.total >= ncalls) {
682 sampletime = curtime.tv_sec + SAMPLETIME;
683 get_delta(&startct, &Curct);
684 if (Curct.total >= ncalls) {
688 ((curtime.tv_sec - starttime.tv_sec) * 1000
689 +(curtime.tv_usec-starttime.tv_usec) / 1000)
691 pct = 1000 * (Curct.total - wantcalls) / ncalls;
692 mspc = avgmspc + avgmspc * pct / 20;
695 * mspc must be positive or we will
696 * never advance time.
705 * Store total time in last slot of counts array
707 Optime[NOPS].tv_sec = curtime.tv_sec - starttime.tv_sec;
708 Optime[NOPS].tv_usec = curtime.tv_usec - starttime.tv_usec;
711 * write stats to log file (append mode)
713 if (write(Log, (char *)Optime, sizeof (Optime)) == -1) {
715 (void) fprintf(stderr, "%s: can't write log ", Myname);
718 (void) kill(0, SIGINT);
727 * Initialize test directory
729 * If the directory already exists, check to see that all of the
730 * files exist and we can write them. If directory doesn't exist
731 * create it and fill it using the LOOKUP and WRITE ops.
732 * Chdir to the directory.
735 init_testdir(int dirnum, char *parentdir)
742 (void) sprintf(Testdirname, "%s/testdir%d", parentdir, dirnum);
743 if (stat(Testdirname, &statb) == -1) {
744 if (mkdir(Testdirname, 0777) == -1) {
746 (void) fprintf(stderr,
747 "%s: can't create test directory ", Myname);
750 (void) kill(0, SIGINT);
753 if (chdir(Testdirname) == -1) {
755 (void) fprintf(stderr,
756 "%s: can't cd to test directory ", Myname);
759 (void) kill(0, SIGINT);
764 * create some files with long names and average size
766 for (i = 0; i < NFILES; i++) {
769 if (Openfd[Curnum] == 0 || writefile() == 0) {
771 (void) fprintf(stderr,
772 "%s: can't create test file '%s'\n",
776 (void) kill(0, SIGINT);
781 if (chdir(Testdirname) == -1) {
783 (void) fprintf(stderr,
784 "%s: can't cd to test directory ", Myname);
787 (void) kill(0, SIGINT);
792 * Verify that we can read and write the test dir
794 if (check_access(statb) == -1) {
795 (void) fprintf(stderr,
796 "%s: wrong permissions on test dir %s\n",
797 Myname, Testdirname);
798 (void) kill(0, SIGINT);
803 * Verify that we can read and write all the files
805 for (i = 0; i < NFILES; i++) {
807 if (stat(Curname, &statb) == -1 || statb.st_size == 0) {
809 * File doesn't exist or is 0 size
812 if (Openfd[Curnum] == 0 || writefile() == 0) {
813 (void) kill(0, SIGINT);
816 } else if (check_access(statb) == -1) {
818 * should try to remove and recreate it
820 (void) fprintf(stderr,
821 "%s: wrong permissions on testfile %s\n",
823 (void) kill(0, SIGINT);
825 } else if (Openfd[Curnum] == 0) {
827 if (Openfd[Curnum] == 0) {
828 (void) kill(0, SIGINT);
836 * Start with Rename1 and no Rename2 so the
837 * rename op can ping pong back and forth.
839 (void) unlink(Rename2);
840 if ((fd = open(Rename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) == -1) {
842 (void) fprintf(stderr, "%s: can't create rename file ", Myname);
845 (void) kill(0, SIGINT);
850 * Remove and recreate the test sub-directories
851 * for mkdir symlink and hard link.
853 (void) sprintf(cmd, "rm -rf %s %s %s", DIRSTR, SYMSTR, LINSTR);
854 if (system(cmd) != 0) {
855 (void) fprintf(stderr, "%s: can't %s\n", Myname, cmd);
856 (void) kill(0, SIGINT);
860 if (mkdir(DIRSTR, 0777) == -1) {
861 (void) fprintf(stderr,
862 "%s: can't create subdir %s\n", Myname, DIRSTR);
863 (void) kill(0, SIGINT);
867 if (mkdir(SYMSTR, 0777) == -1) {
868 (void) fprintf(stderr,
869 "%s: can't create subdir %s\n", Myname, SYMSTR);
870 (void) kill(0, SIGINT);
875 if (mkdir(LINSTR, 0777) == -1) {
876 (void) fprintf(stderr, "%s: can't create subdir %s\n", Myname,
878 (void) kill(0, SIGINT);
886 * The routines below attempt to do over-the-wire operations.
887 * Each op tries to cause one or more of a particular
888 * NFS operation to go over the wire. OPs return the number
889 * of OTW calls they think they have generated.
891 * An array of open file descriptors is kept for the files in each
892 * test directory. The open fd's are used to get access to the files
893 * without generating lookups. An fd value of 0 mean the corresponding
894 * file name is closed. Ops that need a name use Curname.
898 * Call an op based on a random number and the current
899 * op calling weights. Op weights are derived from the
900 * mix percentage and the current NFS stats mix percentage.
914 for (opnum = rpct % NOPS; rpct >= 0; opnum = (opnum + 1) % NOPS) {
916 oppct = (Curct.calls[opnum] * 100) / Curct.total;
921 * Weight is mix percent - (how far off we are * fudge)
922 * fudge factor is required because some ops (read, write)
923 * generate many NFS calls for a single op call
925 weight = Mix[opnum] - ((oppct - Mix[opnum]) << 4);
931 if (opnum == RMDIR && Dirnum == 0) {
933 } else if (opnum != CREATE && opnum != LOOKUP &&
938 if (Openfd[Curnum] == 0) {
950 * Call an op generator and keep track of its running time
955 struct timeval start;
959 (void) gettimeofday(&start, (struct timezone *)NULL);
960 nops = (*Op_vect[opnum].funct)();
961 (void) gettimeofday(&stop, (struct timezone *)NULL);
962 stop.tv_sec -= start.tv_sec;
963 stop.tv_usec -= start.tv_usec;
967 * SunOS 4.0 does a lookup and a getattr on each open
968 * so we have to account for that in the getattr op
970 if (opnum == GETATTR && nops == 2) {
974 Curct.total += Nprocs;
975 Curct.calls[LOOKUP] += Nprocs;
976 addtime(Optime[LOOKUP], stop);
982 Curct.calls[opnum] += nops;
983 addtime(Optime[opnum], stop);
987 * Advance file number (Curnum) and name (Curname)
992 static char *numpart = NULL;
995 Curnum = (Curnum + 1) % NFILES;
996 if (numpart == NULL) {
997 (void) sprintf(Curname, "%03dabcdefghijklmn", Curnum);
1001 numpart[0] = '0' + num / 100;
1003 numpart[1] = '0' + num / 10;
1005 numpart[2] = '0' + num;
1016 fd = Openfd[Curnum];
1018 if ((fd && close(fd) == -1) ||
1019 (fd = open(Curname, O_CREAT|O_RDWR|O_TRUNC, 0666)) == -1) {
1024 Openfd[Curnum] = fd;
1035 fd = Openfd[Curnum];
1036 if (fd == 0 && (fd = open(Curname, O_RDWR, 0666)) == -1) {
1041 Openfd[Curnum] = fd;
1055 fd = Openfd[Curnum];
1057 if (lseek(fd, 0L, 0) == (off_t) -1) {
1058 error("write: lseek");
1062 randnum = (int) random();
1063 bufs = randnum % 100; /* using this for distribution desired */
1065 * Attempt to create a distribution of file sizes
1066 * to reflect reality. Most files are small,
1067 * but there are a few files that are very large.
1069 * The sprite paper (USENIX 198?) claims :
1070 * 50% of all files are < 2.5K
1071 * 80% of all file accesses are to files < 10K
1072 * 40% of all file I/O is to files > 25K
1074 * static examination of the files in our file system
1075 * seems to support the claim that 50% of all files are
1079 bufs = (randnum % 3) + 1;
1081 } else if (bufs < 97) {
1082 bufs = (randnum % 6) + 1;
1089 for (wrote = 0; wrote < bufs; wrote++) {
1090 if (write(fd, buf, size) == -1) {
1108 * Generate a getattr call by fstat'ing the current file
1109 * or by closing and re-opening it. This helps to keep the
1110 * attribute cache cold.
1117 if ((random() % 2) == 0) {
1118 (void) close(Openfd[Curnum]);
1120 if (openfile() == -1) {
1125 if (fstat(Openfd[Curnum], &statb) == -1) {
1132 int op_setattr(void)
1135 if (fchmod(Openfd[Curnum], 0666) == -1) {
1151 * Generate a lookup by stat'ing the current name.
1157 if (stat(Curname, &statb) == -1) {
1172 fd = Openfd[Curnum];
1174 if (lseek(fd, 0L, 0) == (off_t) -1) {
1175 error("read: lseek");
1179 while ((got = read(fd, buf, sizeof (buf))) > 0) {
1186 bufs++; /* did one extra read to find EOF */
1192 int op_wrcache(void)
1207 (void) fsync(Openfd[Curnum]);
1216 if (createfile() == -1) {
1229 got = unlink(Linkname);
1231 (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
1232 } else if (Symnum > 1) {
1233 got = unlink(Symname);
1235 (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
1237 fd = Openfd[Curnum];
1239 if (fd && (close(fd) == -1)) {
1240 error("remove: close");
1243 got = unlink(Curname);
1258 if (toggle++ & 01) {
1259 got = rename(Rename2, Rename1);
1261 got = rename(Rename1, Rename2);
1274 (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
1275 if (link(Curname, Linkname) == -1) {
1282 int op_readlink(void)
1284 char buf[MAXPATHLEN];
1290 if (readlink(Symname, buf, sizeof (buf)) == -1) {
1297 int op_symlink(void)
1301 (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
1302 if (symlink(Symlinkpath, Symname) == -1) {
1313 (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
1314 if (mkdir(Dirname, 0777) == -1) {
1328 if (rmdir(Dirname) == -1) {
1332 (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
1337 int op_readdir(void)
1341 while (readdir(Testdir) != (struct dirent *)NULL)
1349 struct statfs statfsb;
1351 if (statfs(".", &statfsb) == -1) {
1363 * Read counter arrays out of log file and accumulate them in "Optime"
1366 collect_counters(void)
1371 (void) lseek(Log, 0L, 0);
1373 for (i = 0; i < Nprocs; i++) {
1374 struct timeval buf[NOPS+1];
1376 if (read(Log, (char *)buf, sizeof (buf)) == -1) {
1378 (void) fprintf(stderr, "%s: can't read log ", Myname);
1381 (void) kill(0, SIGINT);
1385 for (j = 0; j < NOPS+1; j++) {
1386 addtime(Optime[j], buf[j]);
1392 * Check consistance of results
1395 check_counters(void)
1403 for (i = 0; i < NOPS; i++) {
1404 got = Curct.calls[i] * 10000 / Curct.total;
1405 want = Mix[i] * 100;
1407 mixdiff += got - want;
1409 mixdiff += want - got;
1412 if (mixdiff > 1000) {
1413 (void) fprintf(stdout,
1414 "%s: INVALID RUN, mix generated is off by %d.%02d%%\n",
1415 Myname, mixdiff / 100, mixdiff % 100);
1433 for (i = 0; i < NOPS; i++) {
1434 totalmsec += Optime[i].tv_sec * 1000;
1435 totalmsec += Optime[i].tv_usec / 1000;
1439 const char *format = sizeof (Optime[0].tv_sec) == sizeof (long)
1440 ? "%-10s%3d%% %2d.%02d%% %6d %4ld.%02ld %4d.%02d %2d.%02d%%\n"
1441 : "%-10s%3d%% %2d.%02d%% %6d %4d.%02d %4d.%02d %2d.%02d%%\n";
1442 (void) fprintf(stdout,
1443 "op want got calls secs msec/call time %%\n");
1444 for (i = 0; i < NOPS; i++) {
1445 msec = Optime[i].tv_sec * 1000
1446 + Optime[i].tv_usec / 1000;
1447 (void) fprintf(stdout, format,
1449 Curct.calls[i] * 100 / Curct.total,
1450 (Curct.calls[i] * 100 % Curct.total)
1451 * 100 / Curct.total,
1453 Optime[i].tv_sec, Optime[i].tv_usec / 10000,
1455 ? msec / Curct.calls[i]
1458 ? (msec % Curct.calls[i]) * 100 / Curct.calls[i]
1460 msec * 100 / totalmsec,
1461 (msec * 100 % totalmsec) * 100 / totalmsec);
1465 runtime = Optime[NOPS].tv_sec / Nprocs;
1466 (void) fprintf(stdout,
1467 "%d sec %d calls %d.%02d calls/sec %d.%02d msec/call\n",
1468 runtime, Curct.total,
1469 Curct.total / runtime,
1470 ((Curct.total % runtime) * 100) / runtime,
1471 totalmsec / Curct.total,
1472 ((totalmsec % Curct.total) * 100) / Curct.total);
1476 * Use select to sleep for some number of milliseconds
1477 * granularity is 20 msec
1480 msec_sleep(int msecs)
1482 struct timeval sleeptime;
1487 sleeptime.tv_sec = msecs / 1000;
1488 sleeptime.tv_usec = (msecs % 1000) * 1000;
1490 if (select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &sleeptime) == -1){
1492 (void) fprintf(stderr, "%s: select failed ", Myname);
1495 (void) kill(0, SIGINT);
1501 * Open the synchronization file with append mode
1507 (void) sprintf(Logname, "/tmp/nhfsstone%d", getpid());
1508 if ((Log = open(Logname, O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0666)) == -1){
1510 (void) fprintf(stderr,
1511 "%s: can't open log file %s ", Myname, Logname);
1527 for (i = 0; i < NOPS; i++) {
1529 Optime[i].tv_sec = 0;
1530 Optime[i].tv_usec = 0;
1532 Optime[NOPS].tv_sec = 0;
1533 Optime[NOPS].tv_usec = 0;
1537 * Set cur = cur - start
1540 get_delta(struct count *start, struct count *cur)
1545 cur->total -= start->total;
1546 for (i = 0; i < NOPS; i++) {
1547 cur->calls[i] -= start->calls[i];
1555 get_opct(struct count *count)
1557 static FILE *fp = NULL;
1561 if (fp == NULL && !(fp = fopen("/proc/net/rpc/nfs", "r"))) {
1562 perror("/proc/net/rpc/nfs");
1563 (void) kill(0, SIGINT);
1570 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
1571 char *sp, *line = buffer;
1573 if ((sp = strchr(line, '\n')) != NULL)
1575 if (!(sp = strtok(line, " \t")) || strcmp(line, "proc2"))
1577 if (!(sp = strtok(NULL, " \t")))
1580 for (i = 0; i < 18; i++) {
1581 if (!(sp = strtok(NULL, " \t")))
1583 /* printf("call %d -> %s\n", i, sp); */
1584 count->calls[i] = atoi(sp);
1585 count->total += count->calls[i];
1587 /* printf("total calls %d\n", count->total); */
1594 fprintf(stderr, "parse error in /proc/net/rpc/nfs!\n");
1599 #define LINELEN 128 /* max bytes/line in mix file */
1601 #define MIX_DATALINE 1
1603 #define MIX_FIRSTLINE 3
1607 * Assumes that the input file is in the same format as
1608 * the output of the nfsstat(8) command.
1610 * Uses a simple state transition to keep track of what to expect.
1611 * Parsing is done a line at a time.
1613 * State Input action New state
1614 * MIX_START ".*nfs:.*" skip one line MIX_FIRSTLINE
1615 * MIX_FIRSTLINE ".*[0-9]*.*" get ncalls MIX_DATALINE
1616 * MIX_DATALINE "[0-9]* [0-9]*%"X6 get op counts MIX_DATALINE
1617 * MIX_DATALINE "[0-9]* [0-9]*%"X4 get op counts MIX_DONE
1618 * MIX_DONE EOF return
1633 while (state != MIX_DONE && fgets(line, LINELEN, fp)) {
1639 if (len >= 4 && substr(line, "nfs:")) {
1640 if (fgets(line, LINELEN, fp) == NULL) {
1641 (void) fprintf(stderr,
1642 "%s: bad mix format: unexpected EOF after 'nfs:'\n", Myname);
1645 state = MIX_FIRSTLINE;
1650 got = sscanf(line, "%d", &calls);
1652 (void) fprintf(stderr,
1653 "%s: bad mix format: can't find 'calls' value %d\n", Myname, got);
1656 if (fgets(line, LINELEN, fp) == NULL) {
1657 (void) fprintf(stderr,
1658 "%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
1661 state = MIX_DATALINE;
1666 "%d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%%",
1667 &Mix[opnum], &Mix[opnum+1], &Mix[opnum+2], &Mix[opnum+3],
1668 &Mix[opnum+4], &Mix[opnum+5], &Mix[opnum+6]);
1669 if (got == 4 && opnum == 14) {
1671 * looks like the last line
1674 } else if (got == 7) {
1676 if (fgets(line, LINELEN, fp) == NULL) {
1677 (void) fprintf(stderr,
1678 "%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
1682 (void) fprintf(stderr,
1683 "%s: bad mix format: can't find %d op values\n", Myname, got);
1688 (void) fprintf(stderr,
1689 "%s: unknown state %d\n", Myname, state);
1693 if (state != MIX_DONE) {
1694 (void) fprintf(stderr,
1695 "%s: bad mix format: unexpected EOF\n", Myname);
1698 for (opnum = 0; opnum < NOPS; opnum++) {
1699 Mix[opnum] = Mix[opnum] * 100 / calls
1700 + ((Mix[opnum] * 1000 / calls % 10) >= 5);
1706 * return true if sp contains the substring subsp, false otherwise
1709 substr(char *sp, char *subsp)
1715 if (sp == NULL || subsp == NULL) {
1719 want = strlen(subsp);
1721 while (*sp != '\0') {
1722 while (*sp != *subsp && *sp != '\0') {
1727 while (*sp == *s2) {
1732 if (found == want) {
1740 * check to make sure that we have
1741 * both read and write permissions
1742 * for this file or directory.
1745 check_access(struct stat statb)
1748 gid_t gidset[NGROUPS];
1751 if (statb.st_uid == getuid()) {
1752 if ((statb.st_mode & 0200) && (statb.st_mode & 0400)) {
1759 gidsetlen = NGROUPS;
1761 if (getgroups(gidsetlen, gidset) == -1) {
1762 perror("getgroups");
1766 for (i = 0; i < NGROUPS; i++) {
1767 if (statb.st_gid == gidset[i]) {
1768 if ((statb.st_mode & 020) && (statb.st_mode & 040)) {
1776 if ((statb.st_mode & 02) && (statb.st_mode & 04)) {
1787 (void) fprintf(stderr, "usage: %s [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs] [-m mixfile] [dir]...\n", Myname);
1795 (void) fprintf(stderr, "%s: op failed: %s ", Myname, str);