qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH] add fio plugin


From: Paolo Bonzini
Subject: [Qemu-devel] [PATCH] add fio plugin
Date: Tue, 13 Dec 2016 16:17:53 +0100

Signed-off-by: Paolo Bonzini <address@hidden>
---
 configure                 |   3 +-
 contrib/fio/Makefile      |  62 +++++++
 contrib/fio/README        |  13 ++
 contrib/fio/fio.c         | 404 ++++++++++++++++++++++++++++++++++++++++++++++
 contrib/fio/qemu.fio      |  26 +++
 contrib/fio/uninclude.awk |  78 +++++++++
 6 files changed, 585 insertions(+), 1 deletion(-)
 create mode 100644 contrib/fio/Makefile
 create mode 100644 contrib/fio/README
 create mode 100644 contrib/fio/fio.c
 create mode 100644 contrib/fio/qemu.fio
 create mode 100644 contrib/fio/uninclude.awk

diff --git a/configure b/configure
index 7dec6cd..8687461 100755
--- a/configure
+++ b/configure
@@ -6181,13 +6181,14 @@ fi
 
 # build tree in object directory in case the source is not in the current 
directory
 DIRS="tests tests/tcg tests/tcg/cris tests/tcg/lm32 tests/libqos 
tests/qapi-schema tests/tcg/xtensa tests/qemu-iotests"
-DIRS="$DIRS fsdev"
+DIRS="$DIRS fsdev contrib/fio"
 DIRS="$DIRS pc-bios/optionrom pc-bios/spapr-rtas pc-bios/s390-ccw"
 DIRS="$DIRS roms/seabios roms/vgabios"
 DIRS="$DIRS qapi-generated"
 FILES="Makefile tests/tcg/Makefile qdict-test-data.txt"
 FILES="$FILES tests/tcg/cris/Makefile tests/tcg/cris/.gdbinit"
 FILES="$FILES tests/tcg/lm32/Makefile tests/tcg/xtensa/Makefile po/Makefile"
+FILES="$FILES contrib/fio/Makefile"
 FILES="$FILES pc-bios/optionrom/Makefile pc-bios/keymaps"
 FILES="$FILES pc-bios/spapr-rtas/Makefile"
 FILES="$FILES pc-bios/s390-ccw/Makefile"
diff --git a/contrib/fio/Makefile b/contrib/fio/Makefile
new file mode 100644
index 0000000..4a86b3f
--- /dev/null
+++ b/contrib/fio/Makefile
@@ -0,0 +1,62 @@
+# -*- Mode: makefile -*-
+
+BUILD_DIR?=$(CURDIR)/../..
+
+include ../../config-host.mak
+include $(SRC_PATH)/rules.mak
+
+$(call set-vpath, $(SRC_PATH):$(SRC_PATH)/contrib/fio:$(CURDIR))
+
+PROGS=qemu.so
+
+all: $(PROGS)
+# Dummy command so that make thinks it has done something
+       @true
+
+QEMU_CFLAGS += -I$(BUILD_DIR) -Wno-error -Wno-redundant-decls
+LIBS += $(LIBS_TOOLS)
+
+# We need two fio header files, but we don't want to include
+# fio's config-host.h because its #defined symbols conflict
+# with QEMU's own config-host.h.  In general we do not want to
+# have -I$(FIO_PATH) while compiling fio.o because the possible
+# conflicts are a mess.
+#
+# optgroup.h is tame and we can just copy it to the build directory,
+# but fio.h includes a lot of other header files, from both fio
+# itself and the system.  Therefore we preprocess it so that
+# fio's headers are merged into a single file, fio-qemu.h, while
+# system headers are left as #include directives.  Because the
+# preprocessing step removes all preprocessor conditionals,
+# there is no dependency left on fio's config-host.h file.
+#
+# While at it, we hack some symbols that conflict with QEMU's,
+# by prefixing them with a "FIO_" or "fio_" namespace.
+fio.o: fio-qemu.h fio-optgroup-qemu.h
+
+fio-optgroup-qemu.h:
+       cp $(FIO_PATH)/optgroup.h $@
+
+FIO_HACK_SYMBOLS=sed -e 's/\<JSON_/FIO_&/' -e 's/\<cpu_to_/fio_&/'
+fio-qemu.h:
+       $(CC) -dD -dI -C -E -o - -I$(FIO_PATH) $(QEMU_CFLAGS) \
+            -include $(FIO_PATH)/config-host.h $(FIO_PATH)/fio.h | \
+            awk -f $(SRC_PATH)/contrib/fio/uninclude.awk | \
+            $(FIO_HACK_SYMBOLS) > $@
+
+include $(SRC_PATH)/Makefile.objs
+dummy := $(call unnest-vars,../.., \
+               block-obj-y \
+               block-obj-m \
+               crypto-obj-y \
+               qom-obj-y \
+               io-obj-y)
+obj-y := fio.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y)
+
+dummy := $(shell mkdir -p $(dir $(obj-y)))
+qemu.so: $(obj-y) ../../libqemuutil.a ../../libqemustub.a
+       $(call LINK, $^)
+
+clean: clean-target
+       rm -f *.a $(filter-out ../../%, $(obj-y))
+       rm -f $(shell find . -name '*.[od]')
diff --git a/contrib/fio/README b/contrib/fio/README
new file mode 100644
index 0000000..4aef2a0
--- /dev/null
+++ b/contrib/fio/README
@@ -0,0 +1,13 @@
+This is a plugin to test the QEMU block layer with fio,
+the flexible I/O tester.
+
+To build this plugin you have to:
+
+1) hack the configure file to use -fPIC instead of -fPIE
+
+2) configure qemu with --enable-pie
+
+3) build the plugin with
+
+       make qemu-img
+       make -C contrib/fio
diff --git a/contrib/fio/fio.c b/contrib/fio/fio.c
new file mode 100644
index 0000000..eedc3b9
--- /dev/null
+++ b/contrib/fio/fio.c
@@ -0,0 +1,404 @@
+/*
+ * QEMU engine for fio
+ *
+ */
+#include "fio-qemu.h"
+#include "fio-optgroup-qemu.h"
+#include "qemu/osdep.h"
+#include "qemu/iov.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qstring.h"
+#include "block/aio.h"
+#include "block/qapi.h"
+#include "crypto/init.h"
+#include "sysemu/block-backend.h"
+
+struct qemu_data {
+       AioContext *ctx;
+       unsigned int completed;
+       unsigned int to_submit;
+       struct io_u *aio_events[];
+};
+
+struct qemu_options {
+       void *pad;
+       const char *aio;
+       const char *format;
+       const char *driver;
+};
+
+static QemuMutex iothread_lock;
+
+static int str_aio_cb(void *data, const char *str)
+{
+       struct qemu_options *o = data;
+
+       if (!strcmp(str, "native") || !strcmp(str, "threads"))
+               o->aio = strdup(str);
+       else
+               return 1;
+
+       return 0;
+}
+
+static struct fio_option options[] = {
+       {
+               .name   = "qemu_driver",
+               .lname  = "QEMU block driver",
+               .type   = FIO_OPT_STR_STORE,
+               .off1   = offsetof(struct qemu_options, driver),
+               .category = FIO_OPT_C_ENGINE,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "qemu_format",
+               .lname  = "Image format",
+               .type   = FIO_OPT_STR_STORE,
+               .off1   = offsetof(struct qemu_options, format),
+               .category = FIO_OPT_C_ENGINE,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "qemu_aio",
+               .lname  = "Use native AIO",
+               .type   = FIO_OPT_STR_STORE,
+               .off1   = offsetof(struct qemu_options, aio),
+               .cb     = str_aio_cb,
+               .category = FIO_OPT_C_ENGINE,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = NULL,
+       },
+};
+
+static int fio_qemu_getevents(struct thread_data *td, unsigned int min,
+                             unsigned int max, const struct timespec *t)
+{
+       struct qemu_data *qd = td->io_ops_data;
+
+       /* TODO: set timer */
+       do
+               aio_poll(qd->ctx, true);
+       while (qd->completed < min);
+
+       return qd->completed;
+}
+
+static struct io_u *fio_qemu_event(struct thread_data *td, int event)
+{
+       struct qemu_data *qd = td->io_ops_data;
+
+       qd->completed--;
+       return qd->aio_events[event];
+}
+
+static inline BlockBackend *fio_qemu_get_blk(struct fio_file *file)
+{
+       return (BlockBackend *)(uintptr_t)(file->engine_data & ~1);
+}
+
+static inline bool fio_qemu_mark_plugged(struct fio_file *file)
+{
+       bool plugged = (file->engine_data & 1);
+       file->engine_data |= 1;
+       return plugged;
+}
+
+static inline bool fio_qemu_test_and_clear_plugged(struct fio_file *file)
+{
+       bool plugged = (file->engine_data & 1);
+       file->engine_data &= ~1;
+       return plugged;
+}
+
+static void fio_qemu_entry(void *opaque)
+{
+       struct io_u *io_u = opaque;
+       struct fio_file *file = io_u->file;
+       BlockBackend *blk = fio_qemu_get_blk(file);
+       struct iovec iov = { io_u->xfer_buf, io_u->xfer_buflen };
+
+       struct thread_data *td;
+       struct qemu_data *qd;
+       unsigned r;
+       int ret;
+
+       if (!fio_qemu_mark_plugged(io_u->file))
+               blk_io_plug(blk);
+
+       if (io_u->ddir == DDIR_READ) {
+               QEMUIOVector qiov;
+               qemu_iovec_init_external(&qiov, &iov, 1);
+               ret = blk_co_preadv(blk, io_u->offset, iov.iov_len, &qiov, 0);
+       } else if (io_u->ddir == DDIR_WRITE) {
+               QEMUIOVector qiov;
+               qemu_iovec_init_external(&qiov, &iov, 1);
+               ret = blk_co_pwritev(blk, io_u->offset, iov.iov_len, &qiov, 0);
+       } else if (io_u->ddir == DDIR_TRIM) {
+               ret = blk_co_pdiscard(blk, io_u->offset, iov.iov_len);
+       } else {
+               ret = blk_flush(blk);
+       }
+
+       if (!ret) {
+               io_u->resid = 0;
+               io_u->error = 0;
+       } else if (ret == -ECANCELED) {
+               io_u->resid = io_u->xfer_buflen;
+               io_u->error = 0;
+       } else {
+               io_u->error = -ret;
+       }
+
+       td = io_u->engine_data;
+       qd = td->io_ops_data;
+       r = qd->completed++;
+       qd->aio_events[r] = io_u;
+}
+
+static int fio_qemu_queue(struct thread_data *td,
+                             struct io_u *io_u)
+{
+       Coroutine *co;
+       struct qemu_data *qd;
+
+       fio_ro_check(td, io_u);
+
+       co = qemu_coroutine_create(fio_qemu_entry, io_u);
+       io_u->error = EINPROGRESS;
+       io_u->engine_data = td;
+       qemu_coroutine_enter(co);
+
+       qd = td->io_ops_data;
+       if (io_u->error == EINPROGRESS) {
+               io_u->error = 0;
+               qd->to_submit++;
+               return FIO_Q_QUEUED;
+       }
+
+       /* This I/O operation has completed.  If all of them are, fio will not
+        * call fio_qemu_commit, so unplug immediately.
+        */
+       qd->completed--;
+       if (qd->to_submit == 0) {
+               BlockBackend *blk = fio_qemu_get_blk(io_u->file);
+               fio_qemu_test_and_clear_plugged(io_u->file);
+               blk_io_unplug(blk);
+       }
+
+       return FIO_Q_COMPLETED;
+}
+
+static int fio_qemu_commit(struct thread_data *td)
+{
+       struct qemu_data *qd = td->io_ops_data;
+       struct fio_file *file;
+       int i;
+
+       for_each_file(td, file, i) {
+               if (fio_qemu_test_and_clear_plugged(file)) {
+                       BlockBackend *blk = fio_qemu_get_blk(file);
+                       blk_io_unplug(blk);
+               }
+       }
+       qd->to_submit = 0;
+
+       return 0;
+}
+
+static int fio_qemu_invalidate(struct thread_data *td, struct fio_file *file)
+{
+       return 0;
+}
+static void fio_qemu_cleanup(struct thread_data *td)
+{
+       struct qemu_data *qd = td->io_ops_data;
+
+       if (qd) {
+               aio_context_unref(qd->ctx);
+               free(qd);
+       }
+}
+
+static QDict *fio_qemu_opts(struct thread_data *td, struct fio_file *file)
+{
+       QDict *bs_opts;
+       struct qemu_options *o = td->eo;
+
+       bs_opts = qdict_new();
+       if (td_read(td) && read_only)
+               qdict_put(bs_opts, BDRV_OPT_READ_ONLY,
+                         qstring_from_str("on"));
+       qdict_put(bs_opts, BDRV_OPT_CACHE_DIRECT,
+                 qstring_from_str(td->o.odirect ? "on" : "off"));
+       if (o->format)
+               qdict_put(bs_opts, "format", qstring_from_str(o->format));
+
+       /* If no format is provided, but a driver is, skip the raw format.  */
+       if (o->driver)
+               qdict_put(bs_opts, !o->format ? "driver" : "file.driver",
+                         qstring_from_str(o->driver));
+
+
+       /* This is mostly a convenience, because the aio option of the file
+        * driver is commonly specified.
+        */
+       if (o->aio)
+               qdict_put(bs_opts, !o->format && o->driver ? "aio" : "file.aio",
+                         qstring_from_str(o->aio));
+
+       return bs_opts;
+}
+
+static int fio_qemu_get_file_size(struct thread_data *td, struct fio_file 
*file)
+{
+       Error *local_error = NULL;
+       BlockBackend *blk;
+       QDict *bs_opts;
+       ImageInfo *info;
+
+       bs_opts = fio_qemu_opts(td, file);
+       blk = blk_new_open(file->file_name, NULL, bs_opts, 0, &local_error);
+       if (local_error) {
+               struct qemu_options *o = td->eo;
+               if (!td->o.create_on_open || !td->o.allow_create) {
+                       error_report_err(local_error);
+                       return -EINVAL;
+               }
+
+               error_free(local_error);
+               local_error = NULL;
+
+               bdrv_img_create(file->file_name, o->format ? : "raw", NULL, 
NULL,
+                               NULL, td->o.size / td->o.nr_files, 0, 
&local_error, false);
+               if (local_error) {
+                   error_report_err(local_error);
+                   return -EINVAL;
+               }
+
+               bs_opts = fio_qemu_opts(td, file);
+               blk = blk_new_open(file->file_name, NULL, bs_opts, 0, 
&local_error);
+               if (local_error) {
+                   error_report_err(local_error);
+                   return -EINVAL;
+               }
+       }
+
+       /* QDECREF(bs_opts); ??? */
+       bdrv_query_image_info(blk_bs(blk), &info, &local_error);
+       blk_unref(blk);
+
+       file->real_file_size = info->virtual_size;
+       fio_file_set_size_known(file);
+       qapi_free_ImageInfo(info);
+
+       return 0;
+}
+
+static void fio_qemu_setup_globals(void)
+{
+       qemu_init_main_loop(&error_abort);
+       qcrypto_init(&error_fatal);
+       module_call_init(MODULE_INIT_QOM);
+       bdrv_init();
+       qemu_mutex_init(&iothread_lock);
+}
+
+static int fio_qemu_setup(struct thread_data *td)
+{
+       static pthread_once_t fio_qemu_globals = PTHREAD_ONCE_INIT;
+       struct fio_file *file;
+       int i;
+
+       td->o.use_thread = 1;
+       pthread_once(&fio_qemu_globals, fio_qemu_setup_globals);
+
+       for_each_file(td, file, i) {
+               int ret;
+               dprint(FD_FILE, "get file size for %p/%d/%p\n", file, i,
+                                                               
file->file_name);
+
+               ret = fio_qemu_get_file_size(td, file);
+               if (ret < 0) {
+                       log_err("%s\n", strerror(-ret));
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+static int fio_qemu_init(struct thread_data *td)
+{
+       size_t sz = sizeof(struct qemu_data) + td->o.iodepth * sizeof(struct 
io_u *);
+       struct qemu_data *qd = malloc(sz);
+
+       memset(qd, 0, sz);
+       qd->ctx = aio_context_new(&error_abort);
+
+       /* dlclosing QEMU leaves a pthread_key behind.  We'd need RTLD_NODELETE,
+        * but fio does not use it.  Instead, just prevent fio from dlclosing.
+        */
+       td->io_ops_dlhandle = NULL;
+
+       td->io_ops_data = qd;
+       td->o.use_thread = 1;
+       return 0;
+}
+
+static int fio_qemu_open_file(struct thread_data *td, struct fio_file *file)
+{
+       struct qemu_data *qd = td->io_ops_data;
+       Error *local_error = NULL;
+       QDict *bs_opts;
+       BlockBackend *blk;
+
+       qemu_mutex_lock(&iothread_lock);
+       bs_opts = fio_qemu_opts(td, file);
+       blk = blk_new_open(file->file_name, NULL, bs_opts, 0, &local_error);
+       /* QDECREF(bs_opts); ??? */
+
+       if (local_error) {
+               error_report_err(local_error);
+               return -EINVAL;
+       }
+
+       blk_set_aio_context(blk, qd->ctx);
+       blk_set_enable_write_cache(blk, !td->o.sync_io);
+       qemu_mutex_unlock(&iothread_lock);
+
+       file->engine_data = (uintptr_t)blk;
+       td->o.open_files ++;
+       return 0;
+}
+
+static int fio_qemu_close_file(struct thread_data *td, struct fio_file *file)
+{
+       BlockBackend *blk = fio_qemu_get_blk(file);
+
+       if (blk) {
+               blk_unref(blk);
+               file->engine_data = 0;
+       }
+
+       return 0;
+}
+
+struct ioengine_ops ioengine = {
+       .name           = "qemu",
+       .version        = FIO_IOOPS_VERSION,
+       .init           = fio_qemu_init,
+       .queue          = fio_qemu_queue,
+       .commit         = fio_qemu_commit,
+       .getevents      = fio_qemu_getevents,
+       .event          = fio_qemu_event,
+       .invalidate     = fio_qemu_invalidate,
+       .cleanup        = fio_qemu_cleanup,
+       .setup          = fio_qemu_setup,
+       .open_file      = fio_qemu_open_file,
+       .close_file     = fio_qemu_close_file,
+       .options        = options,
+       .option_struct_size = sizeof(struct qemu_options),
+};
diff --git a/contrib/fio/qemu.fio b/contrib/fio/qemu.fio
new file mode 100644
index 0000000..565c015
--- /dev/null
+++ b/contrib/fio/qemu.fio
@@ -0,0 +1,26 @@
+; Read 4 files with aio at different depths
+[global]
+ioengine=./qemu.so
+direct=1
+qemu_aio=native
+rw=randread
+bs=128k
+filesize=128m
+runtime=10s
+time_based
+
+[file1]
+filename=file1
+iodepth=4
+
+[file2]
+filename=file2
+iodepth=32
+
+[file3]
+filename=file3
+iodepth=8
+
+[file4]
+filename=file4
+iodepth=16
diff --git a/contrib/fio/uninclude.awk b/contrib/fio/uninclude.awk
new file mode 100644
index 0000000..88afa59
--- /dev/null
+++ b/contrib/fio/uninclude.awk
@@ -0,0 +1,78 @@
+BEGIN {
+    # gcc strips #ifdef blocks, which removes the multiple-inclusion guard too.
+    # Repair with #pragma once.
+    print "#pragma once"
+}
+
+{
+    # Documentation says builtins are not printed by -dD, but reality 
disagrees.
+    if ($1 == "#" && $3 ~ "\"<built-in>\"") {
+        delete_until_hash = 1
+        next
+    }
+    if (delete_until_hash) {
+        if ($1 == "#") {
+            delete_until_hash = 0
+        } else {
+           next
+       }
+   }
+
+    # Handle the delete state: skip files included with <...> and -include,
+    # plus their nested includes
+    if (delete_depth) {
+        if ($1 == "#") {
+            if ($4 == "1") {
+                delete_depth++
+            } else if ($4 == "2") {
+                delete_depth--
+            }
+        }
+        if (delete_depth) {
+            next
+        } else {
+            # Out of delete state.  We are on a # directive, if necessary
+            # we can use it to set command_line again
+            command_line = 0
+        }
+    }
+
+    # Handle the command-line state: skip -D definitions and -included files
+    if ($1 == "#" && $3 == "\"<command-line>\"") {
+        command_line = 1
+    }
+
+    if (command_line) {
+        if ($1 == "#" && $4 == "1") {
+            # This is a -included file, enter the delete state
+            delete_depth = 1
+        }
+        next
+    }
+
+    if ($1 == "#include") {
+        if ($2 ~ /^</) {
+            print
+            # We printed the #include directive.  Now skip until the # line
+            # that enters the included file, and enter the delete state.
+            do {
+                getline
+            } while ($1 == "#" && $4 != "1")
+            if ($1 == "#" && $4 == "1") {
+                delete_depth = 1
+                next
+            }
+        } else {
+            # For local includes we include their content, so the #include
+            # directive must go.
+            next
+        }
+    }
+
+    # Remove line directives emitted by the preprocessor
+    if ($1 == "#") {
+        next
+    }
+
+    print
+}
-- 
2.9.3




reply via email to

[Prev in Thread] Current Thread [Next in Thread]