diff options
author | hjl <hjl> | 1999-10-18 23:21:12 +0000 |
---|---|---|
committer | hjl <hjl> | 1999-10-18 23:21:12 +0000 |
commit | 8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9 (patch) | |
tree | 0904ef8554ed680fe3244fa618685e1fb7ea148b /utils/nhfsstone | |
download | nfs-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/DISCLAIMER | 33 | ||||
-rw-r--r-- | utils/nhfsstone/Makefile | 8 | ||||
-rw-r--r-- | utils/nhfsstone/README | 111 | ||||
-rw-r--r-- | utils/nhfsstone/README.linux | 11 | ||||
-rwxr-xr-x | utils/nhfsstone/nhfsgraph | 23 | ||||
-rwxr-xr-x | utils/nhfsstone/nhfsnums | 22 | ||||
-rwxr-xr-x | utils/nhfsstone/nhfsrun | 59 | ||||
-rw-r--r-- | utils/nhfsstone/nhfsstone.1 | 381 | ||||
-rw-r--r-- | utils/nhfsstone/nhfsstone.c | 1798 |
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(""); +} |