home *** CD-ROM | disk | FTP | other *** search
-
- /*
- *
- * fsx is a File System eXerciser. It is a benchmark that attempts to
- * quantify the performance of several filesystem operations that have been
- * observed to be bottlenecks in I/O-intensive applications, specifically
- * the text database work done in connection with the New Oxford English
- * Dictionary Project at the University of Waterloo.
- *
- * It performs a series of tests on a file of known size. By default, that
- * size is 50 Mb. For each test, fsx reports the bytes processed per
- * elapsed second, per CPU second, and the % CPU usage (user and system).
- *
- * In each case, some care is taken to keep optimizers, no matter how
- * clever, from noticing it's all bogus. The idea is to make sure that
- * these are real transfers from user space to the physical disk. The
- * tests are:
- *
- * 1. Sequential Output
- *
- * 1.1 Per-Character. The file is written using the putc() stdio macro.
- * The loop that does the writing should be small enough to fit into any
- * reasonable I-cache. The CPU exercised here is that required to do the
- * stdio code plus the OS file space allocation.
- *
- * 1.2 Block. The file is created using write(2). The CPU consumption
- * should be just the OS file space allocation.
- *
- * 1.3 Rewrite. Each BUFSIZ of the file is read with read(2), dirtied, and
- * rewritten with write(2), requiring an lseek(2). Since no space
- * allocation is done, and the I/O is well-localized, this should test the
- * effectiveness of the filesystem cache and the speed of data transfer.
- *
- * 2. Sequential Input
- *
- * 2.1 Per-Character. The file is read using the getc() stdio macro. Once
- * again, the inner loop is small. This should exercise only stdio and
- * sequential input.
- *
- * 2.2 Block. The file is read using read(2). This should be a very pure
- * test of sequential input performance.
- *
- * 3. Random Seeks
- *
- * This test seeks 1000 times to locations in the file specified by
- * random() in bsd systems, drand48() on sysV systems. In each case, the
- * block is read with read(2). In 10% of cases, it is dirtied and written
- * back with write(2).
- *
- * AXIOM: For any unix filesystem, the effective number of lseek(2) calls
- * per second declines asymptotically to near 30, once the effect of
- * caching is defeated.
- *
- * The size of the file has a strong nonlinear effect on the results of
- * this test. Many Unix systems that have the memory available will make
- * aggressive efforts to cache the whole thing, and report random I/O rates
- * in the thousands per second, which is ridiculous. As an extreme
- * example, an IBM RISC 6000 with 64 Mb of memory reported 3,722 per second
- * on a 50 Mb file.
- *
- * COPYRIGHT NOTICE:
- * Copyright (c) Tim Bray, 1990.
- * Everybody is hereby granted rights to use, copy, and modify this program,
- * provided only that the copyright notice above and the disclaimer below
- * are preserved without change.
- * DISCLAIMER:
- * This program is provided AS IS with no warranty of any kind, and
- * The author makes no representation with respect to the adequacy of this
- * program for any particular purpose or with respect to its adequacy to
- * produce any particular result, and
- * The author shall not be liable for loss or damage arising out of
- * the use of this program regardless of how sustained, and
- * In no event shall the author be liable for special, direct, indirect
- * or consequential damage, loss, costs or fees or expenses of any
- * nature or kind.
- */
-
- #include <stdio.h>
- #include <fcntl.h>
- #include <sys/types.h>
- #include <sys/time.h>
- #ifdef i386
- #include <limits.h>
- #include <sys/times.h>
- #else
- #include <sys/resource.h>
- #endif
-
- #define IntSize (4)
- #define Elapsed (0)
- #define CPU (1)
- #define Searches (1000)
- #define UpdateSeek (10)
-
- static double cpu_so_far();
- static void doseek();
- static void get_delta_t();
- static void io_error();
- static void newfile();
- static void report();
- static double time_so_far();
- static void timestamp();
- static void usage();
- #ifdef i386
- static long random();
- #endif
-
- typedef enum
- {
- Putc,
- ReWrite,
- FastWrite,
- Getc,
- FastRead,
- Binary,
- TestCount
- } tests_t;
-
- static int basetime;
- static double delta[(int) TestCount][2];
- static char * myname;
- static double last_cpustamp = 0.0;
- static double last_timestamp = 0.0;
- static int lseek_count = 0;
-
- main(argc, argv)
- int argc;
- char * argv[];
- {
- int buf[BUFSIZ / IntSize];
- int bufindex;
- int chars[256];
- char * dir;
- int fd;
- char name[BUFSIZ];
- int next;
- int size;
- FILE * stream;
- int words;
-
- myname = argv[0];
- fd = -1;
- basetime = (int) time((time_t *) NULL);
- size = 50;
- dir = ".";
-
- for (next = 1; next < argc - 1; next++)
- if (argv[next][0] == '-')
- { /* option? */
- if (strcmp(argv[next] + 1, "d") == 0)
- dir = argv[next + 1];
- else if (strcmp(argv[next] + 1, "s") == 0)
- size = atoi(argv[next + 1]);
- else
- usage();
- next++;
- } /* option? */
- else
- usage();
-
- if (size < 1)
- usage();
- size *= (1024 * 1024);
- sprintf(name, "%s/fsx.%d", dir, getpid());
- printf("File '%s', size: %d\n", name, size);
- fflush(stdout);
-
- /*
- * Fill up a file, writing it a char at a time with the stdio putc()
- * facility. This models the activity of a negligible-CPU filter
- * and exercises kernel file space allocation. Should be measured with
- * disks of varying degrees of fullness.
- */
- printf("Writing with putc()...");
- newfile(name, &fd, &stream, 1);
- fflush(stdout);
- timestamp();
- for (words = 0; words < size; words++)
- if (putc(words & 0x7f, stream) == EOF)
- io_error("putc");
-
- /*
- * note that we always close the file before measuring time, in an
- * effort to force as much of the I/O out as we can
- */
- if (fclose(stream) == -1)
- io_error("fclose after putc");
- get_delta_t((int) Putc);
- printf("done\n");
- fflush(stdout);
-
- /*
- * Now read & rewrite it, a block at a time. Change one word in each block
- * in case somebody's being clever about rewritten dirty blocks.
- * This exercises sequential read-write performance and hence the cache,
- * but avoids any space-allocation work.
- */
- newfile(name, &fd, &stream, 0);
- if (lseek(fd, (off_t) 0, 0) == (off_t) -1)
- io_error("lseek(2) before rewrite");
- printf("Rewriting...");
- fflush(stdout);
- timestamp();
- bufindex = 0;
- do
- { /* while we can read a block */
- if ((words = read(fd, (char *) buf, BUFSIZ)) == -1)
- io_error("read(2)");
- if (bufindex == words / IntSize)
- bufindex = 0;
- buf[bufindex++]++;
- if (lseek(fd, (off_t) -words, 1) == -1)
- io_error("relative lseek(2)");
- if (write(fd, (char *) buf, words) == -1)
- io_error("re write(2)");
- } /* while we can read a block */
- while (words == BUFSIZ);
- if (close(fd) == -1)
- io_error("close after rewrite");
- get_delta_t((int) ReWrite);
- printf("done\n");
- fflush(stdout);
-
- /*
- * Now rewrite the whole file from scratch, but a block at a time rather
- * than with stdio. Exercises space allocation a little more stringently,
- * and by comparison with the putc test, quantifies stdio overhead a bit.
- * Once again, dirty each block.
- */
- newfile(name, &fd, &stream, 1);
- printf("Writing intelligently...");
- for (words = 0; words < BUFSIZ / IntSize; words++)
- buf[words] = 0;
- fflush(stdout);
- timestamp();
- for (words = bufindex = 0; words < (size / BUFSIZ); words++)
- { /* for each word */
- if (bufindex == (BUFSIZ/IntSize))
- bufindex = 0;
- buf[bufindex++]++;
- if (write(fd, (char *) buf, BUFSIZ) == -1)
- io_error("write(2)");
- } /* for each word */
- if (close(fd) == -1)
- io_error("close after fast write");
- get_delta_t((int) FastWrite);
- printf("done\n");
- fflush(stdout);
-
- /*
- * Now read them all back with getc, excercising default character-
- * at-a-time input. Do a character frequency count just to fool
- * any optimizers that may notice that they're not being used.
- */
- newfile(name, &fd, &stream, 0);
- for (words = 0; words < 256; words++)
- chars[words] = 0;
- printf("Reading with getc()...");
- fflush(stdout);
- timestamp();
- for (words = 0; words < size; words++)
- { /* for each byte */
- if ((next = getc(stream)) == EOF)
- io_error("getc(3)");
- chars[next]++;
- } /* for each byte */
- if (fclose(stream) == -1)
- io_error("fclose after getc");
- get_delta_t((int) Getc);
- printf("done\n");
- fflush(stdout);
-
- /* use the frequency count */
- for (words = 0; words < 256; words++)
- sprintf((char *) buf, "%d", chars[words]);
-
- /*
- * Now suck it in, BUFSIZ at a time, as fast as we can.
- */
- newfile(name, &fd, &stream, 0);
- if (lseek(fd, 0L, 0) == -1)
- io_error("lseek before read");
- printf("Reading intelligently...");
- fflush(stdout);
- timestamp();
- do
- if ((words = read(fd, (char *) buf, BUFSIZ)) == -1)
- io_error("read(2)");
- while (words);
- if (close(fd) == -1)
- io_error("close after read");
- get_delta_t((int) FastRead);
- printf("done\n");
- fflush(stdout);
-
- /*
- * Now do some random I/O. Originally these were binary searches, but
- * that is well-behaved, since the first few seeks are always the same.
- * Probably an application that was doing this kind of thing would keep
- * its own cache of the top few levels of the tree or whatever - we're
- * just interested in 'How much random I/O can be done?'
- */
- newfile(name, &fd, &stream, 0);
- timestamp();
- printf("Seeking...");
- fflush(stdout);
- for (lseek_count = 0; lseek_count < Searches; lseek_count++)
- doseek(random() % size, fd, ((lseek_count % UpdateSeek) == 0));
- if (close(fd) == -1)
- io_error("close after read");
- get_delta_t((int) Binary);
- printf("done\n");
- fflush(stdout);
-
- report(size);
- unlink(name);
- }
-
- static void
- report(size)
- int size;
- {
- printf("\n");
- printf("Times reported are elapsed / cpu / %%cpu usage.\n");
-
- printf("\nSequential output\n");
- printf("putc() bytes/sec: %d / %d / %.1f%%\n",
- (int) (((double) size) / delta[(int) Putc][Elapsed]),
- (int) (((double) size) / delta[(int) Putc][CPU]),
- delta[(int) Putc][CPU] / delta[(int) Putc][Elapsed] * 100.0);
- printf("write() bytes/sec: %d / %d / %.1f%%\n",
- (int) (((double) size) / delta[(int) FastWrite][Elapsed]),
- (int) (((double) size) / delta[(int) FastWrite][CPU]),
- delta[(int) FastWrite][CPU] / delta[(int) FastWrite][Elapsed] * 100.0);
- printf("putc() multiplier: %.1f / %.1f\n",
- delta[(int) Putc][Elapsed] / delta[(int) FastWrite][Elapsed],
- delta[(int) Putc][CPU] / delta[(int) FastWrite][CPU]);
- printf("Sequential output time: %.1f / %.1f / %.1f%%\n\n",
- delta[(int) FastWrite][Elapsed] + delta[(int) Putc][Elapsed],
- delta[(int) FastWrite][CPU] + delta[(int) Putc][CPU],
- (delta[(int) FastWrite][CPU] + delta[(int) Putc][CPU])
- / (delta[(int) FastWrite][Elapsed] + delta[(int) Putc][Elapsed])
- * 100.0);
-
- printf("\nSequential input\n");
- printf("getc() bytes/sec: %d / %d / %.1f%%\n",
- (int) (((double) size) / delta[(int) Getc][Elapsed]),
- (int) (((double) size) / delta[(int) Getc][CPU]),
- delta[(int) Getc][CPU] / delta[(int) Getc][Elapsed] * 100.0);
- printf("read() bytes/sec: %d / %d / %.1f%%\n",
- (int) (((double) size) / delta[(int) FastRead][Elapsed]),
- (int) (((double) size) / delta[(int) FastRead][CPU]),
- delta[(int) FastRead][CPU] / delta[(int) FastRead][Elapsed] * 100.0);
- printf("getc() multiplier: %.1f / %.1f\n",
- delta[(int) Getc][Elapsed] / delta[(int) FastRead][Elapsed],
- delta[(int) Getc][CPU] / delta[(int) FastRead][CPU]);
- printf("Sequential input time: %.1f / %.1f / %.1f%%\n\n",
- delta[(int) Getc][Elapsed] + delta[(int) FastRead][Elapsed],
- delta[(int) Getc][CPU] + delta[(int) FastRead][CPU],
- (delta[(int) Getc][CPU] + delta[(int) FastRead][CPU])
- / (delta[(int) Getc][Elapsed] + delta[(int) FastRead][Elapsed])
- * 100.0);
-
- printf("\nSequential rewrite\n");
- printf("Sequential rewrite bytes/sec: %d / %d / %.1f%%\n",
- (int) (((double) size) / delta[(int) ReWrite][Elapsed]),
- (int) (((double) size) / delta[(int) ReWrite][CPU]),
- delta[(int) ReWrite][CPU] / delta[(int) ReWrite][Elapsed] * 100.0);
- printf("Sequential rewrite time: %.1f / %.1f / %.1f%%\n",
- delta[(int) ReWrite][Elapsed], delta[(int) ReWrite][CPU],
- delta[(int) ReWrite][CPU] / delta[(int) ReWrite][Elapsed] * 100.0);
-
- printf("\nRandom I/O\n");
- printf("Random reads/sec: %.1f / %.1f / %.1f%%\n",
- ((double) lseek_count) / delta[(int) Binary][Elapsed],
- ((double) lseek_count) / delta[(int) Binary][CPU],
- delta[(int) Binary][CPU] / delta[(int) Binary][Elapsed] * 100.0);
- }
-
- static void
- newfile(name, fd, stream, create)
- char * name;
- int * fd;
- FILE * * stream;
- int create;
- {
- if (create)
- { /* create from scratch */
- if (unlink(name) == -1 && *fd != -1)
- io_error("unlink");
- *fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0777);
- } /* create from scratch */
- else
- *fd = open(name, O_RDWR, 0777);
-
- if (*fd == -1)
- io_error(name);
- *stream = fdopen(*fd, "r+");
- if (*stream == NULL)
- io_error("fdopen");
- }
-
- static void
- usage()
- {
- fprintf(stderr, "usage: %s [-d scratch-directory] [-s size-in-megabytes]\n",
- myname);
- exit(1);
- }
-
- static void
- timestamp()
- {
- last_timestamp = time_so_far();
- last_cpustamp = cpu_so_far();
- }
-
- static void
- get_delta_t(which)
- int which;
- {
- delta[which][Elapsed] = time_so_far() - last_timestamp;
- delta[which][CPU] = cpu_so_far() - last_cpustamp;
- }
-
- static double
- cpu_so_far()
- {
- #ifdef i386
- struct tms tms;
-
- if (times(&tms) == -1)
- io_error("times");
- return ((double) tms.tms_utime) / ((double) CLK_TCK) +
- ((double) tms.tms_stime) / ((double) CLK_TCK);
-
- #else
- struct rusage rusage;
-
- getrusage(RUSAGE_SELF, &rusage);
- return
- ((double) rusage.ru_utime.tv_sec) +
- (((double) rusage.ru_utime.tv_usec) / 1000000.0) +
- ((double) rusage.ru_stime.tv_sec) +
- (((double) rusage.ru_stime.tv_usec) / 1000000.0);
- #endif
- }
-
- static double
- time_so_far()
- {
- #ifdef i386
- int val;
- struct tms tms;
-
- if ((val = times(&tms)) == -1)
- io_error("times");
-
- return ((double) val) / ((double) CLK_TCK);
-
- #else
- struct timeval tp;
-
- if (gettimeofday(&tp, (struct timezone *) NULL) == -1)
- io_error("gettimeofday");
- return ((double) (tp.tv_sec - basetime)) +
- (((double) tp.tv_usec) / 1000000.0);
- #endif
- }
-
- static void
- io_error(message)
- char * message;
- {
- char buf[BUFSIZ];
-
- sprintf(buf, "%s: drastic I/O error (%s)", myname, message);
- perror(buf);
- exit(1);
- }
-
- /*
- * Do a typical-of-something random I/O. Any serious application that
- * has a random I/O bottleneck is going to be smart enough to operate
- * in a page mode, and not stupidly pull individual words out at
- * odd offsets. To keep the cache from getting too clever, some
- * pages must be updated. However an application that updated each of
- * many random pages that it looked at is hard to imagine.
- * However, it would be wrong to put the update percentage in as a
- * parameter - the effect is too nonlinear. Need a profile
- * of what Oracle or Ingres or some such actually does.
- * Be warned - there is a *sharp* elbow in this curve - on a 1-Mb file,
- * most substantial unix systems show >2000 random I/Os per second -
- * obviously they've cached the whole thing and are just doing buffer
- * copies.
- */
- static void
- doseek(where, fd, update)
- long where;
- int fd;
- int update;
- {
- int buf[BUFSIZ / IntSize];
- long probe;
- int size;
-
- probe = (where / BUFSIZ) * BUFSIZ;
- if (lseek(fd, probe, 0) != probe)
- io_error("lseek in doseek");
- if ((size = read(fd, (char *) buf, BUFSIZ)) == -1)
- io_error("read in doseek");
-
- /* every so often, update a block */
- if (update)
- { /* update this block */
-
- /* touch a word */
- buf[((int) random() % (size/IntSize - 2)) + 1]--;
- if (lseek(fd, (long) probe, 0) != probe)
- io_error("lseek in doseek update");
- if (write(fd, (char *) buf, size) == -1)
- io_error("write in doseek");
- } /* update this block */
- }
-
- #ifdef i386
- static char randseed[] = "ioioio";
-
- static long
- random()
- {
- return nrand48(randseed);
- }
- #endif
-
-
-