Whamcloud - gitweb
LU-9511 utils: fix parallel_grouplock test timeout
[fs/lustre-release.git] / lustre / tests / mpi / mdsrate.c
index 097d4a8..cdfab50 100644 (file)
@@ -1,6 +1,4 @@
-/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
- * vim:expandtab:shiftwidth=8:tabstop=8:
- *
+/*
  * 2003, Copyright, Hewlett-Packard Development Compnay, LP.
  *
  * Developed under the sponsorship of the U.S. Government
@@ -8,8 +6,10 @@
  */
 
 /*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
  * Use is subject to license terms.
+ *
+ * Copyright (c) 2012, 2015, Intel Corporation.
  */
 
 #include <stdio.h>
 #include <signal.h>
 #include <sys/ioctl.h>
 #include <dirent.h>
+#include <sys/xattr.h>
 
 #include "mpi.h"
 
 /* lustre */
-#include <liblustre.h>
-#include <lustre/liblustreapi.h>        /* for O_LOV_DELAY_CREATE */
+#include <lustre/lustreapi.h>        /* for O_LOV_DELAY_CREATE */
 
 #define CHECK_COUNT 10000
 #define DISPLAY_COUNT (CHECK_COUNT * 10)
 #define DISPLAY_TIME 100
 
 enum {
-        CREATE   = 'c',
-        LOOKUP   = 'l',
-        MKNOD    = 'm',
-        OPEN     = 'o',
-        STAT     = 's',
-        UNLINK   = 'u',
-        BEGIN    = 'b',
-        ITERS    = 'i',
-        TIME     = 't',
-        DIRFMT   = 'd',
-        NDIRS    = 'D',
-        FILEFMT  = 'f',
-        NFILES   = 'F',
-        NOEXCL   = 'X',
-        STRIPES  = 'S',
-        SEED     = 'r',
-        SEEDFILE = 'R',
-        RANDOM   = 'A',
-        READDIR  = 'B',
-        RECREATE = 'C',
-        IGNORE   = 'E',
-        VERBOSE  = 'V',
-        DEBUG    = 'v',
-        HELP     = 'h',
+       CREATE          = 'c',
+       LOOKUP          = 'l',
+       MKNOD           = 'm',
+       OPEN            = 'o',
+       STAT            = 's',
+       UNLINK          = 'u',
+       BEGIN           = 'b',
+       ITERS           = 'i',
+       TIME            = 't',
+       DIRFMT          = 'd',
+       NDIRS           = 'D',
+       FILEFMT         = 'f',
+       NFILES          = 'F',
+       NOEXCL          = 'X',
+       STRIPES         = 'S',
+       SEED            = 'r',
+       SEEDFILE        = 'R',
+       RANDOM          = 'A',
+       READDIR         = 'B',
+       RECREATE        = 'C',
+       SETXATTR        = 'x',
+       SMALLWRITE      = 'w',
+       IGNORE          = 'E',
+       VERBOSE         = 'V',
+       DEBUG           = 'v',
+       HELP            = 'h',
+       MNT             = 'M',
+       MNTCOUNT        = 'N',
+       MDTCOUNT        = 'T',
 };
 
 struct option longOpts[] = {
-        {"create",        0, NULL, CREATE     },
-        {"lookup",        0, NULL, LOOKUP     },
-        {"mknod",         0, NULL, MKNOD      },
-        {"open",          0, NULL, OPEN       },
-        {"stat",          0, NULL, STAT       },
-        {"unlink",        0, NULL, UNLINK     },
-        {"begin",         1, NULL, BEGIN      },
-        {"iters",         1, NULL, ITERS      },
-        {"time",          1, NULL, TIME       },   /* seconds */
-        {"dirfmt",        1, NULL, DIRFMT     },
-        {"ndirs",         1, NULL, NDIRS      },
-        {"filefmt",       1, NULL, FILEFMT    },
-        {"nfiles",        1, NULL, NFILES     },
-        {"noexcl",        0, NULL, NOEXCL     },
-        {"stripes",       1, NULL, STRIPES    },
-        {"seed",          1, NULL, SEED       },
-        {"seedfile",      1, NULL, SEEDFILE   },
-        {"random_order",  0, NULL, RANDOM     },
-        {"readdir_order", 0, NULL, READDIR    },
-        {"recreate",      0, NULL, RECREATE   },
-        {"ignore",        0, NULL, IGNORE     },
-        {"verbose",       0, NULL, VERBOSE    },
-        {"debug",         0, NULL, DEBUG      },
-        {"help",          0, NULL, HELP       },
-        { 0,              0, NULL, 0          }
+       { .name = "create", .has_arg = no_argument, .val = CREATE },
+       { .name = "lookup", .has_arg = no_argument, .val = LOOKUP },
+       { .name = "mknod", .has_arg = no_argument, .val = MKNOD },
+       { .name = "open", .has_arg = no_argument, .val = OPEN },
+       { .name = "stat", .has_arg = no_argument, .val = STAT },
+       { .name = "unlink", .has_arg = no_argument, .val = UNLINK },
+       { .name = "begin", .has_arg = required_argument, .val = BEGIN },
+       { .name = "iters", .has_arg = required_argument, .val = ITERS },
+       /* time is in seconds */
+       { .name = "time", .has_arg = required_argument, .val = TIME },
+       { .name = "dirfmt", .has_arg = required_argument, .val = DIRFMT },
+       { .name = "ndirs", .has_arg = required_argument, .val = NDIRS },
+       { .name = "filefmt", .has_arg = required_argument, .val = FILEFMT },
+       { .name = "nfiles", .has_arg = required_argument, .val = NFILES },
+       { .name = "noexcl", .has_arg = no_argument, .val = NOEXCL },
+       { .name = "stripes", .has_arg = required_argument, .val = STRIPES },
+       { .name = "seed", .has_arg = required_argument, .val = SEED },
+       { .name = "seedfile", .has_arg = required_argument, .val = SEEDFILE },
+       { .name = "random_order", .has_arg = no_argument, .val = RANDOM },
+       { .name = "readdir_order", .has_arg = no_argument, .val = READDIR },
+       { .name = "recreate", .has_arg = no_argument, .val = RECREATE },
+       { .name = "setxattr", .has_arg = no_argument, .val = SETXATTR },
+       { .name = "smallwrite", .has_arg = no_argument, .val = SMALLWRITE },
+       { .name = "ignore", .has_arg = no_argument, .val = IGNORE },
+       { .name = "verbose", .has_arg = no_argument, .val = VERBOSE },
+       { .name = "debug", .has_arg = no_argument, .val = DEBUG },
+       { .name = "help", .has_arg = no_argument, .val = HELP },
+       { .name = "mdtcount", .has_arg = required_argument, .val = MDTCOUNT },
+       { .name = "mntcount", .has_arg = required_argument, .val = MNTCOUNT },
+       { .name = "mntfmt", .has_arg = required_argument, .val = MNT },
+       { .name = NULL }
 };
 
 int foo1, foo2;
@@ -115,6 +126,7 @@ struct dirent *dir_entry;
 int    nfiles;
 char   filefmt[PATH_MAX];
 char   filename[PATH_MAX];
+char   path[PATH_MAX];
 int    stripes = -1;
 int    begin;
 int    beginsave;
@@ -130,19 +142,26 @@ int    ignore;
 int    verbose;
 int    debug;
 struct stat statbuf;
+bool   with_xattr;
+char   xattrname[] = "user.mdsrate";
+char   xattrbuf[4096];
+/* max xattr name + value length is block size, use 4000 here to avoid ENOSPC */
+int    xattrlen = 4000;
+bool   smallwrite;
+int    mnt_count = -1;
+int    mdt_count = 1;
+char  *mntfmt;
 
 #define dmesg if (debug) printf
 
 #define DISPLAY_PROGRESS() {                                                \
-        if ((++nops % CHECK_COUNT) == 0 && verbose) {                       \
-                curTime = time(0);                                          \
+        if (verbose && (nops % CHECK_COUNT == 0)) {                         \
+                curTime = MPI_Wtime();                                      \
                 interval = curTime - lastTime;                              \
                 if (interval > DISPLAY_TIME || nops % DISPLAY_COUNT == 0) { \
-                        rate = (float)(nops - lastOps);                     \
-                        if (interval > 1)                                   \
-                                rate /= (float)interval;                    \
-                        printf("Rank %d: %.2f %ss/sec %lu secs "            \
-                               "(total: %d %ss %lu secs)\n",                \
+                        rate = (double)(nops - lastOps)/interval;           \
+                        printf("Rank %d: %.2f %ss/sec %.2f secs "           \
+                               "(total: %d %ss %.2f secs)\n",               \
                                myrank, rate, cmd, interval,                 \
                                nops, cmd, curTime - startTime);             \
                         lastOps = nops;                                     \
@@ -152,15 +171,19 @@ struct stat statbuf;
 }
 
 char *usage_msg = "usage: %s\n"
-                  "    { --create [ --noexcl ] | --lookup | --mknod |\n"
-                  "      --open | --stat | --unlink  [ --recreate ] [ --ignore ] }\n"
-                  "    [ --help ] [ --verbose ] [ --debug ]\n"
-                  "    { [ --begin <num> ] --nfiles <num> }\n"
-                  "    [ --iters <num> ] [ --time <secs> ]\n"
-                  "    [ --dirfmt <str> ] [ --ndirs  <num> ]\n"
-                  "    [ --filefmt <str> ] [ --stripes <num> ]\n"
-                  "    [ --random_order [--seed <num> | --seedfile <file>] ]\n"
-                  "    [ --readdir_order ]\n";
+                 "    { --create [ --noexcl | --setxattr | --smallwrite ] |\n"
+                 "      --lookup | --mknod [ --setxattr ] | --open |\n"
+                 "      --stat | --unlink [ --recreate ] [ --ignore ] |\n"
+                 "      --setxattr }\n"
+                 "    [ --help ] [ --verbose ] [ --debug ]\n"
+                 "    { [ --begin <num> ] --nfiles <num> }\n"
+                 "    [ --iters <num> ] [ --time <secs> ]\n"
+                 "    [ --dirfmt <str> ] [ --ndirs  <num> ]\n"
+                 "    [ --filefmt <str> ] [ --stripes <num> ]\n"
+                 "    [ --random_order [--seed <num> | --seedfile <file>] ]\n"
+                 "    [ --readdir_order ] [ --mntfmt <str> ]\n"
+                 "    [ --mntcount <num> ] [ --mdtcount <num> ]\n"
+                 "    [ --setxattr ] }\n";
 
 static void
 usage(FILE *stream, char *fmt, ...)
@@ -204,47 +227,10 @@ sigalrm_handler(int signum)
         alarm_caught++;
 }
 
-/* HAVE_LLAPI_FILE_LOOKUP is defined by liblustreapi.h if this function is
- * defined therein.  Otherwise we can do the equivalent operation via ioctl
- * if we have access to a complete lustre build tree to get the various
- * definitions - then compile with USE_MDC_LOOKUP defined. */
-#if defined(HAVE_LLAPI_FILE_LOOKUP)
-#define HAVE_MDC_LOOKUP
-#elif defined(USE_MDC_LOOKUP)
-#include <config.h>
-#include <liblustre.h>
-#include <linux/lustre_lib.h>
-
-int llapi_file_lookup(int dirfd, const char *name)
-{
-        struct obd_ioctl_data data = { 0 };
-        char rawbuf[8192];
-        char *buf = rawbuf;
-        int rc;
-
-        if (dirfd < 0 || name == NULL)
-                return -EINVAL;
-
-        data.ioc_version = OBD_IOCTL_VERSION;
-        data.ioc_len = sizeof(data);
-        data.ioc_inlbuf1 = name;
-        data.ioc_inllen1 = strlen(name) + 1;
-
-        rc = obd_ioctl_pack(&data, &buf, sizeof(rawbuf));
-        if (rc) {
-                fatal(myrank, "ioctl_pack failed: rc = %d\n", rc);
-                return rc;
-        }
-
-        return ioctl(fd, IOC_MDC_LOOKUP, buf);
-}
-#define HAVE_MDC_LOOKUP
-#endif
-
 static void
 process_args(int argc, char *argv[])
 {
-        char   c, *cp, *endptr;
+        char   *cp, *endptr;
         int    i, index, offset, tmpend, rc;
         char   tmp[16];
         FILE * seed_file;
@@ -263,14 +249,12 @@ process_args(int argc, char *argv[])
                         *++cp = ':';
         }
 
-        while ((c = getopt_long(argc,argv, shortOpts, longOpts,&index)) != -1) {
-                switch (c) {
+        while ((rc = getopt_long(argc,argv, shortOpts, longOpts,&index)) != -1) {
+                switch (rc) {
                 case OPEN:
                         openflags &= ~(O_CREAT|O_EXCL);
                 case CREATE:
-#ifdef HAVE_MDC_LOOKUP
                 case LOOKUP:
-#endif
                 case MKNOD:
                 case STAT:
                 case UNLINK:
@@ -279,7 +263,7 @@ process_args(int argc, char *argv[])
                                            "specified: --%s\n",
                                         longOpts[index].name);
                         }
-                        mode = c;
+                        mode = rc;
                         cmd = (char *)longOpts[index].name;
                         break;
                 case NOEXCL:
@@ -296,6 +280,23 @@ process_args(int argc, char *argv[])
                         }
                         recreate++;
                         break;
+               case SETXATTR:
+                       if (cmd == NULL) {
+                               mode = SETXATTR;
+                               cmd = (char *)longOpts[index].name;
+                       } else if (mode == CREATE || mode == MKNOD) {
+                               with_xattr = true;
+                       } else {
+                               usage(stderr, "--setxattr only makes sense "
+                                     "with --create, --mknod or alone.\n");
+                       }
+                       break;
+               case SMALLWRITE:
+                       if (mode != CREATE)
+                               usage(stderr, "--smallwrite only applies to "
+                                             "--create.\n");
+                       smallwrite = true;
+                       break;
                 case BEGIN:
                         begin = strtol(optarg, &endptr, 0);
                         if ((*endptr != 0) || (begin < 0)) {
@@ -307,9 +308,9 @@ process_args(int argc, char *argv[])
                         if ((*endptr != 0) || (iters <= 0)) {
                                 fatal(0, "Invalid --iters value.\n");
                         }
-                        if (mode != LOOKUP && mode != OPEN && mode != STAT) {
+                        if (mode != LOOKUP && mode != OPEN) {
                                 usage(stderr, "--iters only makes sense with "
-                                              "--lookup, --open, or --stat.\n");
+                                              "--lookup or --open.\n");
                         }
                         break;
                 case TIME:
@@ -399,12 +400,12 @@ process_args(int argc, char *argv[])
                         break;
                 case RANDOM:
                 case READDIR:
-                        if (mode != LOOKUP && mode != OPEN && mode != STAT)  {
+                        if (mode != LOOKUP && mode != OPEN)  {
                                 fatal(0, "--%s can only be specified with "
-                                         "--lookup, --open, or --stat.\n",
+                                         "--lookup, or --open.\n",
                                       (char *)longOpts[index].name);
                         }
-                        order = c;
+                        order = rc;
                         break;
                 case IGNORE:
                         ++ignore;
@@ -416,6 +417,26 @@ process_args(int argc, char *argv[])
                         break;
                 case HELP:
                         usage(stdout, NULL);
+                       break;
+               case MNT:
+                       if (strlen(optarg) > (PATH_MAX - 16))
+                               fatal(0, "--mnt too long\n");
+                       mntfmt = optarg;
+                       break;
+               case MNTCOUNT:
+                       mnt_count = strtol(optarg, &endptr, 0);
+                       if ((*endptr != 0) || (mnt_count <= 0)) {
+                               fatal(0, "Invalid --mnt_count value %s.\n",
+                                     optarg);
+                       }
+                       break;
+               case MDTCOUNT:
+                       mdt_count = strtol(optarg, &endptr, 0);
+                       if ((*endptr != 0) || (mdt_count <= 0)) {
+                               fatal(0, "Invalid --mdt_count value %s.\n",
+                                     optarg);
+                       }
+                       break;
                 default:
                         usage(stderr, "unrecognized option: '%c'.\n", optopt);
                 }
@@ -425,15 +446,22 @@ process_args(int argc, char *argv[])
                 usage(stderr, "too many arguments %d >= %d.\n", optind, argc);
         }
 
-        if (mode == CREATE || mode == MKNOD || mode == UNLINK) {
-                if (seconds != 0) {
-                        if (nfiles == 0)
-                                nfiles = INT_MAX;
-                } else if (nfiles == 0) {
-                        usage(stderr, "--nfiles or --time must be specified "
-                                      "with %s.\n", cmd);
-                }
-        } else if (mode == LOOKUP || mode == OPEN || mode == STAT) {
+       if ((mnt_count != -1 && mntfmt == NULL) ||
+           (mnt_count == -1 && mntfmt != NULL)) {
+               usage(stderr, "mnt_count and mntfmt must be specified at the "
+                            "same time\n");
+       }
+
+       if (mode == CREATE || mode == MKNOD || mode == UNLINK ||
+           mode == STAT || mode == SETXATTR) {
+               if (seconds != 0) {
+                       if (nfiles == 0)
+                               nfiles = INT_MAX;
+               } else if (nfiles == 0) {
+                       usage(stderr, "--nfiles or --time must be specified "
+                                     "with %s.\n", cmd);
+               }
+        } else if (mode == LOOKUP || mode == OPEN) {
                 if (seconds != 0) {
                         if (iters == 0)
                                 iters = INT_MAX;
@@ -465,11 +493,9 @@ process_args(int argc, char *argv[])
                 dmesg("%s: rank %d seed %d (%s).\n", prog, myrank, seed,
                       (order == RANDOM) ? "random_order" : "readdir_order");
         } else {
-                usage(stderr, "one --create, --mknod, --open, --stat,"
-#ifdef HAVE_MDC_LOOKUP
-                      " --lookup,"
-#endif
-                      " or --unlink must be specifed.");
+               usage(stderr, "one --create, --mknod, --open, --stat,"
+                     " --lookup,"
+                     " --unlink or --setxattr must be specifed.");
         }
 
         /* support for multiple threads in a dir, set begin/end appropriately.*/
@@ -488,6 +514,11 @@ process_args(int argc, char *argv[])
         if ((end > tmpend) || (end <= 0))
                 end -= dirthreads;
 
+       /* make sure mnt_count <= nthreads, otherwise it might div 0 in
+        * the following test */
+       if (mnt_count > nthreads)
+               mnt_count = nthreads;
+
         begin += offset;
         if (begin < 0)
                 begin = INT_MAX;
@@ -500,17 +531,38 @@ process_args(int argc, char *argv[])
         if (dirfmt == NULL) {
                 strcpy(dir, ".");
         } else {
-                sprintf(dir, dirfmt, dirnum);
-
-                sprintf(mkdir_cmd, "/bin/mkdir -p %s", dir);
-                #ifdef _LIGHTWEIGHT_KERNEL
-                        printf("NOTICE: not running system(%s)\n", mkdir_cmd);
-                #else
-                        rc = system(mkdir_cmd);
-                        if (rc) {
-                                fatal(myrank, "'%s' failed.\n", mkdir_cmd);
-                        }
-                #endif
+               int dir_len = 0;
+
+               if (mntfmt != NULL) {
+                       sprintf(dir, mntfmt, (myrank / (nthreads/mnt_count)));
+                       strcat(dir, "/");
+                       dir_len = strlen(dir);
+               }
+               sprintf(dir + dir_len, dirfmt, dirnum);
+
+               if (mdt_count > 1) {
+                       struct stat sb;
+                       if (stat(dir, &sb) == 0) {
+                               if (!S_ISDIR(sb.st_mode))
+                                       fatal(myrank, "'%s' is not dir\n", dir);
+                       } else if (errno == ENOENT) {
+                               sprintf(mkdir_cmd, "lfs mkdir -i %d %s",
+                                       myrank % mdt_count, dir);
+                       } else {
+                               fatal(myrank, "'%s' stat failed\n", dir);
+                       }
+               } else {
+                       sprintf(mkdir_cmd, "mkdir -p %s", dir);
+               }
+
+               dmesg("%d: %s\n", myrank, mkdir_cmd);
+#ifdef _LIGHTWEIGHT_KERNEL
+               printf("NOTICE: not running system(%s)\n", mkdir_cmd);
+#else
+               rc = system(mkdir_cmd);
+               if (rc)
+                       fatal(myrank, "'%s' failed.\n", mkdir_cmd);
+#endif
 
                 rc = chdir(dir);
                 if (rc) {
@@ -546,9 +598,13 @@ static inline char *next_file()
 int
 main(int argc, char *argv[])
 {
-        int    i, j, fd, rc, nops, lastOps, ag_ops;
-        float  rate, ag_rate;
-        time_t startTime, lastTime, curTime, interval;
+        int    i, j, fd, rc, nops, lastOps;
+        int ag_ops = 0;
+        double ag_interval = 0;
+        double ag_rate = 0;
+        double rate, avg_rate, effective_rate;
+        double startTime, curTime, lastTime, interval;
+        time_t timestamp;
         char * file;
 
         rc = MPI_Init(&argc, &argv);
@@ -565,15 +621,16 @@ main(int argc, char *argv[])
 
         process_args(argc, argv);
 
-        startTime = time(0);
+        timestamp = time(0);
         if ((myrank == 0) || debug) {
                printf("%d: %s starting at %s",
-                      myrank, hostname, ctime(&startTime));
+                      myrank, hostname, ctime(&timestamp));
        }
 
         /* if we're not measuring creation rates then precreate
          * the files we're operating on. */
-        if ((mode != CREATE) && (mode != MKNOD) && !ignore) {
+        if ((mode != CREATE) && (mode != MKNOD) && !ignore &&
+            (mode != UNLINK || recreate)) {
                 /* create the files in reverse order. When we encounter
                  * a file that already exists, assume the remainder of 
                  * the files exist to save time. The timed performance
@@ -606,10 +663,10 @@ main(int argc, char *argv[])
                               dir, strerror(rc));
                 }
 
-                startTime = time(0);
+                timestamp = time(0);
                 j = random() % nfiles;
                 dmesg("%d: %s initializing dir offset %u: %s",
-                      myrank, hostname, j, ctime(&startTime));
+                      myrank, hostname, j, ctime(&timestamp));
 
                 for (i = 0; i <= j; i++) {
                         if ((dir_entry = readdir(directory)) == NULL) {
@@ -618,17 +675,13 @@ main(int argc, char *argv[])
                         }
                 }
 
-                lastTime = time(0);
+                timestamp = time(0);
                 dmesg("%d: index %d, filename %s, offset %ld: "
                       "%s initialization complete: %s",
                       myrank, i, dir_entry->d_name, telldir(directory),
-                      hostname, ctime(&lastTime));
+                      hostname, ctime(&timestamp));
         }
 
-        rc = MPI_Barrier(MPI_COMM_WORLD);
-        if (rc != MPI_SUCCESS)
-                fatal(myrank, "prep MPI_Barrier failed: %d\n", rc);
-
         if (seconds) {
                 act.sa_handler = sigalrm_handler;
                 (void)sigemptyset(&act.sa_mask);
@@ -637,28 +690,58 @@ main(int argc, char *argv[])
                 alarm(seconds);
         }
 
-        startTime = lastTime = time(0);
+        rc = MPI_Barrier(MPI_COMM_WORLD);
+        if (rc != MPI_SUCCESS)
+                fatal(myrank, "prep MPI_Barrier failed: %d\n", rc);
+
+        startTime = lastTime = MPI_Wtime();
         nops = lastOps = 0;
 
         switch (mode) {
-        case CREATE:
-                for (; begin <= end && !alarm_caught; begin += dirthreads) {
-                        sprintf(filename, filefmt, begin);
-                        if ((fd = open(filename, openflags, 0644)) < 0) {
-                                if (((rc = errno) == EINTR) && alarm_caught)
-                                        break;
-                                fatal(myrank, "open(%s) error: %s\n",
-                                      filename, strerror(rc));
-                        }
-
-                        close(fd);
-                        DISPLAY_PROGRESS();
-                }
-
-                dmesg("%d: created %d files, last file '%s'.\n",
-                      myrank, nops, filename);
-                break;
-#ifdef HAVE_MDC_LOOKUP
+       case CREATE:
+               for (; begin <= end && !alarm_caught; begin += dirthreads) {
+                       snprintf(filename, sizeof(filename), filefmt, begin);
+                       fd = open(filename, openflags, 0644);
+                       if (fd < 0) {
+                               rc = errno;
+                               if (rc == EINTR && alarm_caught)
+                                       break;
+                               fatal(myrank, "open(%s) error: %s\n",
+                                     filename, strerror(rc));
+                       }
+
+                       if (with_xattr) {
+                               rc = fsetxattr(fd, xattrname, xattrbuf,
+                                              xattrlen, XATTR_CREATE);
+                               if (rc) {
+                                       rc = errno;
+                                       if (rc == EINTR && alarm_caught)
+                                               break;
+                                       fatal(myrank,
+                                             "setxattr(%s) error: %s\n",
+                                             filename, strerror(rc));
+                               }
+                       }
+                       if (smallwrite) {
+                               rc = write(fd, xattrbuf, xattrlen);
+                               if (rc < 0) {
+                                       rc = errno;
+                                       if (rc == EINTR && alarm_caught)
+                                               break;
+                                       fatal(myrank,
+                                             "write(%s) error: %s\n",
+                                             filename, strerror(rc));
+                               }
+                       }
+
+                       close(fd);
+                       nops++;
+                       DISPLAY_PROGRESS();
+               }
+
+               dmesg("%d: created %d files, last file '%s'.\n",
+                     myrank, nops, filename);
+               break;
         case LOOKUP:
                 fd = open(dir, O_RDONLY);
                 if (fd < 0) {
@@ -676,24 +759,39 @@ main(int argc, char *argv[])
                                       "error: %s\n", filename, strerror(rc));
                         }
 
+                        nops++;
                         DISPLAY_PROGRESS();
                 }
                 break;
-#endif
-        case MKNOD:
-                for (; begin <= end && !alarm_caught; begin += dirthreads) {
-                        sprintf(filename, filefmt, begin);
-                        rc = mknod(filename, S_IFREG| 0644, 0);
-                        if (rc) {
-                                if (((rc = errno) == EINTR) && alarm_caught)
-                                        break;
-                                fatal(myrank, "mknod(%s) error: %s\n",
-                                      filename, strerror(rc));
-                        }
-
-                        DISPLAY_PROGRESS();
-                }
-                break;
+       case MKNOD:
+               for (; begin <= end && !alarm_caught; begin += dirthreads) {
+                       snprintf(filename, sizeof(filename), filefmt, begin);
+                       rc = mknod(filename, S_IFREG | 0644, 0);
+                       if (rc) {
+                               rc = errno;
+                               if (rc == EINTR && alarm_caught)
+                                       break;
+                               fatal(myrank, "mknod(%s) error: %s\n",
+                                     filename, strerror(rc));
+                       }
+
+                       if (with_xattr) {
+                               rc = setxattr(filename, xattrname, xattrbuf,
+                                             xattrlen, XATTR_CREATE);
+                               if (rc) {
+                                       rc = errno;
+                                       if (rc == EINTR && alarm_caught)
+                                               break;
+                                       fatal(myrank,
+                                             "setxattr(%s) error: %s\n",
+                                             filename, strerror(rc));
+                               }
+                       }
+
+                       nops++;
+                       DISPLAY_PROGRESS();
+               }
+               break;
         case OPEN:
                 for (; nops < iters && !alarm_caught;) {
                         file = next_file();
@@ -706,19 +804,24 @@ main(int argc, char *argv[])
 
                         close(fd);
 
+                        nops++;
                         DISPLAY_PROGRESS();
                 }
                 break;
         case STAT:
-                for (; nops < iters && !alarm_caught;) {
-                        rc = stat(file = next_file(), &statbuf);
+                for (; begin <= end && !alarm_caught; begin += dirthreads) {
+                        sprintf(filename, filefmt, begin);
+                        rc = stat(filename, &statbuf);
                         if (rc) {
                                 if (((rc = errno) == EINTR) && alarm_caught)
                                         break;
+                                if (((rc = errno) == ENOENT) && ignore)
+                                        continue;
                                 fatal(myrank, "stat(%s) error: %s\n",
-                                      file, strerror(rc));
+                                      filename, strerror(rc));
                         }
 
+                        nops++;
                         DISPLAY_PROGRESS();
                 }
                 break;
@@ -729,22 +832,47 @@ main(int argc, char *argv[])
                         if (rc) {
                                 if (((rc = errno) == EINTR) && alarm_caught)
                                         break;
-                                if (((rc = errno) == ENOENT) && ignore)
-                                        continue;
+                                if ((rc = errno) == ENOENT) {
+                                        if (ignore)
+                                                continue;
+                                        /* no more files to unlink */
+                                        break;
+                                }
                                 fatal(myrank, "unlink(%s) error: %s\n",
                                       filename, strerror(rc));
                         }
 
+                        nops++;
                         DISPLAY_PROGRESS();
                 }
                 break;
+       case SETXATTR:
+               for (; begin <= end && !alarm_caught; begin += dirthreads) {
+                       snprintf(filename, sizeof(filename), filefmt, begin);
+                       rc = setxattr(filename, xattrname, xattrbuf, xattrlen,
+                                     XATTR_CREATE);
+                       if (rc) {
+                               rc = errno;
+                               if (rc == EINTR && alarm_caught)
+                                       break;
+                               if (rc == ENOENT && ignore)
+                                       continue;
+                               fatal(myrank, "setxattr(%s) error: %s\n",
+                                     filename, strerror(rc));
+                       }
+
+                       nops++;
+                       DISPLAY_PROGRESS();
+               }
+               break;
         }
 
-        curTime = time(0);
+        rc = MPI_Barrier(MPI_COMM_WORLD);
+        if (rc != MPI_SUCCESS)
+               fatal(myrank, "prep MPI_Barrier failed: %d\n", rc);
+        curTime = MPI_Wtime();
         interval = curTime - startTime;
-        rate = (float)(nops);
-        if (interval != 0)
-                rate /= (float)interval;
+        rate = (double) (nops) / interval;
 
         rc = MPI_Reduce(&nops, &ag_ops, 1, MPI_INT, MPI_SUM, 0,
                         MPI_COMM_WORLD);
@@ -752,15 +880,31 @@ main(int argc, char *argv[])
                 fatal(myrank, "Failure in MPI_Reduce of total ops.\n");
         }
 
-        rc = MPI_Reduce(&rate, &ag_rate, 1, MPI_FLOAT, MPI_SUM, 0,
+        rc = MPI_Reduce(&interval, &ag_interval, 1, MPI_DOUBLE, MPI_SUM, 0,
+                        MPI_COMM_WORLD);
+        if (rc != MPI_SUCCESS) {
+                fatal(myrank, "Failure in MPI_Reduce of total interval.\n");
+        }
+
+        rc = MPI_Reduce(&rate, &ag_rate, 1, MPI_DOUBLE, MPI_SUM, 0,
                         MPI_COMM_WORLD);
         if (rc != MPI_SUCCESS) {
                 fatal(myrank, "Failure in MPI_Reduce of aggregated rate.\n");
         }
 
         if (myrank == 0) {
-                printf("Rate: %.2f %ss/sec (total: %d threads %d %ss %lu secs)"
-                       "\n", ag_rate, cmd, nthreads, ag_ops, cmd, interval);
+                curTime = MPI_Wtime();
+                interval = curTime - startTime;
+                effective_rate = (double) ag_ops / interval;
+                avg_rate = (double) ag_ops / ag_interval;
+
+                printf("Rate: %.2f eff %.2f aggr %.2f avg client %ss/sec "
+                       "(total: %d threads %d %ss %d dirs %d threads/dir %.2f secs)\n",
+                       effective_rate, ag_rate, avg_rate, cmd, nthreads, ag_ops,
+                       cmd, ndirs, dirthreads, interval);
+                if (mode == UNLINK && !recreate && !ignore && ag_ops != nfiles)
+                        printf("Warning: only unlinked %d files instead of %d"
+                               "\n", ag_ops, nfiles);
         }
 
         if (recreate) {
@@ -778,10 +922,10 @@ main(int argc, char *argv[])
                 }
         }
 
-        curTime = time(0);
+        timestamp = time(0);
         if ((myrank == 0) || debug) {
                printf("%d: %s finished at %s",
-                      myrank, hostname, ctime(&curTime));
+                      myrank, hostname, ctime(&timestamp));
        }
 
         MPI_Finalize();