summaryrefslogtreecommitdiffstats
path: root/utils/nhfsstone
diff options
context:
space:
mode:
authorhjl <hjl>1999-10-18 23:21:12 +0000
committerhjl <hjl>1999-10-18 23:21:12 +0000
commit8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9 (patch)
tree0904ef8554ed680fe3244fa618685e1fb7ea148b /utils/nhfsstone
downloadnfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.tar.gz
nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.tar.xz
nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.zip
Initial revision
Diffstat (limited to 'utils/nhfsstone')
-rw-r--r--utils/nhfsstone/DISCLAIMER33
-rw-r--r--utils/nhfsstone/Makefile8
-rw-r--r--utils/nhfsstone/README111
-rw-r--r--utils/nhfsstone/README.linux11
-rwxr-xr-xutils/nhfsstone/nhfsgraph23
-rwxr-xr-xutils/nhfsstone/nhfsnums22
-rwxr-xr-xutils/nhfsstone/nhfsrun59
-rw-r--r--utils/nhfsstone/nhfsstone.1381
-rw-r--r--utils/nhfsstone/nhfsstone.c1798
9 files changed, 2446 insertions, 0 deletions
diff --git a/utils/nhfsstone/DISCLAIMER b/utils/nhfsstone/DISCLAIMER
new file mode 100644
index 0000000..afde6a3
--- /dev/null
+++ b/utils/nhfsstone/DISCLAIMER
@@ -0,0 +1,33 @@
+@(#)DISCLAIMER 1.4 89/07/07 Legato Systems, Inc.
+
+ IMPORTANT. READ BEFORE USING. USE OF THE PROGRAM WILL
+ CONSTITUTE ACCEPTANCE OF THE FOLLOWING LICENSE TERMS.
+
+Legato nhfsstone source code is a copyrighted product of Legato
+Systems, Inc. and is provided for unrestricted use and distribution of
+the binary program derived from it.
+
+You may copy Legato nhfsstone source, object code and related
+documentation as necessary, but are not authorized to license it to
+anyone else. Legato nhfsstone may be modified only for the purpose of
+porting. If the basic algorithms are changed the resulting program may
+not be called nhfsstone.
+
+Legato nhfsstone is provided with no support and without any obligation
+on the part of Legato Systems, Inc. to assist in its use, correction,
+modification or enhancement.
+
+LEGATO NHFSSTONE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND
+INCLUDING THE WARRANTIES OF DESIGN, MERCHANTIBILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NONINFRINGEMENT, OR ARISING FROM A COURSE OF
+DEALING, USAGE OR TRADE PRACTICE.
+
+LEGATO SYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY LEGATO
+NHFSSTONE, ANY PART THEREOF OR THE USE THEREOF.
+
+IN NO EVENT WILL LEGATO SYSTEMS, INC. BE LIABLE UNDER ANY CONTRACT,
+NEGLIGENCE, STRICT LIABILITY OR OTHER THEORY FOR ANY LOST REVENUE OR
+PROFITS OR OTHER SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR COST OF
+PROCUREMENT OF SUBSTITUTE GOODS OR TECHNOLOGY, EVEN IF LEGATO HAS BEEN
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
diff --git a/utils/nhfsstone/Makefile b/utils/nhfsstone/Makefile
new file mode 100644
index 0000000..d73d85a
--- /dev/null
+++ b/utils/nhfsstone/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for nhfsstone
+#
+
+PROGRAM = nhfsstone
+OBJS = nhfsstone.o
+
+include $(TOP)rules.mk
diff --git a/utils/nhfsstone/README b/utils/nhfsstone/README
new file mode 100644
index 0000000..f13dde5
--- /dev/null
+++ b/utils/nhfsstone/README
@@ -0,0 +1,111 @@
+@(#)README 1.6 89/07/07 Legato Systems, Inc.
+
+This directory contains the source for the nhfsstone (pronounced
+n-f-s-stone, the "h" is silent) NFS load generating program. This
+version of the program can only be compiled on 4.x BSD based UNIX
+systems.
+
+nhfsstone is used on an NFS client to generate an artificial load
+with a particular mix of 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 NFS statistics and the elapsed time. Load can be
+generated over a given time or number of NFS calls. See the "nhfsstone.1"
+manual page for more details.
+
+The files in this directory are:
+
+ DISCLAIMER legal requirements
+ Makefile Makefile used to build nhfsstone
+ README This file
+ nhfsstone.c source file
+ nhfsstone.1 manual page
+ nhfsrun shell script to run nhfsstone over multiple loads
+ nhfsnums shell script to convert nhfsrun output to plot(5)
+ nhfsgraph shell script to create a graph from nhfsnums output
+
+The file "nhfsstone.1" is a manual page that describes how to use the
+nhfsstone program. To look at it type "nroff -man nhfsstone.1".
+
+To build an executable type "make nhfsstone". To install it, become
+super-user and then type "make install". This will strip the
+executable, set the group to "kmem" and set the setgid bit. If your
+site requires different installation of programs that read /dev/kmem
+you may have to use different ownership or permissions. Make install
+will also set the execute bits on the shell scripts nhfsrun, nhfsnums
+and nhfsgraph.
+
+To run an nhfsstone test, create a parent test directory on a filesystem
+that is NFS mounted, cd to that directory and type "nhfsstone". This will
+do a run with the default settings, load = 30 calls/sec, 5000 calls,
+and 7 sub-processes.
+
+If you want to spread the load across several server disks, first
+figure out on the server which disk partitions are exported as which
+filesystems. If you don't already have more than one of these
+filesystems mounted on your test client you can mount them in temporary
+locations, like /mnt. Create test directories on these filesystems so
+that the load will be distributed according to the simulation that you
+want to run (for example, you might put 4 test directories on the
+filesystem where the diskless client's root and swap live, and 2 on the
+home directories filesystem, and one on the executables filesystem).
+Now create a parent test directory cd to it, and make symbolic links
+with the names testdir0, testdir1, ... testdir6, that point to the
+real test directories. Finally, run nhfsstone from the parent test
+directory.
+
+If you are doing the test from a diskless machine, putting half of the
+test directories in /tmp or /usr/tmp and running the test from your
+home directory will simulate real diskless load patterns fairly well.
+
+To do a run over multiple load levels, edit the shell script "nhfsrun" and
+set the shell variables "START", "END", and "INCR" to be the correct
+starting and ending loads, and load increment. The script will iterate
+from START to END with an increment of INCR, run nhfsstone at each
+load level, and put the output in the file "run.out". The output file
+name can be changed by editing the nhfsrun script and changing the
+"OUTFILE" variable or by passing a file name suffix on the command line:
+
+ nhfsrun xysd
+
+This produces the output file "run.xysd".
+
+The script "nhfsnums" takes the output from nhfsrun and converts it
+into plot(5) format so that it can be graphed using graph(1) and other
+tools. It takes its input either from files given on the command line
+or from standard in:
+
+ nhfsnums [numsfile] ...
+
+If file names are given, the suffix of each name (the part after the
+".") is used as the line label for the set of numbers produced (see
+plot(5)).
+
+"nhfsgraph" takes the output from nhfsnums and passes it to graph(1)
+with the right arguments to produce PostScript output for a labeled
+graph. The nhfsgraph script can be used as a filter:
+
+ nhfsnums run.* | nhfsgraph | lpr
+
+
+
+
+This program is provided free of charge to anyone who wants it provided
+certain conditions are met (see DISCLAIMER file for more details).
+
+If you would like to receive regular information and bug fixes please
+send your name, and both your Email and U.S. mail addresses to:
+
+ Legato Systems, Inc.
+ Nhfsstone
+ 260 Sheridan Avenue
+ Palo Alto, California 94306
+
+ nhfsstone-request@legato.com or uunet!legato.com!nhfsstone-request
+
+and we will add your name to the nhfsstone mailing list. Comments and bug
+reports should be sent to:
+
+ nhfsstone@legato.com or uunet!legato.com!nhfsstone
+
+
diff --git a/utils/nhfsstone/README.linux b/utils/nhfsstone/README.linux
new file mode 100644
index 0000000..e9b7899
--- /dev/null
+++ b/utils/nhfsstone/README.linux
@@ -0,0 +1,11 @@
+
+
+ This is my port of nhfsstone to Linux. As a benchmark, it has been
+ superseded by LADDIS (but unfortunately, LADDIS comes with a 1200 buck
+ price tag), but it's quite good at catching NFS bugs :-)
+
+ Of course, this port does not work with the old NFS client code, as
+ it does not collect RPC stats.
+
+ Olaf
+
diff --git a/utils/nhfsstone/nhfsgraph b/utils/nhfsstone/nhfsgraph
new file mode 100755
index 0000000..56e2c77
--- /dev/null
+++ b/utils/nhfsstone/nhfsgraph
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# @(#)nhfsgraph.sh 1.3 89/07/07 Copyright (c) 1989, Legato Systems, Inc.
+#
+# See DISCLAIMER file for restrictions
+#
+
+#
+# Usage: nhfsgraph <graphfile> ...
+#
+# Produce a PostScript graph of nhfsstone numbers.
+# Graphfile is a file with number pairs in plot(5) format derived
+# from runs of nhfsstone at different loads (see "nhfsrun" and "nhfsnums"
+# scripts.
+#
+# If you want something other than PostScript output replace "psplot"
+# with "plot". See plot(1) for more details.
+#
+
+LABEL="x=Load (calls/sec) y=Response (msec/call)"
+
+cat $* \
+ | graph -b -u .1 -h 1.2 -g 2 -l "$LABEL" -x 10 80 10 | psplot
diff --git a/utils/nhfsstone/nhfsnums b/utils/nhfsstone/nhfsnums
new file mode 100755
index 0000000..aae625d
--- /dev/null
+++ b/utils/nhfsstone/nhfsnums
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# @(#)nhfsnums.sh 1.3 89/07/07 Copyright (c) 1989, Legato Systems, Inc.
+#
+# See DISCLAIMER file for restrictions
+#
+
+#
+# Usage: nhfsnums <numsfile> ...
+#
+# Collect raw numbers from nhfsstone output and print in plot(5) format.
+# The nums files should be named "run.xxx" where xxx is a name related
+# to the numbers gathered. Each file will produce one line with a label
+# that is the file suffix (the part following the dot.)
+#
+
+for i in $*; do
+ RUNNAME=`echo $i | sed -e 's/.*\\.//'`
+ awk '{ print $5 " " $7 }' $i \
+ | sort -n\
+ | sed -e "\$s/\$/ \"$RUNNAME\"/"
+done
diff --git a/utils/nhfsstone/nhfsrun b/utils/nhfsstone/nhfsrun
new file mode 100755
index 0000000..dfc24eb
--- /dev/null
+++ b/utils/nhfsstone/nhfsrun
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# @(#)nhfsrun.sh 1.3 89/07/07 Copyright (c) 1989, Legato Systems, Inc.
+#
+# See DISCLAIMER file for restrictions
+#
+
+#
+# Usage: nhfsrun [suffix]
+#
+# Run nhfsstone with a range of different loads and put
+# results in a file called run.<suffix>
+#
+
+if [ $# -gt 1 ]; then
+ echo "usage: $0 [suffix]"
+ exit 1
+fi
+
+#
+# Output file
+#
+if [ $# -eq 1 ]; then
+ OUTFILE=run.$1
+else
+ OUTFILE=run.out
+fi
+
+#
+# Starting load
+#
+START=10
+
+#
+# Ending load
+#
+END=80
+
+#
+# Load increment
+#
+INCR=10
+
+#
+# Catch SIGUSR1 and ignore it.
+# SIGUSR1 is used by nhfsstone to synchronize child processes.
+#
+nothing() { echo -n ""; }
+trap nothing 30
+
+rm -f $OUTFILE
+
+LOAD=$START
+while [ $LOAD -le $END ]; do
+ echo nhfsstone -l $LOAD
+ nhfsstone -l $LOAD >> $OUTFILE
+ tail -1 $OUTFILE
+ LOAD=`expr $LOAD + $INCR`
+done
diff --git a/utils/nhfsstone/nhfsstone.1 b/utils/nhfsstone/nhfsstone.1
new file mode 100644
index 0000000..e56eb9e
--- /dev/null
+++ b/utils/nhfsstone/nhfsstone.1
@@ -0,0 +1,381 @@
+.\" @(#)nhfsstone.1 1.13 89/10/05 Copyright (c) 1989, Legato Systems Inc
+.\" See DISCLAIMER file for restrictions
+.TH NHFSSTONE 1 "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
diff --git a/utils/nhfsstone/nhfsstone.c b/utils/nhfsstone/nhfsstone.c
new file mode 100644
index 0000000..034ba79
--- /dev/null
+++ b/utils/nhfsstone/nhfsstone.c
@@ -0,0 +1,1798 @@
+#ifndef lint
+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 <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 */
+ int oldmask; /* saved signal mask */
+ 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);
+ oldmask = sigblock(sigmask(SIGUSR1));
+
+ 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);
+ }
+
+ sigpause(oldmask);
+
+ /*
+ * 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("");
+}