+++ /dev/null
-#if 0
-static char sccsid[] = "@(#)nhfsstone.c 1.22 90/05/08 Copyright (c) 1990, Legato Systems Inc";
-#endif
-
-/*
- * Copyright (c) 1990 Legato Systems Inc.
- *
- * See DISCLAIMER file for restrictions
- *
- * Ported to Linux by Olaf Kirch <okir@monad.swb.de>
- */
-
-#include "config.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/param.h>
-#include <sys/time.h>
-#include <sys/vfs.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#ifdef BSD
-#include <sys/dir.h>
-#define dirent direct
-#else
-#include <dirent.h>
-#endif
-#include <signal.h>
-
-#ifndef NULL
-#define NULL 0
-#endif
-
-/*
- * Usage: nhfsstone [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs]
- * [-m mixfile] [dir]...
- *
- * Generates an artifical NFS client load based on a given mix of
- * operations.
- *
- * Strategy: loop for some number of NFS calls doing a random sleep
- * followed by a call to one of the op generator routines. The routines
- * are called based on a weighting factor determined by the difference
- * between the current ops percentages (derived from kernel NFS stats)
- * and a set of default percentages or a mix supplied by the caller.
- *
- * The generator routines try very hard to guess how many NFS operations
- * they are generating so that the calling routine can keep a running
- * estimate of the number of calls and the mix to avoid having to get
- * the NFS statistics from the kernel too often.
- *
- * The operations are done in a directory that has a set of file names
- * that are long enough that they won't be cached by the name cache
- * in the kernel. The "lookup" operation steps through the names and
- * creates a file if that name does not exist, or closes and reopens it
- * if it does. This generates a table of open file descriptors. Most of the
- * other operations are done on random descriptors in the table. The "getattr"
- * operation tries to avoid the kernel attribute cache by doing "fstat"
- * system calls on random descriptors in the table. There must be enough
- * files in the directory so that, on average, the getattr operation hits
- * any file less often than once each 6 seconds (the default timeout for
- * the attributes cache).
- *
- * The parent process starts children to do the real work of generating load.
- * The parent coordinates them so that they all start at the same time, and
- * collects statistics from them when they are done. To coordinate the
- * start up, the parent waits for each child to write one byte into
- * a common log file (opened in append mode to avoid overwriting).
- * After they write a byte the children pause, and the parent send SIGUSR1
- * when it has heard from all of the kids. The children write their statistics
- * into the same common log file and the parent reads and accumulates the
- * statics and prints them out.
- *
- * This code will only compile and run on 4.X BSD based systems.
- */
-
-#define DEFAULT_LOAD 30 /* default calls per sec */
-#define DEFAULT_CALLS 5000 /* default number of calls */
-#define NFILES 40 /* number of test files/dir */
-#define BUFSIZE 8192 /* block size for read and write */
-#define MAXFILESIZE 32 /* size, in blocks, of large file */
-#define SAMPLETIME 5 /* secs between samples of NFS stats */
-#define NPROCS 7 /* number of children to run */
-
-
-/*
- * The names of NFS operations
- */
-char *Opnames[] = {
- "null", "getattr", "setattr", "root", "lookup", "readlink", "read",
- "wrcache", "write", "create", "remove", "rename", "link", "symlink",
- "mkdir", "rmdir", "readdir", "fsstat",
-};
-
-/*
- * NFS operation numbers
- *
- * Used to index the Opnames, Mix and statistics arrays.
- */
-#define NOPS 18 /* number of NFS ops */
-#define NULLCALL 0
-#define GETATTR 1
-#define SETATTR 2
-#define ROOT 3
-#define LOOKUP 4
-#define READLINK 5
-#define READ 6
-#define WRCACHE 7
-#define WRITE 8
-#define CREATE 9
-#define REMOVE 10
-#define RENAME 11
-#define LINK 12
-#define SYMLINK 13
-#define MKDIR 14
-#define RMDIR 15
-#define READDIR 16
-#define FSSTAT 17
-
-/*
- * Operations counts
- */
-struct count {
- int total;
- int calls[NOPS];
-};
-
-/*
- * Software development mix for server with about 50/50 mix of
- * diskless and diskful clients running SunOS 4.0.
- */
-int Mix[NOPS] = {
- 0, /* null */
- 13, /* getattr */
- 1, /* setattr */
- 0, /* root */
- 34, /* lookup */
- 8, /* readlink */
- 22, /* read */
- 0, /* wrcache */
- 15, /* write */
- 2, /* create */
- 1, /* remove */
- 0, /* rename */
- 0, /* link */
- 0, /* symlink */
- 0, /* mkdir */
- 0, /* rmdir */
- 3, /* readdir */
- 1, /* fsstat */
-};
-
-/* Prototype decls */
-int setmix(FILE *fp);
-void usage(void);
-void init_logfile(void);
-void init_counters(void);
-void get_delta(struct count *start, struct count *cur);
-void init_testdir(int dirnum, char *parentdir);
-void do_op(int rpct);
-void op(int opnum);
-void nextfile(void);
-int createfile(void);
-int openfile(void);
-int writefile(void);
-void collect_counters(void);
-int check_counters(void);
-void print(void);
-void msec_sleep(int msecs);
-void get_opct(struct count *count);
-int substr(char *sp, char *subsp);
-int check_access(struct stat statb);
-void error(char *str);
-
-/*
- * NFS operations generator routines
- */
-int op_null();
-int op_getattr();
-int op_setattr();
-int op_root();
-int op_lookup();
-int op_readlink();
-int op_read();
-int op_wrcache();
-int op_write();
-int op_create();
-int op_remove();
-int op_rename();
-int op_link();
-int op_symlink();
-int op_mkdir();
-int op_rmdir();
-int op_readdir();
-int op_fsstat();
-
-/*
- * Operations generator vector
- */
-struct op_vect {
- int (*funct)(); /* op */
-} Op_vect[NOPS] = {
- { op_null },
- { op_getattr },
- { op_setattr },
- { op_root },
- { op_lookup },
- { op_readlink },
- { op_read },
- { op_wrcache },
- { op_write },
- { op_create },
- { op_remove },
- { op_rename },
- { op_link },
- { op_symlink },
- { op_mkdir },
- { op_rmdir },
- { op_readdir },
- { op_fsstat },
-};
-
-/*
- * Name sub-strings
- */
-#define DIRSTR "dir" /* directory */
-#define SYMSTR "sym" /* symbolic link */
-#define LINSTR "lin" /* hard link */
-
-struct timeval Optime[NOPS+1]; /* cumulative running time for ops */
-struct count Curct; /* total number ops called */
-int Openfd[NFILES]; /* open file descriptors */
-int Curnum; /* current file number */
-int Symnum; /* current symlink file number */
-int Linknum; /* current link file number */
-int Dirnum; /* current directory number */
-DIR *Testdir; /* my test directory */
-char Testdirname[MAXNAMLEN*2]; /* my test directory name */
-char Curname[MAXNAMLEN]; /* current file name */
-char Dirname[MAXNAMLEN]; /* current directory name */
-char Symname[MAXNAMLEN]; /* symlink file name */
-char Linkname[MAXNAMLEN]; /* link file name */
-char *Otherspec = "%s/%03d"; /* sprintf spec for other names */
-char *Rename1 = "rename1"; /* first name of rename pair */
-char *Rename2 = "rename2"; /* second name of rename pair */
-char *Symlinkpath = "./symlinknamelongstuff";
- /* symlink file data */
-char *Myname; /* name program invoked under */
-char Namebuf[MAXNAMLEN]; /* unique name for this program */
-int Log; /* synchronization log */
-char Logname[MAXNAMLEN]; /* synchronization log name */
-int Kmem; /* /dev/kmem file descriptor */
-off_t Statoffset; /* offset to op count in NFS stats */
-int Nprocs; /* sub-processes started */
-int Verbose; /* print more info */
-int Testop = -1; /* operation to test */
-int Saveerrno; /* place to save errno */
-
-#define subtime(t1, t2) {if ((t1.tv_usec -= t2.tv_usec) >= 1000000) {\
- t1.tv_sec += (t1.tv_usec / 1000000); \
- t1.tv_usec %= 1000000; \
- } else if (t1.tv_usec < 0) { \
- t1.tv_usec += 1000000; \
- t1.tv_sec--; \
- } \
- t1.tv_sec -= t2.tv_sec; \
- }
-
-#define addtime(t1, t2) {if ((t1.tv_usec += t2.tv_usec) >= 1000000) {\
- t1.tv_sec += (t1.tv_usec / 1000000); \
- t1.tv_usec %= 1000000; \
- } else if (t1.tv_usec < 0) { \
- t1.tv_usec += 1000000; \
- t1.tv_sec--; \
- } \
- t1.tv_sec += t2.tv_sec; \
- }
-
-/*
- * Used to catch the parent's "start" signal
- */
-void
-startup()
-{
-
- return;
-}
-
-/*
- * Clean up and exit
- */
-void
-cleanup()
-{
-
- (void) unlink(Logname);
- exit(1);
-}
-
-int
-main(int argc, char **argv)
-{
- int runtime; /* length of run, in seconds */
- int load; /* load factor, in client loads */
- int ncalls; /* total number of calls to make */
- int avgmspc; /* average millisec per call */
- int mspc; /* millisec per call */
- int wantcalls; /* ncalls that should have happend by now */
- int pid; /* process id */
- int delay; /* msecs since last checked current time */
- int randnum; /* a random number */
-#if HAVE_SIGPROCMASK
- sigset_t oldmask; /* saved signal mask */
-#else
- int oldmask; /* saved signal mask */
-#endif
- int sampletime; /* secs between reading kernel stats */
- char *opts; /* option parsing */
- int pct;
- int procnum;
- FILE *fp;
- struct timeval curtime;
- struct timeval starttime;
- struct count startct;
- struct stat statb;
- char workdir[MAXPATHLEN];
- char *getwd();
-
- Myname = argv[0];
-
- argc--;
- argv++;
-
- load = DEFAULT_LOAD;
- ncalls = 0;
- runtime = 0;
- Nprocs = NPROCS;
- pid = 0;
-
- (void) umask(0);
-
- /*
- * Parse options
- */
- while (argc && **argv == '-') {
- opts = &argv[0][1];
- while (*opts) {
- switch (*opts) {
-
- case 'c':
- /*
- * Set number of calls
- */
- if (!isdigit(argv[1][0])) {
- (void) fprintf(stderr,
- "%s: illegal calls value %s\n",
- Myname, argv[1]);
- exit(1);
- }
- ncalls = atoi(argv[1]);
- argv++;
- argc--;
- break;
-
- case 'l':
- /*
- * Set load
- */
- if (!isdigit(argv[1][0])) {
- (void) fprintf(stderr,
- "%s: illegal load value %s\n",
- Myname, argv[1]);
- exit(1);
- }
- load = atoi(argv[1]);
- argv++;
- argc--;
- break;
-
- case 'm':
- /*
- * Set mix from a file
- */
- if ((fp = fopen(argv[1], "r")) == NULL) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: bad mix file", Myname);
- errno = Saveerrno;
- perror("");
- exit(1);
- }
- if (setmix(fp) < 0) {
- exit(1);
- }
- (void) fclose(fp);
- argv++;
- argc--;
- break;
-
- case 'p':
- /*
- * Set number of child processes
- */
- if (!isdigit(argv[1][0])) {
- (void) fprintf(stderr,
- "%s: illegal procs value %s\n",
- Myname, argv[1]);
- exit(1);
- }
- Nprocs = atoi(argv[1]);
- argv++;
- argc--;
- break;
-
- case 'T':
- /*
- * Set test mode, number following is opnum
- */
- if (!isdigit(argv[1][0])) {
- (void) fprintf(stderr,
- "%s: illegal test value %s\n",
- Myname, argv[1]);
- exit(1);
- }
- Testop = atoi(argv[1]);
- if (Testop >= NOPS) {
- (void) fprintf(stderr,
- "%s: illegal test value %d\n",
- Myname, Testop);
- exit(1);
- }
- argv++;
- argc--;
- break;
-
- case 't':
- /*
- * Set running time
- */
- if (!isdigit(argv[1][0])) {
- (void) fprintf(stderr,
- "%s: illegal time value %s\n",
- Myname, argv[1]);
- exit(1);
- }
- runtime = atoi(argv[1]);
- argv++;
- argc--;
- break;
-
- case 'v':
- /*
- * Set verbose mode
- */
- Verbose++;
- break;
-
- default:
- usage();
- exit(1);
-
- }
- opts++;
- }
- argv++;
- argc--;
- }
-
- init_logfile(); /* Set up synchronizatin log file */
-
- if (getcwd(workdir, sizeof(workdir)) == (char *) 0) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't find current directory ", Myname);
- errno = Saveerrno;
- perror("");
- exit(1);
- }
-
- (void) signal(SIGINT, cleanup);
- (void) signal(SIGUSR1, startup);
-#if HAVE_SIGPROCMASK
- {
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGUSR1);
- sigprocmask(SIG_BLOCK, &mask, &oldmask);
- }
-#else
- /*
- * sigblock() is marked deprecated in modern
- * glibc and hence generates a warning.
- */
- oldmask = sigblock(sigmask(SIGUSR1));
-#endif
-
- if (ncalls == 0) {
- if (runtime == 0) {
- ncalls = DEFAULT_CALLS;
- } else {
- ncalls = runtime * load;
- }
- }
- avgmspc = Nprocs * 1000 / load;
-
- /*
- * Fork kids
- */
- for (procnum = 0; procnum < Nprocs; procnum++) {
- if ((pid = fork()) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr, "%s: can't fork ", Myname);
- errno = Saveerrno;
- perror("");
- (void) kill(0, SIGINT);
- exit(1);
- }
- /*
- * Kids go initialize
- */
- if (pid == 0) {
- break;
- }
- }
-
- /*
- * Parent: wait for kids to get ready, start them, wait for them to
- * finish, read and accumulate results.
- */
- if (pid != 0) {
- /*
- * wait for kids to initialize
- */
- do {
- sleep(1);
- if (fstat(Log, &statb) == -1) {
- (void) fprintf(stderr, "%s: can't stat log %s",
- Myname, Logname);
- (void) kill(0, SIGINT);
- exit(1);
- }
- } while (statb.st_size != Nprocs);
-
- if (ftruncate(Log, 0L) == -1) {
- (void) fprintf(stderr, "%s: can't truncate log %s",
- Myname, Logname);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- sync();
- sleep(3);
-
- /*
- * Be sure there isn't something else going on
- */
- get_opct(&startct);
- msec_sleep(2000);
- get_delta(&startct, &Curct);
- if (Curct.total > 20) {
- (void) fprintf(stderr,
- "%s: too much background activity (%d calls/sec)\n",
- Myname, Curct.total);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- /*
- * get starting stats
- */
- get_opct(&startct);
-
- /*
- * Start kids
- */
- (void) kill(0, SIGUSR1);
-
- /*
- * Kids started, wait for first one to finish, signal the
- * rest and wait for them to finish.
- */
- if (wait((union wait *) 0) != -1) {
- (void) kill(0, SIGUSR1);
- while (wait((union wait *) 0) != -1)
- /* nothing */;
- }
-
- /*
- * Initialize and sum up counters
- */
- init_counters();
- get_delta(&startct, &Curct);
- collect_counters();
- if (check_counters() == -1) {
- Verbose = 1;
- }
- print();
-
- (void) close(Log);
- (void) unlink(Logname);
-
- exit(0);
- }
-
- /*
- * Children: initialize, then notify parent through log file,
- * wait to get signal, beat the snot out of the server, write
- * stats to the log file, and exit.
- */
-
- /*
- * Change my name for error logging
- */
- (void) sprintf(Namebuf, "%s%d", Myname, procnum);
- Myname = Namebuf;
-
- /*
- * Initialize and cd to test directory
- */
- if (argc != 0) {
- init_testdir(procnum, argv[procnum % argc]);
- } else {
- init_testdir(procnum, ".");
- }
- if ((Testdir = opendir(".")) == NULL) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't open test directory ", Myname);
- errno = Saveerrno;
- perror(Testdirname);
- exit(1);
- }
-
- init_counters();
- srandom(procnum+1);
-
- /*
- * Tell parent I'm ready then wait for go ahead
- */
- if (write(Log, " ", 1) != 1) {
- (void) fprintf(stderr, "%s: can't write sync file %s",
- Myname, Logname);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
-#if HAVE_SIGPROCMASK
- sigsuspend(&oldmask);
-#else
- sigpause(oldmask);
-#endif
-
- /*
- * Initialize counters
- */
- get_opct(&startct);
- (void) gettimeofday(&starttime, (struct timezone *)NULL);
- sampletime = starttime.tv_sec + ((int) random()) % (2 * SAMPLETIME);
- curtime = starttime;
-
- /*
- * Do pseudo NFS operations and adapt to dynamic changes in load
- * by adjusting the sleep time between operations based on the
- * number of calls that should have occured since starttime and
- * the number that have actually occured. A delay is used to avoid
- * doing gettimeofday calls too often, and a sampletime is
- * used to avoid reading kernel NFS stats too often.
- * If parent interrupts, get out and clean up.
- */
- delay = 0;
- mspc = avgmspc;
- for (;;) {
- randnum = (int) random();
- if (mspc > 0) {
- msec_sleep(randnum % (mspc << 1));
- }
-
- /*
- * Do the NFS operation
- * We use a random number from 0-199 to avoid starvation
- * of the operations at the end of the mix.
- */
- do_op(randnum % 200);
-
- /*
- * Do a gettimeofday call only once per second
- */
- delay += mspc;
- if (delay > 1000 || Curct.total >= ncalls) {
- delay = 0;
- (void) gettimeofday(&curtime, (struct timezone *)NULL);
-
- /*
- * If sample time is up, check the kernel stats
- * and adjust our parameters to either catch up or
- * slow down.
- */
- if (curtime.tv_sec > sampletime ||
- Curct.total >= ncalls) {
- sampletime = curtime.tv_sec + SAMPLETIME;
- get_delta(&startct, &Curct);
- if (Curct.total >= ncalls) {
- break;
- }
- wantcalls =
- ((curtime.tv_sec - starttime.tv_sec) * 1000
- +(curtime.tv_usec-starttime.tv_usec) / 1000)
- * Nprocs / avgmspc;
- pct = 1000 * (Curct.total - wantcalls) / ncalls;
- mspc = avgmspc + avgmspc * pct / 20;
- if (mspc <= 0) {
- /*
- * mspc must be positive or we will
- * never advance time.
- */
- mspc = 10;
- }
- }
- }
- }
-
- /*
- * Store total time in last slot of counts array
- */
- Optime[NOPS].tv_sec = curtime.tv_sec - starttime.tv_sec;
- Optime[NOPS].tv_usec = curtime.tv_usec - starttime.tv_usec;
-
- /*
- * write stats to log file (append mode)
- */
- if (write(Log, (char *)Optime, sizeof (Optime)) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr, "%s: can't write log ", Myname);
- errno = Saveerrno;
- perror("");
- (void) kill(0, SIGINT);
- exit(1);
- }
- (void) close(Log);
-
- exit(0);
-}
-
-/*
- * Initialize test directory
- *
- * If the directory already exists, check to see that all of the
- * files exist and we can write them. If directory doesn't exist
- * create it and fill it using the LOOKUP and WRITE ops.
- * Chdir to the directory.
- */
-void
-init_testdir(int dirnum, char *parentdir)
-{
- int i;
- int fd;
- char cmd[256];
- struct stat statb;
-
- (void) sprintf(Testdirname, "%s/testdir%d", parentdir, dirnum);
- if (stat(Testdirname, &statb) == -1) {
- if (mkdir(Testdirname, 0777) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't create test directory ", Myname);
- errno = Saveerrno;
- perror(Testdirname);
- (void) kill(0, SIGINT);
- exit(1);
- }
- if (chdir(Testdirname) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't cd to test directory ", Myname);
- errno = Saveerrno;
- perror(Testdirname);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- /*
- * create some files with long names and average size
- */
- for (i = 0; i < NFILES; i++) {
- nextfile();
- (void) createfile();
- if (Openfd[Curnum] == 0 || writefile() == 0) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't create test file '%s'\n",
- Myname, Curname);
- errno = Saveerrno;
- perror(Testdirname);
- (void) kill(0, SIGINT);
- exit(1);
- }
- }
- } else {
- if (chdir(Testdirname) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't cd to test directory ", Myname);
- errno = Saveerrno;
- perror(Testdirname);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- /*
- * Verify that we can read and write the test dir
- */
- if (check_access(statb) == -1) {
- (void) fprintf(stderr,
- "%s: wrong permissions on test dir %s\n",
- Myname, Testdirname);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- /*
- * Verify that we can read and write all the files
- */
- for (i = 0; i < NFILES; i++) {
- nextfile();
- if (stat(Curname, &statb) == -1 || statb.st_size == 0) {
- /*
- * File doesn't exist or is 0 size
- */
- (void) createfile();
- if (Openfd[Curnum] == 0 || writefile() == 0) {
- (void) kill(0, SIGINT);
- exit(1);
- }
- } else if (check_access(statb) == -1) {
- /*
- * should try to remove and recreate it
- */
- (void) fprintf(stderr,
- "%s: wrong permissions on testfile %s\n",
- Myname, Curname);
- (void) kill(0, SIGINT);
- exit(1);
- } else if (Openfd[Curnum] == 0) {
- (void) openfile();
- if (Openfd[Curnum] == 0) {
- (void) kill(0, SIGINT);
- exit(1);
- }
- }
- }
- }
-
- /*
- * Start with Rename1 and no Rename2 so the
- * rename op can ping pong back and forth.
- */
- (void) unlink(Rename2);
- if ((fd = open(Rename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr, "%s: can't create rename file ", Myname);
- errno = Saveerrno;
- perror(Rename1);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- /*
- * Remove and recreate the test sub-directories
- * for mkdir symlink and hard link.
- */
- (void) sprintf(cmd, "rm -rf %s %s %s", DIRSTR, SYMSTR, LINSTR);
- if (system(cmd) != 0) {
- (void) fprintf(stderr, "%s: can't %s\n", Myname, cmd);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- if (mkdir(DIRSTR, 0777) == -1) {
- (void) fprintf(stderr,
- "%s: can't create subdir %s\n", Myname, DIRSTR);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- if (mkdir(SYMSTR, 0777) == -1) {
- (void) fprintf(stderr,
- "%s: can't create subdir %s\n", Myname, SYMSTR);
- (void) kill(0, SIGINT);
- exit(1);
- }
- op(SYMLINK);
-
- if (mkdir(LINSTR, 0777) == -1) {
- (void) fprintf(stderr, "%s: can't create subdir %s\n", Myname,
- LINSTR);
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- (void) close(fd);
-}
-
-/*
- * The routines below attempt to do over-the-wire operations.
- * Each op tries to cause one or more of a particular
- * NFS operation to go over the wire. OPs return the number
- * of OTW calls they think they have generated.
- *
- * An array of open file descriptors is kept for the files in each
- * test directory. The open fd's are used to get access to the files
- * without generating lookups. An fd value of 0 mean the corresponding
- * file name is closed. Ops that need a name use Curname.
- */
-
-/*
- * Call an op based on a random number and the current
- * op calling weights. Op weights are derived from the
- * mix percentage and the current NFS stats mix percentage.
- */
-void
-do_op(int rpct)
-{
- int opnum;
- int weight;
- int oppct;
-
- if (Testop != -1) {
- nextfile();
- op(Testop);
- return;
- }
- for (opnum = rpct % NOPS; rpct >= 0; opnum = (opnum + 1) % NOPS) {
- if (Curct.total) {
- oppct = (Curct.calls[opnum] * 100) / Curct.total;
- } else {
- oppct = 0;
- }
- /*
- * Weight is mix percent - (how far off we are * fudge)
- * fudge factor is required because some ops (read, write)
- * generate many NFS calls for a single op call
- */
- weight = Mix[opnum] - ((oppct - Mix[opnum]) << 4);
- if (weight <= 0) {
- continue;
- }
- rpct -= weight;
- if (rpct < 0) {
- if (opnum == RMDIR && Dirnum == 0) {
- op(MKDIR);
- } else if (opnum != CREATE && opnum != LOOKUP &&
- opnum != REMOVE) {
- nextfile();
- }
- op(opnum);
- if (Openfd[Curnum] == 0) {
- op(CREATE);
-#ifdef XXX
- op(WRITE);
-#endif /* XXX */
- }
- return;
- }
- }
-}
-
-/*
- * Call an op generator and keep track of its running time
- */
-void
-op(int opnum)
-{
- struct timeval start;
- struct timeval stop;
- int nops;
-
- (void) gettimeofday(&start, (struct timezone *)NULL);
- nops = (*Op_vect[opnum].funct)();
- (void) gettimeofday(&stop, (struct timezone *)NULL);
- stop.tv_sec -= start.tv_sec;
- stop.tv_usec -= start.tv_usec;
-
-#ifdef SUNOS4
- /*
- * SunOS 4.0 does a lookup and a getattr on each open
- * so we have to account for that in the getattr op
- */
- if (opnum == GETATTR && nops == 2) {
- nops = 1;
- stop.tv_sec /= 2;
- stop.tv_usec /= 2;
- Curct.total += Nprocs;
- Curct.calls[LOOKUP] += Nprocs;
- addtime(Optime[LOOKUP], stop);
- }
-#endif
-
- nops *= Nprocs;
- Curct.total += nops;
- Curct.calls[opnum] += nops;
- addtime(Optime[opnum], stop);
-}
-
-/*
- * Advance file number (Curnum) and name (Curname)
- */
-void
-nextfile(void)
-{
- static char *numpart = NULL;
- int num;
-
- Curnum = (Curnum + 1) % NFILES;
- if (numpart == NULL) {
- (void) sprintf(Curname, "%03dabcdefghijklmn", Curnum);
- numpart = Curname;
- } else {
- num = Curnum;
- numpart[0] = '0' + num / 100;
- num %= 100;
- numpart[1] = '0' + num / 10;
- num %= 10;
- numpart[2] = '0' + num;
- }
-}
-
-int
-createfile(void)
-{
- int ret;
- int fd;
-
- ret = 0;
- fd = Openfd[Curnum];
-
- if ((fd && close(fd) == -1) ||
- (fd = open(Curname, O_CREAT|O_RDWR|O_TRUNC, 0666)) == -1) {
- fd = 0;
- ret = -1;
- error("create");
- }
- Openfd[Curnum] = fd;
- return (ret);
-}
-
-int
-openfile(void)
-{
- int ret;
- int fd;
-
- ret = 0;
- fd = Openfd[Curnum];
- if (fd == 0 && (fd = open(Curname, O_RDWR, 0666)) == -1) {
- fd = 0;
- ret = -1;
- error("open");
- }
- Openfd[Curnum] = fd;
- return (ret);
-}
-
-int
-writefile(void)
-{
- int fd;
- int wrote;
- int bufs;
- int size;
- int randnum;
- char buf[BUFSIZE];
-
- fd = Openfd[Curnum];
-
- if (lseek(fd, 0L, 0) == (off_t) -1) {
- error("write: lseek");
- return (-1);
- }
-
- randnum = (int) random();
- bufs = randnum % 100; /* using this for distribution desired */
- /*
- * Attempt to create a distribution of file sizes
- * to reflect reality. Most files are small,
- * but there are a few files that are very large.
- *
- * The sprite paper (USENIX 198?) claims :
- * 50% of all files are < 2.5K
- * 80% of all file accesses are to files < 10K
- * 40% of all file I/O is to files > 25K
- *
- * static examination of the files in our file system
- * seems to support the claim that 50% of all files are
- * smaller than 2.5K
- */
- if (bufs < 50) {
- bufs = (randnum % 3) + 1;
- size = 1024;
- } else if (bufs < 97) {
- bufs = (randnum % 6) + 1;
- size = BUFSIZE;
- } else {
- bufs = MAXFILESIZE;
- size = BUFSIZE;
- }
-
- for (wrote = 0; wrote < bufs; wrote++) {
- if (write(fd, buf, size) == -1) {
- error("write");
- break;
- }
- }
-
- return (wrote);
-}
-
-int
-op_null(void)
-{
-
- return (1);
-}
-
-
-/*
- * Generate a getattr call by fstat'ing the current file
- * or by closing and re-opening it. This helps to keep the
- * attribute cache cold.
- */
-int
-op_getattr(void)
-{
- struct stat statb;
-
- if ((random() % 2) == 0) {
- (void) close(Openfd[Curnum]);
- Openfd[Curnum] = 0;
- if (openfile() == -1) {
- return (0);
- }
- return (2);
- }
- if (fstat(Openfd[Curnum], &statb) == -1) {
- error("getattr");
- }
- return (1);
-}
-
-
-int op_setattr(void)
-{
-
- if (fchmod(Openfd[Curnum], 0666) == -1) {
- error("setattr");
- }
- return (1);
-}
-
-
-int op_root(void)
-{
-
- error("root");
- return (0);
-}
-
-
-/*
- * Generate a lookup by stat'ing the current name.
- */
-int op_lookup(void)
-{
- struct stat statb;
-
- if (stat(Curname, &statb) == -1) {
- error("lookup");
- }
- return (1);
-}
-
-
-int op_read(void)
-{
- int got;
- int bufs;
- int fd;
- char buf[BUFSIZE];
-
- bufs = 0;
- fd = Openfd[Curnum];
-
- if (lseek(fd, 0L, 0) == (off_t) -1) {
- error("read: lseek");
- return (0);
- }
-
- while ((got = read(fd, buf, sizeof (buf))) > 0) {
- bufs++;
- }
-
- if (got == -1) {
- error("read");
- } else {
- bufs++; /* did one extra read to find EOF */
- }
- return (bufs);
-}
-
-
-int op_wrcache(void)
-{
- error("wrcache");
- return 0;
-}
-
-
-int op_write(void)
-{
- int bufs;
-
- bufs = writefile();
- if (bufs == 0) {
- return (0);
- }
- (void) fsync(Openfd[Curnum]);
-
- return (bufs + 2);
-}
-
-
-int op_create(void)
-{
-
- if (createfile() == -1) {
- return (0);
- }
- return (1);
-}
-
-
-int op_remove(void)
-{
- int fd;
- int got;
-
- if (Linknum > 0) {
- got = unlink(Linkname);
- Linknum--;
- (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
- } else if (Symnum > 1) {
- got = unlink(Symname);
- Symnum--;
- (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
- } else {
- fd = Openfd[Curnum];
-
- if (fd && (close(fd) == -1)) {
- error("remove: close");
- }
- Openfd[Curnum] = 0;
- got = unlink(Curname);
- }
- if (got == -1) {
- error("remove");
- }
- return (1);
-}
-
-
-int toggle = 0;
-
-int op_rename(void)
-{
- int got;
-
- if (toggle++ & 01) {
- got = rename(Rename2, Rename1);
- } else {
- got = rename(Rename1, Rename2);
- }
- if (got == -1) {
- error("rename");
- }
- return (1);
-}
-
-
-int op_link(void)
-{
-
- Linknum++;
- (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
- if (link(Curname, Linkname) == -1) {
- error("link");
- }
- return (1);
-}
-
-
-int op_readlink(void)
-{
- char buf[MAXPATHLEN];
-
- if (Symnum == 0) {
- error("readlink");
- return (0);
- }
- if (readlink(Symname, buf, sizeof (buf)) == -1) {
- error("readlink");
- }
- return (1);
-}
-
-
-int op_symlink(void)
-{
-
- Symnum++;
- (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
- if (symlink(Symlinkpath, Symname) == -1) {
- error("symlink");
- }
- return (1);
-}
-
-
-int op_mkdir(void)
-{
-
- Dirnum++;
- (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
- if (mkdir(Dirname, 0777) == -1) {
- error("mkdir");
- }
- return (1);
-}
-
-
-int op_rmdir(void)
-{
-
- if (Dirnum == 0) {
- error("rmdir");
- return (0);
- }
- if (rmdir(Dirname) == -1) {
- error("rmdir");
- }
- Dirnum--;
- (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
- return (1);
-}
-
-
-int op_readdir(void)
-{
-
- rewinddir(Testdir);
- while (readdir(Testdir) != (struct dirent *)NULL)
- /* nothing */;
- return (1);
-}
-
-
-int op_fsstat(void)
-{
- struct statfs statfsb;
-
- if (statfs(".", &statfsb) == -1) {
- error("statfs");
- }
- return (1);
-}
-
-
-/*
- * Utility routines
- */
-
-/*
- * Read counter arrays out of log file and accumulate them in "Optime"
- */
-void
-collect_counters(void)
-{
- int i;
- int j;
-
- (void) lseek(Log, 0L, 0);
-
- for (i = 0; i < Nprocs; i++) {
- struct timeval buf[NOPS+1];
-
- if (read(Log, (char *)buf, sizeof (buf)) == -1) {
- Saveerrno = errno;
- (void) fprintf(stderr, "%s: can't read log ", Myname);
- errno = Saveerrno;
- perror("");
- (void) kill(0, SIGINT);
- exit(1);
- }
-
- for (j = 0; j < NOPS+1; j++) {
- addtime(Optime[j], buf[j]);
- }
- }
-}
-
-/*
- * Check consistance of results
- */
-int
-check_counters(void)
-{
- int i;
- int mixdiff;
- int got;
- int want;
-
- mixdiff = 0;
- for (i = 0; i < NOPS; i++) {
- got = Curct.calls[i] * 10000 / Curct.total;
- want = Mix[i] * 100;
- if (got > want) {
- mixdiff += got - want;
- } else {
- mixdiff += want - got;
- }
- }
- if (mixdiff > 1000) {
- (void) fprintf(stdout,
- "%s: INVALID RUN, mix generated is off by %d.%02d%%\n",
- Myname, mixdiff / 100, mixdiff % 100);
- return (-1);
- }
- return (0);
-}
-
-/*
- * Print results
- */
-void
-print(void)
-{
- int totalmsec;
- int runtime;
- int msec;
- int i;
-
- totalmsec = 0;
- for (i = 0; i < NOPS; i++) {
- totalmsec += Optime[i].tv_sec * 1000;
- totalmsec += Optime[i].tv_usec / 1000;
- }
-
- if (Verbose) {
- const char *format = sizeof (Optime[0].tv_sec) == sizeof (long)
- ? "%-10s%3d%% %2d.%02d%% %6d %4ld.%02ld %4d.%02d %2d.%02d%%\n"
- : "%-10s%3d%% %2d.%02d%% %6d %4d.%02d %4d.%02d %2d.%02d%%\n";
- (void) fprintf(stdout,
-"op want got calls secs msec/call time %%\n");
- for (i = 0; i < NOPS; i++) {
- msec = Optime[i].tv_sec * 1000
- + Optime[i].tv_usec / 1000;
- (void) fprintf(stdout, format,
- Opnames[i], Mix[i],
- Curct.calls[i] * 100 / Curct.total,
- (Curct.calls[i] * 100 % Curct.total)
- * 100 / Curct.total,
- Curct.calls[i],
- Optime[i].tv_sec, Optime[i].tv_usec / 10000,
- Curct.calls[i]
- ? msec / Curct.calls[i]
- : 0,
- Curct.calls[i]
- ? (msec % Curct.calls[i]) * 100 / Curct.calls[i]
- : 0,
- msec * 100 / totalmsec,
- (msec * 100 % totalmsec) * 100 / totalmsec);
- }
- }
-
- runtime = Optime[NOPS].tv_sec / Nprocs;
- (void) fprintf(stdout,
- "%d sec %d calls %d.%02d calls/sec %d.%02d msec/call\n",
- runtime, Curct.total,
- Curct.total / runtime,
- ((Curct.total % runtime) * 100) / runtime,
- totalmsec / Curct.total,
- ((totalmsec % Curct.total) * 100) / Curct.total);
-}
-
-/*
- * Use select to sleep for some number of milliseconds
- * granularity is 20 msec
- */
-void
-msec_sleep(int msecs)
-{
- struct timeval sleeptime;
-
- if (msecs < 20) {
- return;
- }
- sleeptime.tv_sec = msecs / 1000;
- sleeptime.tv_usec = (msecs % 1000) * 1000;
-
- if (select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &sleeptime) == -1){
- Saveerrno = errno;
- (void) fprintf(stderr, "%s: select failed ", Myname);
- errno = Saveerrno;
- perror("");
- (void) kill(0, SIGINT);
- exit(1);
- }
-}
-
-/*
- * Open the synchronization file with append mode
- */
-void
-init_logfile(void)
-{
-
- (void) sprintf(Logname, "/tmp/nhfsstone%d", getpid());
- if ((Log = open(Logname, O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0666)) == -1){
- Saveerrno = errno;
- (void) fprintf(stderr,
- "%s: can't open log file %s ", Myname, Logname);
- errno = Saveerrno;
- perror("");
- exit(1);
- }
-}
-
-/*
- * Zero counters
- */
-void
-init_counters(void)
-{
- int i;
-
- Curct.total = 0;
- for (i = 0; i < NOPS; i++) {
- Curct.calls[i] = 0;
- Optime[i].tv_sec = 0;
- Optime[i].tv_usec = 0;
- }
- Optime[NOPS].tv_sec = 0;
- Optime[NOPS].tv_usec = 0;
-}
-
-/*
- * Set cur = cur - start
- */
-void
-get_delta(struct count *start, struct count *cur)
-{
- int i;
-
- get_opct(cur);
- cur->total -= start->total;
- for (i = 0; i < NOPS; i++) {
- cur->calls[i] -= start->calls[i];
- }
-}
-
-/*
- * Read kernel stats
- */
-void
-get_opct(struct count *count)
-{
- static FILE *fp = NULL;
- char buffer[256];
- int i;
-
- if (fp == NULL && !(fp = fopen("/proc/net/rpc/nfs", "r"))) {
- perror("/proc/net/rpc/nfs");
- (void) kill(0, SIGINT);
- exit(1);
- } else {
- fflush(fp);
- rewind(fp);
- }
-
- while (fgets(buffer, sizeof(buffer), fp) != NULL) {
- char *sp, *line = buffer;
-
- if ((sp = strchr(line, '\n')) != NULL)
- *sp = '\0';
- if (!(sp = strtok(line, " \t")) || strcmp(line, "proc2"))
- continue;
- if (!(sp = strtok(NULL, " \t")))
- goto bummer;
- count->total = 0;
- for (i = 0; i < 18; i++) {
- if (!(sp = strtok(NULL, " \t")))
- goto bummer;
- /* printf("call %d -> %s\n", i, sp); */
- count->calls[i] = atoi(sp);
- count->total += count->calls[i];
- }
- /* printf("total calls %d\n", count->total); */
- break;
- }
-
- return;
-
-bummer:
- fprintf(stderr, "parse error in /proc/net/rpc/nfs!\n");
- kill(0, SIGINT);
- exit(1);
-}
-
-#define LINELEN 128 /* max bytes/line in mix file */
-#define MIX_START 0
-#define MIX_DATALINE 1
-#define MIX_DONE 2
-#define MIX_FIRSTLINE 3
-
-/*
- * Mix file parser.
- * Assumes that the input file is in the same format as
- * the output of the nfsstat(8) command.
- *
- * Uses a simple state transition to keep track of what to expect.
- * Parsing is done a line at a time.
- *
- * State Input action New state
- * MIX_START ".*nfs:.*" skip one line MIX_FIRSTLINE
- * MIX_FIRSTLINE ".*[0-9]*.*" get ncalls MIX_DATALINE
- * MIX_DATALINE "[0-9]* [0-9]*%"X6 get op counts MIX_DATALINE
- * MIX_DATALINE "[0-9]* [0-9]*%"X4 get op counts MIX_DONE
- * MIX_DONE EOF return
- */
-int
-setmix(FILE *fp)
-{
- int state;
- int got;
- int opnum;
- int calls;
- int len;
- char line[LINELEN];
-
- state = MIX_START;
- opnum = 0;
-
- while (state != MIX_DONE && fgets(line, LINELEN, fp)) {
-
- switch (state) {
-
- case MIX_START:
- len = strlen(line);
- if (len >= 4 && substr(line, "nfs:")) {
- if (fgets(line, LINELEN, fp) == NULL) {
- (void) fprintf(stderr,
-"%s: bad mix format: unexpected EOF after 'nfs:'\n", Myname);
- return (-1);
- }
- state = MIX_FIRSTLINE;
- }
- break;
-
- case MIX_FIRSTLINE:
- got = sscanf(line, "%d", &calls);
- if (got != 1) {
- (void) fprintf(stderr,
-"%s: bad mix format: can't find 'calls' value %d\n", Myname, got);
- return (-1);
- }
- if (fgets(line, LINELEN, fp) == NULL) {
- (void) fprintf(stderr,
-"%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
- return (-1);
- }
- state = MIX_DATALINE;
- break;
-
- case MIX_DATALINE:
- got = sscanf(line,
- "%d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%%",
- &Mix[opnum], &Mix[opnum+1], &Mix[opnum+2], &Mix[opnum+3],
- &Mix[opnum+4], &Mix[opnum+5], &Mix[opnum+6]);
- if (got == 4 && opnum == 14) {
- /*
- * looks like the last line
- */
- state = MIX_DONE;
- } else if (got == 7) {
- opnum += 7;
- if (fgets(line, LINELEN, fp) == NULL) {
- (void) fprintf(stderr,
-"%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
- return (-1);
- }
- } else {
- (void) fprintf(stderr,
-"%s: bad mix format: can't find %d op values\n", Myname, got);
- return (-1);
- }
- break;
- default:
- (void) fprintf(stderr,
- "%s: unknown state %d\n", Myname, state);
- return (-1);
- }
- }
- if (state != MIX_DONE) {
- (void) fprintf(stderr,
- "%s: bad mix format: unexpected EOF\n", Myname);
- return (-1);
- }
- for (opnum = 0; opnum < NOPS; opnum++) {
- Mix[opnum] = Mix[opnum] * 100 / calls
- + ((Mix[opnum] * 1000 / calls % 10) >= 5);
- }
- return (0);
-}
-
-/*
- * return true if sp contains the substring subsp, false otherwise
- */
-int
-substr(char *sp, char *subsp)
-{
- int found;
- int want;
- char *s2;
-
- if (sp == NULL || subsp == NULL) {
- return (0);
- }
-
- want = strlen(subsp);
-
- while (*sp != '\0') {
- while (*sp != *subsp && *sp != '\0') {
- sp++;
- }
- found = 0;
- s2 = subsp;
- while (*sp == *s2) {
- sp++;
- s2++;
- found++;
- }
- if (found == want) {
- return (1);
- }
- }
- return (0);
-}
-
-/*
- * check to make sure that we have
- * both read and write permissions
- * for this file or directory.
- */
-int
-check_access(struct stat statb)
-{
- int gidsetlen;
- gid_t gidset[NGROUPS];
- int i;
-
- if (statb.st_uid == getuid()) {
- if ((statb.st_mode & 0200) && (statb.st_mode & 0400)) {
- return 1;
- } else {
- return -1;
- }
- }
-
- gidsetlen = NGROUPS;
-
- if (getgroups(gidsetlen, gidset) == -1) {
- perror("getgroups");
- return -1;
- }
-
- for (i = 0; i < NGROUPS; i++) {
- if (statb.st_gid == gidset[i]) {
- if ((statb.st_mode & 020) && (statb.st_mode & 040)) {
- return 1;
- } else {
- return -1;
- }
- }
- }
-
- if ((statb.st_mode & 02) && (statb.st_mode & 04)) {
- return 1;
- } else {
- return -1;
- }
-}
-
-void
-usage(void)
-{
-
- (void) fprintf(stderr, "usage: %s [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs] [-m mixfile] [dir]...\n", Myname);
-}
-
-void
-error(char *str)
-{
-
- Saveerrno = errno;
- (void) fprintf(stderr, "%s: op failed: %s ", Myname, str);
- errno = Saveerrno;
- perror("");
-}
+++ /dev/null
-.\" @(#)nhfsstone.man 1.13 89/10/05 Copyright (c) 1989, Legato Systems Inc
-.\" See DISCLAIMER file for restrictions
-.TH NHFSSTONE 8 "4 October 1989"
-.SH NAME
-nhfsstone \- Network File System benchmark program
-.SH SYNOPSIS
-.B nhfsstone
-[
-.B \-v
-] [[
-.B \-t secs
-] | [
-.B -c calls
-]] [
-.B \-l load
-] [
-.B \-p nprocs
-] [
-.B \-m mixfile
-] [
-.B dir
-]...
-.SH DESCRIPTION
-.B nhfsstone
-(pronounced n\-f\-s\-stone, the "h" is silent)
-is used on a
-.SM NFS
-client to generate an artificial load with a particular mix of
-.SM NFS
-operations. It reports the average response time of the server in
-milliseconds per call and the load in calls per second.
-The program adjusts its calling patterns based on the client's kernel
-.SM NFS
-statistics and the elapsed time.
-Load can be generated over a given time or number of
-.SM NFS
-calls.
-.LP
-Because it uses the kernel
-.SM NFS
-statistics to monitor its progress,
-.B nhfsstone
-cannot be used to measure the performance of non\-NFS filesystems.
-.LP
-The
-.B nhfsstone
-program uses file and directory manipulation in an attempt to generate
-particular
-.SM NFS
-operations in response to particular system calls.
-To do this it uses several tricks
-that are based on a knowledge of the implementation of the
-.SM NFS
-client side reference port.
-For example, it uses long file names to circumvent the kernel name lookup
-cache so that a
-.BR stat (2)
-system call generates an
-.SM NFS
-lookup operation.
-.LP
-The mix of
-.SM NFS
-operations can be set with a mix file, which is the output of the
-.BR nfsstat (8C)
-command (see the "\-m" option below).
-The percentages taken from
-the mix file are calculated based on the number of
-.SM NFS
-calls, not on the percentages printed by nfsstat. Operations with
-0% in the mix will never get called by
-.BR nhfsstone .
-In a real server load mix, even though the percentage of call for
-a particular
-.SM NFS
-operation may be zero, the number of calls is often nonzero.
-.B Nhfsstone
-makes the assumption that the number of calls to these 0 percent
-operations will have an insignificant effect on server response.
-.LP
-Normally
-.B nhfsstone
-should be given a list of two or more test directories to use
-(default is to use the current directory).
-The test directories used should be located on different disks and
-partitions on the server to realistically simulate typical server loads.
-Each
-.B nhfsstone
-process looks for a directory
-.B <dir>/testdir<n>
-(where <n> is a number from 0 to
-.B nprocs
-\- 1).
-If a process directory name already exists,
-it is checked for the correct set of test files.
-Otherwise the directory is created and populated.
-.SH OPTIONS
-.TP 12
-.B \-v
-Verbose output.
-.TP
-.B \-t secs
-Sets
-.B calls
-based on the given running time (in seconds) and the load.
-.TP
-.B \-c calls
-Total number of
-.SM NFS
-calls to generate (default is 5000).
-.TP
-.B \-l load
-Load to generate in
-.SM NFS
-calls per second (default is 30).
-.TP
-.B \-p nprocs
-Number of load generating sub\-processes to fork (default is 7).
-This can be used to maximize the amount of load a single machine can generate.
-On a small client machine (slow CPU or small amount of memory)
-fewer processes might be used to avoid swapping.
-.TP
-.B \-m mixfile
-Mix of
-.SM NFS
-operations to generate.
-The format of
-.B mixfile
-is the same as the output of the
-.BR nfsstat (8C)
-program.
-A mix file can be created on a server by typing "nfsstat \-s > mixfile".
-The default mix of operations is: null 0%, getattr 13%, setattr 1%,
-root 0%, lookup 34%, readlink 8%, read 22%, wrcache 0%, write 15%, create 2%,
-remove 1%, rename 0%, link 0%, symlink 0%, mkdir 0%, rmdir 0%, readdir 3%,
-fsstat 1%.
-.SH USING NHFSSTONE
-As with all benchmarks,
-.B nhfsstone
-can only provide numbers that are useful if experiments that use it are
-set up carefully.
-Since it is measuring servers, it should be run on a client
-that will not limit the generation of
-.SM NFS
-requests.
-This means it should have a fast CPU,
-a good ethernet interface and the machine
-should not be used for anything else during testing.
-A Sun\-3/50 can generate about 60
-.SM NFS
-calls per second before it runs out of CPU.
-.LP
-.B Nhfsstone
-assumes that all
-.SM NFS
-calls generated on the client are going to a single server, and that
-all of the
-.SM NFS
-load on that server is due to this client.
-To make this assumption hold,
-both the client and server should be as quiescent as possible during tests.
-.LP
-If the network is heavily utilized the delays due to collisions
-may hide any changes in server performance.
-High error rates on either the client or server can also
-cause delays due to retransmissions of lost or damaged packets.
-.BR netstat (8C)
-.B \-i
-can be used to measure the error and collision rates on the client and server.
-.LP
-To best simulate the effects of
-.SM NFS
-clients on the server, the test
-directories should be set up so that they are on at least two of the
-disk partitions that the server exports and the partitions should be
-as far apart as possible. The
-.BR dkinfo (8)
-command can be used to find the physical geometry of disk on BSD based systems.
-.SM NFS
-operations tend to randomize
-access the whole disk so putting all of the
-.B nhfsstone
-test directories on a single partition or on
-two partitions that are close together will not show realistic results.
-.LP
-On all tests it is a good idea to run the tests repeatedly and compare results.
-The number of calls can be increased
-(with the
-.B \-c
-option) until the variance in milliseconds per call is acceptably small.
-If increasing the number of calls does not help there may be something
-wrong with the experimental setup.
-One common problem is too much memory on the client
-test machine. With too much memory,
-.B nhfsstone
-is not able to defeat the client caches and the
-.SM NFS
-operations do not end up going to the server at all. If you suspect that
-there is a caching problem you can use the
-.B -p
-option to increase the number of processes.
-.LP
-The numbers generated by
-.B nhfsstone
-are most useful for comparison if the test setup on the client machine
-is the same between different server configurations.
-Changing
-.B nhfsstone
-parameters between runs will produce numbers that can not be
-meaningfully compared.
-For example, changing the number of generator processes
-may affect the measured response
-time due to context switching or other delays on the client machine, while
-changing the mix of
-.SM NFS
-operations will change the whole nature of the experiment.
-Other changes to the client configuration may also effect the comparability
-of results.
-While
-.B nhfsstone
-tries to compensate for differences in client configurations
-by sampling the actual
-.SM NFS
-statistics and adjusting both the load and mix of operations, some changes
-are not reflected in either the load or the mix. For example, installing
-a faster CPU or mounting different
-.SM NFS
-filesystems may effect the response time without changing either the
-load or the mix.
-.LP
-To do a comparison of different server configurations, first set up the
-client test directories and do
-.B nhfsstone
-runs at different loads to be sure that the variability is
-reasonably low. Second, run
-.B nhfsstone
-at different loads of interest and
-save the results. Third, change the server configuration (for example,
-add more memory, replace a disk controller, etc.). Finally, run the same
-.B nhfsstone
-loads again and compare the results.
-.SH SEE ALSO
-.LP
-The
-.B nhfsstone.c
-source file has comments that describe in detail the operation of
-of the program.
-.SH ERROR MESSAGES
-.TP
-.B "illegal calls value"
-The
-.B calls
-argument following the
-.B \-c
-flag on the command line is not a positive number.
-.TP
-.B "illegal load value"
-The
-.B load
-argument following the
-.B \-l
-flag on the command line is not a positive number.
-.TP
-.B "illegal time value"
-The
-.B time
-argument following the
-.B \-t
-flag on the command line is not a positive number.
-.TP
-.B "bad mix file"
-The
-.B mixfile
-file argument following the
-.B \-m
-flag on the command line could not be accessed.
-.TP
-.B "can't find current directory"
-The parent process couldn't find the pathname of the current directory.
-This usually indicates a permission problem.
-.TP
-.B "can't fork"
-The parent couldn't fork the child processes. This usually results from
-lack of resources, such as memory or swap space.
-.TP
-.PD 0
-.B "can't open log file"
-.TP
-.B "can't stat log"
-.TP
-.B "can't truncate log"
-.TP
-.B "can't write sync file"
-.TP
-.B "can't write log"
-.TP
-.B "can't read log"
-.PD
-A problem occurred during the creation, truncation, reading or writing of the
-synchronization log file. The parent process creates the
-log file in /tmp and uses it to synchronize and communicate with its children.
-.TP
-.PD 0
-.B "can't open test directory"
-.TP
-.B "can't create test directory"
-.TP
-.B "can't cd to test directory"
-.TP
-.B "wrong permissions on test dir"
-.TP
-.B "can't stat testfile"
-.TP
-.B "wrong permissions on testfile"
-.TP
-.B "can't create rename file"
-.TP
-.B "can't create subdir"
-.PD
-A child process had problems creating or checking the contents of its
-test directory. This is usually due to a permission problem (for example
-the test directory was created by a different user) or a full filesystem.
-.TP
-.PD 0
-.B "bad mix format: unexpected EOF after 'nfs:'"
-.TP
-.B "bad mix format: can't find 'calls' value"
-.TP
-.B "bad mix format: unexpected EOF after 'calls'"
-.TP
-.B "bad mix format: can't find %d op values"
-.TP
-.B "bad mix format: unexpected EOF"
-.PD
-A problem occurred while parsing the
-.B mix
-file. The expected format of the file is the same as the output of
-the
-.BR nfsstat (8C)
-command when run with the "\-s" option.
-.TP
-.B "op failed: "
-One of the internal pseudo\-NFS operations failed. The name of the operation,
-e.g. read, write, lookup, will be printed along with an indication of the
-nature of the failure.
-.TP
-.B "select failed"
-The select system call returned an unexpected error.
-.SH BUGS
-.LP
-Running
-.B nhfsstone
-on a non\-NFS filesystem can cause the program to run forever because it
-uses the kernel NFS statistics to determine when enough calls have been made.
-.LP
-.B Nhfsstone
-uses many file descriptors. The kernel on the client may
-have to be reconfigured to increase the number of available file table entries.
-.LP
-Shell scripts that used
-.B nhfsstone
-will have to catch and ignore SIGUSR1 (see
-.BR signal (3)).
-This signal is
-used to synchronize the test processes. If the signal is not caught
-the shell that is running the script will be killed.
-.SH FILES
-.PD 0
-.TP 20
-.B /vmunix
-system namelist
-.TP
-.B /dev/kmem
-kernel virtual memory
-.TP
-.B ./testdir*
-per process test directory
-.TP
-.B /tmp/nhfsstone%d
-process synchronization log file
-.PD