zbd: Fix build errors on Windows and MacOS master
authorDamien Le Moal <damien.lemoal@wdc.com>
Wed, 8 Apr 2020 01:54:26 +0000 (10:54 +0900)
committerJens Axboe <axboe@kernel.dk>
Wed, 8 Apr 2020 02:20:36 +0000 (20:20 -0600)
Including dirent.h is not needed, so remove it to avoid a compilation
error on Windows and MacOS. Also make sure that EREMOTEIO is defined as
some OSes do not have this error code.

Fixes: b76949618d55 ("fio: Generalize zonemode=zbd")
Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
42 files changed:
.appveyor.yml
.gitignore
.travis.yml
FIO-VERSION-GEN
Makefile
backend.c
configure
engines/e4defrag.c
engines/filestat.c
engines/io_uring.c
engines/libhdfs.c
engines/libzbc.c [new file with mode: 0644]
engines/rbd.c
engines/rdma.c
engines/skeleton_external.c
filesetup.c
fio.1
fio.h
gclient.c
io_u.c
io_u.h
ioengines.h
lib/gauss.c
options.c
os/os-windows.h
oslib/blkzoned.h [new file with mode: 0644]
oslib/linux-blkzoned.c [new file with mode: 0644]
oslib/statx.c [new file with mode: 0644]
oslib/statx.h [new file with mode: 0644]
stat.c
t/arch.c
t/io_uring.c
t/jsonplus2csv_test.py [new file with mode: 0755]
t/latency_percentiles.py
t/run-fio-tests.py
t/zbd/functions
t/zbd/test-zbd-support
tools/fio_jsonplus_clat2csv
tools/genfio
zbd.c
zbd.h
zbd_types.h [new file with mode: 0644]

index bf0978a..2f962c4 100644 (file)
@@ -15,7 +15,7 @@ environment:
 install:
   - '%CYG_ROOT%\setup-x86_64.exe --quiet-mode --no-shortcuts --only-site --site "%CYG_MIRROR%" --packages "mingw64-%PACKAGE_ARCH%-zlib,mingw64-%PACKAGE_ARCH%-CUnit" > NUL'
   - SET PATH=C:\Python38-x64;%CYG_ROOT%\bin;%PATH% # NB: Changed env variables persist to later sections
-  - python.exe -m pip install scipy
+  - python.exe -m pip install scipy six
 
 build_script:
   - 'bash.exe -lc "cd \"${APPVEYOR_BUILD_FOLDER}\" && ./configure --disable-native --extra-cflags=\"-Werror\" ${CONFIGURE_OPTIONS} && make.exe'
index b228938..b84b0fd 100644 (file)
 /t/fio-verify-state
 /t/gen-rand
 /t/ieee754
+t/io_uring
 /t/lfsr-test
+t/memlock
+t/read-to-pipe-async
 /t/stest
 /unittests/unittest
 y.tab.*
index 77c31b7..6b710cc 100644 (file)
@@ -48,8 +48,9 @@ before_install:
         brew install cunit;
         if [[ "$TRAVIS_OSX_IMAGE" == "xcode11.2" ]]; then
             pip3 install scipy;
+        else
+            pip install scipy;
         fi;
-        pip install scipy;
     fi;
 script:
   - ./configure --extra-cflags="${EXTRA_CFLAGS}" && make
index 6c2bcc8..3220aaa 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=FIO-VERSION-FILE
-DEF_VER=fio-3.18
+DEF_VER=fio-3.19
 
 LF='
 '
index 027b62b..5bcd606 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@ SOURCE :=     $(sort $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/crc/*.c)) \
                gettime-thread.c helpers.c json.c idletime.c td_error.c \
                profiles/tiobench.c profiles/act.c io_u_queue.c filelock.c \
                workqueue.c rate-submit.c optgroup.c helper_thread.c \
-               steadystate.c zone-dist.c
+               steadystate.c zone-dist.c zbd.c
 
 ifdef CONFIG_LIBHDFS
   HDFSFLAGS= -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/linux -I $(FIO_LIBHDFS_INCLUDE)
@@ -132,6 +132,9 @@ endif
 ifndef CONFIG_INET_ATON
   SOURCE += oslib/inet_aton.c
 endif
+ifndef CONFIG_HAVE_STATX
+  SOURCE += oslib/statx.c
+endif
 ifdef CONFIG_GFAPI
   SOURCE += engines/glusterfs.c
   SOURCE += engines/glusterfs_sync.c
@@ -157,13 +160,16 @@ endif
 ifdef CONFIG_IME
   SOURCE += engines/ime.c
 endif
-ifdef CONFIG_LINUX_BLKZONED
-  SOURCE += zbd.c
+ifdef CONFIG_LIBZBC
+  SOURCE += engines/libzbc.c
 endif
 
 ifeq ($(CONFIG_TARGET_OS), Linux)
   SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c engines/sg.c \
                oslib/linux-dev-lookup.c engines/io_uring.c
+ifdef CONFIG_HAS_BLKZONED
+  SOURCE += oslib/linux-blkzoned.c
+endif
   LIBS += -lpthread -ldl
   LDFLAGS += -rdynamic
 endif
@@ -511,6 +517,7 @@ endif
 
 clean: FORCE
        @rm -f .depend $(FIO_OBJS) $(GFIO_OBJS) $(OBJS) $(T_OBJS) $(UT_OBJS) $(PROGS) $(T_PROGS) $(T_TEST_PROGS) core.* core gfio unittests/unittest FIO-VERSION-FILE *.[do] lib/*.d oslib/*.[do] crc/*.d engines/*.[do] profiles/*.[do] t/*.[do] unittests/*.[do] unittests/*/*.[do] config-host.mak config-host.h y.tab.[ch] lex.yy.c exp/*.[do] lexer.h
+       @rm -f t/fio-btrace2fio t/io_uring t/read-to-pipe-async
        @rm -rf  doc/output
 
 distclean: clean FORCE
index 936203d..feb34e5 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -237,15 +237,10 @@ static void cleanup_pending_aio(struct thread_data *td)
 {
        int r;
 
-       if (td->error)
-               return;
-
        /*
         * get immediately available events, if any
         */
        r = io_u_queued_complete(td, 0);
-       if (r < 0)
-               return;
 
        /*
         * now cancel remaining active events
index a121770..ae2b358 100755 (executable)
--- a/configure
+++ b/configure
@@ -892,7 +892,8 @@ cat > $TMPC << EOF
 
 int main(int argc, char **argv)
 {
-  return vasprintf(NULL, "%s", NULL) == 0;
+  va_list ap;
+  return vasprintf(NULL, "%s", ap) == 0;
 }
 EOF
 if compile_prog "" "" "have_vasprintf"; then
@@ -2396,6 +2397,37 @@ if compile_prog "" "" "linux_blkzoned"; then
 fi
 print_config "Zoned block device support" "$linux_blkzoned"
 
+##########################################
+# libzbc probe
+if test "$libzbc" != "yes" ; then
+  libzbc="no"
+fi
+cat > $TMPC << EOF
+#include <libzbc/zbc.h>
+int main(int argc, char **argv)
+{
+  struct zbc_device *dev = NULL;
+
+  return zbc_open("foo=bar", O_RDONLY, &dev);
+}
+EOF
+if compile_prog "" "-lzbc" "libzbc"; then
+  libzbcvermaj=$(pkg-config --modversion libzbc | sed 's/\.[0-9]*\.[0-9]*//')
+  if test "$libzbcvermaj" -ge "5" ; then
+    libzbc="yes"
+    LIBS="-lzbc $LIBS"
+  else
+    print_config "libzbc engine" "Unsupported libzbc version (version 5 or above required)"
+    libzbc="no"
+  fi
+else
+  if test "$libzbc" = "yes" ; then
+      feature_not_found "libzbc" "libzbc or libzbc/zbc.h"
+  fi
+  libzbc="no"
+fi
+print_config "libzbc engine" "$libzbc"
+
 ##########################################
 # check march=armv8-a+crc+crypto
 if test "$march_armv8_a_crc_crypto" != "yes" ; then
@@ -2551,6 +2583,50 @@ if compile_prog "" "" "gettid"; then
 fi
 print_config "gettid" "$gettid"
 
+##########################################
+# check for statx(2) support by libc
+statx="no"
+cat > $TMPC << EOF
+#include <unistd.h>
+#include <sys/stat.h>
+
+int main(int argc, char **argv)
+{
+       struct statx st;
+       return statx(-1, *argv, 0, 0, &st);
+}
+EOF
+if compile_prog "" "" "statx"; then
+  statx="yes"
+fi
+print_config "statx(2)/libc" "$statx"
+
+##########################################
+# check for statx(2) support by kernel
+statx_syscall="no"
+cat > $TMPC << EOF
+#include <unistd.h>
+#include <linux/stat.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+static int _statx(int dfd, const char *pathname, int flags, unsigned int mask,
+                 struct statx *buffer)
+{
+       return syscall(__NR_statx, dfd, pathname, flags, mask, buffer);
+}
+
+int main(int argc, char **argv)
+{
+       struct statx st;
+       return _statx(-1, *argv, 0, 0, &st);
+}
+EOF
+if compile_prog "" "" "statx_syscall"; then
+  statx_syscall="yes"
+fi
+print_config "statx(2)/syscall" "$statx_syscall"
+
 #############################################################################
 
 if test "$wordsize" = "64" ; then
@@ -2817,7 +2893,10 @@ if test "$valgrind_dev" = "yes"; then
   output_sym "CONFIG_VALGRIND_DEV"
 fi
 if test "$linux_blkzoned" = "yes" ; then
-  output_sym "CONFIG_LINUX_BLKZONED"
+  output_sym "CONFIG_HAS_BLKZONED"
+fi
+if test "$libzbc" = "yes" ; then
+  output_sym "CONFIG_LIBZBC"
 fi
 if test "$zlib" = "no" ; then
   echo "Consider installing zlib-dev (zlib-devel, some fio features depend on it."
@@ -2843,6 +2922,12 @@ fi
 if test "$gettid" = "yes"; then
   output_sym "CONFIG_HAVE_GETTID"
 fi
+if test "$statx" = "yes"; then
+  output_sym "CONFIG_HAVE_STATX"
+fi
+if test "$statx_syscall" = "yes"; then
+  output_sym "CONFIG_HAVE_STATX_SYSCALL"
+fi
 if test "$fallthrough" = "yes"; then
   CFLAGS="$CFLAGS -Wimplicit-fallthrough"
 fi
index 8f71d02..0a0004d 100644 (file)
@@ -72,7 +72,7 @@ static int fio_e4defrag_init(struct thread_data *td)
        struct stat stub;
        char donor_name[PATH_MAX];
 
-       if (!strlen(o->donor_name)) {
+       if (!o->donor_name || !strlen(o->donor_name)) {
                log_err("'donorname' options required\n");
                return 1;
        }
index 68a340b..405f028 100644 (file)
@@ -5,6 +5,7 @@
  * of the file stat.
  */
 #include <stdio.h>
+#include <stdlib.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <sys/types.h>
@@ -12,6 +13,7 @@
 #include <unistd.h>
 #include "../fio.h"
 #include "../optgroup.h"
+#include "../oslib/statx.h"
 
 struct fc_data {
        enum fio_ddir stat_ddir;
@@ -25,7 +27,7 @@ struct filestat_options {
 enum {
        FIO_FILESTAT_STAT       = 1,
        FIO_FILESTAT_LSTAT      = 2,
-       /*FIO_FILESTAT_STATX    = 3,*/
+       FIO_FILESTAT_STATX      = 3,
 };
 
 static struct fio_option options[] = {
@@ -45,12 +47,10 @@ static struct fio_option options[] = {
                            .oval = FIO_FILESTAT_LSTAT,
                            .help = "Use lstat(2)",
                          },
-                         /*
                          { .ival = "statx",
                            .oval = FIO_FILESTAT_STATX,
-                           .help = "Use statx(2)",
+                           .help = "Use statx(2) if exists",
                          },
-                         */
                },
                .category = FIO_OPT_C_ENGINE,
                .group  = FIO_OPT_G_FILESTAT,
@@ -66,6 +66,10 @@ static int stat_file(struct thread_data *td, struct fio_file *f)
        struct timespec start;
        int do_lat = !td->o.disable_lat;
        struct stat statbuf;
+#ifndef WIN32
+       struct statx statxbuf;
+       char *abspath;
+#endif
        int ret;
 
        dprint(FD_FILE, "fd stat %s\n", f->file_name);
@@ -89,6 +93,18 @@ static int stat_file(struct thread_data *td, struct fio_file *f)
        case FIO_FILESTAT_LSTAT:
                ret = lstat(f->file_name, &statbuf);
                break;
+       case FIO_FILESTAT_STATX:
+#ifndef WIN32
+               abspath = realpath(f->file_name, NULL);
+               if (abspath) {
+                       ret = statx(-1, abspath, 0, STATX_ALL, &statxbuf);
+                       free(abspath);
+               } else
+                       ret = -1;
+#else
+               ret = -1;
+#endif
+               break;
        default:
                ret = -1;
                break;
index 5e59f97..ac57af8 100644 (file)
@@ -63,6 +63,8 @@ struct ioring_data {
        int queued;
        int cq_ring_off;
        unsigned iodepth;
+       bool ioprio_class_set;
+       bool ioprio_set;
 
        struct ioring_mmap mmap[3];
 };
@@ -233,9 +235,9 @@ static int fio_ioring_prep(struct thread_data *td, struct io_u *io_u)
                }
                if (!td->o.odirect && o->uncached)
                        sqe->rw_flags = RWF_UNCACHED;
-               if (fio_option_is_set(&td->o, ioprio_class))
+               if (ld->ioprio_class_set)
                        sqe->ioprio = td->o.ioprio_class << 13;
-               if (fio_option_is_set(&td->o, ioprio))
+               if (ld->ioprio_set)
                        sqe->ioprio |= td->o.ioprio;
                sqe->off = io_u->offset;
        } else if (ddir_sync(io_u->ddir)) {
@@ -374,8 +376,6 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td,
        if (next_tail == *ring->head)
                return FIO_Q_BUSY;
 
-       /* ensure sqe stores are ordered with tail update */
-       write_barrier();
        if (o->cmdprio_percentage)
                fio_ioring_prio_prep(td, io_u);
        ring->array[tail & ld->sq_ring_mask] = io_u->index;
@@ -687,6 +687,12 @@ static int fio_ioring_init(struct thread_data *td)
                td_verror(td, EINVAL, "fio_io_uring_init");
                return 1;
        }
+
+       if (fio_option_is_set(&td->o, ioprio_class))
+               ld->ioprio_class_set = true;
+       if (fio_option_is_set(&td->o, ioprio))
+               ld->ioprio_set = true;
+
        return 0;
 }
 
index 6000160..c57fcea 100644 (file)
@@ -2,7 +2,7 @@
  * libhdfs engine
  *
  * this engine helps perform read/write operations on hdfs cluster using
- * libhdfs. hdfs doesnot support modification of data once file is created.
+ * libhdfs. hdfs does not support modification of data once file is created.
  *
  * so to mimic that create many files of small size (e.g 256k), and this
  * engine select a file based on the offset generated by fio.
@@ -75,7 +75,7 @@ static struct fio_option options[] = {
                .type   = FIO_OPT_STR_STORE,
                .off1   = offsetof(struct hdfsio_options, directory),
                .def    = "/",
-               .help   = "The HDFS directory where fio will create chuncks",
+               .help   = "The HDFS directory where fio will create chunks",
                .category = FIO_OPT_C_ENGINE,
                .group  = FIO_OPT_G_HDFS,
        },
@@ -86,7 +86,7 @@ static struct fio_option options[] = {
                .type   = FIO_OPT_INT,
                .off1   = offsetof(struct hdfsio_options, chunck_size),
                .def    = "1048576",
-               .help   = "Size of individual chunck",
+               .help   = "Size of individual chunk",
                .category = FIO_OPT_C_ENGINE,
                .group  = FIO_OPT_G_HDFS,
        },
@@ -177,7 +177,7 @@ static enum fio_q_status fio_hdfsio_queue(struct thread_data *td,
        
        if( (io_u->ddir == DDIR_READ || io_u->ddir == DDIR_WRITE) && 
             hdfsTell(hd->fs, hd->fp) != offset && hdfsSeek(hd->fs, hd->fp, offset) != 0 ) {
-               log_err("hdfs: seek failed: %s, are you doing random write smaller than chunck size ?\n", strerror(errno));
+               log_err("hdfs: seek failed: %s, are you doing random write smaller than chunk size ?\n", strerror(errno));
                io_u->error = errno;
                return FIO_Q_COMPLETED;
        };
@@ -338,9 +338,9 @@ static int fio_hdfsio_setup(struct thread_data *td)
                }
                f->real_file_size = file_size;
        }
-       /* If the size doesn't divide nicely with the chunck size,
+       /* If the size doesn't divide nicely with the chunk size,
         * make the last files bigger.
-        * Used only if filesize was not explicitely given
+        * Used only if filesize was not explicitly given
         */
        if (!td->o.file_size_low && total_file_size < td->o.size) {
                f->real_file_size += (td->o.size - total_file_size);
@@ -374,7 +374,7 @@ static int fio_hdfsio_io_u_init(struct thread_data *td, struct io_u *io_u)
        }
        hd->fs = hdfsBuilderConnect(bld);
        
-       /* hdfsSetWorkingDirectory succeed on non existend directory */
+       /* hdfsSetWorkingDirectory succeed on non-existent directory */
        if (hdfsExists(hd->fs, options->directory) < 0 || hdfsSetWorkingDirectory(hd->fs, options->directory) < 0) {
                failure = errno;
                log_err("hdfs: invalid working directory %s: %s\n", options->directory, strerror(errno));
diff --git a/engines/libzbc.c b/engines/libzbc.c
new file mode 100644 (file)
index 0000000..8c682de
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2019 Western Digital Corporation or its affiliates.
+ *
+ * This file is released under the GPL.
+ *
+ * libzbc engine
+ * IO engine using libzbc library to talk to SMR disks.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <libzbc/zbc.h>
+
+#include "fio.h"
+#include "err.h"
+#include "zbd_types.h"
+
+struct libzbc_data {
+       struct zbc_device       *zdev;
+       enum zbc_dev_model      model;
+       uint64_t                nr_sectors;
+};
+
+static int libzbc_get_dev_info(struct libzbc_data *ld, struct fio_file *f)
+{
+       struct zbc_device_info *zinfo;
+
+       zinfo = calloc(1, sizeof(*zinfo));
+       if (!zinfo)
+               return -ENOMEM;
+
+       zbc_get_device_info(ld->zdev, zinfo);
+       ld->model = zinfo->zbd_model;
+       ld->nr_sectors = zinfo->zbd_sectors;
+
+       dprint(FD_ZBD, "%s: vendor_id:%s, type: %s, model: %s\n",
+              f->file_name, zinfo->zbd_vendor_id,
+              zbc_device_type_str(zinfo->zbd_type),
+              zbc_device_model_str(zinfo->zbd_model));
+
+       free(zinfo);
+
+       return 0;
+}
+
+static int libzbc_open_dev(struct thread_data *td, struct fio_file *f,
+                          struct libzbc_data **p_ld)
+{
+       struct libzbc_data *ld = td->io_ops_data;
+        int ret, flags = OS_O_DIRECT;
+
+       if (ld) {
+               /* Already open */
+               assert(ld->zdev);
+               goto out;
+       }
+
+       if (f->filetype != FIO_TYPE_BLOCK && f->filetype != FIO_TYPE_CHAR) {
+               td_verror(td, EINVAL, "wrong file type");
+               log_err("ioengine libzbc only works on block or character devices\n");
+               return -EINVAL;
+       }
+
+        if (td_write(td)) {
+               if (!read_only)
+                       flags |= O_RDWR;
+       } else if (td_read(td)) {
+               if (f->filetype == FIO_TYPE_CHAR && !read_only)
+                       flags |= O_RDWR;
+               else
+                       flags |= O_RDONLY;
+       } else if (td_trim(td)) {
+               td_verror(td, EINVAL, "libzbc does not support trim");
+                log_err("%s: libzbc does not support trim\n",
+                        f->file_name);
+                return -EINVAL;
+       }
+
+        if (td->o.oatomic) {
+               td_verror(td, EINVAL, "libzbc does not support O_ATOMIC");
+                log_err("%s: libzbc does not support O_ATOMIC\n",
+                        f->file_name);
+                return -EINVAL;
+        }
+
+       ld = calloc(1, sizeof(*ld));
+       if (!ld)
+               return -ENOMEM;
+
+       ret = zbc_open(f->file_name,
+                      flags | ZBC_O_DRV_SCSI | ZBC_O_DRV_ATA, &ld->zdev);
+       if (ret) {
+               log_err("%s: zbc_open() failed, err=%d\n",
+                       f->file_name, ret);
+               return ret;
+       }
+
+       ret = libzbc_get_dev_info(ld, f);
+       if (ret) {
+               zbc_close(ld->zdev);
+               free(ld);
+               return ret;
+       }
+
+       td->io_ops_data = ld;
+out:
+       if (p_ld)
+               *p_ld = ld;
+
+       return 0;
+}
+
+static int libzbc_close_dev(struct thread_data *td)
+{
+       struct libzbc_data *ld = td->io_ops_data;
+       int ret = 0;
+
+       td->io_ops_data = NULL;
+       if (ld) {
+               if (ld->zdev)
+                       ret = zbc_close(ld->zdev);
+               free(ld);
+       }
+
+       return ret;
+}
+static int libzbc_open_file(struct thread_data *td, struct fio_file *f)
+{
+       return libzbc_open_dev(td, f, NULL);
+}
+
+static int libzbc_close_file(struct thread_data *td, struct fio_file *f)
+{
+       int ret;
+
+       ret = libzbc_close_dev(td);
+       if (ret)
+               log_err("%s: close device failed err %d\n",
+                       f->file_name, ret);
+
+       return ret;
+}
+
+static void libzbc_cleanup(struct thread_data *td)
+{
+       libzbc_close_dev(td);
+}
+
+static int libzbc_invalidate(struct thread_data *td, struct fio_file *f)
+{
+       /* Passthrough IO do not cache data. Nothing to do */
+       return 0;
+}
+
+static int libzbc_get_file_size(struct thread_data *td, struct fio_file *f)
+{
+       struct libzbc_data *ld;
+       int ret;
+
+       if (fio_file_size_known(f))
+               return 0;
+
+       ret = libzbc_open_dev(td, f, &ld);
+       if (ret)
+               return ret;
+
+       f->real_file_size = ld->nr_sectors << 9;
+       fio_file_set_size_known(f);
+
+       return 0;
+}
+
+static int libzbc_get_zoned_model(struct thread_data *td, struct fio_file *f,
+                                 enum zbd_zoned_model *model)
+{
+       struct libzbc_data *ld;
+       int ret;
+
+       if (f->filetype != FIO_TYPE_BLOCK && f->filetype != FIO_TYPE_CHAR) {
+               *model = ZBD_IGNORE;
+               return 0;
+       }
+
+       ret = libzbc_open_dev(td, f, &ld);
+       if (ret)
+               return ret;
+
+       switch (ld->model) {
+       case ZBC_DM_HOST_AWARE:
+               *model = ZBD_HOST_AWARE;
+               break;
+       case ZBC_DM_HOST_MANAGED:
+               *model = ZBD_HOST_MANAGED;
+               break;
+       default:
+               *model = ZBD_NONE;
+               break;
+       }
+
+       return 0;
+}
+
+static int libzbc_report_zones(struct thread_data *td, struct fio_file *f,
+                              uint64_t offset, struct zbd_zone *zbdz,
+                              unsigned int nr_zones)
+{
+       struct libzbc_data *ld;
+       uint64_t sector = offset >> 9;
+       struct zbc_zone *zones;
+       unsigned int i;
+       int ret;
+
+       ret = libzbc_open_dev(td, f, &ld);
+       if (ret)
+               return ret;
+
+       if (sector >= ld->nr_sectors)
+               return 0;
+
+       zones = calloc(nr_zones, sizeof(struct zbc_zone));
+       if (!zones) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       ret = zbc_report_zones(ld->zdev, sector, ZBC_RO_ALL, zones, &nr_zones);
+       if (ret < 0) {
+               log_err("%s: zbc_report_zones failed, err=%d\n",
+                       f->file_name, ret);
+               goto out;
+       }
+
+       for (i = 0; i < nr_zones; i++, zbdz++) {
+               zbdz->start = zones[i].zbz_start << 9;
+               zbdz->len = zones[i].zbz_length << 9;
+               zbdz->wp = zones[i].zbz_write_pointer << 9;
+
+               switch (zones[i].zbz_type) {
+               case ZBC_ZT_CONVENTIONAL:
+                       zbdz->type = ZBD_ZONE_TYPE_CNV;
+                       break;
+               case ZBC_ZT_SEQUENTIAL_REQ:
+                       zbdz->type = ZBD_ZONE_TYPE_SWR;
+                       break;
+               case ZBC_ZT_SEQUENTIAL_PREF:
+                       zbdz->type = ZBD_ZONE_TYPE_SWP;
+                       break;
+               default:
+                       td_verror(td, errno, "invalid zone type");
+                       log_err("%s: invalid type for zone at sector %llu.\n",
+                               f->file_name, (unsigned long long)zbdz->start);
+                       ret = -EIO;
+                       goto out;
+               }
+
+               switch (zones[i].zbz_condition) {
+               case ZBC_ZC_NOT_WP:
+                       zbdz->cond = ZBD_ZONE_COND_NOT_WP;
+                       break;
+               case ZBC_ZC_EMPTY:
+                       zbdz->cond = ZBD_ZONE_COND_EMPTY;
+                       break;
+               case ZBC_ZC_IMP_OPEN:
+                       zbdz->cond = ZBD_ZONE_COND_IMP_OPEN;
+                       break;
+               case ZBC_ZC_EXP_OPEN:
+                       zbdz->cond = ZBD_ZONE_COND_EXP_OPEN;
+                       break;
+               case ZBC_ZC_CLOSED:
+                       zbdz->cond = ZBD_ZONE_COND_CLOSED;
+                       break;
+               case ZBC_ZC_FULL:
+                       zbdz->cond = ZBD_ZONE_COND_FULL;
+                       break;
+               case ZBC_ZC_RDONLY:
+               case ZBC_ZC_OFFLINE:
+               default:
+                       /* Treat all these conditions as offline (don't use!) */
+                       zbdz->cond = ZBD_ZONE_COND_OFFLINE;
+                       break;
+               }
+       }
+
+       ret = nr_zones;
+out:
+       free(zones);
+       return ret;
+}
+
+static int libzbc_reset_wp(struct thread_data *td, struct fio_file *f,
+                          uint64_t offset, uint64_t length)
+{
+       struct libzbc_data *ld = td->io_ops_data;
+       uint64_t sector = offset >> 9;
+       uint64_t end_sector = (offset + length) >> 9;
+       unsigned int nr_zones;
+       struct zbc_errno err;
+       int i, ret;
+
+       assert(ld);
+       assert(ld->zdev);
+
+       nr_zones = (length + td->o.zone_size - 1) / td->o.zone_size;
+       if (!sector && end_sector >= ld->nr_sectors) {
+               /* Reset all zones */
+               ret = zbc_reset_zone(ld->zdev, 0, ZBC_OP_ALL_ZONES);
+               if (ret)
+                       goto err;
+
+               return 0;
+       }
+
+       for (i = 0; i < nr_zones; i++, sector += td->o.zone_size >> 9) {
+               ret = zbc_reset_zone(ld->zdev, sector, 0);
+               if (ret)
+                       goto err;
+       }
+
+       return 0;
+
+err:
+       zbc_errno(ld->zdev, &err);
+       td_verror(td, errno, "zbc_reset_zone failed");
+       if (err.sk)
+               log_err("%s: reset wp failed %s:%s\n",
+                       f->file_name,
+                       zbc_sk_str(err.sk), zbc_asc_ascq_str(err.asc_ascq));
+       return -ret;
+}
+
+ssize_t libzbc_rw(struct thread_data *td, struct io_u *io_u)
+{
+       struct libzbc_data *ld = td->io_ops_data;
+       struct fio_file *f = io_u->file;
+       uint64_t sector = io_u->offset >> 9;
+       size_t count = io_u->xfer_buflen >> 9;
+       struct zbc_errno err;
+       ssize_t ret;
+
+       if (io_u->ddir == DDIR_WRITE)
+               ret = zbc_pwrite(ld->zdev, io_u->xfer_buf, count, sector);
+       else
+               ret = zbc_pread(ld->zdev, io_u->xfer_buf, count, sector);
+       if (ret == count)
+               return ret;
+
+       if (ret > 0) {
+               log_err("Short %s, len=%zu, ret=%zd\n",
+                       io_u->ddir == DDIR_READ ? "read" : "write",
+                       count << 9, ret << 9);
+               return -EIO;
+       }
+
+       /* I/O error */
+       zbc_errno(ld->zdev, &err);
+       td_verror(td, errno, "libzbc i/o failed");
+       if (err.sk) {
+               log_err("%s: op %u offset %llu+%llu failed (%s:%s), err %zd\n",
+                       f->file_name, io_u->ddir,
+                       io_u->offset, io_u->xfer_buflen,
+                       zbc_sk_str(err.sk),
+                       zbc_asc_ascq_str(err.asc_ascq), ret);
+       } else {
+               log_err("%s: op %u offset %llu+%llu failed, err %zd\n",
+                       f->file_name, io_u->ddir,
+                       io_u->offset, io_u->xfer_buflen, ret);
+       }
+
+       return -EIO;
+}
+
+static enum fio_q_status libzbc_queue(struct thread_data *td, struct io_u *io_u)
+{
+       struct libzbc_data *ld = td->io_ops_data;
+       struct fio_file *f = io_u->file;
+       ssize_t ret = 0;
+
+       fio_ro_check(td, io_u);
+
+       dprint(FD_ZBD, "%p:%s: libzbc queue %llu\n",
+              td, f->file_name, io_u->offset);
+
+       if (io_u->ddir == DDIR_READ || io_u->ddir == DDIR_WRITE) {
+               ret = libzbc_rw(td, io_u);
+       } else if (ddir_sync(io_u->ddir)) {
+               ret = zbc_flush(ld->zdev);
+               if (ret)
+                       log_err("zbc_flush error %zd\n", ret);
+       } else if (io_u->ddir != DDIR_TRIM) {
+               log_err("Unsupported operation %u\n", io_u->ddir);
+               ret = -EINVAL;
+       }
+       if (ret < 0)
+               io_u->error = -ret;
+
+       return FIO_Q_COMPLETED;
+}
+
+static struct ioengine_ops ioengine = {
+       .name                   = "libzbc",
+       .version                = FIO_IOOPS_VERSION,
+       .open_file              = libzbc_open_file,
+       .close_file             = libzbc_close_file,
+       .cleanup                = libzbc_cleanup,
+       .invalidate             = libzbc_invalidate,
+       .get_file_size          = libzbc_get_file_size,
+       .get_zoned_model        = libzbc_get_zoned_model,
+       .report_zones           = libzbc_report_zones,
+       .reset_wp               = libzbc_reset_wp,
+       .queue                  = libzbc_queue,
+       .flags                  = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO,
+};
+
+static void fio_init fio_libzbc_register(void)
+{
+       register_ioengine(&ioengine);
+}
+
+static void fio_exit fio_libzbc_unregister(void)
+{
+       unregister_ioengine(&ioengine);
+}
index 7d4d3fa..a08f477 100644 (file)
@@ -200,6 +200,14 @@ static int _fio_rbd_connect(struct thread_data *td)
                log_err("rados_create failed.\n");
                goto failed_early;
        }
+       if (o->pool_name == NULL) {
+               log_err("rbd pool name must be provided.\n");
+               goto failed_early;
+       }
+       if (!o->rbd_name) {
+               log_err("rbdname must be provided.\n");
+               goto failed_early;
+       }
 
        r = rados_conf_read_file(rbd->cluster, NULL);
        if (r < 0) {
index 2569a8e..f192f43 100644 (file)
@@ -1050,7 +1050,7 @@ static int fio_rdmaio_setup_connect(struct thread_data *td, const char *host,
                return err;
 
        /* resolve route */
-       if (strcmp(o->bindname, "") != 0) {
+       if (o->bindname && strlen(o->bindname)) {
                addrb.ss_family = AF_INET;
                err = aton(td, o->bindname, (struct sockaddr_in *)&addrb);
                if (err)
@@ -1116,7 +1116,7 @@ static int fio_rdmaio_setup_listen(struct thread_data *td, short port)
        rd->addr.sin_family = AF_INET;
        rd->addr.sin_port = htons(port);
 
-       if (strcmp(o->bindname, "") == 0)
+       if (!o->bindname || !strlen(o->bindname))
                rd->addr.sin_addr.s_addr = htonl(INADDR_ANY);
        else
                rd->addr.sin_addr.s_addr = htonl(*o->bindname);
@@ -1249,8 +1249,7 @@ static int fio_rdmaio_init(struct thread_data *td)
 {
        struct rdmaio_data *rd = td->io_ops_data;
        struct rdmaio_options *o = td->eo;
-       unsigned int max_bs;
-       int ret, i;
+       int ret;
 
        if (td_rw(td)) {
                log_err("fio: rdma connections must be read OR write\n");
@@ -1318,6 +1317,13 @@ static int fio_rdmaio_init(struct thread_data *td)
                rd->is_client = 1;
                ret = fio_rdmaio_setup_connect(td, td->o.filename, o->port);
        }
+       return ret;
+}
+static int fio_rdmaio_post_init(struct thread_data *td)
+{
+       unsigned int max_bs;
+       int i;
+       struct rdmaio_data *rd = td->io_ops_data;
 
        max_bs = max(td->o.max_bs[DDIR_READ], td->o.max_bs[DDIR_WRITE]);
        rd->send_buf.max_bs = htonl(max_bs);
@@ -1351,7 +1357,7 @@ static int fio_rdmaio_init(struct thread_data *td)
 
        rd->send_buf.nr = htonl(i);
 
-       return ret;
+       return 0;
 }
 
 static void fio_rdmaio_cleanup(struct thread_data *td)
@@ -1388,6 +1394,7 @@ static struct ioengine_ops ioengine_rw = {
        .version                = FIO_IOOPS_VERSION,
        .setup                  = fio_rdmaio_setup,
        .init                   = fio_rdmaio_init,
+       .post_init              = fio_rdmaio_post_init,
        .prep                   = fio_rdmaio_prep,
        .queue                  = fio_rdmaio_queue,
        .commit                 = fio_rdmaio_commit,
index 1b6625b..7f3e4cb 100644 (file)
@@ -153,6 +153,46 @@ static int fio_skeleton_close(struct thread_data *td, struct fio_file *f)
        return generic_close_file(td, f);
 }
 
+/*
+ * Hook for getting the zoned model of a zoned block device for zonemode=zbd.
+ * The zoned model can be one of (see zbd_types.h):
+ * - ZBD_IGNORE: skip regular files
+ * - ZBD_NONE: regular block device (zone emulation will be used)
+ * - ZBD_HOST_AWARE: host aware zoned block device
+ * - ZBD_HOST_MANAGED: host managed zoned block device
+ */
+static int fio_skeleton_get_zoned_model(struct thread_data *td,
+                       struct fio_file *f, enum zbd_zoned_model *model)
+{
+       *model = ZBD_NONE;
+       return 0;
+}
+
+/*
+ * Hook called for getting zone information of a ZBD_HOST_AWARE or
+ * ZBD_HOST_MANAGED zoned block device. Up to @nr_zones zone information
+ * structures can be reported using the array zones for zones starting from
+ * @offset. The number of zones reported must be returned or a negative error
+ * code in case of error.
+ */
+static int fio_skeleton_report_zones(struct thread_data *td, struct fio_file *f,
+                                    uint64_t offset, struct zbd_zone *zones,
+                                    unsigned int nr_zones)
+{
+       return 0;
+}
+
+/*
+ * Hook called for resetting the write pointer position of zones of a
+ * ZBD_HOST_AWARE or ZBD_HOST_MANAGED zoned block device. The write pointer
+ * position of all zones in the range @offset..@offset + @length must be reset.
+ */
+static int fio_skeleton_reset_wp(struct thread_data *td, struct fio_file *f,
+                                uint64_t offset, uint64_t length)
+{
+       return 0;
+}
+
 /*
  * Note that the structure is exported, so that fio can get it via
  * dlsym(..., "ioengine"); for (and only for) external engines.
@@ -169,6 +209,9 @@ struct ioengine_ops ioengine = {
        .cleanup        = fio_skeleton_cleanup,
        .open_file      = fio_skeleton_open,
        .close_file     = fio_skeleton_close,
+       .get_zoned_model = fio_skeleton_get_zoned_model,
+       .report_zones   = fio_skeleton_report_zones,
+       .reset_wp       = fio_skeleton_reset_wp,
        .options        = options,
        .option_struct_size     = sizeof(struct fio_skeleton_options),
 };
index b45a582..8a4091f 100644 (file)
@@ -913,15 +913,61 @@ uint64_t get_start_offset(struct thread_data *td, struct fio_file *f)
        return offset;
 }
 
+/*
+ * Find longest path component that exists and return its length
+ */
+int longest_existing_path(char *path) {
+       char buf[PATH_MAX];
+       bool done;
+       char *buf_pos;
+       int offset;
+#ifdef WIN32
+       DWORD dwAttr;
+#else
+       struct stat sb;
+#endif
+
+       sprintf(buf, "%s", path);
+       done = false;
+       while (!done) {
+               buf_pos = strrchr(buf, FIO_OS_PATH_SEPARATOR);
+               if (!buf_pos) {
+                       done = true;
+                       offset = 0;
+                       break;
+               }
+
+               *(buf_pos + 1) = '\0';
+
+#ifdef WIN32
+               dwAttr = GetFileAttributesA(buf);
+               if (dwAttr != INVALID_FILE_ATTRIBUTES) {
+                       done = true;
+               }
+#else
+               if (stat(buf, &sb) == 0)
+                       done = true;
+#endif
+               if (done)
+                       offset = buf_pos - buf;
+               else
+                       *buf_pos = '\0';
+       }
+
+       return offset;
+}
+
 static bool create_work_dirs(struct thread_data *td, const char *fname)
 {
        char path[PATH_MAX];
        char *start, *end;
+       int offset;
 
        snprintf(path, PATH_MAX, "%s", fname);
        start = path;
 
-       end = start;
+       offset = longest_existing_path(path);
+       end = start + offset;
        while ((end = strchr(end, FIO_OS_PATH_SEPARATOR)) != NULL) {
                if (end == start) {
                        end++;
@@ -930,8 +976,8 @@ static bool create_work_dirs(struct thread_data *td, const char *fname)
                *end = '\0';
                errno = 0;
                if (fio_mkdir(path, 0700) && errno != EEXIST) {
-                       log_err("fio: failed to create dir (%s): %d\n",
-                               start, errno);
+                       log_err("fio: failed to create dir (%s): %s\n",
+                               start, strerror(errno));
                        return false;
                }
                *end = FIO_OS_PATH_SEPARATOR;
diff --git a/fio.1 b/fio.1
index 1db12c2..a2379f9 100644 (file)
--- a/fio.1
+++ b/fio.1
@@ -1629,6 +1629,12 @@ I/O. Requires \fBfilename\fR option to specify either block or
 character devices. This engine supports trim operations. The
 sg engine includes engine specific options.
 .TP
+.B libzbc
+Synchronous I/O engine for SMR hard-disks using the \fBlibzbc\fR
+library. The target can be either an sg character device or
+a block device file. This engine supports the zonemode=zbd zone
+operations.
+.TP
 .B null
 Doesn't transfer any data, just pretends to. This is mainly used to
 exercise fio itself and for debugging/testing purposes.
diff --git a/fio.h b/fio.h
index 2a9eef4..bbf057c 100644 (file)
--- a/fio.h
+++ b/fio.h
@@ -172,8 +172,6 @@ struct zone_split_index {
        uint64_t size_prev;
 };
 
-#define FIO_MAX_OPEN_ZBD_ZONES 128
-
 /*
  * This describes a single thread/process executing a fio job.
  */
index d2044f3..fe83382 100644 (file)
--- a/gclient.c
+++ b/gclient.c
@@ -1155,18 +1155,21 @@ out:
 #define GFIO_CLAT      1
 #define GFIO_SLAT      2
 #define GFIO_LAT       4
+#define GFIO_HILAT     8
+#define GFIO_LOLAT     16
 
 static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
                                  struct group_run_stats *rs,
                                  struct thread_stat *ts, int ddir)
 {
        const char *ddir_label[3] = { "Read", "Write", "Trim" };
+       const char *hilat, *lolat;
        GtkWidget *frame, *label, *box, *vbox, *main_vbox;
-       unsigned long long min[3], max[3];
+       unsigned long long min[5], max[5];
        unsigned long runt;
        unsigned long long bw, iops;
        unsigned int flags = 0;
-       double mean[3], dev[3];
+       double mean[5], dev[5];
        char *io_p, *io_palt, *bw_p, *bw_palt, *iops_p;
        char tmp[128];
        int i2p;
@@ -1265,6 +1268,14 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
                flags |= GFIO_CLAT;
        if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
                flags |= GFIO_LAT;
+       if (calc_lat(&ts->clat_high_prio_stat[ddir], &min[3], &max[3], &mean[3], &dev[3])) {
+               flags |= GFIO_HILAT;
+               if (calc_lat(&ts->clat_low_prio_stat[ddir], &min[4], &max[4], &mean[4], &dev[4]))
+                       flags |= GFIO_LOLAT;
+               /* we only want to print low priority statistics if other IOs were
+                * submitted with the priority bit set
+                */
+       }
 
        if (flags) {
                frame = gtk_frame_new("Latency");
@@ -1273,12 +1284,24 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
                vbox = gtk_vbox_new(FALSE, 3);
                gtk_container_add(GTK_CONTAINER(frame), vbox);
 
+               if (ts->lat_percentiles) {
+                       hilat = "High priority total latency";
+                       lolat = "Low priority total latency";
+               } else {
+                       hilat = "High priority completion latency";
+                       lolat = "Low priority completion latency";
+               }
+
                if (flags & GFIO_SLAT)
                        gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
                if (flags & GFIO_CLAT)
                        gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
                if (flags & GFIO_LAT)
                        gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
+               if (flags & GFIO_HILAT)
+                       gfio_show_lat(vbox, hilat, min[3], max[3], mean[3], dev[3]);
+               if (flags & GFIO_LOLAT)
+                       gfio_show_lat(vbox, lolat, min[4], max[4], mean[4], dev[4]);
        }
 
        if (ts->slat_percentiles && flags & GFIO_SLAT)
@@ -1286,16 +1309,40 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
                                ts->io_u_plat[FIO_SLAT][ddir],
                                ts->slat_stat[ddir].samples,
                                "Submission");
-       if (ts->clat_percentiles && flags & GFIO_CLAT)
+       if (ts->clat_percentiles && flags & GFIO_CLAT) {
                gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
                                ts->io_u_plat[FIO_CLAT][ddir],
                                ts->clat_stat[ddir].samples,
                                "Completion");
-       if (ts->lat_percentiles && flags & GFIO_LAT)
+               if (!ts->lat_percentiles) {
+                       if (flags & GFIO_HILAT)
+                               gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+                                               ts->io_u_plat_high_prio[ddir],
+                                               ts->clat_high_prio_stat[ddir].samples,
+                                               "High priority completion");
+                       if (flags & GFIO_LOLAT)
+                               gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+                                               ts->io_u_plat_low_prio[ddir],
+                                               ts->clat_low_prio_stat[ddir].samples,
+                                               "Low priority completion");
+               }
+       }
+       if (ts->lat_percentiles && flags & GFIO_LAT) {
                gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
                                ts->io_u_plat[FIO_LAT][ddir],
                                ts->lat_stat[ddir].samples,
                                "Total");
+               if (flags & GFIO_HILAT)
+                       gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+                                       ts->io_u_plat_high_prio[ddir],
+                                       ts->clat_high_prio_stat[ddir].samples,
+                                       "High priority total");
+               if (flags & GFIO_LOLAT)
+                       gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+                                       ts->io_u_plat_low_prio[ddir],
+                                       ts->clat_low_prio_stat[ddir].samples,
+                                       "Low priority total");
+       }
 
        free(io_p);
        free(bw_p);
diff --git a/io_u.c b/io_u.c
index bcb893c..5d62a76 100644 (file)
--- a/io_u.c
+++ b/io_u.c
@@ -606,7 +606,7 @@ static inline enum fio_ddir get_rand_ddir(struct thread_data *td)
 
 int io_u_quiesce(struct thread_data *td)
 {
-       int ret = 0, completed = 0;
+       int ret = 0, completed = 0, err = 0;
 
        /*
         * We are going to sleep, ensure that we flush anything pending as
@@ -625,7 +625,7 @@ int io_u_quiesce(struct thread_data *td)
                if (ret > 0)
                        completed += ret;
                else if (ret < 0)
-                       break;
+                       err = ret;
        }
 
        if (td->flags & TD_F_REGROW_LOGS)
@@ -634,7 +634,7 @@ int io_u_quiesce(struct thread_data *td)
        if (completed)
                return completed;
 
-       return ret;
+       return err;
 }
 
 static enum fio_ddir rate_ddir(struct thread_data *td, enum fio_ddir ddir)
diff --git a/io_u.h b/io_u.h
index 0f63cdd..87c2920 100644 (file)
--- a/io_u.h
+++ b/io_u.h
@@ -93,7 +93,6 @@ struct io_u {
                struct workqueue_work work;
        };
 
-#ifdef CONFIG_LINUX_BLKZONED
        /*
         * ZBD mode zbd_queue_io callback: called after engine->queue operation
         * to advance a zone write pointer and eventually unlock the I/O zone.
@@ -108,7 +107,6 @@ struct io_u {
         * or commit of an async I/O to unlock the I/O target zone.
         */
        void (*zbd_put_io)(const struct io_u *);
-#endif
 
        /*
         * Callback for io completion
index 01a9b58..f48b4db 100644 (file)
@@ -6,8 +6,9 @@
 #include "compiler/compiler.h"
 #include "flist.h"
 #include "io_u.h"
+#include "zbd_types.h"
 
-#define FIO_IOOPS_VERSION      25
+#define FIO_IOOPS_VERSION      26
 
 /*
  * io_ops->queue() return values
@@ -44,6 +45,12 @@ struct ioengine_ops {
        void (*iomem_free)(struct thread_data *);
        int (*io_u_init)(struct thread_data *, struct io_u *);
        void (*io_u_free)(struct thread_data *, struct io_u *);
+       int (*get_zoned_model)(struct thread_data *td,
+                              struct fio_file *f, enum zbd_zoned_model *);
+       int (*report_zones)(struct thread_data *, struct fio_file *,
+                           uint64_t, struct zbd_zone *, unsigned int);
+       int (*reset_wp)(struct thread_data *, struct fio_file *,
+                       uint64_t, uint64_t);
        int option_struct_size;
        struct fio_option *options;
 };
index 1d24e18..3f84dbc 100644 (file)
@@ -51,7 +51,7 @@ void gauss_init(struct gauss_state *gs, unsigned long nranges, double dev,
        gs->nranges = nranges;
 
        if (dev != 0.0) {
-               gs->stddev = ceil((double) (nranges * 100.0) / dev);
+               gs->stddev = ceil((double)(nranges * dev) / 100.0);
                if (gs->stddev > nranges / 2)
                        gs->stddev = nranges / 2;
        }
index 4714a3a..2372c04 100644 (file)
--- a/options.c
+++ b/options.c
@@ -13,6 +13,7 @@
 #include "lib/pattern.h"
 #include "options.h"
 #include "optgroup.h"
+#include "zbd.h"
 
 char client_sockaddr_str[INET6_ADDRSTRLEN] = { 0 };
 
@@ -3362,7 +3363,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .lname  = "Maximum number of open zones",
                .type   = FIO_OPT_INT,
                .off1   = offsetof(struct thread_options, max_open_zones),
-               .maxval = FIO_MAX_OPEN_ZBD_ZONES,
+               .maxval = ZBD_MAX_OPEN_ZONES,
                .help   = "Limit random writes to SMR drives to the specified"
                          " number of sequential zones",
                .def    = "0",
index 6d48ffe..fa2955f 100644 (file)
@@ -203,7 +203,11 @@ static inline int fio_mkdir(const char *path, mode_t mode) {
        }
 
        if (CreateDirectoryA(path, NULL) == 0) {
-               log_err("CreateDirectoryA = %d\n", GetLastError());
+               /* Ignore errors if path is a device namespace */
+               if (strcmp(path, "\\\\.") == 0) {
+                       errno = EEXIST;
+                       return -1;
+               }
                errno = win_to_posix_error(GetLastError());
                return -1;
        }
diff --git a/oslib/blkzoned.h b/oslib/blkzoned.h
new file mode 100644 (file)
index 0000000..4cc071d
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 Western Digital Corporation or its affiliates.
+ *
+ * This file is released under the GPL.
+ */
+#ifndef FIO_BLKZONED_H
+#define FIO_BLKZONED_H
+
+#include "zbd_types.h"
+
+#ifdef CONFIG_HAS_BLKZONED
+extern int blkzoned_get_zoned_model(struct thread_data *td,
+                       struct fio_file *f, enum zbd_zoned_model *model);
+extern int blkzoned_report_zones(struct thread_data *td,
+                               struct fio_file *f, uint64_t offset,
+                               struct zbd_zone *zones, unsigned int nr_zones);
+extern int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f,
+                               uint64_t offset, uint64_t length);
+#else
+/*
+ * Define stubs for systems that do not have zoned block device support.
+ */
+static inline int blkzoned_get_zoned_model(struct thread_data *td,
+                       struct fio_file *f, enum zbd_zoned_model *model)
+{
+       /*
+        * If this is a block device file, allow zbd emulation.
+        */
+       if (f->filetype == FIO_TYPE_BLOCK) {
+               *model = ZBD_NONE;
+               return 0;
+       }
+
+       return -ENODEV;
+}
+static inline int blkzoned_report_zones(struct thread_data *td,
+                               struct fio_file *f, uint64_t offset,
+                               struct zbd_zone *zones, unsigned int nr_zones)
+{
+       return -EIO;
+}
+static inline int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f,
+                                   uint64_t offset, uint64_t length)
+{
+       return -EIO;
+}
+#endif
+
+#endif /* FIO_BLKZONED_H */
diff --git a/oslib/linux-blkzoned.c b/oslib/linux-blkzoned.c
new file mode 100644 (file)
index 0000000..61ea3a5
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2020 Western Digital Corporation or its affiliates.
+ *
+ * This file is released under the GPL.
+ */
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "file.h"
+#include "fio.h"
+#include "lib/pow2.h"
+#include "log.h"
+#include "oslib/asprintf.h"
+#include "smalloc.h"
+#include "verify.h"
+#include "zbd_types.h"
+
+#include <linux/blkzoned.h>
+
+/*
+ * Read up to 255 characters from the first line of a file. Strip the trailing
+ * newline.
+ */
+static char *read_file(const char *path)
+{
+       char line[256], *p = line;
+       FILE *f;
+
+       f = fopen(path, "rb");
+       if (!f)
+               return NULL;
+       if (!fgets(line, sizeof(line), f))
+               line[0] = '\0';
+       strsep(&p, "\n");
+       fclose(f);
+
+       return strdup(line);
+}
+
+int blkzoned_get_zoned_model(struct thread_data *td, struct fio_file *f,
+                            enum zbd_zoned_model *model)
+{
+       const char *file_name = f->file_name;
+       char *zoned_attr_path = NULL;
+       char *model_str = NULL;
+       struct stat statbuf;
+       char *sys_devno_path = NULL;
+       char *part_attr_path = NULL;
+       char *part_str = NULL;
+       char sys_path[PATH_MAX];
+       ssize_t sz;
+       char *delim = NULL;
+
+       if (f->filetype != FIO_TYPE_BLOCK) {
+               *model = ZBD_IGNORE;
+               return 0;
+       }
+
+       *model = ZBD_NONE;
+
+       if (stat(file_name, &statbuf) < 0)
+               goto out;
+
+       if (asprintf(&sys_devno_path, "/sys/dev/block/%d:%d",
+                    major(statbuf.st_rdev), minor(statbuf.st_rdev)) < 0)
+               goto out;
+
+       sz = readlink(sys_devno_path, sys_path, sizeof(sys_path) - 1);
+       if (sz < 0)
+               goto out;
+       sys_path[sz] = '\0';
+
+       /*
+        * If the device is a partition device, cut the device name in the
+        * canonical sysfs path to obtain the sysfs path of the holder device.
+        *   e.g.:  /sys/devices/.../sda/sda1 -> /sys/devices/.../sda
+        */
+       if (asprintf(&part_attr_path, "/sys/dev/block/%s/partition",
+                    sys_path) < 0)
+               goto out;
+       part_str = read_file(part_attr_path);
+       if (part_str && *part_str == '1') {
+               delim = strrchr(sys_path, '/');
+               if (!delim)
+                       goto out;
+               *delim = '\0';
+       }
+
+       if (asprintf(&zoned_attr_path,
+                    "/sys/dev/block/%s/queue/zoned", sys_path) < 0)
+               goto out;
+
+       model_str = read_file(zoned_attr_path);
+       if (!model_str)
+               goto out;
+       dprint(FD_ZBD, "%s: zbd model string: %s\n", file_name, model_str);
+       if (strcmp(model_str, "host-aware") == 0)
+               *model = ZBD_HOST_AWARE;
+       else if (strcmp(model_str, "host-managed") == 0)
+               *model = ZBD_HOST_MANAGED;
+out:
+       free(model_str);
+       free(zoned_attr_path);
+       free(part_str);
+       free(part_attr_path);
+       free(sys_devno_path);
+       return 0;
+}
+
+int blkzoned_report_zones(struct thread_data *td, struct fio_file *f,
+                         uint64_t offset, struct zbd_zone *zones,
+                         unsigned int nr_zones)
+{
+       struct blk_zone_report *hdr = NULL;
+       struct blk_zone *blkz;
+       struct zbd_zone *z;
+       unsigned int i;
+       int fd = -1, ret;
+
+       fd = open(f->file_name, O_RDONLY | O_LARGEFILE);
+       if (fd < 0)
+               return -errno;
+
+       hdr = calloc(1, sizeof(struct blk_zone_report) +
+                       nr_zones * sizeof(struct blk_zone));
+       if (!hdr) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       hdr->nr_zones = nr_zones;
+       hdr->sector = offset >> 9;
+       ret = ioctl(fd, BLKREPORTZONE, hdr);
+       if (ret) {
+               ret = -errno;
+               goto out;
+       }
+
+       nr_zones = hdr->nr_zones;
+       blkz = &hdr->zones[0];
+       z = &zones[0];
+       for (i = 0; i < nr_zones; i++, z++, blkz++) {
+               z->start = blkz->start << 9;
+               z->wp = blkz->wp << 9;
+               z->len = blkz->len << 9;
+
+               switch (blkz->type) {
+               case BLK_ZONE_TYPE_CONVENTIONAL:
+                       z->type = ZBD_ZONE_TYPE_CNV;
+                       break;
+               case BLK_ZONE_TYPE_SEQWRITE_REQ:
+                       z->type = ZBD_ZONE_TYPE_SWR;
+                       break;
+               case BLK_ZONE_TYPE_SEQWRITE_PREF:
+                       z->type = ZBD_ZONE_TYPE_SWP;
+                       break;
+               default:
+                       td_verror(td, errno, "invalid zone type");
+                       log_err("%s: invalid type for zone at sector %llu.\n",
+                               f->file_name, (unsigned long long)offset >> 9);
+                       ret = -EIO;
+                       goto out;
+               }
+
+               switch (blkz->cond) {
+               case BLK_ZONE_COND_NOT_WP:
+                       z->cond = ZBD_ZONE_COND_NOT_WP;
+                       break;
+               case BLK_ZONE_COND_EMPTY:
+                       z->cond = ZBD_ZONE_COND_EMPTY;
+                       break;
+               case BLK_ZONE_COND_IMP_OPEN:
+                       z->cond = ZBD_ZONE_COND_IMP_OPEN;
+                       break;
+               case BLK_ZONE_COND_EXP_OPEN:
+                       z->cond = ZBD_ZONE_COND_EXP_OPEN;
+                       break;
+               case BLK_ZONE_COND_CLOSED:
+                       z->cond = ZBD_ZONE_COND_CLOSED;
+                       break;
+               case BLK_ZONE_COND_FULL:
+                       z->cond = ZBD_ZONE_COND_FULL;
+                       break;
+               case BLK_ZONE_COND_READONLY:
+               case BLK_ZONE_COND_OFFLINE:
+               default:
+                       /* Treat all these conditions as offline (don't use!) */
+                       z->cond = ZBD_ZONE_COND_OFFLINE;
+                       break;
+               }
+       }
+
+       ret = nr_zones;
+out:
+       free(hdr);
+       close(fd);
+
+       return ret;
+}
+
+int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f,
+                     uint64_t offset, uint64_t length)
+{
+       struct blk_zone_range zr = {
+               .sector         = offset >> 9,
+               .nr_sectors     = length >> 9,
+       };
+
+       if (ioctl(f->fd, BLKRESETZONE, &zr) < 0)
+               return -errno;
+
+       return 0;
+}
diff --git a/oslib/statx.c b/oslib/statx.c
new file mode 100644 (file)
index 0000000..1ca81ad
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef CONFIG_HAVE_STATX
+#include "statx.h"
+
+#ifdef CONFIG_HAVE_STATX_SYSCALL
+#include <unistd.h>
+#include <sys/syscall.h>
+
+int statx(int dfd, const char *pathname, int flags, unsigned int mask,
+         struct statx *buffer)
+{
+       return syscall(__NR_statx, dfd, pathname, flags, mask, buffer);
+}
+#else
+#include <errno.h>
+
+int statx(int dfd, const char *pathname, int flags, unsigned int mask,
+         struct statx *buffer)
+{
+       errno = EINVAL;
+       return -1;
+}
+#endif
+#endif
diff --git a/oslib/statx.h b/oslib/statx.h
new file mode 100644 (file)
index 0000000..d9758f7
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef CONFIG_HAVE_STATX
+#ifdef CONFIG_HAVE_STATX_SYSCALL
+#include <linux/stat.h>
+#include <sys/stat.h>
+#else
+#define STATX_ALL 0
+#undef statx
+struct statx
+{
+};
+#endif
+int statx(int dfd, const char *pathname, int flags, unsigned int mask,
+         struct statx *buffer);
+#endif
diff --git a/stat.c b/stat.c
index 2c59c0f..efa811d 100644 (file)
--- a/stat.c
+++ b/stat.c
@@ -482,9 +482,13 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
                display_lat("clat", min, max, mean, dev, out);
        if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
                display_lat(" lat", min, max, mean, dev, out);
-       if (calc_lat(&ts->clat_high_prio_stat[ddir], &min, &max, &mean, &dev))
-               display_lat(ts->lat_percentiles ? "prio_lat" : "prio_clat",
+       if (calc_lat(&ts->clat_high_prio_stat[ddir], &min, &max, &mean, &dev)) {
+               display_lat(ts->lat_percentiles ? "high prio_lat" : "high prio_clat",
                                min, max, mean, dev, out);
+               if (calc_lat(&ts->clat_low_prio_stat[ddir], &min, &max, &mean, &dev))
+                       display_lat(ts->lat_percentiles ? "low prio_lat" : "low prio_clat",
+                                       min, max, mean, dev, out);
+       }
 
        if (ts->slat_percentiles && ts->slat_stat[ddir].samples > 0)
                show_clat_percentiles(ts->io_u_plat[FIO_SLAT][ddir],
@@ -2745,7 +2749,7 @@ static unsigned long add_log_sample(struct thread_data *td,
                        return diff;
        }
 
-       _add_stat_to_log(iolog, elapsed, td->o.log_max != 0, priority_bit);
+       __add_stat_to_log(iolog, ddir, elapsed, td->o.log_max != 0, priority_bit);
 
        iolog->avg_last[ddir] = elapsed - (this_window - iolog->avg_msec);
        return iolog->avg_msec;
index bd28a84..a72cef3 100644 (file)
--- a/t/arch.c
+++ b/t/arch.c
@@ -1,5 +1,4 @@
 #include "../arch/arch.h"
 
 unsigned long arch_flags = 0;
-bool tsc_reliable;
 int arch_random;
index 55b75f6..d48db1e 100644 (file)
@@ -63,7 +63,6 @@ struct file {
 struct submitter {
        pthread_t thread;
        int ring_fd;
-       struct drand48_data rand;
        struct io_sq_ring sq_ring;
        struct io_uring_sqe *sqes;
        struct io_cq_ring cq_ring;
@@ -170,7 +169,7 @@ static void init_io(struct submitter *s, unsigned index)
        }
        f->pending_ios++;
 
-       lrand48_r(&s->rand, &r);
+       r = lrand48();
        offset = (r % (f->max_blocks - 1)) * BS;
 
        if (register_files) {
@@ -216,8 +215,6 @@ static int prep_more_ios(struct submitter *s, int max_ios)
        } while (prepped < max_ios);
 
        if (*ring->tail != tail) {
-               /* order tail store with writes to sqes above */
-               write_barrier();
                *ring->tail = tail;
                write_barrier();
        }
@@ -288,7 +285,7 @@ static void *submitter_fn(void *data)
 
        printf("submitter=%d\n", gettid());
 
-       srand48_r(pthread_self(), &s->rand);
+       srand48(pthread_self());
 
        prepped = 0;
        do {
diff --git a/t/jsonplus2csv_test.py b/t/jsonplus2csv_test.py
new file mode 100755 (executable)
index 0000000..2b34ef2
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (c) 2020 Western Digital Corporation or its affiliates.
+#
+"""
+jsonplus2csv-test.py
+
+Do one basic test of tools/fio_jsonplus2csv
+
+USAGE
+python jsonplus2csv-test.py [-f fio-executable] [-s script-location]
+
+EXAMPLES
+python t/jsonplus2csv-test.py
+python t/jsonplus2csv-test.py -f ./fio -s tools
+
+REQUIREMENTS
+Python 3.5+
+"""
+
+import os
+import sys
+import platform
+import argparse
+import subprocess
+
+
+def parse_args():
+    """Parse command-line arguments."""
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-f', '--fio',
+                        help='path to fio executable (e.g., ./fio)')
+    parser.add_argument('-s', '--script',
+                        help='directory containing fio_jsonplus2csv script')
+    return parser.parse_args()
+
+
+def run_fio(fio):
+    """Run fio to generate json+ data.
+
+    Parameters:
+        fio     path to fio executable.
+    """
+
+    if platform.system() == 'Linux':
+        aio = 'libaio'
+    elif platform.system() == 'Windows':
+        aio = 'windowsaio'
+    else:
+        aio = 'posixaio'
+
+    fio_args = [
+        "--output=fio-output.json",
+        "--output-format=json+",
+        "--filename=fio_jsonplus_clat2csv.test",
+        "--ioengine=" + aio,
+        "--time_based",
+        "--runtime=3s",
+        "--size=1G",
+        "--slat_percentiles=1",
+        "--clat_percentiles=1",
+        "--lat_percentiles=1",
+        "--thread=1",
+        "--name=test1",
+        "--rw=randrw",
+        "--name=test2",
+        "--rw=read",
+        "--name=test3",
+        "--rw=write",
+        ]
+
+    output = subprocess.run([fio] + fio_args, stdout=subprocess.PIPE,
+                            stderr=subprocess.PIPE)
+
+    return output
+
+
+def check_output(fio_output, script_path):
+    """Run t/fio_jsonplus_clat2csv and validate the generated CSV files
+    against the original json+ fio output.
+
+    Parameters:
+        fio_output      subprocess.run object describing fio run.
+        script_path     path to fio_jsonplus_clat2csv script.
+    """
+
+    if fio_output.returncode != 0:
+        return False
+
+    if platform.system() == 'Windows':
+        script = ['python.exe', script_path]
+    else:
+        script = [script_path]
+
+    script_args = ["fio-output.json", "fio-output.csv"]
+    script_args_validate = script_args + ["--validate"]
+
+    script_output = subprocess.run(script + script_args)
+    if script_output.returncode != 0:
+        return False
+
+    script_output = subprocess.run(script + script_args_validate)
+    if script_output.returncode != 0:
+        return False
+
+    return True
+
+
+def main():
+    """Entry point for this script."""
+
+    args = parse_args()
+
+    index = 1
+    passed = 0
+    failed = 0
+
+    if args.fio:
+        fio_path = args.fio
+    else:
+        fio_path = os.path.join(os.path.dirname(__file__), '../fio')
+        if not os.path.exists(fio_path):
+            fio_path = 'fio'
+    print("fio path is", fio_path)
+
+    if args.script:
+        script_path = args.script
+    else:
+        script_path = os.path.join(os.path.dirname(__file__), '../tools/fio_jsonplus_clat2csv')
+        if not os.path.exists(script_path):
+            script_path = 'fio_jsonplus_clat2csv'
+    print("script path is", script_path)
+
+    fio_output = run_fio(fio_path)
+    status = check_output(fio_output, script_path)
+    print("Test {0} {1}".format(index, ("PASSED" if status else "FAILED")))
+    if status:
+        passed = passed + 1
+    else:
+        failed = failed + 1
+    index = index + 1
+
+    print("{0} tests passed, {1} failed".format(passed, failed))
+
+    sys.exit(failed)
+
+if __name__ == '__main__':
+    main()
index 0c8d0c1..5cdd49c 100755 (executable)
@@ -395,6 +395,11 @@ class FioLatTest():
         approximation   value of the bin used by fio to store a given latency
         actual          actual latency value
         """
+
+        # Avoid a division by zero. The smallest latency values have no error.
+        if actual == 0:
+            return approximation == 0
+
         delta = abs(approximation - actual) / actual
         return delta <= 1/128
 
index 003ff66..8e326ed 100755 (executable)
@@ -67,8 +67,14 @@ class FioTest(object):
         self.test_dir = None
         self.passed = True
         self.failure_reason = ''
+        self.command_file = None
+        self.stdout_file = None
+        self.stderr_file = None
+        self.exitcode_file = None
 
     def setup(self, artifact_root, testnum):
+        """Setup instance variables for test."""
+
         self.artifact_root = artifact_root
         self.testnum = testnum
         self.test_dir = os.path.join(artifact_root, "{:04d}".format(testnum))
@@ -76,22 +82,26 @@ class FioTest(object):
             os.mkdir(self.test_dir)
 
         self.command_file = os.path.join(
-                self.test_dir,
-                "{0}.command".format(os.path.basename(self.exe_path)))
+            self.test_dir,
+            "{0}.command".format(os.path.basename(self.exe_path)))
         self.stdout_file = os.path.join(
-                self.test_dir,
-                "{0}.stdout".format(os.path.basename(self.exe_path)))
+            self.test_dir,
+            "{0}.stdout".format(os.path.basename(self.exe_path)))
         self.stderr_file = os.path.join(
-                self.test_dir,
-                "{0}.stderr".format(os.path.basename(self.exe_path)))
-        self.exticode_file = os.path.join(
-                self.test_dir,
-                "{0}.exitcode".format(os.path.basename(self.exe_path)))
+            self.test_dir,
+            "{0}.stderr".format(os.path.basename(self.exe_path)))
+        self.exitcode_file = os.path.join(
+            self.test_dir,
+            "{0}.exitcode".format(os.path.basename(self.exe_path)))
 
     def run(self):
+        """Run the test."""
+
         raise NotImplementedError()
 
     def check_result(self):
+        """Check test results."""
+
         raise NotImplementedError()
 
 
@@ -109,10 +119,9 @@ class FioExeTest(FioTest):
 
         FioTest.__init__(self, exe_path, parameters, success)
 
-    def setup(self, artifact_root, testnum):
-        super(FioExeTest, self).setup(artifact_root, testnum)
-
     def run(self):
+        """Execute the binary or script described by this instance."""
+
         if self.parameters:
             command = [self.exe_path] + self.parameters
         else:
@@ -123,7 +132,7 @@ class FioExeTest(FioTest):
 
         stdout_file = open(self.stdout_file, "w+")
         stderr_file = open(self.stderr_file, "w+")
-        exticode_file = open(self.exticode_file, "w+")
+        exitcode_file = open(self.exitcode_file, "w+")
         try:
             proc = None
             # Avoid using subprocess.run() here because when a timeout occurs,
@@ -136,8 +145,8 @@ class FioExeTest(FioTest):
                                     cwd=self.test_dir,
                                     universal_newlines=True)
             proc.communicate(timeout=self.success['timeout'])
-            exticode_file.write('{0}\n'.format(proc.returncode))
-            logging.debug("Test %d: return code: %d" % (self.testnum, proc.returncode))
+            exitcode_file.write('{0}\n'.format(proc.returncode))
+            logging.debug("Test %d: return code: %d", self.testnum, proc.returncode)
             self.output['proc'] = proc
         except subprocess.TimeoutExpired:
             proc.terminate()
@@ -154,17 +163,19 @@ class FioExeTest(FioTest):
         finally:
             stdout_file.close()
             stderr_file.close()
-            exticode_file.close()
+            exitcode_file.close()
 
     def check_result(self):
+        """Check results of test run."""
+
         if 'proc' not in self.output:
             if self.output['failure'] == 'timeout':
                 self.failure_reason = "{0} timeout,".format(self.failure_reason)
             else:
                 assert self.output['failure'] == 'exception'
                 self.failure_reason = '{0} exception: {1}, {2}'.format(
-                        self.failure_reason, self.output['exc_info'][0],
-                        self.output['exc_info'][1])
+                    self.failure_reason, self.output['exc_info'][0],
+                    self.output['exc_info'][1])
 
             self.passed = False
             return
@@ -222,22 +233,26 @@ class FioJobTest(FioExeTest):
         FioExeTest.__init__(self, fio_path, self.fio_args, success)
 
     def setup(self, artifact_root, testnum):
+        """Setup instance variables for fio job test."""
+
         super(FioJobTest, self).setup(artifact_root, testnum)
 
         self.command_file = os.path.join(
-                self.test_dir,
-                "{0}.command".format(os.path.basename(self.fio_job)))
+            self.test_dir,
+            "{0}.command".format(os.path.basename(self.fio_job)))
         self.stdout_file = os.path.join(
-                self.test_dir,
-                "{0}.stdout".format(os.path.basename(self.fio_job)))
+            self.test_dir,
+            "{0}.stdout".format(os.path.basename(self.fio_job)))
         self.stderr_file = os.path.join(
-                self.test_dir,
-                "{0}.stderr".format(os.path.basename(self.fio_job)))
-        self.exticode_file = os.path.join(
-                self.test_dir,
-                "{0}.exitcode".format(os.path.basename(self.fio_job)))
+            self.test_dir,
+            "{0}.stderr".format(os.path.basename(self.fio_job)))
+        self.exitcode_file = os.path.join(
+            self.test_dir,
+            "{0}.exitcode".format(os.path.basename(self.fio_job)))
 
     def run_pre_job(self):
+        """Run fio job precondition step."""
+
         precon = FioJobTest(self.exe_path, self.fio_pre_job,
                             self.fio_pre_success,
                             output_format=self.output_format)
@@ -248,15 +263,19 @@ class FioJobTest(FioExeTest):
         self.failure_reason = precon.failure_reason
 
     def run(self):
+        """Run fio job test."""
+
         if self.fio_pre_job:
             self.run_pre_job()
 
         if not self.precon_failed:
             super(FioJobTest, self).run()
         else:
-            logging.debug("Test %d: precondition step failed" % self.testnum)
+            logging.debug("Test %d: precondition step failed", self.testnum)
 
     def check_result(self):
+        """Check fio job results."""
+
         if self.precon_failed:
             self.passed = False
             self.failure_reason = "{0} precondition step failed,".format(self.failure_reason)
@@ -267,7 +286,7 @@ class FioJobTest(FioExeTest):
         if not self.passed:
             return
 
-        if not 'json' in self.output_format:
+        if 'json' not in self.output_format:
             return
 
         try:
@@ -291,7 +310,7 @@ class FioJobTest(FioExeTest):
             except json.JSONDecodeError:
                 continue
             else:
-                logging.debug("Test %d: skipped %d lines decoding JSON data" % (self.testnum, i))
+                logging.debug("Test %d: skipped %d lines decoding JSON data", self.testnum, i)
                 return
 
         self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason)
@@ -328,7 +347,7 @@ class FioJobTest_t0006(FioJobTest):
 
         ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \
             / self.json_data['jobs'][0]['write']['io_kbytes']
-        logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
+        logging.debug("Test %d: ratio: %f", self.testnum, ratio)
         if ratio < 1.99 or ratio > 2.01:
             self.failure_reason = "{0} read/write ratio mismatch,".format(self.failure_reason)
             self.passed = False
@@ -364,7 +383,7 @@ class FioJobTest_t0008(FioJobTest):
             return
 
         ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16568
-        logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
+        logging.debug("Test %d: ratio: %f", self.testnum, ratio)
 
         if ratio < 0.99 or ratio > 1.01:
             self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
@@ -384,7 +403,7 @@ class FioJobTest_t0009(FioJobTest):
         if not self.passed:
             return
 
-        logging.debug('Test %d: elapsed: %d' % (self.testnum, self.json_data['jobs'][0]['elapsed']))
+        logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed'])
 
         if self.json_data['jobs'][0]['elapsed'] < 60:
             self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason)
@@ -406,8 +425,8 @@ class FioJobTest_t0011(FioJobTest):
         iops1 = self.json_data['jobs'][0]['read']['iops']
         iops2 = self.json_data['jobs'][1]['read']['iops']
         ratio = iops2 / iops1
-        logging.debug("Test %d: iops1: %f" % (self.testnum, iops1))
-        logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
+        logging.debug("Test %d: iops1: %f", self.testnum, iops1)
+        logging.debug("Test %d: ratio: %f", self.testnum, ratio)
 
         if iops1 < 998 or iops1 > 1002:
             self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason)
@@ -446,16 +465,16 @@ class Requirements(object):
                 print("Unable to open {0} to check requirements".format(config_file))
                 Requirements._zbd = True
             else:
-                Requirements._zbd = "CONFIG_LINUX_BLKZONED" in contents
+                Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents
                 Requirements._libaio = "CONFIG_LIBAIO" in contents
 
             Requirements._root = (os.geteuid() == 0)
             if Requirements._zbd and Requirements._root:
-                    subprocess.run(["modprobe", "null_blk"],
-                                   stdout=subprocess.PIPE,
-                                   stderr=subprocess.PIPE)
-                    if os.path.exists("/sys/module/null_blk/parameters/zoned"):
-                        Requirements._zoned_nullb = True
+                subprocess.run(["modprobe", "null_blk"],
+                               stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+                if os.path.exists("/sys/module/null_blk/parameters/zoned"):
+                    Requirements._zoned_nullb = True
 
         if platform.system() == "Windows":
             utest_exe = "unittest.exe"
@@ -477,253 +496,281 @@ class Requirements(object):
                     Requirements.cpucount4]
         for req in req_list:
             value, desc = req()
-            logging.debug("Requirements: Requirement '%s' met? %s" % (desc, value))
+            logging.debug("Requirements: Requirement '%s' met? %s", desc, value)
 
-    def linux():
+    @classmethod
+    def linux(cls):
+        """Are we running on Linux?"""
         return Requirements._linux, "Linux required"
 
-    def libaio():
+    @classmethod
+    def libaio(cls):
+        """Is libaio available?"""
         return Requirements._libaio, "libaio required"
 
-    def zbd():
+    @classmethod
+    def zbd(cls):
+        """Is ZBD support available?"""
         return Requirements._zbd, "Zoned block device support required"
 
-    def root():
+    @classmethod
+    def root(cls):
+        """Are we running as root?"""
         return Requirements._root, "root required"
 
-    def zoned_nullb():
+    @classmethod
+    def zoned_nullb(cls):
+        """Are zoned null block devices available?"""
         return Requirements._zoned_nullb, "Zoned null block device support required"
 
-    def not_macos():
+    @classmethod
+    def not_macos(cls):
+        """Are we running on a platform other than macOS?"""
         return Requirements._not_macos, "platform other than macOS required"
 
-    def not_windows():
+    @classmethod
+    def not_windows(cls):
+        """Are we running on a platform other than Windws?"""
         return Requirements._not_windows, "platform other than Windows required"
 
-    def unittests():
+    @classmethod
+    def unittests(cls):
+        """Were unittests built?"""
         return Requirements._unittests, "Unittests support required"
 
-    def cpucount4():
+    @classmethod
+    def cpucount4(cls):
+        """Do we have at least 4 CPUs?"""
         return Requirements._cpucount4, "4+ CPUs required"
 
 
 SUCCESS_DEFAULT = {
-        'zero_return': True,
-        'stderr_empty': True,
-        'timeout': 600,
-        }
+    'zero_return': True,
+    'stderr_empty': True,
+    'timeout': 600,
+    }
 SUCCESS_NONZERO = {
-        'zero_return': False,
-        'stderr_empty': False,
-        'timeout': 600,
-        }
+    'zero_return': False,
+    'stderr_empty': False,
+    'timeout': 600,
+    }
 SUCCESS_STDERR = {
-        'zero_return': True,
-        'stderr_empty': False,
-        'timeout': 600,
-        }
+    'zero_return': True,
+    'stderr_empty': False,
+    'timeout': 600,
+    }
 TEST_LIST = [
-        {
-            'test_id':          1,
-            'test_class':       FioJobTest,
-            'job':              't0001-52c58027.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'requirements':     [],
-        },
-        {
-            'test_id':          2,
-            'test_class':       FioJobTest,
-            'job':              't0002-13af05ae-post.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          't0002-13af05ae-pre.fio',
-            'pre_success':      None,
-            'requirements':     [Requirements.linux, Requirements.libaio],
-        },
-        {
-            'test_id':          3,
-            'test_class':       FioJobTest,
-            'job':              't0003-0ae2c6e1-post.fio',
-            'success':          SUCCESS_NONZERO,
-            'pre_job':          't0003-0ae2c6e1-pre.fio',
-            'pre_success':      SUCCESS_DEFAULT,
-            'requirements':     [Requirements.linux, Requirements.libaio],
-        },
-        {
-            'test_id':          4,
-            'test_class':       FioJobTest,
-            'job':              't0004-8a99fdf6.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'requirements':     [Requirements.linux, Requirements.libaio],
-        },
-        {
-            'test_id':          5,
-            'test_class':       FioJobTest_t0005,
-            'job':              't0005-f7078f7b.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'output_format':    'json',
-            'requirements':     [Requirements.not_windows],
-        },
-        {
-            'test_id':          6,
-            'test_class':       FioJobTest_t0006,
-            'job':              't0006-82af2a7c.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'output_format':    'json',
-            'requirements':     [Requirements.linux, Requirements.libaio],
-        },
-        {
-            'test_id':          7,
-            'test_class':       FioJobTest_t0007,
-            'job':              't0007-37cf9e3c.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'output_format':    'json',
-            'requirements':     [],
-        },
-        {
-            'test_id':          8,
-            'test_class':       FioJobTest_t0008,
-            'job':              't0008-ae2fafc8.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'output_format':    'json',
-            'requirements':     [],
-        },
-        {
-            'test_id':          9,
-            'test_class':       FioJobTest_t0009,
-            'job':              't0009-f8b0bd10.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'output_format':    'json',
-            'requirements':     [Requirements.not_macos,
-                                 Requirements.cpucount4],
-                                # mac os does not support CPU affinity
-        },
-        {
-            'test_id':          10,
-            'test_class':       FioJobTest,
-            'job':              't0010-b7aae4ba.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'requirements':     [],
-        },
-        {
-            'test_id':          11,
-            'test_class':       FioJobTest_t0011,
-            'job':              't0011-5d2788d5.fio',
-            'success':          SUCCESS_DEFAULT,
-            'pre_job':          None,
-            'pre_success':      None,
-            'output_format':    'json',
-            'requirements':     [],
-        },
-        {
-            'test_id':          1000,
-            'test_class':       FioExeTest,
-            'exe':              't/axmap',
-            'parameters':       None,
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1001,
-            'test_class':       FioExeTest,
-            'exe':              't/ieee754',
-            'parameters':       None,
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1002,
-            'test_class':       FioExeTest,
-            'exe':              't/lfsr-test',
-            'parameters':       ['0xFFFFFF', '0', '0', 'verify'],
-            'success':          SUCCESS_STDERR,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1003,
-            'test_class':       FioExeTest,
-            'exe':              't/readonly.py',
-            'parameters':       ['-f', '{fio_path}'],
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1004,
-            'test_class':       FioExeTest,
-            'exe':              't/steadystate_tests.py',
-            'parameters':       ['{fio_path}'],
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1005,
-            'test_class':       FioExeTest,
-            'exe':              't/stest',
-            'parameters':       None,
-            'success':          SUCCESS_STDERR,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1006,
-            'test_class':       FioExeTest,
-            'exe':              't/strided.py',
-            'parameters':       ['{fio_path}'],
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [],
-        },
-        {
-            'test_id':          1007,
-            'test_class':       FioExeTest,
-            'exe':              't/zbd/run-tests-against-regular-nullb',
-            'parameters':       None,
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [Requirements.linux, Requirements.zbd,
-                                 Requirements.root],
-        },
-        {
-            'test_id':          1008,
-            'test_class':       FioExeTest,
-            'exe':              't/zbd/run-tests-against-zoned-nullb',
-            'parameters':       None,
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [Requirements.linux, Requirements.zbd,
-                                 Requirements.root, Requirements.zoned_nullb],
-        },
-        {
-            'test_id':          1009,
-            'test_class':       FioExeTest,
-            'exe':              'unittests/unittest',
-            'parameters':       None,
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [Requirements.unittests],
-        },
-        {
-            'test_id':          1010,
-            'test_class':       FioExeTest,
-            'exe':              't/latency_percentiles.py',
-            'parameters':       ['-f', '{fio_path}'],
-            'success':          SUCCESS_DEFAULT,
-            'requirements':     [],
-        },
+    {
+        'test_id':          1,
+        'test_class':       FioJobTest,
+        'job':              't0001-52c58027.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'requirements':     [],
+    },
+    {
+        'test_id':          2,
+        'test_class':       FioJobTest,
+        'job':              't0002-13af05ae-post.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          't0002-13af05ae-pre.fio',
+        'pre_success':      None,
+        'requirements':     [Requirements.linux, Requirements.libaio],
+    },
+    {
+        'test_id':          3,
+        'test_class':       FioJobTest,
+        'job':              't0003-0ae2c6e1-post.fio',
+        'success':          SUCCESS_NONZERO,
+        'pre_job':          't0003-0ae2c6e1-pre.fio',
+        'pre_success':      SUCCESS_DEFAULT,
+        'requirements':     [Requirements.linux, Requirements.libaio],
+    },
+    {
+        'test_id':          4,
+        'test_class':       FioJobTest,
+        'job':              't0004-8a99fdf6.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'requirements':     [Requirements.linux, Requirements.libaio],
+    },
+    {
+        'test_id':          5,
+        'test_class':       FioJobTest_t0005,
+        'job':              't0005-f7078f7b.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'output_format':    'json',
+        'requirements':     [Requirements.not_windows],
+    },
+    {
+        'test_id':          6,
+        'test_class':       FioJobTest_t0006,
+        'job':              't0006-82af2a7c.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'output_format':    'json',
+        'requirements':     [Requirements.linux, Requirements.libaio],
+    },
+    {
+        'test_id':          7,
+        'test_class':       FioJobTest_t0007,
+        'job':              't0007-37cf9e3c.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'output_format':    'json',
+        'requirements':     [],
+    },
+    {
+        'test_id':          8,
+        'test_class':       FioJobTest_t0008,
+        'job':              't0008-ae2fafc8.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'output_format':    'json',
+        'requirements':     [],
+    },
+    {
+        'test_id':          9,
+        'test_class':       FioJobTest_t0009,
+        'job':              't0009-f8b0bd10.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'output_format':    'json',
+        'requirements':     [Requirements.not_macos,
+                             Requirements.cpucount4],
+        # mac os does not support CPU affinity
+    },
+    {
+        'test_id':          10,
+        'test_class':       FioJobTest,
+        'job':              't0010-b7aae4ba.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'requirements':     [],
+    },
+    {
+        'test_id':          11,
+        'test_class':       FioJobTest_t0011,
+        'job':              't0011-5d2788d5.fio',
+        'success':          SUCCESS_DEFAULT,
+        'pre_job':          None,
+        'pre_success':      None,
+        'output_format':    'json',
+        'requirements':     [],
+    },
+    {
+        'test_id':          1000,
+        'test_class':       FioExeTest,
+        'exe':              't/axmap',
+        'parameters':       None,
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1001,
+        'test_class':       FioExeTest,
+        'exe':              't/ieee754',
+        'parameters':       None,
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1002,
+        'test_class':       FioExeTest,
+        'exe':              't/lfsr-test',
+        'parameters':       ['0xFFFFFF', '0', '0', 'verify'],
+        'success':          SUCCESS_STDERR,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1003,
+        'test_class':       FioExeTest,
+        'exe':              't/readonly.py',
+        'parameters':       ['-f', '{fio_path}'],
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1004,
+        'test_class':       FioExeTest,
+        'exe':              't/steadystate_tests.py',
+        'parameters':       ['{fio_path}'],
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1005,
+        'test_class':       FioExeTest,
+        'exe':              't/stest',
+        'parameters':       None,
+        'success':          SUCCESS_STDERR,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1006,
+        'test_class':       FioExeTest,
+        'exe':              't/strided.py',
+        'parameters':       ['{fio_path}'],
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1007,
+        'test_class':       FioExeTest,
+        'exe':              't/zbd/run-tests-against-regular-nullb',
+        'parameters':       None,
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [Requirements.linux, Requirements.zbd,
+                             Requirements.root],
+    },
+    {
+        'test_id':          1008,
+        'test_class':       FioExeTest,
+        'exe':              't/zbd/run-tests-against-zoned-nullb',
+        'parameters':       None,
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [Requirements.linux, Requirements.zbd,
+                             Requirements.root, Requirements.zoned_nullb],
+    },
+    {
+        'test_id':          1009,
+        'test_class':       FioExeTest,
+        'exe':              'unittests/unittest',
+        'parameters':       None,
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [Requirements.unittests],
+    },
+    {
+        'test_id':          1010,
+        'test_class':       FioExeTest,
+        'exe':              't/latency_percentiles.py',
+        'parameters':       ['-f', '{fio_path}'],
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
+    {
+        'test_id':          1011,
+        'test_class':       FioExeTest,
+        'exe':              't/jsonplus2csv_test.py',
+        'parameters':       ['-f', '{fio_path}'],
+        'success':          SUCCESS_DEFAULT,
+        'requirements':     [],
+    },
 ]
 
 
 def parse_args():
+    """Parse command-line arguments."""
+
     parser = argparse.ArgumentParser()
     parser.add_argument('-r', '--fio-root',
                         help='fio root path')
@@ -745,6 +792,8 @@ def parse_args():
 
 
 def main():
+    """Entry point."""
+
     args = parse_args()
     if args.debug:
         logging.basicConfig(level=logging.DEBUG)
@@ -829,14 +878,14 @@ def main():
             continue
 
         if not args.skip_req:
-            skip = False
+            reqs_met = True
             for req in config['requirements']:
-                ok, reason = req()
-                skip = not ok
-                logging.debug("Test %d: Requirement '%s' met? %s" % (config['test_id'], reason, ok))
-                if skip:
+                reqs_met, reason = req()
+                logging.debug("Test %d: Requirement '%s' met? %s", config['test_id'], reason,
+                              reqs_met)
+                if not reqs_met:
                     break
-            if skip:
+            if not reqs_met:
                 print("Test {0} SKIPPED ({1})".format(config['test_id'], reason))
                 skipped = skipped + 1
                 continue
@@ -851,9 +900,9 @@ def main():
             result = "FAILED: {0}".format(test.failure_reason)
             failed = failed + 1
             with open(test.stderr_file, "r") as stderr_file:
-                logging.debug("Test %d: stderr:\n%s" % (config['test_id'], stderr_file.read()))
+                logging.debug("Test %d: stderr:\n%s", config['test_id'], stderr_file.read())
             with open(test.stdout_file, "r") as stdout_file:
-                logging.debug("Test %d: stdout:\n%s" % (config['test_id'], stdout_file.read()))
+                logging.debug("Test %d: stdout:\n%s", config['test_id'], stdout_file.read())
         print("Test {0} {1}".format(config['test_id'], result))
 
     print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped))
index d49555a..35087b1 100644 (file)
@@ -4,18 +4,27 @@ blkzone=$(type -p blkzone 2>/dev/null)
 sg_inq=$(type -p sg_inq 2>/dev/null)
 zbc_report_zones=$(type -p zbc_report_zones 2>/dev/null)
 zbc_reset_zone=$(type -p zbc_reset_zone 2>/dev/null)
+zbc_info=$(type -p zbc_info 2>/dev/null)
 if [ -z "${blkzone}" ] &&
        { [ -z "${zbc_report_zones}" ] || [ -z "${zbc_reset_zone}" ]; }; then
     echo "Error: neither blkzone nor zbc_report_zones is available"
     exit 1
 fi
 
+if [ -n "${use_libzbc}" ] &&
+       { [ -z "${zbc_report_zones}" ] || [ -z "${zbc_reset_zone}" ] ||
+         [ -z "${zbc_info}" ]; }; then
+    echo "Error: zbc_report_zones, or zbc_reset_zone or zbc_info is not available"
+    echo "Error: reinstall libzbc tools"
+    exit 1
+fi
+
 # Reports the starting sector and length of the first sequential zone of device
 # $1.
 first_sequential_zone() {
     local dev=$1
 
-    if [ -n "${blkzone}" ]; then
+    if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then
        ${blkzone} report "$dev" |
            sed -n 's/^[[:blank:]]*start:[[:blank:]]\([0-9a-zA-Z]*\),[[:blank:]]len[[:blank:]]\([0-9a-zA-Z]*\),.*type:[[:blank:]]2(.*/\1 \2/p' |
            {
@@ -33,7 +42,7 @@ first_sequential_zone() {
 max_open_zones() {
     local dev=$1
 
-    if [ -n "${sg_inq}" ]; then
+    if [ -n "${sg_inq}" ] && [ ! -n "${use_libzbc}" ]; then
        if ! ${sg_inq} -e --page=0xB6 --len=20 --hex "$dev" 2> /dev/null; then
            # Non scsi device such as null_blk can not return max open zones.
            # Use default value.
@@ -56,13 +65,36 @@ max_open_zones() {
     fi
 }
 
+is_zbc() {
+       local dev=$1
+
+       [[ -z "$(${zbc_info} "$dev" | grep "is not a zoned block device")" ]]
+}
+
+zbc_logical_block_size() {
+       local dev=$1
+
+       ${zbc_info} "$dev" |
+               grep "logical blocks" |
+               sed -n 's/^[[:blank:]]*[0-9]* logical blocks of[[:blank:]]*//p' |
+               sed 's/ B//'
+}
+
+zbc_disk_sectors() {
+        local dev=$1
+
+       zbc_info "$dev" |
+               grep "512-bytes sectors" |
+               sed -e 's/[[:blank:]]*\([0-9]*\)512-bytes sectors.*/\1/'
+}
+
 # Reset the write pointer of one zone on device $1 at offset $2. The offset
 # must be specified in units of 512 byte sectors. Offset -1 means reset all
 # zones.
 reset_zone() {
     local dev=$1 offset=$2 sectors
 
-    if [ -n "${blkzone}" ]; then
+    if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then
        if [ "$offset" -lt 0 ]; then
            sectors=$(<"/sys/class/block/${dev#/dev/}/size")
            ${blkzone} reset -o "${offset}" -l "$sectors" "$dev"
index 5d079a8..be889f3 100755 (executable)
@@ -5,7 +5,7 @@
 # This file is released under the GPL.
 
 usage() {
-    echo "Usage: $(basename "$0") [-d] [-e] [-r] [-v] [-t <test>] <SMR drive device node>"
+    echo "Usage: $(basename "$0") [-d] [-e] [-l] [-r] [-v] [-t <test>] [-z] <SMR drive device node>"
 }
 
 max() {
@@ -24,6 +24,14 @@ min() {
     fi
 }
 
+ioengine() {
+       if [ -n "$use_libzbc" ]; then
+               echo -n "--ioengine=libzbc"
+       else
+               echo -n "--ioengine=$1"
+       fi
+}
+
 set_io_scheduler() {
     local dev=$1 sched=$2
 
@@ -87,6 +95,7 @@ run_fio() {
 
     opts=("--aux-path=/tmp" "--allow_file_create=0" \
                            "--significant_figures=10" "$@")
+    opts+=(${var_opts[@]})
     { echo; echo "fio ${opts[*]}"; echo; } >>"${logfile}.${test_number}"
 
     "${dynamic_analyzer[@]}" "$fio" "${opts[@]}"
@@ -115,7 +124,7 @@ run_fio_on_seq() {
 # Check whether buffered writes are refused.
 test1() {
     run_fio --name=job1 --filename="$dev" --rw=write --direct=0 --bs=4K        \
-           --size="${zone_size}" --thread=1                            \
+           "$(ioengine "psync")" --size="${zone_size}" --thread=1      \
            --zonemode=zbd --zonesize="${zone_size}" 2>&1 |
        tee -a "${logfile}.${test_number}" |
        grep -q 'Using direct I/O is mandatory for writing to ZBD drives'
@@ -137,6 +146,7 @@ test2() {
 
     off=$(((first_sequential_zone_sector + 2 * sectors_per_zone) * 512))
     bs=$((2 * zone_size))
+    opts+=("$(ioengine "psync")")
     opts+=("--name=job1" "--filename=$dev" "--rw=write" "--direct=1")
     opts+=("--zonemode=zbd" "--offset=$off" "--bs=$bs" "--size=$bs")
     if [ -z "$is_zbd" ]; then
@@ -155,7 +165,7 @@ test3() {
     [ -n "$is_zbd" ] && reset_zone "$dev" $((off / 512))
     opts+=("--name=$dev" "--filename=$dev" "--offset=$off" "--bs=4K")
     opts+=("--size=$size" "--zonemode=zbd")
-    opts+=("--ioengine=psync" "--rw=read" "--direct=1" "--thread=1")
+    opts+=("$(ioengine "psync")" "--rw=read" "--direct=1" "--thread=1")
     if [ -z "$is_zbd" ]; then
        opts+=("--zonesize=${zone_size}")
     fi
@@ -178,7 +188,7 @@ test4() {
     [ -n "$is_zbd" ] && reset_zone "$dev" $((off / 512))
     opts+=("--name=$dev" "--filename=$dev" "--offset=$off" "--bs=$size")
     opts+=("--size=$size" "--thread=1" "--read_beyond_wp=1")
-    opts+=("--ioengine=psync" "--rw=read" "--direct=1" "--disable_lat=1")
+    opts+=("$(ioengine "psync")" "--rw=read" "--direct=1" "--disable_lat=1")
     opts+=("--zonemode=zbd" "--zonesize=${zone_size}")
     run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $?
     check_read $size || return $?
@@ -189,7 +199,7 @@ test5() {
     local size
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=psync --iodepth=1 --rw=write             \
+    run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=write        \
                   --bs="$(max $((zone_size / 64)) "$logical_block_size")"\
                   --do_verify=1 --verify=md5                           \
                   >>"${logfile}.${test_number}" 2>&1 || return $?
@@ -202,7 +212,7 @@ test6() {
     local size
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=psync --iodepth=1 --rw=read              \
+    run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=read \
                   --bs="$(max $((zone_size / 64)) "$logical_block_size")"\
                   >>"${logfile}.${test_number}" 2>&1 || return $?
     check_read $size || return $?
@@ -212,7 +222,7 @@ test6() {
 test7() {
     local size=$((zone_size))
 
-    run_fio_on_seq --ioengine=libaio --iodepth=1 --rw=randwrite                \
+    run_fio_on_seq "$(ioengine "libaio")" --iodepth=1 --rw=randwrite   \
                   --bs="$(min 16384 "${zone_size}")"                   \
                   --do_verify=1 --verify=md5 --size="$size"            \
                   >>"${logfile}.${test_number}" 2>&1 || return $?
@@ -225,7 +235,7 @@ test8() {
     local size
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=libaio --iodepth=64 --rw=randwrite       \
+    run_fio_on_seq "$(ioengine "libaio")" --iodepth=64 --rw=randwrite  \
                   --bs="$(min 16384 "${zone_size}")"                   \
                   --do_verify=1 --verify=md5                           \
                   >>"${logfile}.${test_number}" 2>&1 || return $?
@@ -243,7 +253,8 @@ test9() {
     fi
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=sg --iodepth=1 --rw=randwrite --bs=16K   \
+    run_fio_on_seq --ioengine=sg                                       \
+                  --iodepth=1 --rw=randwrite --bs=16K                  \
                   --do_verify=1 --verify=md5                           \
                   >>"${logfile}.${test_number}" 2>&1 || return $?
     check_written $size || return $?
@@ -260,7 +271,8 @@ test10() {
     fi
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=sg --iodepth=64 --rw=randwrite --bs=16K  \
+    run_fio_on_seq --ioengine=sg                                       \
+                  --iodepth=64 --rw=randwrite --bs=16K                 \
                   --do_verify=1 --verify=md5                           \
                   >>"${logfile}.${test_number}" 2>&1 || return $?
     check_written $size || return $?
@@ -272,7 +284,7 @@ test11() {
     local size
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=libaio --iodepth=64 --rw=randwrite       \
+    run_fio_on_seq "$(ioengine "libaio")" --iodepth=64 --rw=randwrite  \
                   --bsrange=4K-64K --do_verify=1 --verify=md5          \
                   --debug=zbd >>"${logfile}.${test_number}" 2>&1 || return $?
     check_written $size || return $?
@@ -284,7 +296,7 @@ test12() {
     local size
 
     size=$((8 * zone_size))
-    run_fio_on_seq --ioengine=libaio --iodepth=64 --rw=randwrite --bs=16K     \
+    run_fio_on_seq "$(ioengine "libaio")" --iodepth=64 --rw=randwrite --bs=16K \
                   --max_open_zones=1 --size=$size --do_verify=1 --verify=md5 \
                   --debug=zbd >>"${logfile}.${test_number}" 2>&1 || return $?
     check_written $size || return $?
@@ -296,7 +308,7 @@ test13() {
     local size
 
     size=$((8 * zone_size))
-    run_fio_on_seq --ioengine=libaio --iodepth=64 --rw=randwrite --bs=16K     \
+    run_fio_on_seq "$(ioengine "libaio")" --iodepth=64 --rw=randwrite --bs=16K \
                   --max_open_zones=4 --size=$size --do_verify=1 --verify=md5 \
                   --debug=zbd                                                \
                   >>"${logfile}.${test_number}" 2>&1 || return $?
@@ -314,7 +326,7 @@ test14() {
             >>"${logfile}.${test_number}"
        return 0
     fi
-    run_one_fio_job --ioengine=libaio --iodepth=64 --rw=randwrite --bs=16K \
+    run_one_fio_job "$(ioengine "libaio")" --iodepth=64 --rw=randwrite --bs=16K \
                    --zonemode=zbd --zonesize="${zone_size}" --do_verify=1 \
                    --verify=md5 --size=$size                              \
                    >>"${logfile}.${test_number}" 2>&1 || return $?
@@ -333,14 +345,14 @@ test15() {
     done
     off=$(((first_sequential_zone_sector + 2 * sectors_per_zone) * 512))
     size=$((2 * zone_size))
-    run_one_fio_job --ioengine=psync --rw=write --bs=$((zone_size / 16))\
+    run_one_fio_job "$(ioengine "psync")" --rw=write --bs=$((zone_size / 16))\
                    --zonemode=zbd --zonesize="${zone_size}" --offset=$off \
                    --size=$size >>"${logfile}.${test_number}" 2>&1 ||
        return $?
     check_written $size || return $?
     off=$((first_sequential_zone_sector * 512))
     size=$((4 * zone_size))
-    run_one_fio_job --ioengine=psync --rw=read --bs=$((zone_size / 16))        \
+    run_one_fio_job "$(ioengine "psync")" --rw=read --bs=$((zone_size / 16)) \
                    --zonemode=zbd --zonesize="${zone_size}" --offset=$off \
                    --size=$((size)) >>"${logfile}.${test_number}" 2>&1 ||
        return $?
@@ -357,7 +369,7 @@ test16() {
 
     off=$((first_sequential_zone_sector * 512))
     size=$((4 * zone_size))
-    run_one_fio_job --ioengine=libaio --iodepth=64 --rw=randread --bs=16K \
+    run_one_fio_job "$(ioengine "libaio")" --iodepth=64 --rw=randread --bs=16K \
                    --zonemode=zbd --zonesize="${zone_size}" --offset=$off \
                    --size=$size >>"${logfile}.${test_number}" 2>&1 || return $?
     check_read $size || return $?
@@ -373,12 +385,12 @@ test17() {
     if [ -n "$is_zbd" ]; then
        reset_zone "$dev" $((off / 512)) || return $?
     fi
-    run_one_fio_job --ioengine=psync --rw=write --offset="$off"                \
+    run_one_fio_job "$(ioengine "psync")" --rw=write --offset="$off"   \
                    --zonemode=zbd --zonesize="${zone_size}"            \
                    --bs="$zone_size" --size="$zone_size"               \
                    >>"${logfile}.${test_number}" 2>&1 || return $?
     check_written "$zone_size" || return $?
-    run_one_fio_job --ioengine=libaio --iodepth=8 --rw=randrw --bs=4K  \
+    run_one_fio_job "$(ioengine "libaio")" --iodepth=8 --rw=randrw --bs=4K \
                    --zonemode=zbd --zonesize="${zone_size}"            \
                    --offset=$off --loops=2 --norandommap=1\
                    >>"${logfile}.${test_number}" 2>&1 || return $?
@@ -431,8 +443,8 @@ test24() {
     local bs loops=9 size=$((zone_size))
 
     bs=$(min $((256*1024)) "$zone_size")
-    run_fio_on_seq --ioengine=psync --rw=write --bs="$bs" --size=$size  \
-                  --loops=$loops                                        \
+    run_fio_on_seq "$(ioengine "psync")" --rw=write --bs="$bs"         \
+                  --size=$size --loops=$loops                          \
                   --zone_reset_frequency=.01 --zone_reset_threshold=.90 \
                   >> "${logfile}.${test_number}" 2>&1 || return $?
     check_written $((size * loops)) || return $?
@@ -452,8 +464,9 @@ test25() {
     for ((i=0;i<16;i++)); do
        opts+=("--name=job$i" "--filename=$dev" "--thread=1" "--direct=1")
        opts+=("--offset=$((first_sequential_zone_sector*512 + zone_size*i))")
-       opts+=("--size=$zone_size" "--ioengine=psync" "--rw=write" "--bs=16K")
+       opts+=("--size=$zone_size" "$(ioengine "psync")" "--rw=write" "--bs=16K")
        opts+=("--zonemode=zbd" "--zonesize=${zone_size}" "--group_reporting=1")
+       opts+=(${var_opts[@]})
     done
     run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $?
 }
@@ -462,7 +475,7 @@ write_to_first_seq_zone() {
     local loops=4 r
 
     r=$(((RANDOM << 16) | RANDOM))
-    run_fio --name="$dev" --filename="$dev" --ioengine=psync --rw="$1" \
+    run_fio --name="$dev" --filename="$dev" "$(ioengine "psync")" --rw="$1" \
            --thread=1 --do_verify=1 --verify=md5 --direct=1 --bs=4K    \
            --offset=$((first_sequential_zone_sector * 512))            \
            "--size=$zone_size" --loops=$loops --randseed="$r"          \
@@ -490,9 +503,10 @@ test28() {
     opts=("--debug=zbd")
     for ((i=0;i<jobs;i++)); do
        opts+=("--name=job$i" "--filename=$dev" "--offset=$off" "--bs=16K")
-       opts+=("--size=$zone_size" "--ioengine=psync" "--rw=randwrite")
+       opts+=("--size=$zone_size" "$(ioengine "psync")" "--rw=randwrite")
        opts+=("--thread=1" "--direct=1" "--zonemode=zbd")
        opts+=("--zonesize=${zone_size}" "--group_reporting=1")
+       opts+=(${var_opts[@]})
     done
     run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $?
     check_written $((jobs * zone_size)) || return $?
@@ -513,9 +527,10 @@ test29() {
     for ((i=0;i<jobs;i++)); do
        opts+=("--name=job$i" "--filename=$dev" "--offset=$off" "--bs=16K")
        opts+=("--size=$size" "--io_size=$zone_size" "--thread=1")
-       opts+=("--ioengine=psync" "--rw=randwrite" "--direct=1")
+       opts+=("$(ioengine "psync")" "--rw=randwrite" "--direct=1")
        opts+=("--max_open_zones=4" "--group_reporting=1")
        opts+=("--zonemode=zbd" "--zonesize=${zone_size}")
+       opts+=(${var_opts[@]})
     done
     run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $?
     check_written $((jobs * zone_size)) || return $?
@@ -526,7 +541,7 @@ test30() {
     local off
 
     off=$((first_sequential_zone_sector * 512))
-    run_one_fio_job --ioengine=libaio --iodepth=8 --rw=randrw          \
+    run_one_fio_job "$(ioengine "libaio")" --iodepth=8 --rw=randrw     \
                    --bs="$(max $((zone_size / 128)) "$logical_block_size")"\
                    --zonemode=zbd --zonesize="${zone_size}" --offset=$off\
                    --loops=2 --time_based --runtime=30s --norandommap=1\
@@ -548,16 +563,17 @@ test31() {
     for ((off = first_sequential_zone_sector * 512; off < disk_size;
          off += inc)); do
        opts+=("--name=$dev" "--filename=$dev" "--offset=$off" "--io_size=$bs")
-       opts+=("--bs=$bs" "--size=$zone_size" "--ioengine=libaio")
+       opts+=("--bs=$bs" "--size=$zone_size" "$(ioengine "libaio")")
        opts+=("--rw=write" "--direct=1" "--thread=1" "--stats=0")
        opts+=("--zonemode=zbd" "--zonesize=${zone_size}")
+       opts+=(${var_opts[@]})
     done
     "$(dirname "$0")/../../fio" "${opts[@]}" >> "${logfile}.${test_number}" 2>&1
     # Next, run the test.
     off=$((first_sequential_zone_sector * 512))
     size=$((disk_size - off))
     opts=("--name=$dev" "--filename=$dev" "--offset=$off" "--size=$size")
-    opts+=("--bs=$bs" "--ioengine=psync" "--rw=randread" "--direct=1")
+    opts+=("--bs=$bs" "$(ioengine "psync")" "--rw=randread" "--direct=1")
     opts+=("--thread=1" "--time_based" "--runtime=30" "--zonemode=zbd")
     opts+=("--zonesize=${zone_size}")
     run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $?
@@ -571,7 +587,7 @@ test32() {
     off=$((first_sequential_zone_sector * 512))
     size=$((disk_size - off))
     opts+=("--name=$dev" "--filename=$dev" "--offset=$off" "--size=$size")
-    opts+=("--bs=128K" "--ioengine=psync" "--rw=randwrite" "--direct=1")
+    opts+=("--bs=128K" "$(ioengine "psync")" "--rw=randwrite" "--direct=1")
     opts+=("--thread=1" "--time_based" "--runtime=30")
     opts+=("--max_open_zones=$max_open_zones" "--zonemode=zbd")
     opts+=("--zonesize=${zone_size}")
@@ -586,8 +602,8 @@ test33() {
     size=$((2 * zone_size))
     io_size=$((5 * zone_size))
     bs=$((3 * zone_size / 4))
-    run_fio_on_seq --ioengine=psync --iodepth=1 --rw=write --size=$size        \
-                  --io_size=$io_size --bs=$bs                          \
+    run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=write        \
+                  --size=$size --io_size=$io_size --bs=$bs     \
                   >> "${logfile}.${test_number}" 2>&1 || return $?
     check_written $(((io_size + bs - 1) / bs * bs)) || return $?
 }
@@ -598,7 +614,7 @@ test34() {
     local size
 
     size=$((2 * zone_size))
-    run_fio_on_seq --ioengine=psync --iodepth=1 --rw=write --size=$size          \
+    run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=write --size=$size \
                   --do_verify=1 --verify=md5 --bs=$((3 * zone_size / 4)) \
                   >> "${logfile}.${test_number}" 2>&1 && return 1
     grep -q 'not a divisor of' "${logfile}.${test_number}"
@@ -611,9 +627,9 @@ test35() {
     off=$(((first_sequential_zone_sector + 1) * 512))
     size=$((zone_size - 2 * 512))
     bs=$((zone_size / 4))
-    run_one_fio_job --offset=$off --size=$size --ioengine=psync        --iodepth=1 \
-                   --rw=write --do_verify=1 --verify=md5 --bs=$bs          \
-                   --zonemode=zbd --zonesize="${zone_size}"                \
+    run_one_fio_job --offset=$off --size=$size "$(ioengine "psync")"   \
+                   --iodepth=1 --rw=write --do_verify=1 --verify=md5   \
+                   --bs=$bs --zonemode=zbd --zonesize="${zone_size}"   \
                    >> "${logfile}.${test_number}" 2>&1 && return 1
     grep -q 'io_size must be at least one zone' "${logfile}.${test_number}"
 }
@@ -625,9 +641,9 @@ test36() {
     off=$(((first_sequential_zone_sector) * 512))
     size=$((zone_size - 512))
     bs=$((zone_size / 4))
-    run_one_fio_job --offset=$off --size=$size --ioengine=psync        --iodepth=1 \
-                   --rw=write --do_verify=1 --verify=md5 --bs=$bs          \
-                   --zonemode=zbd --zonesize="${zone_size}"                \
+    run_one_fio_job --offset=$off --size=$size "$(ioengine "psync")"   \
+                   --iodepth=1 --rw=write --do_verify=1 --verify=md5   \
+                   --bs=$bs --zonemode=zbd --zonesize="${zone_size}"   \
                    >> "${logfile}.${test_number}" 2>&1 && return 1
     grep -q 'io_size must be at least one zone' "${logfile}.${test_number}"
 }
@@ -643,9 +659,9 @@ test37() {
     fi
     size=$((zone_size + 2 * 512))
     bs=$((zone_size / 4))
-    run_one_fio_job --offset=$off --size=$size --ioengine=psync        --iodepth=1 \
-                   --rw=write --do_verify=1 --verify=md5 --bs=$bs          \
-                   --zonemode=zbd --zonesize="${zone_size}"                \
+    run_one_fio_job --offset=$off --size=$size "$(ioengine "psync")"   \
+                   --iodepth=1 --rw=write --do_verify=1 --verify=md5   \
+                   --bs=$bs --zonemode=zbd --zonesize="${zone_size}"   \
                    >> "${logfile}.${test_number}" 2>&1
     check_written $((zone_size)) || return $?
 }
@@ -657,9 +673,9 @@ test38() {
     size=$((logical_block_size))
     off=$((disk_size - logical_block_size))
     bs=$((logical_block_size))
-    run_one_fio_job --offset=$off --size=$size --ioengine=psync        --iodepth=1 \
-                   --rw=write --do_verify=1 --verify=md5 --bs=$bs          \
-                   --zonemode=zbd --zonesize="${zone_size}"                \
+    run_one_fio_job --offset=$off --size=$size "$(ioengine "psync")"   \
+                   --iodepth=1 --rw=write --do_verify=1 --verify=md5   \
+                   --bs=$bs --zonemode=zbd --zonesize="${zone_size}"   \
                    >> "${logfile}.${test_number}" 2>&1 && return 1
     grep -q 'io_size must be at least one zone' "${logfile}.${test_number}"
 }
@@ -669,7 +685,7 @@ read_one_block() {
     local bs
 
     bs=$((logical_block_size))
-    run_one_fio_job --rw=read --ioengine=psync --bs=$bs --size=$bs "$@" 2>&1 |
+    run_one_fio_job --rw=read "$(ioengine "psync")" --bs=$bs --size=$bs "$@" 2>&1 |
        tee -a "${logfile}.${test_number}"
 }
 
@@ -725,7 +741,7 @@ test45() {
 
     [ -z "$is_zbd" ] && return 0
     bs=$((logical_block_size))
-    run_one_fio_job --ioengine=psync --iodepth=1 --rw=randwrite --bs=$bs\
+    run_one_fio_job "$(ioengine "psync")" --iodepth=1 --rw=randwrite --bs=$bs\
                    --offset=$((first_sequential_zone_sector * 512)) \
                    --size="$zone_size" --do_verify=1 --verify=md5 2>&1 |
        tee -a "${logfile}.${test_number}" |
@@ -737,7 +753,7 @@ test46() {
     local size
 
     size=$((4 * zone_size))
-    run_fio_on_seq --ioengine=libaio --iodepth=64 --rw=randwrite --bs=4K \
+    run_fio_on_seq "$(ioengine "libaio")" --iodepth=64 --rw=randwrite --bs=4K \
                   --group_reporting=1 --numjobs=8 \
                   >> "${logfile}.${test_number}" 2>&1 || return $?
     check_written $((size * 8)) || return $?
@@ -749,15 +765,47 @@ test47() {
 
     [ -z "$is_zbd" ] && return 0
     bs=$((logical_block_size))
-    run_one_fio_job --ioengine=psync --rw=write --bs=$bs \
+    run_one_fio_job "$(ioengine "psync")" --rw=write --bs=$bs \
                    --zonemode=zbd --zoneskip=1          \
                    >> "${logfile}.${test_number}" 2>&1 && return 1
     grep -q 'zoneskip 1 is not a multiple of the device zone size' "${logfile}.${test_number}"
 }
 
+# Multiple overlapping random write jobs for the same drive and with a
+# limited number of open zones. This is similar to test29, but uses libaio
+# to stress test zone locking.
+test48() {
+    local i jobs=16 off opts=()
+
+    off=$((first_sequential_zone_sector * 512 + 64 * zone_size))
+    size=$((16*zone_size))
+    [ -n "$is_zbd" ] && reset_zone "$dev" $((off / 512))
+    opts=("--aux-path=/tmp" "--allow_file_create=0" "--significant_figures=10")
+    opts+=("--debug=zbd")
+    opts+=("$(ioengine "libaio")" "--rw=randwrite" "--direct=1")
+    opts+=("--time_based" "--runtime=30")
+    opts+=("--zonemode=zbd" "--zonesize=${zone_size}")
+    opts+=("--max_open_zones=4")
+    for ((i=0;i<jobs;i++)); do
+       opts+=("--name=job$i" "--filename=$dev" "--offset=$off" "--bs=16K")
+       opts+=("--io_size=$zone_size" "--iodepth=256" "--thread=1")
+       opts+=("--group_reporting=1")
+    done
+
+    fio=$(dirname "$0")/../../fio
+
+    { echo; echo "fio ${opts[*]}"; echo; } >>"${logfile}.${test_number}"
+
+    timeout -v -s KILL 45s \
+           "${dynamic_analyzer[@]}" "$fio" "${opts[@]}" \
+           >> "${logfile}.${test_number}" 2>&1 || return $?
+}
+
 tests=()
 dynamic_analyzer=()
 reset_all_zones=
+use_libzbc=
+zbd_debug=
 
 while [ "${1#-}" != "$1" ]; do
   case "$1" in
@@ -766,10 +814,12 @@ while [ "${1#-}" != "$1" ]; do
        shift;;
     -e) dynamic_analyzer=(valgrind "--read-var-info=yes" "--tool=helgrind");
        shift;;
+    -l) use_libzbc=1; shift;;
     -r) reset_all_zones=1; shift;;
     -t) tests+=("$2"); shift; shift;;
     -v) dynamic_analyzer=(valgrind "--read-var-info=yes");
        shift;;
+    -z) zbd_debug=1; shift;;
     --) shift; break;;
   esac
 done
@@ -782,48 +832,93 @@ fi
 # shellcheck source=functions
 source "$(dirname "$0")/functions" || exit $?
 
+var_opts=()
+if [ -n "$zbd_debug" ]; then
+    var_opts+=("--debug=zbd")
+fi
 dev=$1
 realdev=$(readlink -f "$dev")
 basename=$(basename "$realdev")
-major=$((0x$(stat -L -c '%t' "$realdev"))) || exit $?
-minor=$((0x$(stat -L -c '%T' "$realdev"))) || exit $?
-disk_size=$(($(<"/sys/dev/block/$major:$minor/size")*512))
-# When the target is a partition device, get basename of its holder device to
-# access sysfs path of the holder device
-if [[ -r "/sys/dev/block/$major:$minor/partition" ]]; then
-       realsysfs=$(readlink "/sys/dev/block/$major:$minor")
-       basename=$(basename "${realsysfs%/*}")
-fi
-logical_block_size=$(<"/sys/block/$basename/queue/logical_block_size")
-case "$(<"/sys/class/block/$basename/queue/zoned")" in
-    host-managed|host-aware)
+
+if [[ -b "$realdev" ]]; then
+       major=$((0x$(stat -L -c '%t' "$realdev"))) || exit $?
+       minor=$((0x$(stat -L -c '%T' "$realdev"))) || exit $?
+       disk_size=$(($(<"/sys/dev/block/$major:$minor/size")*512))
+
+       # When the target is a partition device, get basename of its
+       # holder device to access sysfs path of the holder device
+       if [[ -r "/sys/dev/block/$major:$minor/partition" ]]; then
+               realsysfs=$(readlink "/sys/dev/block/$major:$minor")
+               basename=$(basename "${realsysfs%/*}")
+       fi
+       logical_block_size=$(<"/sys/block/$basename/queue/logical_block_size")
+       case "$(<"/sys/class/block/$basename/queue/zoned")" in
+       host-managed|host-aware)
+               is_zbd=true
+               if ! result=($(first_sequential_zone "$dev")); then
+                       echo "Failed to determine first sequential zone"
+                       exit 1
+               fi
+               first_sequential_zone_sector=${result[0]}
+               sectors_per_zone=${result[1]}
+               zone_size=$((sectors_per_zone * 512))
+               if ! max_open_zones=$(max_open_zones "$dev"); then
+                       echo "Failed to determine maximum number of open zones"
+                       exit 1
+               fi
+               set_io_scheduler "$basename" deadline || exit $?
+               if [ -n "$reset_all_zones" ]; then
+                       reset_zone "$dev" -1
+               fi
+               ;;
+       *)
+               first_sequential_zone_sector=$(((disk_size / 2) &
+                                               (logical_block_size - 1)))
+               zone_size=$(max 65536 "$logical_block_size")
+               sectors_per_zone=$((zone_size / 512))
+               max_open_zones=128
+               set_io_scheduler "$basename" none || exit $?
+               ;;
+       esac
+elif [[ -c "$realdev" ]]; then
+       # For an SG node, we must have libzbc option specified
+       if [[ ! -n "$use_libzbc" ]]; then
+               echo "Character device files can only be used with -l (libzbc) option"
+               exit 1
+       fi
+
+       if ! $(is_zbc "$dev"); then
+               echo "Device is not a ZBC disk"
+               exit 1
+       fi
        is_zbd=true
+
+       if ! disk_size=($(( $(zbc_disk_sectors "$dev") * 512))); then
+               echo "Failed to determine disk size"
+               exit 1
+       fi
+       if ! logical_block_size=($(zbc_logical_block_size "$dev")); then
+               echo "Failed to determine logical block size"
+               exit 1
+       fi
        if ! result=($(first_sequential_zone "$dev")); then
-           echo "Failed to determine first sequential zone"
-           exit 1
+               echo "Failed to determine first sequential zone"
+               exit 1
        fi
        first_sequential_zone_sector=${result[0]}
        sectors_per_zone=${result[1]}
        zone_size=$((sectors_per_zone * 512))
        if ! max_open_zones=$(max_open_zones "$dev"); then
-           echo "Failed to determine maximum number of open zones"
-           exit 1
+               echo "Failed to determine maximum number of open zones"
+               exit 1
        fi
-       echo "First sequential zone starts at sector $first_sequential_zone_sector; zone size: $((zone_size >> 20)) MB"
-       set_io_scheduler "$basename" deadline || exit $?
        if [ -n "$reset_all_zones" ]; then
-           reset_zone "$dev" -1
+               reset_zone "$dev" -1
        fi
-       ;;
-    *)
-       first_sequential_zone_sector=$(((disk_size / 2) &
-                                       (logical_block_size - 1)))
-       zone_size=$(max 65536 "$logical_block_size")
-       sectors_per_zone=$((zone_size / 512))
-       max_open_zones=128
-       set_io_scheduler "$basename" none || exit $?
-       ;;
-esac
+fi
+
+echo -n "First sequential zone starts at sector $first_sequential_zone_sector;"
+echo " zone size: $((zone_size >> 20)) MB"
 
 if [ "${#tests[@]}" = 0 ]; then
     readarray -t tests < <(declare -F | grep "test[0-9]*" | \
index 78a007e..9544ab7 100755 (executable)
@@ -1,76 +1,97 @@
 #!/usr/bin/python2.7
 # Note: this script is python2 and python3 compatible.
-#
-# fio_jsonplus_clat2csv
-#
-# This script converts fio's json+ completion latency data to CSV format.
-#
-# For example:
-#
-# Run the following fio jobs:
-# ../fio --output=fio-jsonplus.output --output-format=json+ --name=test1
-#      --ioengine=null --time_based --runtime=5s --size=1G --rw=randrw
-#      --name=test2 --ioengine=null --time_based --runtime=3s --size=1G
-#      --rw=read --name=test3 --ioengine=null --time_based --runtime=4s
-#      --size=8G --rw=write
-#
-# Then run:
-# fio_jsonplus_clat2csv fio-jsonplus.output fio-latency.csv
-#
-# You will end up with the following 3 files
-#
-# -rw-r--r-- 1 root root  6467 Jun 27 14:57 fio-latency_job0.csv
-# -rw-r--r-- 1 root root  3985 Jun 27 14:57 fio-latency_job1.csv
-# -rw-r--r-- 1 root root  4490 Jun 27 14:57 fio-latency_job2.csv
-#
-# fio-latency_job0.csv will look something like:
-#
-# clat_nsec, read_count, read_cumulative, read_percentile, write_count,
-#      write_cumulative, write_percentile, trim_count, trim_cumulative,
-#      trim_percentile,
-# 25, 1, 1, 1.50870705013e-07, , , , , , ,
-# 26, 12, 13, 1.96131916517e-06, 947, 947, 0.000142955890032, , , ,
-# 27, 843677, 843690, 0.127288105112, 838347, 839294, 0.126696959629, , , ,
-# 28, 1877982, 2721672, 0.410620573454, 1870189, 2709483, 0.409014312345, , , ,
-# 29, 4471, 2726143, 0.411295116376, 7718, 2717201, 0.410179395301, , , ,
-# 30, 2142885, 4869028, 0.734593687087, 2138164, 4855365, 0.732949340025, , , ,
-# ...
-# 2544, , , , 2, 6624404, 0.999997433738, , , ,
-# 2576, 3, 6628178, 0.99999788781, 4, 6624408, 0.999998037564, , , ,
-# 2608, 4, 6628182, 0.999998491293, 4, 6624412, 0.999998641391, , , ,
-# 2640, 3, 6628185, 0.999998943905, 2, 6624414, 0.999998943304, , , ,
-# 2672, 1, 6628186, 0.999999094776, 3, 6624417, 0.999999396174, , , ,
-# 2736, 1, 6628187, 0.999999245646, 1, 6624418, 0.99999954713, , , ,
-# 2768, 2, 6628189, 0.999999547388, 1, 6624419, 0.999999698087, , , ,
-# 2800, , , , 1, 6624420, 0.999999849043, , , ,
-# 2832, 1, 6628190, 0.999999698259, , , , , , ,
-# 4192, 1, 6628191, 0.999999849129, , , , , , ,
-# 5792, , , , 1, 6624421, 1.0, , , ,
-# 10304, 1, 6628192, 1.0, , , , , , ,
-#
-# The first line says that you had one read IO with 25ns clat,
-# the cumulative number of read IOs at or below 25ns is 1, and
-# 25ns is the 0.00001509th percentile for read latency
-#
-# The job had 2 write IOs complete in 2544ns,
-# 6624404 write IOs completed in 2544ns or less,
-# and this represents the 99.99974th percentile for write latency
-#
-# The last line says that one read IO had 10304ns clat,
-# 6628192 read IOs had 10304ns or shorter clat, and
-# 10304ns is the 100th percentile for read latency
-#
+
+"""
+fio_jsonplus_clat2csv
+
+This script converts fio's json+ latency data to CSV format.
+
+For example:
+
+Run the following fio jobs:
+$ fio --output=fio-jsonplus.output --output-format=json+ --ioengine=null \
+    --time_based --runtime=3s --size=1G --slat_percentiles=1 \
+    --clat_percentiles=1 --lat_percentiles=1 \
+    --name=test1 --rw=randrw \
+    --name=test2 --rw=read \
+    --name=test3 --rw=write
+
+Then run:
+$ fio_jsonplus_clat2csv fio-jsonplus.output fio-jsonplus.csv
+
+You will end up with the following 3 files:
+
+-rw-r--r-- 1 root root 77547 Mar 24 15:17 fio-jsonplus_job0.csv
+-rw-r--r-- 1 root root 65413 Mar 24 15:17 fio-jsonplus_job1.csv
+-rw-r--r-- 1 root root 63291 Mar 24 15:17 fio-jsonplus_job2.csv
+
+fio-jsonplus_job0.csv will look something like:
+
+nsec, read_slat_ns_count, read_slat_ns_cumulative, read_slat_ns_percentile, read_clat_ns_count, read_clat_ns_cumulative, read_clat_ns_percentile, read_lat_ns_count, read_lat_ns_cumulative, read_lat_ns_percentile, write_slat_ns_count, write_slat_ns_cumulative, write_slat_ns_percentile, write_clat_ns_count, write_clat_ns_cumulative, write_clat_ns_percentile, write_lat_ns_count, write_lat_ns_cumulative, write_lat_ns_percentile, trim_slat_ns_count, trim_slat_ns_cumulative, trim_slat_ns_percentile, trim_clat_ns_count, trim_clat_ns_cumulative, trim_clat_ns_percentile, trim_lat_ns_count, trim_lat_ns_cumulative, trim_lat_ns_percentile,
+12, , , , 3, 3, 6.11006798673e-07, , , , , , , 2, 2, 4.07580840603e-07, , , , , , , , , , , , ,
+13, , , , 1364, 1367, 0.000278415431262, , , , , , , 1776, 1778, 0.000362339367296, , , , , , , , , , , , ,
+14, , , , 181872, 183239, 0.037320091594, , , , , , , 207436, 209214, 0.0426358089929, , , , , , , , , , , , ,
+15, , , , 1574811, 1758050, 0.358060167469, , , , , , , 1661435, 1870649, 0.381220345946, , , , , , , , , , , , ,
+16, , , , 2198478, 3956528, 0.805821835713, , , , , , , 2154571, 4025220, 0.820301275606, , , , , , , , , , , , ,
+17, , , , 724335, 4680863, 0.953346372218, , , , , , , 645351, 4670571, 0.951817627138, , , , , , , , , , , , ,
+18, , , , 71837, 4752700, 0.96797733735, , , , , , , 61084, 4731655, 0.964265961171, , , , , , , , , , , , ,
+19, , , , 15915, 4768615, 0.971218728417, , , , , , , 18419, 4750074, 0.968019576923, , , , , , , , , , , , ,
+20, , , , 12651, 4781266, 0.973795344087, , , , , , , 14176, 4764250, 0.970908509921, , , , , , , , , , , , ,
+...
+168960, , , , , , , , , , , , , 1, 4906999, 0.999999388629, 1, 4906997, 0.999998981048, , , , , , , , , ,
+177152, , , , , , , , , , , , , 1, 4907000, 0.999999592419, 1, 4906998, 0.999999184838, , , , , , , , , ,
+183296, , , , , , , , , , , , , 1, 4907001, 0.99999979621, 1, 4906999, 0.999999388629, , , , , , , , , ,
+189440, , , , , , , 1, 4909925, 0.999999185324, , , , , , , , , , , , , , , , , , ,
+214016, , , , 1, 4909928, 0.999999796331, 2, 4909927, 0.999999592662, , , , , , , , , , , , , , , , , , ,
+246784, , , , , , , , , , , , , , , , 1, 4907000, 0.999999592419, , , , , , , , , ,
+272384, , , , 1, 4909929, 1.0, 1, 4909928, 0.999999796331, , , , , , , , , , , , , , , , , , ,
+329728, , , , , , , , , , , , , 1, 4907002, 1.0, 1, 4907001, 0.99999979621, , , , , , , , , ,
+1003520, , , , , , , , , , , , , , , , 1, 4907002, 1.0, , , , , , , , , ,
+1089536, , , , , , , 1, 4909929, 1.0, , , , , , , , , , , , , , , , , , ,
+
+The first line says that there were three read IOs with 12ns clat,
+the cumulative number of read IOs at or below 12ns was two, and
+12ns was the 0.0000611th percentile for read latency. There were
+two write IOs with 12ns clat, the cumulative number of write IOs
+at or below 12ns was two, and 12ns was the 0.0000408th percentile
+for write latency.
+
+The job had one write IO complete at 168960ns and 4906999 write IOs
+completed at or below this duration. Also this duration was the
+99.99994th percentile for write latency. There was one write IO
+with a total latency of 168960ns, this duration had a cumulative
+frequency of 4906997 write IOs and was the 99.9998981048th percentile
+for write total latency.
+
+The last line says that one read IO had 1089536ns total latency, this
+duration had a cumulative frequency of 4909929 and represented the 100th
+percentile for read total latency.
+
+Running the following:
+
+$ fio_jsonplus_clat2csv fio-jsonplus.output fio-jsonplus.csv --validate
+fio-jsonplus_job0.csv validated
+fio-jsonplus_job1.csv validated
+fio-jsonplus_job2.csv validated
+
+will check the CSV data against the json+ output to confirm that the CSV
+data matches.
+"""
 
 from __future__ import absolute_import
 from __future__ import print_function
 import os
 import json
 import argparse
+import itertools
 import six
-from six.moves import range
 
+DDIR_LIST = ['read', 'write', 'trim']
+LAT_LIST = ['slat_ns', 'clat_ns', 'lat_ns']
 
 def parse_args():
+    """Parse command-line arguments."""
+
     parser = argparse.ArgumentParser()
     parser.add_argument('source',
                         help='fio json+ output file containing completion '
@@ -78,12 +99,26 @@ def parse_args():
     parser.add_argument('dest',
                         help='destination file stub for latency data in CSV '
                              'format. job number will be appended to filename')
+    parser.add_argument('--debug', '-d', action='store_true',
+                        help='enable debug prints')
+    parser.add_argument('--validate', action='store_true',
+                        help='validate CSV against JSON output')
     args = parser.parse_args()
 
     return args
 
 
 def percentile(idx, run_total):
+    """Return a percentile for a specified index based on a running total.
+
+    Parameters:
+        idx         index for which to generate percentile.
+        run_total   list of cumulative sums.
+
+    Returns:
+        Percentile represented by the specified index.
+    """
+
     total = run_total[len(run_total)-1]
     if total == 0:
         return 0
@@ -91,7 +126,18 @@ def percentile(idx, run_total):
     return float(run_total[idx]) / total
 
 
-def more_lines(indices, bins):
+def more_bins(indices, bins):
+    """Determine whether we have more bins to process.
+
+    Parameters:
+        indices     a dict containing the last index processed in each bin.
+        bins        a dict contaiing a set of bins to process.
+
+    Returns:
+        True if the indices do not yet point to the end of each bin in bins.
+        False if the indices point beyond their repsective bins.
+    """
+
     for key, value in six.iteritems(indices):
         if value < len(bins[key]):
             return True
@@ -99,78 +145,187 @@ def more_lines(indices, bins):
     return False
 
 
+def debug_print(debug, *args):
+    """Print debug messages.
+
+    Parameters:
+        debug       emit messages if True.
+        *args       arguments for print().
+    """
+
+    if debug:
+        print(*args)
+
+
+def get_csvfile(dest, jobnum):
+    """Generate CSV filename from command-line arguments and job numbers.
+
+    Paramaters:
+        dest        file specification for CSV filename.
+        jobnum      job number.
+
+    Returns:
+        A string that is a new filename that incorporates the job number.
+    """
+
+    stub, ext = os.path.splitext(dest)
+    return stub + '_job' + str(jobnum) + ext
+
+
+def validate(args, jsondata, col_labels):
+    """Validate CSV data against json+ output.
+
+    This function checks the CSV data to make sure that it was correctly
+    generated from the original json+ output. json+ 'bins' objects are
+    constructed from the CSV data and then compared to the corresponding
+    objects in the json+ data. An AssertionError will appear if a mismatch
+    is found.
+
+    Percentiles and cumulative counts are not checked.
+
+    Parameters:
+        args        command-line arguments for this script.
+        jsondata    json+ output to compare against.
+        col_labels  column labels for CSV data.
+
+    Returns
+        0 if no mismatches found.
+    """
+
+    colnames = [c.strip() for c in col_labels.split(',')]
+
+    for jobnum in range(len(jsondata['jobs'])):
+        job_data = jsondata['jobs'][jobnum]
+        csvfile = get_csvfile(args.dest, jobnum)
+
+        with open(csvfile, 'r') as csvsource:
+            csvlines = csvsource.read().split('\n')
+
+        assert csvlines[0] == col_labels
+        debug_print(args.debug, 'col_labels match for', csvfile)
+
+        # create 'bins' objects from the CSV data
+        counts = {}
+        for ddir in DDIR_LIST:
+            counts[ddir] = {}
+            for lat in LAT_LIST:
+                counts[ddir][lat] = {}
+
+        csvlines.pop(0)
+        for line in csvlines:
+            if line.strip() == "":
+                continue
+            values = line.split(',')
+            nsec = values[0]
+            for col in colnames:
+                if 'count' in col:
+                    val = values[colnames.index(col)]
+                    if val.strip() != "":
+                        count = int(val)
+                        ddir, lat, _, _ = col.split('_')
+                        lat = lat + '_ns'
+                        counts[ddir][lat][nsec] = count
+                        try:
+                            assert count == job_data[ddir][lat]['bins'][nsec]
+                        except Exception:
+                            print("mismatch:", csvfile, ddir, lat, nsec, "ns")
+                            return 1
+
+        # compare 'bins' objects created from the CSV data
+        # with corresponding 'bins' objects in the json+ output
+        for ddir in DDIR_LIST:
+            for lat in LAT_LIST:
+                if lat in job_data[ddir] and 'bins' in job_data[ddir][lat]:
+                    assert job_data[ddir][lat]['bins'] == counts[ddir][lat]
+                    debug_print(args.debug, csvfile, ddir, lat, "bins match")
+                else:
+                    assert counts[ddir][lat] == {}
+                    debug_print(args.debug, csvfile, ddir, lat, "bins empty")
+
+        print(csvfile, "validated")
+
+    return 0
+
+
 def main():
+    """Starting point for this script.
+
+    In standard mode, this script will generate CSV data from fio json+ output.
+    In validation mode it will check to make sure that counts in CSV files
+    match the counts in the json+ data.
+    """
+
     args = parse_args()
 
     with open(args.source, 'r') as source:
         jsondata = json.loads(source.read())
 
+    ddir_lat_list = list(ddir + '_' + lat for ddir, lat in itertools.product(DDIR_LIST, LAT_LIST))
+    debug_print(args.debug, 'ddir_lat_list: ', ddir_lat_list)
+    col_labels = 'nsec, '
+    for ddir_lat in ddir_lat_list:
+        col_labels += "{0}_count, {0}_cumulative, {0}_percentile, ".format(ddir_lat)
+    debug_print(args.debug, 'col_labels: ', col_labels)
+
+    if args.validate:
+        return validate(args, jsondata, col_labels)
+
     for jobnum in range(0, len(jsondata['jobs'])):
         bins = {}
         run_total = {}
-        ddir_set = set(['read', 'write', 'trim'])
-
-        prev_ddir = None
-        for ddir in ddir_set:
-            if 'bins' in jsondata['jobs'][jobnum][ddir]['clat_ns']:
-                bins_loc = 'clat_ns'
-            elif 'bins' in jsondata['jobs'][jobnum][ddir]['lat_ns']:
-                bins_loc = 'lat_ns'
-            else:
-                raise RuntimeError("Latency bins not found. "
-                                   "Are you sure you are using json+ output?")
-
-            bins[ddir] = [[int(key), value] for key, value in
-                          six.iteritems(jsondata['jobs'][jobnum][ddir][bins_loc]
-                          ['bins'])]
-            bins[ddir] = sorted(bins[ddir], key=lambda bin: bin[0])
-
-            run_total[ddir] = [0 for x in range(0, len(bins[ddir]))]
-            if len(bins[ddir]) > 0:
-                run_total[ddir][0] = bins[ddir][0][1]
-                for x in range(1, len(bins[ddir])):
-                    run_total[ddir][x] = run_total[ddir][x-1] + \
-                        bins[ddir][x][1]
-
-        stub, ext = os.path.splitext(args.dest)
-        outfile = stub + '_job' + str(jobnum) + ext
-
-        with open(outfile, 'w') as output:
-            output.write("{0}ec, ".format(bins_loc))
-            ddir_list = list(ddir_set)
-            for ddir in ddir_list:
-                output.write("{0}_count, {0}_cumulative, {0}_percentile, ".
-                             format(ddir))
-            output.write("\n")
+
+        for ddir in DDIR_LIST:
+            ddir_data = jsondata['jobs'][jobnum][ddir]
+            for lat in LAT_LIST:
+                ddir_lat = ddir + '_' + lat
+                if lat not in ddir_data or 'bins' not in ddir_data[lat]:
+                    bins[ddir_lat] = []
+                    debug_print(args.debug, 'job', jobnum, ddir_lat, 'not found')
+                    continue
+
+                debug_print(args.debug, 'job', jobnum, ddir_lat, 'processing')
+                bins[ddir_lat] = [[int(key), value] for key, value in
+                                  six.iteritems(ddir_data[lat]['bins'])]
+                bins[ddir_lat] = sorted(bins[ddir_lat], key=lambda bin: bin[0])
+
+                run_total[ddir_lat] = [0 for x in range(0, len(bins[ddir_lat]))]
+                run_total[ddir_lat][0] = bins[ddir_lat][0][1]
+                for index in range(1, len(bins[ddir_lat])):
+                    run_total[ddir_lat][index] = run_total[ddir_lat][index-1] + \
+                        bins[ddir_lat][index][1]
+
+        csvfile = get_csvfile(args.dest, jobnum)
+        with open(csvfile, 'w') as output:
+            output.write(col_labels + "\n")
 
 #
-# Have a counter for each ddir
+# Have a counter for each ddir_lat pairing
 # In each round, pick the shortest remaining duration
 # and output a line with any values for that duration
 #
-            indices = {x: 0 for x in ddir_list}
-            while more_lines(indices, bins):
+            indices = {x: 0 for x in ddir_lat_list}
+            while more_bins(indices, bins):
+                debug_print(args.debug, 'indices: ', indices)
                 min_lat = 17112760320
-                for ddir in ddir_list:
-                    if indices[ddir] < len(bins[ddir]):
-                        min_lat = min(bins[ddir][indices[ddir]][0], min_lat)
+                for ddir_lat in ddir_lat_list:
+                    if indices[ddir_lat] < len(bins[ddir_lat]):
+                        min_lat = min(bins[ddir_lat][indices[ddir_lat]][0], min_lat)
 
                 output.write("{0}, ".format(min_lat))
 
-                for ddir in ddir_list:
-                    if indices[ddir] < len(bins[ddir]) and \
-                       min_lat == bins[ddir][indices[ddir]][0]:
-                        count = bins[ddir][indices[ddir]][1]
-                        cumulative = run_total[ddir][indices[ddir]]
-                        ptile = percentile(indices[ddir], run_total[ddir])
-                        output.write("{0}, {1}, {2}, ".format(count,
-                                     cumulative, ptile))
-                        indices[ddir] += 1
+                for ddir_lat in ddir_lat_list:
+                    if indices[ddir_lat] < len(bins[ddir_lat]) and \
+                       min_lat == bins[ddir_lat][indices[ddir_lat]][0]:
+                        count = bins[ddir_lat][indices[ddir_lat]][1]
+                        cumulative = run_total[ddir_lat][indices[ddir_lat]]
+                        ptile = percentile(indices[ddir_lat], run_total[ddir_lat])
+                        output.write("{0}, {1}, {2}, ".format(count, cumulative, ptile))
+                        indices[ddir_lat] += 1
                     else:
                         output.write(", , , ")
                 output.write("\n")
 
-            print("{0} generated".format(outfile))
+            print("{0} generated".format(csvfile))
 
 
 if __name__ == '__main__':
index 286d814..8518bbc 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/bash
+#!/bin/bash
 #
 #  Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
 #  Author: Erwan Velu  <erwan@enovance.com>
diff --git a/zbd.c b/zbd.c
index ee8bcb3..f406780 100644 (file)
--- a/zbd.c
+++ b/zbd.c
@@ -7,12 +7,9 @@
 #include <errno.h>
 #include <string.h>
 #include <stdlib.h>
-#include <dirent.h>
 #include <fcntl.h>
-#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <linux/blkzoned.h>
 
 #include "file.h"
 #include "fio.h"
 #include "verify.h"
 #include "zbd.h"
 
+/**
+ * zbd_get_zoned_model - Get a device zoned model
+ * @td: FIO thread data
+ * @f: FIO file for which to get model information
+ */
+int zbd_get_zoned_model(struct thread_data *td, struct fio_file *f,
+                       enum zbd_zoned_model *model)
+{
+       int ret;
+
+       if (td->io_ops && td->io_ops->get_zoned_model)
+               ret = td->io_ops->get_zoned_model(td, f, model);
+       else
+               ret = blkzoned_get_zoned_model(td, f, model);
+       if (ret < 0) {
+               td_verror(td, errno, "get zoned model failed");
+               log_err("%s: get zoned model failed (%d).\n",
+                       f->file_name, errno);
+       }
+
+       return ret;
+}
+
+/**
+ * zbd_report_zones - Get zone information
+ * @td: FIO thread data.
+ * @f: FIO file for which to get zone information
+ * @offset: offset from which to report zones
+ * @zones: Array of struct zbd_zone
+ * @nr_zones: Size of @zones array
+ *
+ * Get zone information into @zones starting from the zone at offset @offset
+ * for the device specified by @f.
+ *
+ * Returns the number of zones reported upon success and a negative error code
+ * upon failure. If the zone report is empty, always assume an error (device
+ * problem) and return -EIO.
+ */
+int zbd_report_zones(struct thread_data *td, struct fio_file *f,
+                    uint64_t offset, struct zbd_zone *zones,
+                    unsigned int nr_zones)
+{
+       int ret;
+
+       if (td->io_ops && td->io_ops->report_zones)
+               ret = td->io_ops->report_zones(td, f, offset, zones, nr_zones);
+       else
+               ret = blkzoned_report_zones(td, f, offset, zones, nr_zones);
+       if (ret < 0) {
+               td_verror(td, errno, "report zones failed");
+               log_err("%s: report zones from sector %llu failed (%d).\n",
+                       f->file_name, (unsigned long long)offset >> 9, errno);
+       } else if (ret == 0) {
+               td_verror(td, errno, "Empty zone report");
+               log_err("%s: report zones from sector %llu is empty.\n",
+                       f->file_name, (unsigned long long)offset >> 9);
+               ret = -EIO;
+       }
+
+       return ret;
+}
+
+/**
+ * zbd_reset_wp - reset the write pointer of a range of zones
+ * @td: FIO thread data.
+ * @f: FIO file for which to reset zones
+ * @offset: Starting offset of the first zone to reset
+ * @length: Length of the range of zones to reset
+ *
+ * Reset the write pointer of all zones in the range @offset...@offset+@length.
+ * Returns 0 upon success and a negative error code upon failure.
+ */
+int zbd_reset_wp(struct thread_data *td, struct fio_file *f,
+                uint64_t offset, uint64_t length)
+{
+       int ret;
+
+       if (td->io_ops && td->io_ops->reset_wp)
+               ret = td->io_ops->reset_wp(td, f, offset, length);
+       else
+               ret = blkzoned_reset_wp(td, f, offset, length);
+       if (ret < 0) {
+               td_verror(td, errno, "resetting wp failed");
+               log_err("%s: resetting wp for %llu sectors at sector %llu failed (%d).\n",
+                       f->file_name, (unsigned long long)length >> 9,
+                       (unsigned long long)offset >> 9, errno);
+       }
+
+       return ret;
+}
+
 /**
  * zbd_zone_idx - convert an offset into a zone number
  * @f: file pointer.
@@ -41,6 +129,15 @@ static uint32_t zbd_zone_idx(const struct fio_file *f, uint64_t offset)
        return min(zone_idx, f->zbd_info->nr_zones);
 }
 
+/**
+ * zbd_zone_swr - Test whether a zone requires sequential writes
+ * @z: zone info pointer.
+ */
+static inline bool zbd_zone_swr(struct fio_zone_info *z)
+{
+       return z->type == ZBD_ZONE_TYPE_SWR;
+}
+
 /**
  * zbd_zone_full - verify whether a minimum number of bytes remain in a zone
  * @f: file pointer.
@@ -54,10 +151,28 @@ static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z,
 {
        assert((required & 511) == 0);
 
-       return z->type == BLK_ZONE_TYPE_SEQWRITE_REQ &&
+       return zbd_zone_swr(z) &&
                z->wp + required > z->start + f->zbd_info->zone_size;
 }
 
+static void zone_lock(struct thread_data *td, struct fio_zone_info *z)
+{
+       /*
+        * Lock the io_u target zone. The zone will be unlocked if io_u offset
+        * is changed or when io_u completes and zbd_put_io() executed.
+        * To avoid multiple jobs doing asynchronous I/Os from deadlocking each
+        * other waiting for zone locks when building an io_u batch, first
+        * only trylock the zone. If the zone is already locked by another job,
+        * process the currently queued I/Os so that I/O progress is made and
+        * zones unlocked.
+        */
+       if (pthread_mutex_trylock(&z->mutex) != 0) {
+               if (!td_ioengine_flagged(td, FIO_SYNCIO))
+                       io_u_quiesce(td);
+               pthread_mutex_lock(&z->mutex);
+       }
+}
+
 static bool is_valid_offset(const struct fio_file *f, uint64_t offset)
 {
        return (uint64_t)(offset - f->file_offset) < f->io_size;
@@ -75,7 +190,7 @@ static bool zbd_using_direct_io(void)
                        continue;
                for_each_file(td, f, j) {
                        if (f->zbd_info &&
-                           f->zbd_info->model == ZBD_DM_HOST_MANAGED)
+                           f->zbd_info->model == ZBD_HOST_MANAGED)
                                return false;
                }
        }
@@ -94,8 +209,7 @@ static bool zbd_is_seq_job(struct fio_file *f)
        zone_idx_b = zbd_zone_idx(f, f->file_offset);
        zone_idx_e = zbd_zone_idx(f, f->file_offset + f->io_size - 1);
        for (zone_idx = zone_idx_b; zone_idx <= zone_idx_e; zone_idx++)
-               if (f->zbd_info->zone_info[zone_idx].type ==
-                   BLK_ZONE_TYPE_SEQWRITE_REQ)
+               if (zbd_zone_swr(&f->zbd_info->zone_info[zone_idx]))
                        return true;
 
        return false;
@@ -206,119 +320,6 @@ static bool zbd_verify_bs(void)
        return true;
 }
 
-/*
- * Read zone information into @buf starting from sector @start_sector.
- * @fd is a file descriptor that refers to a block device and @bufsz is the
- * size of @buf.
- *
- * Returns 0 upon success and a negative error code upon failure.
- * If the zone report is empty, always assume an error (device problem) and
- * return -EIO.
- */
-static int read_zone_info(int fd, uint64_t start_sector,
-                         void *buf, unsigned int bufsz)
-{
-       struct blk_zone_report *hdr = buf;
-       int ret;
-
-       if (bufsz < sizeof(*hdr))
-               return -EINVAL;
-
-       memset(hdr, 0, sizeof(*hdr));
-
-       hdr->nr_zones = (bufsz - sizeof(*hdr)) / sizeof(struct blk_zone);
-       hdr->sector = start_sector;
-       ret = ioctl(fd, BLKREPORTZONE, hdr);
-       if (ret)
-               return -errno;
-       if (!hdr->nr_zones)
-               return -EIO;
-       return 0;
-}
-
-/*
- * Read up to 255 characters from the first line of a file. Strip the trailing
- * newline.
- */
-static char *read_file(const char *path)
-{
-       char line[256], *p = line;
-       FILE *f;
-
-       f = fopen(path, "rb");
-       if (!f)
-               return NULL;
-       if (!fgets(line, sizeof(line), f))
-               line[0] = '\0';
-       strsep(&p, "\n");
-       fclose(f);
-
-       return strdup(line);
-}
-
-static enum blk_zoned_model get_zbd_model(const char *file_name)
-{
-       enum blk_zoned_model model = ZBD_DM_NONE;
-       char *zoned_attr_path = NULL;
-       char *model_str = NULL;
-       struct stat statbuf;
-       char *sys_devno_path = NULL;
-       char *part_attr_path = NULL;
-       char *part_str = NULL;
-       char sys_path[PATH_MAX];
-       ssize_t sz;
-       char *delim = NULL;
-
-       if (stat(file_name, &statbuf) < 0)
-               goto out;
-
-       if (asprintf(&sys_devno_path, "/sys/dev/block/%d:%d",
-                    major(statbuf.st_rdev), minor(statbuf.st_rdev)) < 0)
-               goto out;
-
-       sz = readlink(sys_devno_path, sys_path, sizeof(sys_path) - 1);
-       if (sz < 0)
-               goto out;
-       sys_path[sz] = '\0';
-
-       /*
-        * If the device is a partition device, cut the device name in the
-        * canonical sysfs path to obtain the sysfs path of the holder device.
-        *   e.g.:  /sys/devices/.../sda/sda1 -> /sys/devices/.../sda
-        */
-       if (asprintf(&part_attr_path, "/sys/dev/block/%s/partition",
-                    sys_path) < 0)
-               goto out;
-       part_str = read_file(part_attr_path);
-       if (part_str && *part_str == '1') {
-               delim = strrchr(sys_path, '/');
-               if (!delim)
-                       goto out;
-               *delim = '\0';
-       }
-
-       if (asprintf(&zoned_attr_path,
-                    "/sys/dev/block/%s/queue/zoned", sys_path) < 0)
-               goto out;
-
-       model_str = read_file(zoned_attr_path);
-       if (!model_str)
-               goto out;
-       dprint(FD_ZBD, "%s: zbd model string: %s\n", file_name, model_str);
-       if (strcmp(model_str, "host-aware") == 0)
-               model = ZBD_DM_HOST_AWARE;
-       else if (strcmp(model_str, "host-managed") == 0)
-               model = ZBD_DM_HOST_MANAGED;
-
-out:
-       free(model_str);
-       free(zoned_attr_path);
-       free(part_str);
-       free(part_attr_path);
-       free(sys_devno_path);
-       return model;
-}
-
 static int ilog2(uint64_t i)
 {
        int log = -1;
@@ -371,8 +372,8 @@ static int init_zone_info(struct thread_data *td, struct fio_file *f)
                pthread_mutex_init(&p->mutex, &attr);
                p->start = i * zone_size;
                p->wp = p->start + zone_size;
-               p->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
-               p->cond = BLK_ZONE_COND_EMPTY;
+               p->type = ZBD_ZONE_TYPE_SWR;
+               p->cond = ZBD_ZONE_COND_EMPTY;
        }
        /* a sentinel */
        p->start = nr_zones * zone_size;
@@ -380,58 +381,48 @@ static int init_zone_info(struct thread_data *td, struct fio_file *f)
        f->zbd_info = zbd_info;
        f->zbd_info->zone_size = zone_size;
        f->zbd_info->zone_size_log2 = is_power_of_2(zone_size) ?
-               ilog2(zone_size) : -1;
+               ilog2(zone_size) : 0;
        f->zbd_info->nr_zones = nr_zones;
        pthread_mutexattr_destroy(&attr);
        return 0;
 }
 
 /*
- * Parse the BLKREPORTZONE output and store it in f->zbd_info. Must be called
- * only for devices that support this ioctl, namely zoned block devices.
+ * Maximum number of zones to report in one operation.
+ */
+#define ZBD_REPORT_MAX_ZONES   8192U
+
+/*
+ * Parse the device zone report and store it in f->zbd_info. Must be called
+ * only for devices that are zoned, namely those with a model != ZBD_NONE.
  */
 static int parse_zone_info(struct thread_data *td, struct fio_file *f)
 {
-       const unsigned int bufsz = sizeof(struct blk_zone_report) +
-               4096 * sizeof(struct blk_zone);
-       uint32_t nr_zones;
-       struct blk_zone_report *hdr;
-       const struct blk_zone *z;
+       int nr_zones, nrz;
+       struct zbd_zone *zones, *z;
        struct fio_zone_info *p;
-       uint64_t zone_size, start_sector;
+       uint64_t zone_size, offset;
        struct zoned_block_device_info *zbd_info = NULL;
        pthread_mutexattr_t attr;
-       void *buf;
-       int fd, i, j, ret = 0;
+       int i, j, ret = 0;
 
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutexattr_setpshared(&attr, true);
 
-       buf = malloc(bufsz);
-       if (!buf)
+       zones = calloc(ZBD_REPORT_MAX_ZONES, sizeof(struct zbd_zone));
+       if (!zones)
                goto out;
 
-       fd = open(f->file_name, O_RDONLY | O_LARGEFILE);
-       if (fd < 0) {
-               ret = -errno;
-               goto free;
+       nrz = zbd_report_zones(td, f, 0, zones, ZBD_REPORT_MAX_ZONES);
+       if (nrz < 0) {
+               ret = nrz;
+               log_info("fio: report zones (offset 0) failed for %s (%d).\n",
+                        f->file_name, -ret);
+               goto out;
        }
 
-       ret = read_zone_info(fd, 0, buf, bufsz);
-       if (ret < 0) {
-               log_info("fio: BLKREPORTZONE(%lu) failed for %s (%d).\n",
-                        0UL, f->file_name, -ret);
-               goto close;
-       }
-       hdr = buf;
-       if (hdr->nr_zones < 1) {
-               log_info("fio: %s has invalid zone information.\n",
-                        f->file_name);
-               goto close;
-       }
-       z = (void *)(hdr + 1);
-       zone_size = z->len << 9;
+       zone_size = zones[0].len;
        nr_zones = (f->real_file_size + zone_size - 1) / zone_size;
 
        if (td->o.zone_size == 0) {
@@ -441,7 +432,7 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f)
                        f->file_name, (unsigned long long) td->o.zone_size,
                        (unsigned long long) zone_size);
                ret = -EINVAL;
-               goto close;
+               goto out;
        }
 
        dprint(FD_ZBD, "Device %s has %d zones of size %llu KB\n", f->file_name,
@@ -451,24 +442,24 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f)
                           (nr_zones + 1) * sizeof(zbd_info->zone_info[0]));
        ret = -ENOMEM;
        if (!zbd_info)
-               goto close;
+               goto out;
        pthread_mutex_init(&zbd_info->mutex, &attr);
        zbd_info->refcount = 1;
        p = &zbd_info->zone_info[0];
-       for (start_sector = 0, j = 0; j < nr_zones;) {
-               z = (void *)(hdr + 1);
-               for (i = 0; i < hdr->nr_zones; i++, j++, z++, p++) {
+       for (offset = 0, j = 0; j < nr_zones;) {
+               z = &zones[0];
+               for (i = 0; i < nrz; i++, j++, z++, p++) {
                        pthread_mutex_init(&p->mutex, &attr);
-                       p->start = z->start << 9;
+                       p->start = z->start;
                        switch (z->cond) {
-                       case BLK_ZONE_COND_NOT_WP:
-                       case BLK_ZONE_COND_FULL:
+                       case ZBD_ZONE_COND_NOT_WP:
+                       case ZBD_ZONE_COND_FULL:
                                p->wp = p->start + zone_size;
                                break;
                        default:
                                assert(z->start <= z->wp);
-                               assert(z->wp <= z->start + (zone_size >> 9));
-                               p->wp = z->wp << 9;
+                               assert(z->wp <= z->start + zone_size);
+                               p->wp = z->wp;
                                break;
                        }
                        p->type = z->type;
@@ -477,37 +468,38 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f)
                                log_info("%s: invalid zone data\n",
                                         f->file_name);
                                ret = -EINVAL;
-                               goto close;
+                               goto out;
                        }
                }
                z--;
-               start_sector = z->start + z->len;
+               offset = z->start + z->len;
                if (j >= nr_zones)
                        break;
-               ret = read_zone_info(fd, start_sector, buf, bufsz);
-               if (ret < 0) {
-                       log_info("fio: BLKREPORTZONE(%llu) failed for %s (%d).\n",
-                                (unsigned long long) start_sector, f->file_name, -ret);
-                       goto close;
+               nrz = zbd_report_zones(td, f, offset,
+                                           zones, ZBD_REPORT_MAX_ZONES);
+               if (nrz < 0) {
+                       ret = nrz;
+                       log_info("fio: report zones (offset %llu) failed for %s (%d).\n",
+                                (unsigned long long)offset,
+                                f->file_name, -ret);
+                       goto out;
                }
        }
+
        /* a sentinel */
-       zbd_info->zone_info[nr_zones].start = start_sector << 9;
+       zbd_info->zone_info[nr_zones].start = offset;
 
        f->zbd_info = zbd_info;
        f->zbd_info->zone_size = zone_size;
        f->zbd_info->zone_size_log2 = is_power_of_2(zone_size) ?
-               ilog2(zone_size) : -1;
+               ilog2(zone_size) : 0;
        f->zbd_info->nr_zones = nr_zones;
        zbd_info = NULL;
        ret = 0;
 
-close:
-       sfree(zbd_info);
-       close(fd);
-free:
-       free(buf);
 out:
+       sfree(zbd_info);
+       free(zones);
        pthread_mutexattr_destroy(&attr);
        return ret;
 }
@@ -519,21 +511,31 @@ out:
  */
 static int zbd_create_zone_info(struct thread_data *td, struct fio_file *f)
 {
-       enum blk_zoned_model zbd_model;
-       int ret = 0;
+       enum zbd_zoned_model zbd_model;
+       int ret;
 
        assert(td->o.zone_mode == ZONE_MODE_ZBD);
 
-       zbd_model = get_zbd_model(f->file_name);
+       ret = zbd_get_zoned_model(td, f, &zbd_model);
+       if (ret)
+               return ret;
+
        switch (zbd_model) {
-       case ZBD_DM_HOST_AWARE:
-       case ZBD_DM_HOST_MANAGED:
+       case ZBD_IGNORE:
+               return 0;
+       case ZBD_HOST_AWARE:
+       case ZBD_HOST_MANAGED:
                ret = parse_zone_info(td, f);
                break;
-       case ZBD_DM_NONE:
+       case ZBD_NONE:
                ret = init_zone_info(td, f);
                break;
+       default:
+               td_verror(td, EINVAL, "Unsupported zoned model");
+               log_err("Unsupported zoned model\n");
+               return -EINVAL;
        }
+
        if (ret == 0)
                f->zbd_info->model = zbd_model;
        return ret;
@@ -595,8 +597,6 @@ int zbd_init(struct thread_data *td)
        int i;
 
        for_each_file(td, f, i) {
-               if (f->filetype != FIO_TYPE_BLOCK)
-                       continue;
                if (zbd_init_zone_info(td, f))
                        return 1;
        }
@@ -624,31 +624,23 @@ int zbd_init(struct thread_data *td)
  *
  * Returns 0 upon success and a negative error code upon failure.
  */
-static int zbd_reset_range(struct thread_data *td, const struct fio_file *f,
+static int zbd_reset_range(struct thread_data *td, struct fio_file *f,
                           uint64_t offset, uint64_t length)
 {
-       struct blk_zone_range zr = {
-               .sector         = offset >> 9,
-               .nr_sectors     = length >> 9,
-       };
        uint32_t zone_idx_b, zone_idx_e;
        struct fio_zone_info *zb, *ze, *z;
        int ret = 0;
 
-       assert(f->fd != -1);
        assert(is_valid_offset(f, offset + length - 1));
+
        switch (f->zbd_info->model) {
-       case ZBD_DM_HOST_AWARE:
-       case ZBD_DM_HOST_MANAGED:
-               ret = ioctl(f->fd, BLKRESETZONE, &zr);
-               if (ret < 0) {
-                       td_verror(td, errno, "resetting wp failed");
-                       log_err("%s: resetting wp for %llu sectors at sector %llu failed (%d).\n",
-                               f->file_name, zr.nr_sectors, zr.sector, errno);
+       case ZBD_HOST_AWARE:
+       case ZBD_HOST_MANAGED:
+               ret = zbd_reset_wp(td, f, offset, length);
+               if (ret < 0)
                        return ret;
-               }
                break;
-       case ZBD_DM_NONE:
+       default:
                break;
        }
 
@@ -685,7 +677,7 @@ static unsigned int zbd_zone_nr(struct zoned_block_device_info *zbd_info,
  *
  * Returns 0 upon success and a negative error code upon failure.
  */
-static int zbd_reset_zone(struct thread_data *td, const struct fio_file *f,
+static int zbd_reset_zone(struct thread_data *td, struct fio_file *f,
                          struct fio_zone_info *z)
 {
        dprint(FD_ZBD, "%s: resetting wp of zone %u.\n", f->file_name,
@@ -707,58 +699,29 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f,
                           struct fio_zone_info *const zb,
                           struct fio_zone_info *const ze, bool all_zones)
 {
-       struct fio_zone_info *z, *start_z = ze;
+       struct fio_zone_info *z;
        const uint32_t min_bs = td->o.min_bs[DDIR_WRITE];
        bool reset_wp;
        int res = 0;
 
        dprint(FD_ZBD, "%s: examining zones %u .. %u\n", f->file_name,
                zbd_zone_nr(f->zbd_info, zb), zbd_zone_nr(f->zbd_info, ze));
-       assert(f->fd != -1);
        for (z = zb; z < ze; z++) {
-               pthread_mutex_lock(&z->mutex);
-               switch (z->type) {
-               case BLK_ZONE_TYPE_SEQWRITE_REQ:
-                       reset_wp = all_zones ? z->wp != z->start :
-                                       (td->o.td_ddir & TD_DDIR_WRITE) &&
-                                       z->wp % min_bs != 0;
-                       if (start_z == ze && reset_wp) {
-                               start_z = z;
-                       } else if (start_z < ze && !reset_wp) {
-                               dprint(FD_ZBD,
-                                      "%s: resetting zones %u .. %u\n",
-                                      f->file_name,
-                                       zbd_zone_nr(f->zbd_info, start_z),
-                                       zbd_zone_nr(f->zbd_info, z));
-                               if (zbd_reset_range(td, f, start_z->start,
-                                               z->start - start_z->start) < 0)
-                                       res = 1;
-                               start_z = ze;
-                       }
-                       break;
-               default:
-                       if (start_z == ze)
-                               break;
-                       dprint(FD_ZBD, "%s: resetting zones %u .. %u\n",
-                              f->file_name, zbd_zone_nr(f->zbd_info, start_z),
+               if (!zbd_zone_swr(z))
+                       continue;
+               zone_lock(td, z);
+               reset_wp = all_zones ? z->wp != z->start :
+                               (td->o.td_ddir & TD_DDIR_WRITE) &&
+                               z->wp % min_bs != 0;
+               if (reset_wp) {
+                       dprint(FD_ZBD, "%s: resetting zone %u\n",
+                              f->file_name,
                               zbd_zone_nr(f->zbd_info, z));
-                       if (zbd_reset_range(td, f, start_z->start,
-                                           z->start - start_z->start) < 0)
+                       if (zbd_reset_zone(td, f, z) < 0)
                                res = 1;
-                       start_z = ze;
-                       break;
                }
-       }
-       if (start_z < ze) {
-               dprint(FD_ZBD, "%s: resetting zones %u .. %u\n", f->file_name,
-                       zbd_zone_nr(f->zbd_info, start_z),
-                       zbd_zone_nr(f->zbd_info, z));
-               if (zbd_reset_range(td, f, start_z->start,
-                                   z->start - start_z->start) < 0)
-                       res = 1;
-       }
-       for (z = zb; z < ze; z++)
                pthread_mutex_unlock(&z->mutex);
+       }
 
        return res;
 }
@@ -847,6 +810,9 @@ static void zbd_init_swd(struct fio_file *f)
 {
        uint64_t swd;
 
+       if (!enable_check_swd)
+               return;
+
        swd = zbd_process_swd(f, SET_SWD);
        dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", __func__, f->file_name,
               swd);
@@ -906,7 +872,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct io_u *io_u,
        struct fio_zone_info *z = &f->zbd_info->zone_info[zone_idx];
        bool res = true;
 
-       if (z->cond == BLK_ZONE_COND_OFFLINE)
+       if (z->cond == ZBD_ZONE_COND_OFFLINE)
                return false;
 
        /*
@@ -946,12 +912,19 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f,
        zone_idx = f->zbd_info->open_zones[open_zone_idx];
        memmove(f->zbd_info->open_zones + open_zone_idx,
                f->zbd_info->open_zones + open_zone_idx + 1,
-               (FIO_MAX_OPEN_ZBD_ZONES - (open_zone_idx + 1)) *
+               (ZBD_MAX_OPEN_ZONES - (open_zone_idx + 1)) *
                sizeof(f->zbd_info->open_zones[0]));
        f->zbd_info->num_open_zones--;
        f->zbd_info->zone_info[zone_idx].open = 0;
 }
 
+/* Anything goes as long as it is not a constant. */
+static uint32_t pick_random_zone_idx(const struct fio_file *f,
+                                    const struct io_u *io_u)
+{
+       return io_u->offset * f->zbd_info->num_open_zones / f->real_file_size;
+}
+
 /*
  * Modify the offset of an I/O unit that does not refer to an open zone such
  * that it refers to an open zone. Close an open zone and open a new zone if
@@ -976,9 +949,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
                 * This statement accesses f->zbd_info->open_zones[] on purpose
                 * without locking.
                 */
-               zone_idx = f->zbd_info->open_zones[(io_u->offset -
-                                                   f->file_offset) *
-                               f->zbd_info->num_open_zones / f->io_size];
+               zone_idx = f->zbd_info->open_zones[pick_random_zone_idx(f, io_u)];
        } else {
                zone_idx = zbd_zone_idx(f, io_u->offset);
        }
@@ -992,9 +963,11 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
         * has been obtained. Hence the loop.
         */
        for (;;) {
+               uint32_t tmp_idx;
+
                z = &f->zbd_info->zone_info[zone_idx];
 
-               pthread_mutex_lock(&z->mutex);
+               zone_lock(td, z);
                pthread_mutex_lock(&f->zbd_info->mutex);
                if (td->o.max_open_zones == 0)
                        goto examine_zone;
@@ -1005,9 +978,35 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
                               __func__, f->file_name);
                        return NULL;
                }
-               open_zone_idx = (io_u->offset - f->file_offset) *
-                       f->zbd_info->num_open_zones / f->io_size;
+
+               /*
+                * List of opened zones is per-device, shared across all threads.
+                * Start with quasi-random candidate zone.
+                * Ignore zones which don't belong to thread's offset/size area.
+                */
+               open_zone_idx = pick_random_zone_idx(f, io_u);
                assert(open_zone_idx < f->zbd_info->num_open_zones);
+               tmp_idx = open_zone_idx;
+               for (i = 0; i < f->zbd_info->num_open_zones; i++) {
+                       uint32_t tmpz;
+
+                       if (tmp_idx >= f->zbd_info->num_open_zones)
+                               tmp_idx = 0;
+                       tmpz = f->zbd_info->open_zones[tmp_idx];
+
+                       if (is_valid_offset(f, f->zbd_info->zone_info[tmpz].start)) {
+                               open_zone_idx = tmp_idx;
+                               goto found_candidate_zone;
+                       }
+
+                       tmp_idx++;
+               }
+
+               dprint(FD_ZBD, "%s(%s): no candidate zone\n",
+                       __func__, f->file_name);
+               return NULL;
+
+found_candidate_zone:
                new_zone_idx = f->zbd_info->open_zones[open_zone_idx];
                if (new_zone_idx == zone_idx)
                        break;
@@ -1042,7 +1041,7 @@ examine_zone:
                        z = &f->zbd_info->zone_info[zone_idx];
                }
                assert(is_valid_offset(f, z->start));
-               pthread_mutex_lock(&z->mutex);
+               zone_lock(td, z);
                if (z->open)
                        continue;
                if (zbd_open_zone(td, io_u, zone_idx))
@@ -1060,7 +1059,7 @@ examine_zone:
 
                z = &f->zbd_info->zone_info[zone_idx];
 
-               pthread_mutex_lock(&z->mutex);
+               zone_lock(td, z);
                if (z->wp + min_bs <= (z+1)->start)
                        goto out;
                pthread_mutex_lock(&f->zbd_info->mutex);
@@ -1122,7 +1121,7 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u,
         * the nearest non-empty zone in case of random I/O.
         */
        for (z1 = zb + 1, z2 = zb - 1; z1 < zl || z2 >= zf; z1++, z2--) {
-               if (z1 < zl && z1->cond != BLK_ZONE_COND_OFFLINE) {
+               if (z1 < zl && z1->cond != ZBD_ZONE_COND_OFFLINE) {
                        pthread_mutex_lock(&z1->mutex);
                        if (z1->start + min_bs <= z1->wp)
                                return z1;
@@ -1131,7 +1130,7 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u,
                        break;
                }
                if (td_random(td) && z2 >= zf &&
-                   z2->cond != BLK_ZONE_COND_OFFLINE) {
+                   z2->cond != ZBD_ZONE_COND_OFFLINE) {
                        pthread_mutex_lock(&z2->mutex);
                        if (z2->start + min_bs <= z2->wp)
                                return z2;
@@ -1167,7 +1166,7 @@ static void zbd_queue_io(struct io_u *io_u, int q, bool success)
        assert(zone_idx < zbd_info->nr_zones);
        z = &zbd_info->zone_info[zone_idx];
 
-       if (z->type != BLK_ZONE_TYPE_SEQWRITE_REQ)
+       if (!zbd_zone_swr(z))
                return;
 
        if (!success)
@@ -1224,7 +1223,7 @@ static void zbd_put_io(const struct io_u *io_u)
        assert(zone_idx < zbd_info->nr_zones);
        z = &zbd_info->zone_info[zone_idx];
 
-       if (z->type != BLK_ZONE_TYPE_SEQWRITE_REQ)
+       if (!zbd_zone_swr(z))
                return;
 
        dprint(FD_ZBD,
@@ -1235,6 +1234,13 @@ static void zbd_put_io(const struct io_u *io_u)
        zbd_check_swd(f);
 }
 
+/*
+ * Windows and MacOS do not define this.
+ */
+#ifndef EREMOTEIO
+#define EREMOTEIO      121     /* POSIX value */
+#endif
+
 bool zbd_unaligned_write(int error_code)
 {
        switch (error_code) {
@@ -1315,7 +1321,7 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u)
  */
 enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
 {
-       const struct fio_file *f = io_u->file;
+       struct fio_file *f = io_u->file;
        uint32_t zone_idx_b;
        struct fio_zone_info *zb, *zl, *orig_zb;
        uint32_t orig_len = io_u->buflen;
@@ -1333,33 +1339,20 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        orig_zb = zb;
 
        /* Accept the I/O offset for conventional zones. */
-       if (zb->type == BLK_ZONE_TYPE_CONVENTIONAL)
+       if (!zbd_zone_swr(zb))
                return io_u_accept;
 
        /*
         * Accept the I/O offset for reads if reading beyond the write pointer
         * is enabled.
         */
-       if (zb->cond != BLK_ZONE_COND_OFFLINE &&
+       if (zb->cond != ZBD_ZONE_COND_OFFLINE &&
            io_u->ddir == DDIR_READ && td->o.read_beyond_wp)
                return io_u_accept;
 
        zbd_check_swd(f);
 
-       /*
-        * Lock the io_u target zone. The zone will be unlocked if io_u offset
-        * is changed or when io_u completes and zbd_put_io() executed.
-        * To avoid multiple jobs doing asynchronous I/Os from deadlocking each
-        * other waiting for zone locks when building an io_u batch, first
-        * only trylock the zone. If the zone is already locked by another job,
-        * process the currently queued I/Os so that I/O progress is made and
-        * zones unlocked.
-        */
-       if (pthread_mutex_trylock(&zb->mutex) != 0) {
-               if (!td_ioengine_flagged(td, FIO_SYNCIO))
-                       io_u_quiesce(td);
-               pthread_mutex_lock(&zb->mutex);
-       }
+       zone_lock(td, zb);
 
        switch (io_u->ddir) {
        case DDIR_READ:
@@ -1372,7 +1365,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
                 * I/O of at least min_bs B. If there isn't, find a new zone for
                 * the I/O.
                 */
-               range = zb->cond != BLK_ZONE_COND_OFFLINE ?
+               range = zb->cond != ZBD_ZONE_COND_OFFLINE ?
                        zb->wp - zb->start : 0;
                if (range < min_bs ||
                    ((!td_random(td)) && (io_u->offset + min_bs > zb->wp))) {
@@ -1497,7 +1490,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
 
 accept:
        assert(zb);
-       assert(zb->cond != BLK_ZONE_COND_OFFLINE);
+       assert(zb->cond != ZBD_ZONE_COND_OFFLINE);
        assert(!io_u->zbd_queue_io);
        assert(!io_u->zbd_put_io);
        io_u->zbd_queue_io = zbd_queue_io;
diff --git a/zbd.h b/zbd.h
index e0a7e44..4eaf902 100644 (file)
--- a/zbd.h
+++ b/zbd.h
@@ -7,23 +7,13 @@
 #ifndef FIO_ZBD_H
 #define FIO_ZBD_H
 
-#include <inttypes.h>
-#include "fio.h"       /* FIO_MAX_OPEN_ZBD_ZONES */
-#ifdef CONFIG_LINUX_BLKZONED
-#include <linux/blkzoned.h>
-#endif
+#include "io_u.h"
+#include "ioengines.h"
+#include "oslib/blkzoned.h"
+#include "zbd_types.h"
 
 struct fio_file;
 
-/*
- * Zoned block device models.
- */
-enum blk_zoned_model {
-       ZBD_DM_NONE,    /* Regular block device */
-       ZBD_DM_HOST_AWARE,      /* Host-aware zoned block device */
-       ZBD_DM_HOST_MANAGED,    /* Host-managed zoned block device */
-};
-
 enum io_u_action {
        io_u_accept     = 0,
        io_u_eof        = 1,
@@ -42,16 +32,14 @@ enum io_u_action {
  * @reset_zone: whether or not this zone should be reset before writing to it
  */
 struct fio_zone_info {
-#ifdef CONFIG_LINUX_BLKZONED
        pthread_mutex_t         mutex;
        uint64_t                start;
        uint64_t                wp;
        uint32_t                verify_block;
-       enum blk_zone_type      type:2;
-       enum blk_zone_cond      cond:4;
+       enum zbd_zone_type      type:2;
+       enum zbd_zone_cond      cond:4;
        unsigned int            open:1;
        unsigned int            reset_zone:1;
-#endif
 };
 
 /**
@@ -76,7 +64,7 @@ struct fio_zone_info {
  * will be smaller than 'zone_size'.
  */
 struct zoned_block_device_info {
-       enum blk_zoned_model    model;
+       enum zbd_zoned_model    model;
        pthread_mutex_t         mutex;
        uint64_t                zone_size;
        uint64_t                sectors_with_data;
@@ -85,11 +73,10 @@ struct zoned_block_device_info {
        uint32_t                refcount;
        uint32_t                num_open_zones;
        uint32_t                write_cnt;
-       uint32_t                open_zones[FIO_MAX_OPEN_ZBD_ZONES];
+       uint32_t                open_zones[ZBD_MAX_OPEN_ZONES];
        struct fio_zone_info    zone_info[0];
 };
 
-#ifdef CONFIG_LINUX_BLKZONED
 void zbd_free_zone_info(struct fio_file *f);
 int zbd_init(struct thread_data *td);
 void zbd_file_reset(struct thread_data *td, struct fio_file *f);
@@ -115,45 +102,4 @@ static inline void zbd_put_io_u(struct io_u *io_u)
        }
 }
 
-#else
-static inline void zbd_free_zone_info(struct fio_file *f)
-{
-}
-
-static inline int zbd_init(struct thread_data *td)
-{
-       return 0;
-}
-
-static inline void zbd_file_reset(struct thread_data *td, struct fio_file *f)
-{
-}
-
-static inline bool zbd_unaligned_write(int error_code)
-{
-       return false;
-}
-
-static inline enum io_u_action zbd_adjust_block(struct thread_data *td,
-                                               struct io_u *io_u)
-{
-       return io_u_accept;
-}
-
-static inline char *zbd_write_status(const struct thread_stat *ts)
-{
-       return NULL;
-}
-
-static inline void zbd_queue_io_u(struct io_u *io_u,
-                                 enum fio_q_status status) {}
-static inline void zbd_put_io_u(struct io_u *io_u) {}
-
-static inline void setup_zbd_zone_mode(struct thread_data *td,
-                                       struct io_u *io_u)
-{
-}
-
-#endif
-
 #endif /* FIO_ZBD_H */
diff --git a/zbd_types.h b/zbd_types.h
new file mode 100644 (file)
index 0000000..2f2f132
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 Western Digital Corporation or its affiliates.
+ *
+ * This file is released under the GPL.
+ */
+#ifndef FIO_ZBD_TYPES_H
+#define FIO_ZBD_TYPES_H
+
+#include <inttypes.h>
+
+#define ZBD_MAX_OPEN_ZONES     128
+
+/*
+ * Zoned block device models.
+ */
+enum zbd_zoned_model {
+       ZBD_IGNORE,             /* Ignore file */
+       ZBD_NONE,               /* Regular block device */
+       ZBD_HOST_AWARE,         /* Host-aware zoned block device */
+       ZBD_HOST_MANAGED,       /* Host-managed zoned block device */
+};
+
+/*
+ * Zone types.
+ */
+enum zbd_zone_type {
+       ZBD_ZONE_TYPE_CNV       = 0x1,  /* Conventional */
+       ZBD_ZONE_TYPE_SWR       = 0x2,  /* Sequential write required */
+       ZBD_ZONE_TYPE_SWP       = 0x3,  /* Sequential write preferred */
+};
+
+/*
+ * Zone conditions.
+ */
+enum zbd_zone_cond {
+        ZBD_ZONE_COND_NOT_WP    = 0x0,
+        ZBD_ZONE_COND_EMPTY     = 0x1,
+        ZBD_ZONE_COND_IMP_OPEN  = 0x2,
+        ZBD_ZONE_COND_EXP_OPEN  = 0x3,
+        ZBD_ZONE_COND_CLOSED    = 0x4,
+        ZBD_ZONE_COND_READONLY  = 0xD,
+        ZBD_ZONE_COND_FULL      = 0xE,
+        ZBD_ZONE_COND_OFFLINE   = 0xF,
+};
+
+/*
+ * Zone descriptor.
+ */
+struct zbd_zone {
+       uint64_t                start;
+       uint64_t                wp;
+       uint64_t                len;
+       enum zbd_zone_type      type;
+       enum zbd_zone_cond      cond;
+};
+
+#endif /* FIO_ZBD_TYPES_H */