Merge branch 'master' into gfio
authorJens Axboe <axboe@kernel.dk>
Thu, 11 Oct 2012 03:43:46 +0000 (21:43 -0600)
committerJens Axboe <axboe@kernel.dk>
Thu, 11 Oct 2012 03:43:46 +0000 (21:43 -0600)
97 files changed:
GFIO-TODO [new file with mode: 0644]
HOWTO
Makefile
backend.c
cairo_text_helpers.c [new file with mode: 0644]
cairo_text_helpers.h [new file with mode: 0644]
cconv.c [new file with mode: 0644]
client.c
client.h [new file with mode: 0644]
debug.h
engines/cpu.c
engines/libaio.c
engines/net.c
eta.c
examples/1mbs_clients [deleted file]
examples/1mbs_clients.fio [new file with mode: 0644]
examples/aio-read [deleted file]
examples/aio-read.fio [new file with mode: 0644]
examples/disk-zone-profile [deleted file]
examples/disk-zone-profile.fio [new file with mode: 0644]
examples/flow [deleted file]
examples/flow.fio [new file with mode: 0644]
examples/fsx [deleted file]
examples/fsx.fio [new file with mode: 0644]
examples/iometer-file-access-server [deleted file]
examples/iometer-file-access-server.fio [new file with mode: 0644]
examples/netio [deleted file]
examples/netio.fio [new file with mode: 0644]
examples/null [deleted file]
examples/null.fio [new file with mode: 0644]
examples/rdmaio-client [deleted file]
examples/rdmaio-client.fio [new file with mode: 0644]
examples/rdmaio-server [deleted file]
examples/rdmaio-server.fio [new file with mode: 0644]
examples/ssd-test [deleted file]
examples/ssd-test.fio [new file with mode: 0644]
examples/surface-scan [deleted file]
examples/surface-scan.fio [new file with mode: 0644]
examples/tiobench-example [deleted file]
examples/tiobench-example.fio [new file with mode: 0644]
filesetup.c
fio.1
fio.c
fio.h
gclient.c [new file with mode: 0644]
gclient.h [new file with mode: 0644]
gcompat.c [new file with mode: 0644]
gcompat.h [new file with mode: 0644]
gerror.c [new file with mode: 0644]
gerror.h [new file with mode: 0644]
gfio.c [new file with mode: 0644]
gfio.h [new file with mode: 0644]
ghelpers.c [new file with mode: 0644]
ghelpers.h [new file with mode: 0644]
goptions.c [new file with mode: 0644]
goptions.h [new file with mode: 0644]
graph.c [new file with mode: 0644]
graph.h [new file with mode: 0644]
init.c
io_ddir.h
io_u.c
ioengine.h
ioengines.c
iolog.c
iolog.h
lib/prio_tree.c [new file with mode: 0644]
lib/prio_tree.h [new file with mode: 0644]
lib/rbtree.c [new file with mode: 0644]
lib/rbtree.h [new file with mode: 0644]
libfio.c
log.c
log.h
memory.c
options.c
options.h
os/os-linux.h
os/os.h
parse.c
parse.h
printing.c [new file with mode: 0644]
printing.h [new file with mode: 0644]
profile.c
profiles/tiobench.c
rbtree.c [deleted file]
rbtree.h [deleted file]
server.c
server.h
stat.c
stat.h
t/ieee754.c
t/jobs/t0001-52c58027 [deleted file]
t/jobs/t0001-52c58027.fio [new file with mode: 0644]
thread_options.h [new file with mode: 0644]
tickmarks.c [new file with mode: 0644]
tickmarks.h [new file with mode: 0644]
verify.c
verify.h

diff --git a/GFIO-TODO b/GFIO-TODO
new file mode 100644 (file)
index 0000000..885ffcb
--- /dev/null
+++ b/GFIO-TODO
@@ -0,0 +1,52 @@
+In no particular order:
+
+- Ability to save job files. Probably in an extended gfio format,
+  so we can include options/settings outside of a fio job file.
+
+- End view improvements:
+
+       - Cleanup the layout
+       - Add ability to save the results
+       - Add ability to load end-results as well
+       - Add ability to request graphs of whatever graphing options
+         the fio job included.
+       - Add ability to graph completion latencies, percentiles, etc.
+
+- Add ability to edit job options:
+
+       - We need an options view after sending a job, that allows us to
+         visually see what was parsed, make changes, resubmit.
+
+       - Job options are already converted across the network and
+         are available in gfio_client->o for view/edit. We'll need
+         a FIO_NET_CMD_UPDATE_OPTIONS command to send them back,
+         and backend support for updating an existing set of options.
+
+- Add support for printing end results, graphs, etc.
+
+- Improve the auto-start backend functionality, it's quite buggy.
+
+- Ensure that it works on OSX and Windows. We'll need a bit of porting
+  work there.
+
+- Persistent store of prefences set. This will need a per-OS bit as well,
+  using gfonf on Linux, registry on Windows, ?? on OSX.
+
+- Ensure that local errors go to our log, instead of being displayed on
+  the console.
+
+- Ensure that the whole connect/send/start button logic is sane. Right
+  now it works when you perform the right sequence, but if you connect
+  and disconnect, things can get confused. We'll need to improve how
+  we store and send job files. Right now they are in ge->job_files[]
+  and are always emptied on send. Keep them around?
+
+- Commit rate display is not enabled.
+
+- Group status reporting is not enabled.
+
+- Split gfio.c a bit. Add gfio/ sub directory, and split it into
+  files based on functionality. It's already ~3000 lines long.
+
+- Attempt to ensure that we work with gtk 2.10 and newer. Right
+  now the required version is ~2.18 (not quite known).
diff --git a/HOWTO b/HOWTO
index ee9680a592b77120c9967b722448e49c54ea01f2..1fb30db656e4c722dab850c8de6863ec62fe3f97 100644 (file)
--- a/HOWTO
+++ b/HOWTO
@@ -1165,12 +1165,6 @@ exec_postrun=str After the job completes, issue the command specified
 ioscheduler=str        Attempt to switch the device hosting the file to the specified
                io scheduler before running.
 
-cpuload=int    If the job is a CPU cycle eater, attempt to use the specified
-               percentage of CPU cycles.
-
-cpuchunks=int  If the job is a CPU cycle eater, split the load into
-               cycles of the given time. In microseconds.
-
 disk_util=bool Generate disk utilization statistics, if the platform
                supports it. Defaults to on.
 
@@ -1314,6 +1308,11 @@ that defines them is selected.
                enabled when polling for a minimum of 0 events (eg when
                iodepth_batch_complete=0).
 
+[cpu] cpuload=int Attempt to use the specified percentage of CPU cycles.
+
+[cpu] cpuchunks=int Split the load into cycles of the given time. In
+               microseconds.
+
 [netsplice] hostname=str
 [net] hostname=str The host name or IP address to use for TCP or UDP based IO.
                If the job is a TCP listener or UDP reader, the hostname is not
index ccfa802bfb9bfc74158b3fe5ffa4171ec1297e07..d851640ac38eda2c39da25b9528fbab02268b06f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,18 +4,21 @@ CPPFLAGS= -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \
        $(DEBUGFLAGS)
 OPTFLAGS= -O3 -fno-omit-frame-pointer -g $(EXTFLAGS)
 CFLAGS = -std=gnu99 -Wwrite-strings -Wall $(OPTFLAGS)
-LIBS   = -lm $(EXTLIBS)
+LIBS   = -lm -lz $(EXTLIBS)
 PROGS  = fio
 SCRIPTS = fio_generate_plots
 UNAME  := $(shell uname)
 
-SOURCE := gettime.c fio.c ioengines.c init.c stat.c log.c time.c filesetup.c \
+GTK_CFLAGS = `pkg-config --cflags gtk+-2.0 gthread-2.0`
+GTK_LDFLAGS = `pkg-config --libs gtk+-2.0 gthread-2.0`
+
+SOURCE := gettime.c ioengines.c init.c stat.c log.c time.c filesetup.c \
                eta.c verify.c memory.c io_u.c parse.c mutex.c options.c \
-               rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \
+               lib/rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \
                lib/num2str.c lib/ieee754.c $(wildcard crc/*.c) engines/cpu.c \
                engines/mmap.c engines/sync.c engines/null.c engines/net.c \
                memalign.c server.c client.c iolog.c backend.c libfio.c flow.c \
-               json.c
+               cconv.c lib/prio_tree.c json.c
 
 ifeq ($(UNAME), Linux)
   SOURCE += diskutil.c fifo.c blktrace.c helpers.c cgroup.c trim.c \
@@ -67,13 +70,16 @@ ifneq (,$(findstring CYGWIN,$(UNAME)))
 endif
 
 OBJS = $(SOURCE:.c=.o)
+FIO_OBJS = $(OBJS) fio.o
+GFIO_OBJS = $(OBJS) gfio.o graph.o tickmarks.o ghelpers.o goptions.o gerror.o \
+                       gclient.o gcompat.o cairo_text_helpers.o printing.o
 
 T_SMALLOC_OBJS = t/stest.o
-T_SMALLOC_OBJS += mutex.o smalloc.o t/log.o
+T_SMALLOC_OBJS += mutex.o smalloc.o t/log.o gettime.o time.o
 T_SMALLOC_PROGS = t/stest
 
 T_IEEE_OBJS = t/ieee754.o
-T_IEEE_OBJS += ieee754.o
+T_IEEE_OBJS += lib/ieee754.o
 T_IEEE_PROGS = t/ieee754
 
 T_OBJS = $(T_SMALLOC_OBJS)
@@ -113,14 +119,44 @@ CFLAGS += -DFIO_VERSION='"$(FIO_VERSION)"'
 init.o: FIO-VERSION-FILE
        $(QUIET_CC)$(CC) -o init.o -c $(CFLAGS) $(CPPFLAGS) -c init.c
 
+gcompat.o: gcompat.c gcompat.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gcompat.c
+
+goptions.o: goptions.c goptions.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c goptions.c
+
+ghelpers.o: ghelpers.c ghelpers.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c ghelpers.c
+
+gerror.o: gerror.c gerror.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gerror.c
+
+gclient.o: gclient.c gclient.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gclient.c
+
+gfio.o: gfio.c ghelpers.c
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gfio.c
+
+graph.o: graph.c graph.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c graph.c
+
+cairo_text_helpers.o: cairo_text_helpers.c cairo_text_helpers.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c cairo_text_helpers.c
+
+printing.o: printing.c printing.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c printing.c
+
 t/stest: $(T_SMALLOC_OBJS)
        $(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_SMALLOC_OBJS) $(LIBS) $(LDFLAGS)
 
 t/ieee754: $(T_IEEE_OBJS)
        $(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_IEEE_OBJS) $(LIBS) $(LDFLAGS)
 
-fio: $(OBJS)
-       $(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(LDFLAGS)
+fio: $(FIO_OBJS)
+       $(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(FIO_OBJS) $(LIBS) $(LDFLAGS)
+
+gfio: $(GFIO_OBJS)
+       $(QUIET_CC)$(CC) $(LIBS) -o gfio $(GFIO_OBJS) $(LIBS) $(GTK_LDFLAGS)
 
 .depend: $(SOURCE)
        $(QUIET_DEP)$(CC) -MM $(CFLAGS) $(CPPFLAGS) $(SOURCE) 1> .depend
@@ -128,7 +164,7 @@ fio: $(OBJS)
 $(PROGS): .depend
 
 clean: FORCE
-       -rm -f .depend $(OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) core.* core FIO-VERSION-FILE
+       -rm -f .depend $(GFIO_OBJS )$(OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) core.* core gfio FIO-VERSION-FILE
 
 cscope:
        @cscope -b -R
@@ -143,3 +179,5 @@ install: $(PROGS) $(SCRIPTS) FORCE
 ifneq ($(wildcard .depend),)
 include .depend
 endif
+
+
index 4e3a3ed5701f8227cabddf3bf3af0a6b0750ee9a..974384c17fb04d1dc10bea616835595496a29fe4 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -57,13 +57,13 @@ static struct flist_head *cgroup_list;
 static char *cgroup_mnt;
 static int exit_value;
 static volatile int fio_abort;
+static unsigned int nr_process = 0;
+static unsigned int nr_thread = 0;
 
 struct io_log *agg_io_log[DDIR_RWDIR_CNT];
 
 int groupid = 0;
 unsigned int thread_number = 0;
-unsigned int nr_process = 0;
-unsigned int nr_thread = 0;
 int shm_id = 0;
 int temp_stall_ts;
 unsigned long done_secs = 0;
@@ -985,10 +985,12 @@ static void *thread_main(void *data)
 {
        unsigned long long elapsed;
        struct thread_data *td = data;
+       struct thread_options *o = &td->o;
        pthread_condattr_t attr;
        int clear_state;
+       int ret;
 
-       if (!td->o.use_thread) {
+       if (!o->use_thread) {
                setsid();
                td->pid = getpid();
        } else
@@ -996,6 +998,9 @@ static void *thread_main(void *data)
 
        dprint(FD_PROCESS, "jobs pid=%d started\n", (int) td->pid);
 
+       if (is_backend)
+               fio_server_send_start(td);
+
        INIT_FLIST_HEAD(&td->io_u_freelist);
        INIT_FLIST_HEAD(&td->io_u_busylist);
        INIT_FLIST_HEAD(&td->io_u_requeues);
@@ -1022,16 +1027,17 @@ static void *thread_main(void *data)
         * eating a file descriptor
         */
        fio_mutex_remove(td->mutex);
+       td->mutex = NULL;
 
        /*
         * A new gid requires privilege, so we need to do this before setting
         * the uid.
         */
-       if (td->o.gid != -1U && setgid(td->o.gid)) {
+       if (o->gid != -1U && setgid(o->gid)) {
                td_verror(td, errno, "setgid");
                goto err;
        }
-       if (td->o.uid != -1U && setuid(td->o.uid)) {
+       if (o->uid != -1U && setuid(o->uid)) {
                td_verror(td, errno, "setuid");
                goto err;
        }
@@ -1040,18 +1046,24 @@ static void *thread_main(void *data)
         * If we have a gettimeofday() thread, make sure we exclude that
         * thread from this job
         */
-       if (td->o.gtod_cpu)
-               fio_cpu_clear(&td->o.cpumask, td->o.gtod_cpu);
+       if (o->gtod_cpu)
+               fio_cpu_clear(&o->cpumask, o->gtod_cpu);
 
        /*
         * Set affinity first, in case it has an impact on the memory
         * allocations.
         */
-       if (td->o.cpumask_set && fio_setaffinity(td->pid, td->o.cpumask) == -1) {
-               td_verror(td, errno, "cpu_set_affinity");
-               goto err;
+       if (o->cpumask_set) {
+               ret = fio_setaffinity(td->pid, o->cpumask);
+               if (ret == -1) {
+                       td_verror(td, errno, "cpu_set_affinity");
+                       goto err;
+               }
        }
 
+       if (fio_pin_memory(td))
+               goto err;
+
        /*
         * May alter parameters that init_io_u() will use, so we need to
         * do this first.
@@ -1062,11 +1074,12 @@ static void *thread_main(void *data)
        if (init_io_u(td))
                goto err;
 
-       if (td->o.verify_async && verify_async_init(td))
+       if (o->verify_async && verify_async_init(td))
                goto err;
 
-       if (td->ioprio_set) {
-               if (ioprio_set(IOPRIO_WHO_PROCESS, 0, td->ioprio) == -1) {
+       if (o->ioprio) {
+               ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio);
+               if (ret == -1) {
                        td_verror(td, errno, "ioprio_set");
                        goto err;
                }
@@ -1076,15 +1089,15 @@ static void *thread_main(void *data)
                goto err;
 
        errno = 0;
-       if (nice(td->o.nice) == -1 && errno != 0) {
+       if (nice(o->nice) == -1 && errno != 0) {
                td_verror(td, errno, "nice");
                goto err;
        }
 
-       if (td->o.ioscheduler && switch_ioscheduler(td))
+       if (o->ioscheduler && switch_ioscheduler(td))
                goto err;
 
-       if (!td->o.create_serialize && setup_files(td))
+       if (!o->create_serialize && setup_files(td))
                goto err;
 
        if (td_io_init(td))
@@ -1093,16 +1106,16 @@ static void *thread_main(void *data)
        if (init_random_map(td))
                goto err;
 
-       if (td->o.exec_prerun) {
-               if (exec_string(td->o.exec_prerun))
-                       goto err;
-       }
+       if (o->exec_prerun && exec_string(o->exec_prerun))
+               goto err;
 
-       if (td->o.pre_read) {
+       if (o->pre_read) {
                if (pre_read_files(td) < 0)
                        goto err;
        }
 
+       fio_verify_init(td);
+
        fio_gettime(&td->epoch, NULL);
        getrusage(RUSAGE_SELF, &td->ru_start);
 
@@ -1174,6 +1187,8 @@ static void *thread_main(void *data)
        td->ts.io_bytes[DDIR_WRITE] = td->io_bytes[DDIR_WRITE];
        td->ts.io_bytes[DDIR_TRIM] = td->io_bytes[DDIR_TRIM];
 
+       fio_unpin_memory(td);
+
        fio_mutex_down(writeout_mutex);
        if (td->bw_log) {
                if (td->o.bw_log_file) {
@@ -1231,8 +1246,8 @@ err:
        cleanup_io_u(td);
        cgroup_shutdown(td, &cgroup_mnt);
 
-       if (td->o.cpumask_set) {
-               int ret = fio_cpuset_exit(&td->o.cpumask);
+       if (o->cpumask_set) {
+               int ret = fio_cpuset_exit(&o->cpumask);
 
                td_verror(td, ret, "fio_cpuset_exit");
        }
@@ -1386,14 +1401,19 @@ static void run_threads(void)
        unsigned long spent;
        unsigned int i, todo, nr_running, m_rate, t_rate, nr_started;
 
-       if (fio_pin_memory())
-               return;
-
        if (fio_gtod_offload && fio_start_gtod_thread())
                return;
 
        set_sig_handlers();
 
+       nr_thread = nr_process = 0;
+       for_each_td(td, i) {
+               if (td->o.use_thread)
+                       nr_thread++;
+               else
+                       nr_process++;
+       }
+
        if (output_format == FIO_OUTPUT_NORMAL) {
                log_info("Starting ");
                if (nr_thread)
@@ -1593,25 +1613,16 @@ static void run_threads(void)
 
                reap_threads(&nr_running, &t_rate, &m_rate);
 
-               if (todo) {
-                       if (is_backend)
-                               fio_server_idle_loop();
-                       else
-                               usleep(100000);
-               }
+               if (todo)
+                       usleep(100000);
        }
 
        while (nr_running) {
                reap_threads(&nr_running, &t_rate, &m_rate);
-
-               if (is_backend)
-                       fio_server_idle_loop();
-               else
-                       usleep(10000);
+               usleep(10000);
        }
 
        update_io_ticks();
-       fio_unpin_memory();
 }
 
 void wait_for_disk_thread_exit(void)
@@ -1689,9 +1700,9 @@ int fio_backend(void)
                return 0;
 
        if (write_bw_log) {
-               setup_log(&agg_io_log[DDIR_READ], 0);
-               setup_log(&agg_io_log[DDIR_WRITE], 0);
-               setup_log(&agg_io_log[DDIR_TRIM], 0);
+               setup_log(&agg_io_log[DDIR_READ], 0, IO_LOG_TYPE_BW);
+               setup_log(&agg_io_log[DDIR_WRITE], 0, IO_LOG_TYPE_BW);
+               setup_log(&agg_io_log[DDIR_TRIM], 0, IO_LOG_TYPE_BW);
        }
 
        startup_mutex = fio_mutex_init(FIO_MUTEX_LOCKED);
diff --git a/cairo_text_helpers.c b/cairo_text_helpers.c
new file mode 100644 (file)
index 0000000..19fb8e0
--- /dev/null
@@ -0,0 +1,85 @@
+#include <cairo.h>
+#include <gtk/gtk.h>
+#include <math.h>
+
+static void draw_aligned_text(cairo_t *cr, const char *font, double x, double y,
+                              double fontsize, const char *text, int alignment)
+{
+#define CENTERED 0
+#define LEFT_JUSTIFIED 1
+#define RIGHT_JUSTIFIED 2
+
+       double factor, direction;
+       cairo_text_extents_t extents;
+
+       switch (alignment) {
+               case CENTERED:
+                       direction = -1.0;
+                       factor = 0.5;
+                       break;
+               case RIGHT_JUSTIFIED:
+                       direction = -1.0;
+                       factor = 1.0;
+                       break;
+               case LEFT_JUSTIFIED:
+               default:
+                       direction = 1.0;
+                       factor = 0.0;
+                       break;
+       }
+       cairo_select_font_face(cr, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+
+       cairo_set_font_size(cr, fontsize);
+       cairo_text_extents(cr, text, &extents);
+       x = x + direction * (factor * extents.width  + extents.x_bearing);
+       y = y - (extents.height / 2 + extents.y_bearing);
+
+       cairo_move_to(cr, x, y);
+       cairo_show_text(cr, text);
+}
+
+void draw_centered_text(cairo_t *cr, const char *font, double x, double y,
+                              double fontsize, const char *text)
+{
+       draw_aligned_text(cr, font, x, y, fontsize, text, CENTERED);
+}
+
+void draw_right_justified_text(cairo_t *cr, const char *font,
+                               double x, double y,
+                               double fontsize, const char *text)
+{
+       draw_aligned_text(cr, font, x, y, fontsize, text, RIGHT_JUSTIFIED);
+}
+
+void draw_left_justified_text(cairo_t *cr, const char *font,
+                               double x, double y,
+                               double fontsize, const char *text)
+{
+       draw_aligned_text(cr, font, x, y, fontsize, text, LEFT_JUSTIFIED);
+}
+
+void draw_vertical_centered_text(cairo_t *cr, const char *font, double x,
+                                       double y, double fontsize,
+                                       const char *text)
+{
+       double sx, sy;
+       cairo_text_extents_t extents;
+
+       cairo_select_font_face(cr, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+
+       cairo_set_font_size(cr, fontsize);
+       cairo_text_extents(cr, text, &extents);
+       sx = x;
+       sy = y;
+       y = y + (extents.width / 2.0 + extents.x_bearing);
+       x = x - (extents.height / 2.0 + extents.y_bearing);
+
+       cairo_move_to(cr, x, y);
+       cairo_save(cr);
+       cairo_translate(cr, -sx, -sy);
+       cairo_rotate(cr, -90.0 * M_PI / 180.0);
+       cairo_translate(cr, sx, sy);
+       cairo_show_text(cr, text);
+       cairo_restore(cr);
+}
+
diff --git a/cairo_text_helpers.h b/cairo_text_helpers.h
new file mode 100644 (file)
index 0000000..014001a
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef CAIRO_TEXT_HELPERS_H
+#define CAIRO_TEXT_HELPERS_H
+
+void draw_centered_text(cairo_t *cr, const char *font, double x, double y,
+                              double fontsize, const char *text);
+
+void draw_right_justified_text(cairo_t *cr, const char *font,
+                               double x, double y,
+                               double fontsize, const char *text);
+
+void draw_left_justified_text(cairo_t *cr, const char *font,
+                               double x, double y,
+                               double fontsize, const char *text);
+
+void draw_vertical_centered_text(cairo_t *cr, const char *font, double x,
+                                       double y, double fontsize,
+                                       const char *text);
+#endif
diff --git a/cconv.c b/cconv.c
new file mode 100644 (file)
index 0000000..b023315
--- /dev/null
+++ b/cconv.c
@@ -0,0 +1,402 @@
+#include <string.h>
+
+#include "thread_options.h"
+
+static void string_to_cpu(char **dst, const uint8_t *src)
+{
+       const char *__src = (const char *) src;
+
+       if (strlen(__src))
+               *dst = strdup(__src);
+}
+
+static void string_to_net(uint8_t *dst, const char *src)
+{
+       if (src)
+               strcpy((char *) dst, src);
+       else
+               dst[0] = '\0';
+}
+
+void convert_thread_options_to_cpu(struct thread_options *o,
+                                  struct thread_options_pack *top)
+{
+       int i, j;
+
+       string_to_cpu(&o->description, top->description);
+       string_to_cpu(&o->name, top->name);
+       string_to_cpu(&o->directory, top->directory);
+       string_to_cpu(&o->filename, top->filename);
+       string_to_cpu(&o->opendir, top->opendir);
+       string_to_cpu(&o->ioengine, top->ioengine);
+       string_to_cpu(&o->mmapfile, top->mmapfile);
+       string_to_cpu(&o->read_iolog_file, top->read_iolog_file);
+       string_to_cpu(&o->write_iolog_file, top->write_iolog_file);
+       string_to_cpu(&o->bw_log_file, top->bw_log_file);
+       string_to_cpu(&o->lat_log_file, top->lat_log_file);
+       string_to_cpu(&o->iops_log_file, top->iops_log_file);
+       string_to_cpu(&o->replay_redirect, top->replay_redirect);
+       string_to_cpu(&o->exec_prerun, top->exec_prerun);
+       string_to_cpu(&o->exec_postrun, top->exec_postrun);
+       string_to_cpu(&o->ioscheduler, top->ioscheduler);
+       string_to_cpu(&o->profile, top->profile);
+       string_to_cpu(&o->cgroup, top->cgroup);
+
+       o->td_ddir = le32_to_cpu(top->td_ddir);
+       o->rw_seq = le32_to_cpu(top->rw_seq);
+       o->kb_base = le32_to_cpu(top->kb_base);
+       o->ddir_seq_nr = le32_to_cpu(top->ddir_seq_nr);
+       o->ddir_seq_add = le64_to_cpu(top->ddir_seq_add);
+       o->iodepth = le32_to_cpu(top->iodepth);
+       o->iodepth_low = le32_to_cpu(top->iodepth_low);
+       o->iodepth_batch = le32_to_cpu(top->iodepth_batch);
+       o->iodepth_batch_complete = le32_to_cpu(top->iodepth_batch_complete);
+       o->size = le64_to_cpu(top->size);
+       o->size_percent = le32_to_cpu(top->size_percent);
+       o->fill_device = le32_to_cpu(top->fill_device);
+       o->file_size_low = le64_to_cpu(top->file_size_low);
+       o->file_size_high = le64_to_cpu(top->file_size_high);
+       o->start_offset = le64_to_cpu(top->start_offset);
+
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               o->bs[i] = le32_to_cpu(top->bs[i]);
+               o->ba[i] = le32_to_cpu(top->ba[i]);
+               o->min_bs[i] = le32_to_cpu(top->min_bs[i]);
+               o->max_bs[i] = le32_to_cpu(top->max_bs[i]);
+               o->bssplit_nr[i] = le32_to_cpu(top->bssplit_nr[i]);
+
+               if (o->bssplit_nr[i]) {
+                       o->bssplit[i] = malloc(o->bssplit_nr[i] * sizeof(struct bssplit));
+                       for (j = 0; j < o->bssplit_nr[i]; j++) {
+                               o->bssplit[i][j].bs = le32_to_cpu(top->bssplit[i][j].bs);
+                               o->bssplit[i][j].perc = le32_to_cpu(top->bssplit[i][j].perc);
+                       }
+               }
+
+               o->rwmix[i] = le32_to_cpu(top->rwmix[i]);
+               o->rate[i] = le32_to_cpu(top->rate[i]);
+               o->ratemin[i] = le32_to_cpu(top->ratemin[i]);
+               o->rate_iops[i] = le32_to_cpu(top->rate_iops[i]);
+               o->rate_iops_min[i] = le32_to_cpu(top->rate_iops_min[i]);
+       }
+
+       o->ratecycle = le32_to_cpu(top->ratecycle);
+       o->nr_files = le32_to_cpu(top->nr_files);
+       o->open_files = le32_to_cpu(top->open_files);
+       o->file_lock_mode = le32_to_cpu(top->file_lock_mode);
+       o->lockfile_batch = le32_to_cpu(top->lockfile_batch);
+       o->odirect = le32_to_cpu(top->odirect);
+       o->invalidate_cache = le32_to_cpu(top->invalidate_cache);
+       o->create_serialize = le32_to_cpu(top->create_serialize);
+       o->create_fsync = le32_to_cpu(top->create_fsync);
+       o->create_on_open = le32_to_cpu(top->create_on_open);
+       o->create_only = le32_to_cpu(top->create_only);
+       o->end_fsync = le32_to_cpu(top->end_fsync);
+       o->pre_read = le32_to_cpu(top->pre_read);
+       o->sync_io = le32_to_cpu(top->sync_io);
+       o->verify = le32_to_cpu(top->verify);
+       o->do_verify = le32_to_cpu(top->do_verify);
+       o->verifysort = le32_to_cpu(top->verifysort);
+       o->verify_interval = le32_to_cpu(top->verify_interval);
+       o->verify_offset = le32_to_cpu(top->verify_offset);
+
+       memcpy(o->verify_pattern, top->verify_pattern, MAX_PATTERN_SIZE);
+
+       o->verify_pattern_bytes = le32_to_cpu(top->verify_pattern_bytes);
+       o->verify_fatal = le32_to_cpu(top->verify_fatal);
+       o->verify_dump = le32_to_cpu(top->verify_dump);
+       o->verify_async = le32_to_cpu(top->verify_async);
+       o->verify_batch = le32_to_cpu(top->verify_batch);
+       o->use_thread = le32_to_cpu(top->use_thread);
+       o->unlink = le32_to_cpu(top->unlink);
+       o->do_disk_util = le32_to_cpu(top->do_disk_util);
+       o->override_sync = le32_to_cpu(top->override_sync);
+       o->rand_repeatable = le32_to_cpu(top->rand_repeatable);
+       o->use_os_rand = le32_to_cpu(top->use_os_rand);
+       o->log_avg_msec = le32_to_cpu(top->log_avg_msec);
+       o->norandommap = le32_to_cpu(top->norandommap);
+       o->softrandommap = le32_to_cpu(top->softrandommap);
+       o->bs_unaligned = le32_to_cpu(top->bs_unaligned);
+       o->fsync_on_close = le32_to_cpu(top->fsync_on_close);
+       o->hugepage_size = le32_to_cpu(top->hugepage_size);
+       o->rw_min_bs = le32_to_cpu(top->rw_min_bs);
+       o->thinktime = le32_to_cpu(top->thinktime);
+       o->thinktime_spin = le32_to_cpu(top->thinktime_spin);
+       o->thinktime_blocks = le32_to_cpu(top->thinktime_blocks);
+       o->fsync_blocks = le32_to_cpu(top->fsync_blocks);
+       o->fdatasync_blocks = le32_to_cpu(top->fdatasync_blocks);
+       o->barrier_blocks = le32_to_cpu(top->barrier_blocks);
+
+       o->verify_backlog = le64_to_cpu(top->verify_backlog);
+       o->start_delay = le64_to_cpu(top->start_delay);
+       o->timeout = le64_to_cpu(top->timeout);
+       o->ramp_time = le64_to_cpu(top->ramp_time);
+       o->zone_range = le64_to_cpu(top->zone_range);
+       o->zone_size = le64_to_cpu(top->zone_size);
+       o->zone_skip = le64_to_cpu(top->zone_skip);
+       o->lockmem = le64_to_cpu(top->lockmem);
+       o->offset_increment = le64_to_cpu(top->offset_increment);
+
+       o->overwrite = le32_to_cpu(top->overwrite);
+       o->bw_avg_time = le32_to_cpu(top->bw_avg_time);
+       o->iops_avg_time = le32_to_cpu(top->iops_avg_time);
+       o->loops = le32_to_cpu(top->loops);
+       o->mem_type = le32_to_cpu(top->mem_type);
+       o->mem_align = le32_to_cpu(top->mem_align);
+       o->stonewall = le32_to_cpu(top->stonewall);
+       o->new_group = le32_to_cpu(top->new_group);
+       o->numjobs = le32_to_cpu(top->numjobs);
+       o->cpumask_set = le32_to_cpu(top->cpumask_set);
+       o->verify_cpumask_set = le32_to_cpu(top->verify_cpumask_set);
+       o->iolog = le32_to_cpu(top->iolog);
+       o->rwmixcycle = le32_to_cpu(top->rwmixcycle);
+       o->nice = le32_to_cpu(top->nice);
+       o->ioprio = le32_to_cpu(top->ioprio);
+       o->ioprio_class = le32_to_cpu(top->ioprio_class);
+       o->file_service_type = le32_to_cpu(top->file_service_type);
+       o->group_reporting = le32_to_cpu(top->group_reporting);
+       o->fadvise_hint = le32_to_cpu(top->fadvise_hint);
+       o->fallocate_mode = le32_to_cpu(top->fallocate_mode);
+       o->zero_buffers = le32_to_cpu(top->zero_buffers);
+       o->refill_buffers = le32_to_cpu(top->refill_buffers);
+       o->scramble_buffers = le32_to_cpu(top->scramble_buffers);
+       o->time_based = le32_to_cpu(top->time_based);
+       o->disable_lat = le32_to_cpu(top->disable_lat);
+       o->disable_clat = le32_to_cpu(top->disable_clat);
+       o->disable_slat = le32_to_cpu(top->disable_slat);
+       o->disable_bw = le32_to_cpu(top->disable_bw);
+       o->gtod_reduce = le32_to_cpu(top->gtod_reduce);
+       o->gtod_cpu = le32_to_cpu(top->gtod_cpu);
+       o->gtod_offload = le32_to_cpu(top->gtod_offload);
+       o->clocksource = le32_to_cpu(top->clocksource);
+       o->no_stall = le32_to_cpu(top->no_stall);
+       o->trim_percentage = le32_to_cpu(top->trim_percentage);
+       o->trim_batch = le32_to_cpu(top->trim_batch);
+       o->trim_zero = le32_to_cpu(top->trim_zero);
+       o->clat_percentiles = le32_to_cpu(top->clat_percentiles);
+       o->overwrite_plist = le32_to_cpu(top->overwrite_plist);
+       o->continue_on_error = le32_to_cpu(top->continue_on_error);
+       o->cgroup_weight = le32_to_cpu(top->cgroup_weight);
+       o->cgroup_nodelete = le32_to_cpu(top->cgroup_nodelete);
+       o->uid = le32_to_cpu(top->uid);
+       o->gid = le32_to_cpu(top->gid);
+       o->flow_id = __le32_to_cpu(top->flow_id);
+       o->flow = __le32_to_cpu(top->flow);
+       o->flow_watermark = __le32_to_cpu(top->flow_watermark);
+       o->flow_sleep = le32_to_cpu(top->flow_sleep);
+       o->sync_file_range = le32_to_cpu(top->sync_file_range);
+       o->compress_percentage = le32_to_cpu(top->compress_percentage);
+       o->compress_chunk = le32_to_cpu(top->compress_chunk);
+
+       o->trim_backlog = le64_to_cpu(top->trim_backlog);
+
+       for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++)
+               o->percentile_list[i].u.f = fio_uint64_to_double(le64_to_cpu(top->percentile_list[i].u.i));
+#if 0
+       uint8_t cpumask[FIO_TOP_STR_MAX];
+       uint8_t verify_cpumask[FIO_TOP_STR_MAX];
+#endif
+}
+
+void convert_thread_options_to_net(struct thread_options_pack *top,
+                                  struct thread_options *o)
+{
+       int i, j;
+
+       string_to_net(top->description, o->description);
+       string_to_net(top->name, o->name);
+       string_to_net(top->directory, o->directory);
+       string_to_net(top->filename, o->filename);
+       string_to_net(top->opendir, o->opendir);
+       string_to_net(top->ioengine, o->ioengine);
+       string_to_net(top->mmapfile, o->mmapfile);
+       string_to_net(top->read_iolog_file, o->read_iolog_file);
+       string_to_net(top->write_iolog_file, o->write_iolog_file);
+       string_to_net(top->bw_log_file, o->bw_log_file);
+       string_to_net(top->lat_log_file, o->lat_log_file);
+       string_to_net(top->iops_log_file, o->iops_log_file);
+       string_to_net(top->replay_redirect, o->replay_redirect);
+       string_to_net(top->exec_prerun, o->exec_prerun);
+       string_to_net(top->exec_postrun, o->exec_postrun);
+       string_to_net(top->ioscheduler, o->ioscheduler);
+       string_to_net(top->profile, o->profile);
+       string_to_net(top->cgroup, o->cgroup);
+
+       top->td_ddir = cpu_to_le32(o->td_ddir);
+       top->rw_seq = cpu_to_le32(o->rw_seq);
+       top->kb_base = cpu_to_le32(o->kb_base);
+       top->ddir_seq_nr = cpu_to_le32(o->ddir_seq_nr);
+       top->iodepth = cpu_to_le32(o->iodepth);
+       top->iodepth_low = cpu_to_le32(o->iodepth_low);
+       top->iodepth_batch = cpu_to_le32(o->iodepth_batch);
+       top->iodepth_batch_complete = cpu_to_le32(o->iodepth_batch_complete);
+       top->size_percent = cpu_to_le32(o->size_percent);
+       top->fill_device = cpu_to_le32(o->fill_device);
+       top->ratecycle = cpu_to_le32(o->ratecycle);
+       top->nr_files = cpu_to_le32(o->nr_files);
+       top->open_files = cpu_to_le32(o->open_files);
+       top->file_lock_mode = cpu_to_le32(o->file_lock_mode);
+       top->lockfile_batch = cpu_to_le32(o->lockfile_batch);
+       top->odirect = cpu_to_le32(o->odirect);
+       top->invalidate_cache = cpu_to_le32(o->invalidate_cache);
+       top->create_serialize = cpu_to_le32(o->create_serialize);
+       top->create_fsync = cpu_to_le32(o->create_fsync);
+       top->create_on_open = cpu_to_le32(o->create_on_open);
+       top->create_only = cpu_to_le32(o->create_only);
+       top->end_fsync = cpu_to_le32(o->end_fsync);
+       top->pre_read = cpu_to_le32(o->pre_read);
+       top->sync_io = cpu_to_le32(o->sync_io);
+       top->verify = cpu_to_le32(o->verify);
+       top->do_verify = cpu_to_le32(o->do_verify);
+       top->verifysort = cpu_to_le32(o->verifysort);
+       top->verify_interval = cpu_to_le32(o->verify_interval);
+       top->verify_offset = cpu_to_le32(o->verify_offset);
+       top->verify_pattern_bytes = cpu_to_le32(o->verify_pattern_bytes);
+       top->verify_fatal = cpu_to_le32(o->verify_fatal);
+       top->verify_dump = cpu_to_le32(o->verify_dump);
+       top->verify_async = cpu_to_le32(o->verify_async);
+       top->verify_batch = cpu_to_le32(o->verify_batch);
+       top->use_thread = cpu_to_le32(o->use_thread);
+       top->unlink = cpu_to_le32(o->unlink);
+       top->do_disk_util = cpu_to_le32(o->do_disk_util);
+       top->override_sync = cpu_to_le32(o->override_sync);
+       top->rand_repeatable = cpu_to_le32(o->rand_repeatable);
+       top->use_os_rand = cpu_to_le32(o->use_os_rand);
+       top->log_avg_msec = cpu_to_le32(o->log_avg_msec);
+       top->norandommap = cpu_to_le32(o->norandommap);
+       top->softrandommap = cpu_to_le32(o->softrandommap);
+       top->bs_unaligned = cpu_to_le32(o->bs_unaligned);
+       top->fsync_on_close = cpu_to_le32(o->fsync_on_close);
+       top->hugepage_size = cpu_to_le32(o->hugepage_size);
+       top->rw_min_bs = cpu_to_le32(o->rw_min_bs);
+       top->thinktime = cpu_to_le32(o->thinktime);
+       top->thinktime_spin = cpu_to_le32(o->thinktime_spin);
+       top->thinktime_blocks = cpu_to_le32(o->thinktime_blocks);
+       top->fsync_blocks = cpu_to_le32(o->fsync_blocks);
+       top->fdatasync_blocks = cpu_to_le32(o->fdatasync_blocks);
+       top->barrier_blocks = cpu_to_le32(o->barrier_blocks);
+       top->overwrite = cpu_to_le32(o->overwrite);
+       top->bw_avg_time = cpu_to_le32(o->bw_avg_time);
+       top->iops_avg_time = cpu_to_le32(o->iops_avg_time);
+       top->loops = cpu_to_le32(o->loops);
+       top->mem_type = cpu_to_le32(o->mem_type);
+       top->mem_align = cpu_to_le32(o->mem_align);
+       top->stonewall = cpu_to_le32(o->stonewall);
+       top->new_group = cpu_to_le32(o->new_group);
+       top->numjobs = cpu_to_le32(o->numjobs);
+       top->cpumask_set = cpu_to_le32(o->cpumask_set);
+       top->verify_cpumask_set = cpu_to_le32(o->verify_cpumask_set);
+       top->iolog = cpu_to_le32(o->iolog);
+       top->rwmixcycle = cpu_to_le32(o->rwmixcycle);
+       top->nice = cpu_to_le32(o->nice);
+       top->ioprio = cpu_to_le32(o->ioprio);
+       top->ioprio_class = cpu_to_le32(o->ioprio_class);
+       top->file_service_type = cpu_to_le32(o->file_service_type);
+       top->group_reporting = cpu_to_le32(o->group_reporting);
+       top->fadvise_hint = cpu_to_le32(o->fadvise_hint);
+       top->fallocate_mode = cpu_to_le32(o->fallocate_mode);
+       top->zero_buffers = cpu_to_le32(o->zero_buffers);
+       top->refill_buffers = cpu_to_le32(o->refill_buffers);
+       top->scramble_buffers = cpu_to_le32(o->scramble_buffers);
+       top->time_based = cpu_to_le32(o->time_based);
+       top->disable_lat = cpu_to_le32(o->disable_lat);
+       top->disable_clat = cpu_to_le32(o->disable_clat);
+       top->disable_slat = cpu_to_le32(o->disable_slat);
+       top->disable_bw = cpu_to_le32(o->disable_bw);
+       top->gtod_reduce = cpu_to_le32(o->gtod_reduce);
+       top->gtod_cpu = cpu_to_le32(o->gtod_cpu);
+       top->gtod_offload = cpu_to_le32(o->gtod_offload);
+       top->clocksource = cpu_to_le32(o->clocksource);
+       top->no_stall = cpu_to_le32(o->no_stall);
+       top->trim_percentage = cpu_to_le32(o->trim_percentage);
+       top->trim_batch = cpu_to_le32(o->trim_batch);
+       top->trim_zero = cpu_to_le32(o->trim_zero);
+       top->clat_percentiles = cpu_to_le32(o->clat_percentiles);
+       top->overwrite_plist = cpu_to_le32(o->overwrite_plist);
+       top->continue_on_error = cpu_to_le32(o->continue_on_error);
+       top->cgroup_weight = cpu_to_le32(o->cgroup_weight);
+       top->cgroup_nodelete = cpu_to_le32(o->cgroup_nodelete);
+       top->uid = cpu_to_le32(o->uid);
+       top->gid = cpu_to_le32(o->gid);
+       top->flow_id = __cpu_to_le32(o->flow_id);
+       top->flow = __cpu_to_le32(o->flow);
+       top->flow_watermark = __cpu_to_le32(o->flow_watermark);
+       top->flow_sleep = cpu_to_le32(o->flow_sleep);
+       top->sync_file_range = cpu_to_le32(o->sync_file_range);
+       top->compress_percentage = cpu_to_le32(o->compress_percentage);
+       top->compress_chunk = cpu_to_le32(o->compress_chunk);
+
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               top->bs[i] = cpu_to_le32(o->bs[i]);
+               top->ba[i] = cpu_to_le32(o->ba[i]);
+               top->min_bs[i] = cpu_to_le32(o->min_bs[i]);
+               top->max_bs[i] = cpu_to_le32(o->max_bs[i]);
+               top->bssplit_nr[i] = cpu_to_le32(o->bssplit_nr[i]);
+
+               if (o->bssplit_nr[i]) {
+                       unsigned int bssplit_nr = o->bssplit_nr[i];
+
+                       if (bssplit_nr > BSSPLIT_MAX) {
+                               log_err("fio: BSSPLIT_MAX is too small\n");
+                               bssplit_nr = BSSPLIT_MAX;
+                       }
+                       for (j = 0; j < bssplit_nr; j++) {
+                               top->bssplit[i][j].bs = cpu_to_le32(o->bssplit[i][j].bs);
+                               top->bssplit[i][j].perc = cpu_to_le32(o->bssplit[i][j].perc);
+                       }
+               }
+
+               top->rwmix[i] = cpu_to_le32(o->rwmix[i]);
+               top->rate[i] = cpu_to_le32(o->rate[i]);
+               top->ratemin[i] = cpu_to_le32(o->ratemin[i]);
+               top->rate_iops[i] = cpu_to_le32(o->rate_iops[i]);
+               top->rate_iops_min[i] = cpu_to_le32(o->rate_iops_min[i]);
+       }
+
+       memcpy(top->verify_pattern, o->verify_pattern, MAX_PATTERN_SIZE);
+
+       top->size = __cpu_to_le64(o->size);
+       top->verify_backlog = __cpu_to_le64(o->verify_backlog);
+       top->start_delay = __cpu_to_le64(o->start_delay);
+       top->timeout = __cpu_to_le64(o->timeout);
+       top->ramp_time = __cpu_to_le64(o->ramp_time);
+       top->zone_range = __cpu_to_le64(o->zone_range);
+       top->zone_size = __cpu_to_le64(o->zone_size);
+       top->zone_skip = __cpu_to_le64(o->zone_skip);
+       top->lockmem = __cpu_to_le64(o->lockmem);
+       top->ddir_seq_add = __cpu_to_le64(o->ddir_seq_add);
+       top->file_size_low = __cpu_to_le64(o->file_size_low);
+       top->file_size_high = __cpu_to_le64(o->file_size_high);
+       top->start_offset = __cpu_to_le64(o->start_offset);
+       top->trim_backlog = __cpu_to_le64(o->trim_backlog);
+       top->offset_increment = __cpu_to_le64(o->offset_increment);
+
+       for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++)
+               top->percentile_list[i].u.i = __cpu_to_le64(fio_double_to_uint64(o->percentile_list[i].u.f));
+#if 0
+       uint8_t cpumask[FIO_TOP_STR_MAX];
+       uint8_t verify_cpumask[FIO_TOP_STR_MAX];
+#endif
+
+}
+
+/*
+ * Basic conversion test. We'd really need to fill in more of the options
+ * to have a thorough test. Even better, we should auto-generate the
+ * converter functions...
+ */
+int fio_test_cconv(struct thread_options *__o)
+{
+       struct thread_options o;
+       struct thread_options_pack top1, top2;
+
+       memset(&top1, 0, sizeof(top1));
+       memset(&top2, 0, sizeof(top2));
+
+       convert_thread_options_to_net(&top1, __o);
+       memset(&o, 0, sizeof(o));
+       convert_thread_options_to_cpu(&o, &top1);
+       convert_thread_options_to_net(&top2, &o);
+
+       return memcmp(&top1, &top2, sizeof(top1));
+}
index bf09d7ef4dadad7a343ecda88a80aaca4e2053fe..7b8dc61e4365aa4fd191d4ad0adf084b950ba037 100644 (file)
--- a/client.c
+++ b/client.c
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <signal.h>
+#include <zlib.h>
 
 #include "fio.h"
+#include "client.h"
 #include "server.h"
 #include "flist.h"
 #include "hash.h"
 
-struct client_eta {
-       struct jobs_eta eta;
-       unsigned int pending;
-};
-
-struct fio_client {
-       struct flist_head list;
-       struct flist_head hash_list;
-       struct flist_head arg_list;
-       union {
-               struct sockaddr_in addr;
-               struct sockaddr_in6 addr6;
-               struct sockaddr_un addr_un;
-       };
-       char *hostname;
-       int port;
-       int fd;
-       unsigned int refs;
-
-       char *name;
-
-       int state;
-
-       int skip_newline;
-       int is_sock;
-       int disk_stats_shown;
-       unsigned int jobs;
-       int error;
-       int ipv6;
-       int sent_job;
-
-       struct flist_head eta_list;
-       struct client_eta *eta_in_flight;
-
-       struct flist_head cmd_list;
-
-       uint16_t argc;
-       char **argv;
-
-       char **ini_file;
-       unsigned int nr_ini_file;
+static void handle_du(struct fio_client *client, struct fio_net_cmd *cmd);
+static void handle_ts(struct fio_client *client, struct fio_net_cmd *cmd);
+static void handle_gs(struct fio_client *client, struct fio_net_cmd *cmd);
+static void handle_probe(struct fio_client *client, struct fio_net_cmd *cmd);
+static void handle_text(struct fio_client *client, struct fio_net_cmd *cmd);
+static void handle_stop(struct fio_client *client, struct fio_net_cmd *cmd);
+static void handle_start(struct fio_client *client, struct fio_net_cmd *cmd);
+
+struct client_ops fio_client_ops = {
+       .text           = handle_text,
+       .disk_util      = handle_du,
+       .thread_status  = handle_ts,
+       .group_stats    = handle_gs,
+       .stop           = handle_stop,
+       .start          = handle_start,
+       .eta            = display_thread_status,
+       .probe          = handle_probe,
+       .eta_msec       = FIO_CLIENT_DEF_ETA_MSEC,
+       .client_type    = FIO_CLIENT_TYPE_CLI,
 };
 
 static struct timeval eta_tv;
 
-enum {
-       Client_created          = 0,
-       Client_connected        = 1,
-       Client_started          = 2,
-       Client_running          = 3,
-       Client_stopped          = 4,
-       Client_exited           = 5,
-};
-
 static FLIST_HEAD(client_list);
 static FLIST_HEAD(eta_list);
 
 static FLIST_HEAD(arg_list);
 
-static struct thread_stat client_ts;
-static struct group_run_stats client_gs;
-static int sum_stat_clients;
+struct thread_stat client_ts;
+struct group_run_stats client_gs;
+int sum_stat_clients;
+
 static int sum_stat_nr;
 
 #define FIO_CLIENT_HASH_BITS   7
@@ -89,9 +61,6 @@ static int sum_stat_nr;
 #define FIO_CLIENT_HASH_MASK   (FIO_CLIENT_HASH_SZ - 1)
 static struct flist_head client_hash[FIO_CLIENT_HASH_SZ];
 
-static int handle_client(struct fio_client *client);
-static void dec_jobs_eta(struct client_eta *eta);
-
 static void fio_client_add_hash(struct fio_client *client)
 {
        int bucket = hash_long(client->fd, FIO_CLIENT_HASH_BITS);
@@ -132,23 +101,11 @@ static struct fio_client *find_client_by_fd(int fd)
        return NULL;
 }
 
-static void remove_client(struct fio_client *client)
+void fio_put_client(struct fio_client *client)
 {
-       assert(client->refs);
-
        if (--client->refs)
                return;
 
-       dprint(FD_NET, "client: removed <%s>\n", client->hostname);
-       flist_del(&client->list);
-
-       fio_client_remove_hash(client);
-
-       if (!flist_empty(&client->eta_list)) {
-               flist_del_init(&client->eta_list);
-               dec_jobs_eta(client->eta_in_flight);
-       }
-
        free(client->hostname);
        if (client->argv)
                free(client->argv);
@@ -160,13 +117,40 @@ static void remove_client(struct fio_client *client)
                free(client->ini_file);
 
        free(client);
+}
+
+static void remove_client(struct fio_client *client)
+{
+       assert(client->refs);
+
+       dprint(FD_NET, "client: removed <%s>\n", client->hostname);
+
+       if (!flist_empty(&client->list))
+               flist_del_init(&client->list);
+
+       fio_client_remove_hash(client);
+
+       if (!flist_empty(&client->eta_list)) {
+               flist_del_init(&client->eta_list);
+               fio_client_dec_jobs_eta(client->eta_in_flight, client->ops->eta);
+       }
+
+       close(client->fd);
+       client->fd = -1;
+
+       if (client->ops->removed)
+               client->ops->removed(client);
+
        nr_clients--;
        sum_stat_clients--;
+
+       fio_put_client(client);
 }
 
-static void put_client(struct fio_client *client)
+struct fio_client *fio_get_client(struct fio_client *client)
 {
-       remove_client(client);
+       client->refs++;
+       return client;
 }
 
 static void __fio_client_add_cmd_option(struct fio_client *client,
@@ -200,6 +184,53 @@ void fio_client_add_cmd_option(void *cookie, const char *opt)
        }
 }
 
+struct fio_client *fio_client_add_explicit(struct client_ops *ops,
+                                          const char *hostname, int type,
+                                          int port)
+{
+       struct fio_client *client;
+
+       client = malloc(sizeof(*client));
+       memset(client, 0, sizeof(*client));
+
+       INIT_FLIST_HEAD(&client->list);
+       INIT_FLIST_HEAD(&client->hash_list);
+       INIT_FLIST_HEAD(&client->arg_list);
+       INIT_FLIST_HEAD(&client->eta_list);
+       INIT_FLIST_HEAD(&client->cmd_list);
+
+       client->hostname = strdup(hostname);
+
+       if (type == Fio_client_socket)
+               client->is_sock = 1;
+       else {
+               int ipv6;
+
+               ipv6 = type == Fio_client_ipv6;
+               if (fio_server_parse_host(hostname, &ipv6,
+                                               &client->addr.sin_addr,
+                                               &client->addr6.sin6_addr))
+                       goto err;
+
+               client->port = port;
+       }
+
+       client->fd = -1;
+       client->ops = ops;
+       client->refs = 1;
+       client->type = ops->client_type;
+
+       __fio_client_add_cmd_option(client, "fio");
+
+       flist_add(&client->list, &client_list);
+       nr_clients++;
+       dprint(FD_NET, "client: added <%s>\n", client->hostname);
+       return client;
+err:
+       free(client);
+       return NULL;
+}
+
 void fio_client_add_ini_file(void *cookie, const char *ini_file)
 {
        struct fio_client *client = cookie;
@@ -213,7 +244,7 @@ void fio_client_add_ini_file(void *cookie, const char *ini_file)
        client->nr_ini_file++;
 }
 
-int fio_client_add(const char *hostname, void **cookie)
+int fio_client_add(struct client_ops *ops, const char *hostname, void **cookie)
 {
        struct fio_client *existing = *cookie;
        struct fio_client *client;
@@ -248,7 +279,9 @@ int fio_client_add(const char *hostname, void **cookie)
                return -1;
 
        client->fd = -1;
+       client->ops = ops;
        client->refs = 1;
+       client->type = ops->client_type;
 
        __fio_client_add_cmd_option(client, "fio");
 
@@ -259,6 +292,13 @@ int fio_client_add(const char *hostname, void **cookie)
        return 0;
 }
 
+static void probe_client(struct fio_client *client)
+{
+       dprint(FD_NET, "client: send probe\n");
+
+       fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_PROBE, 0, &client->cmd_list);
+}
+
 static int fio_client_connect_ip(struct fio_client *client)
 {
        struct sockaddr *addr;
@@ -281,16 +321,20 @@ static int fio_client_connect_ip(struct fio_client *client)
 
        fd = socket(domain, SOCK_STREAM, 0);
        if (fd < 0) {
+               int ret = -errno;
+
                log_err("fio: socket: %s\n", strerror(errno));
-               return -1;
+               return ret;
        }
 
        if (connect(fd, addr, socklen) < 0) {
+               int ret = -errno;
+
                log_err("fio: connect: %s\n", strerror(errno));
                log_err("fio: failed to connect to %s:%u\n", client->hostname,
                                                                client->port);
                close(fd);
-               return -1;
+               return ret;
        }
 
        return fd;
@@ -308,21 +352,25 @@ static int fio_client_connect_sock(struct fio_client *client)
 
        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd < 0) {
+               int ret = -errno;
+
                log_err("fio: socket: %s\n", strerror(errno));
-               return -1;
+               return ret;
        }
 
        len = sizeof(addr->sun_family) + strlen(addr->sun_path) + 1;
        if (connect(fd, (struct sockaddr *) addr, len) < 0) {
+               int ret = -errno;
+
                log_err("fio: connect; %s\n", strerror(errno));
                close(fd);
-               return -1;
+               return ret;
        }
 
        return fd;
 }
 
-static int fio_client_connect(struct fio_client *client)
+int fio_client_connect(struct fio_client *client)
 {
        int fd;
 
@@ -336,14 +384,21 @@ static int fio_client_connect(struct fio_client *client)
        dprint(FD_NET, "client: %s connected %d\n", client->hostname, fd);
 
        if (fd < 0)
-               return 1;
+               return fd;
 
        client->fd = fd;
        fio_client_add_hash(client);
        client->state = Client_connected;
+
+       probe_client(client);
        return 0;
 }
 
+int fio_client_terminate(struct fio_client *client)
+{
+       return fio_net_send_quit(client->fd);
+}
+
 void fio_clients_terminate(void)
 {
        struct flist_head *entry;
@@ -353,8 +408,7 @@ void fio_clients_terminate(void)
 
        flist_for_each(entry, &client_list) {
                client = flist_entry(entry, struct fio_client, list);
-
-               fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_QUIT, 0, NULL);
+               fio_client_terminate(client);
        }
 }
 
@@ -397,13 +451,6 @@ static void client_signal_handler(void)
        sigaction(SIGUSR1, &act, NULL);
 }
 
-static void probe_client(struct fio_client *client)
-{
-       dprint(FD_NET, "client: send probe\n");
-
-       fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_PROBE, 0, &client->cmd_list);
-}
-
 static int send_client_cmd_line(struct fio_client *client)
 {
        struct cmd_single_line_pdu *cslp;
@@ -446,7 +493,8 @@ static int send_client_cmd_line(struct fio_client *client)
 
        free(lens);
        clp->lines = cpu_to_le16(client->argc);
-       ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOBLINE, pdu, mem, 0);
+       clp->client_type = __cpu_to_le16(client->type);
+       ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOBLINE, pdu, mem, NULL, NULL);
        free(pdu);
        return ret;
 }
@@ -459,7 +507,7 @@ int fio_clients_connect(void)
 
 #ifdef WIN32
        WSADATA wsd;
-       WSAStartup(MAKEWORD(2,2), &wsd);
+       WSAStartup(MAKEWORD(2, 2), &wsd);
 #endif
 
        dprint(FD_NET, "client: connect all\n");
@@ -475,8 +523,6 @@ int fio_clients_connect(void)
                        continue;
                }
 
-               probe_client(client);
-
                if (client->argc > 1)
                        send_client_cmd_line(client);
        }
@@ -484,14 +530,44 @@ int fio_clients_connect(void)
        return !nr_clients;
 }
 
+int fio_start_client(struct fio_client *client)
+{
+       dprint(FD_NET, "client: start %s\n", client->hostname);
+       return fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_RUN, 0, NULL);
+}
+
+int fio_start_all_clients(void)
+{
+       struct fio_client *client;
+       struct flist_head *entry, *tmp;
+       int ret;
+
+       dprint(FD_NET, "client: start all\n");
+
+       flist_for_each_safe(entry, tmp, &client_list) {
+               client = flist_entry(entry, struct fio_client, list);
+
+               ret = fio_start_client(client);
+               if (ret) {
+                       remove_client(client);
+                       continue;
+               }
+       }
+
+       return flist_empty(&client_list);
+}
+
 /*
  * Send file contents to server backend. We could use sendfile(), but to remain
  * more portable lets just read/write the darn thing.
  */
-static int fio_client_send_ini(struct fio_client *client, const char *filename)
+static int __fio_client_send_ini(struct fio_client *client, const char *filename)
 {
+       struct cmd_job_pdu *pdu;
+       size_t p_size;
        struct stat sb;
-       char *p, *buf;
+       char *p;
+       void *buf;
        off_t len;
        int fd, ret;
 
@@ -499,17 +575,23 @@ static int fio_client_send_ini(struct fio_client *client, const char *filename)
 
        fd = open(filename, O_RDONLY);
        if (fd < 0) {
+               int ret = -errno;
+
                log_err("fio: job file <%s> open: %s\n", filename, strerror(errno));
-               return 1;
+               return ret;
        }
 
        if (fstat(fd, &sb) < 0) {
+               int ret = -errno;
+
                log_err("fio: job file stat: %s\n", strerror(errno));
                close(fd);
-               return 1;
+               return ret;
        }
 
-       buf = malloc(sb.st_size);
+       p_size = sb.st_size + sizeof(*pdu);
+       pdu = malloc(p_size);
+       buf = pdu->buf;
 
        len = sb.st_size;
        p = buf;
@@ -534,13 +616,27 @@ static int fio_client_send_ini(struct fio_client *client, const char *filename)
                return 1;
        }
 
+       pdu->buf_len = __cpu_to_le32(sb.st_size);
+       pdu->client_type = cpu_to_le32(client->type);
+
        client->sent_job = 1;
-       ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOB, buf, sb.st_size, 0);
-       free(buf);
+       ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOB, pdu, p_size, NULL, NULL);
+       free(pdu);
        close(fd);
        return ret;
 }
 
+int fio_client_send_ini(struct fio_client *client, const char *filename)
+{
+       int ret;
+
+       ret = __fio_client_send_ini(client, filename);
+       if (!ret)
+               client->sent_job = 1;
+
+       return ret;
+}
+
 int fio_clients_send_ini(const char *filename)
 {
        struct fio_client *client;
@@ -562,13 +658,23 @@ int fio_clients_send_ini(const char *filename)
                        }
                } else if (!filename || fio_client_send_ini(client, filename))
                        remove_client(client);
-
-               client->sent_job = 1;
        }
 
        return !nr_clients;
 }
 
+int fio_client_update_options(struct fio_client *client,
+                             struct thread_options *o, uint64_t *tag)
+{
+       struct cmd_add_job_pdu pdu;
+
+       pdu.thread_number = cpu_to_le32(client->thread_number);
+       pdu.groupid = cpu_to_le32(client->groupid);
+       convert_thread_options_to_net(&pdu.top, o);
+       
+       return fio_net_send_cmd(client->fd, FIO_NET_CMD_UPDATE_JOB, &pdu, sizeof(pdu), tag, &client->cmd_list);
+}
+
 static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
 {
        dst->max_val    = le64_to_cpu(src->max_val);
@@ -586,12 +692,13 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
 {
        int i, j;
 
-       dst->error      = le32_to_cpu(src->error);
-       dst->groupid    = le32_to_cpu(src->groupid);
-       dst->pid        = le32_to_cpu(src->pid);
-       dst->members    = le32_to_cpu(src->members);
+       dst->error              = le32_to_cpu(src->error);
+       dst->thread_number      = le32_to_cpu(src->thread_number);
+       dst->groupid            = le32_to_cpu(src->groupid);
+       dst->pid                = le32_to_cpu(src->pid);
+       dst->members            = le32_to_cpu(src->members);
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                convert_io_stat(&dst->clat_stat[i], &src->clat_stat[i]);
                convert_io_stat(&dst->slat_stat[i], &src->slat_stat[i]);
                convert_io_stat(&dst->lat_stat[i], &src->lat_stat[i]);
@@ -623,7 +730,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
                dst->io_u_lat_m[i]      = le32_to_cpu(src->io_u_lat_m[i]);
        }
 
-       for (i = 0; i < 2; i++)
+       for (i = 0; i < DDIR_RWDIR_CNT; i++)
                for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
                        dst->io_u_plat[i][j] = le32_to_cpu(src->io_u_plat[i][j]);
 
@@ -635,7 +742,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
        dst->total_submit       = le64_to_cpu(src->total_submit);
        dst->total_complete     = le64_to_cpu(src->total_complete);
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                dst->io_bytes[i]        = le64_to_cpu(src->io_bytes[i]);
                dst->runtime[i]         = le64_to_cpu(src->runtime[i]);
        }
@@ -651,7 +758,7 @@ static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
 {
        int i;
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                dst->max_run[i]         = le64_to_cpu(src->max_run[i]);
                dst->min_run[i]         = le64_to_cpu(src->min_run[i]);
                dst->max_bw[i]          = le64_to_cpu(src->max_bw[i]);
@@ -664,13 +771,10 @@ static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
        dst->groupid    = le32_to_cpu(src->groupid);
 }
 
-static void handle_ts(struct fio_net_cmd *cmd)
+static void handle_ts(struct fio_client *client, struct fio_net_cmd *cmd)
 {
        struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
 
-       convert_ts(&p->ts, &p->ts);
-       convert_gs(&p->rs, &p->rs);
-
        show_thread_status(&p->ts, &p->rs);
 
        if (sum_stat_clients == 1)
@@ -680,6 +784,7 @@ static void handle_ts(struct fio_net_cmd *cmd)
        sum_group_stats(&client_gs, &p->rs);
 
        client_ts.members++;
+       client_ts.thread_number = p->ts.thread_number;
        client_ts.groupid = p->ts.groupid;
 
        if (++sum_stat_nr == sum_stat_clients) {
@@ -688,14 +793,29 @@ static void handle_ts(struct fio_net_cmd *cmd)
        }
 }
 
-static void handle_gs(struct fio_net_cmd *cmd)
+static void handle_gs(struct fio_client *client, struct fio_net_cmd *cmd)
 {
        struct group_run_stats *gs = (struct group_run_stats *) cmd->payload;
 
-       convert_gs(gs, gs);
        show_group_stats(gs);
 }
 
+static void handle_text(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct cmd_text_pdu *pdu = (struct cmd_text_pdu *) cmd->payload;
+       const char *buf = (const char *) pdu->buf;
+       const char *name;
+       int fio_unused ret;
+
+       name = client->name ? client->name : client->hostname;
+
+       if (!client->skip_newline)
+               fprintf(f_out, "<%s> ", name);
+       ret = fwrite(buf, pdu->buf_len, 1, f_out);
+       fflush(f_out);
+       client->skip_newline = strchr(buf, '\n') == NULL;
+}
+
 static void convert_agg(struct disk_util_agg *agg)
 {
        int i;
@@ -733,9 +853,6 @@ static void handle_du(struct fio_client *client, struct fio_net_cmd *cmd)
 {
        struct cmd_du_pdu *du = (struct cmd_du_pdu *) cmd->payload;
 
-       convert_dus(&du->dus);
-       convert_agg(&du->agg);
-
        if (!client->disk_stats_shown) {
                client->disk_stats_shown = 1;
                log_info("\nDisk stats (read/write):\n");
@@ -752,22 +869,23 @@ static void convert_jobs_eta(struct jobs_eta *je)
        je->nr_ramp             = le32_to_cpu(je->nr_ramp);
        je->nr_pending          = le32_to_cpu(je->nr_pending);
        je->files_open          = le32_to_cpu(je->files_open);
-       je->m_rate              = le32_to_cpu(je->m_rate);
-       je->t_rate              = le32_to_cpu(je->t_rate);
-       je->m_iops              = le32_to_cpu(je->m_iops);
-       je->t_iops              = le32_to_cpu(je->t_iops);
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               je->m_rate[i]   = le32_to_cpu(je->m_rate[i]);
+               je->t_rate[i]   = le32_to_cpu(je->t_rate[i]);
+               je->m_iops[i]   = le32_to_cpu(je->m_iops[i]);
+               je->t_iops[i]   = le32_to_cpu(je->t_iops[i]);
                je->rate[i]     = le32_to_cpu(je->rate[i]);
                je->iops[i]     = le32_to_cpu(je->iops[i]);
        }
 
        je->elapsed_sec         = le64_to_cpu(je->elapsed_sec);
        je->eta_sec             = le64_to_cpu(je->eta_sec);
+       je->nr_threads          = le32_to_cpu(je->nr_threads);
        je->is_pow2             = le32_to_cpu(je->is_pow2);
 }
 
-static void sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je)
+void fio_client_sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je)
 {
        int i;
 
@@ -775,12 +893,12 @@ static void sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je)
        dst->nr_ramp            += je->nr_ramp;
        dst->nr_pending         += je->nr_pending;
        dst->files_open         += je->files_open;
-       dst->m_rate             += je->m_rate;
-       dst->t_rate             += je->t_rate;
-       dst->m_iops             += je->m_iops;
-       dst->t_iops             += je->t_iops;
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               dst->m_rate[i]  += je->m_rate[i];
+               dst->t_rate[i]  += je->t_rate[i];
+               dst->m_iops[i]  += je->m_iops[i];
+               dst->t_iops[i]  += je->t_iops[i];
                dst->rate[i]    += je->rate[i];
                dst->iops[i]    += je->iops[i];
        }
@@ -789,38 +907,65 @@ static void sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je)
 
        if (je->eta_sec > dst->eta_sec)
                dst->eta_sec = je->eta_sec;
+
+       dst->nr_threads         += je->nr_threads;
+       /* we need to handle je->run_str too ... */
 }
 
-static void dec_jobs_eta(struct client_eta *eta)
+void fio_client_dec_jobs_eta(struct client_eta *eta, client_eta_op eta_fn)
 {
        if (!--eta->pending) {
-               display_thread_status(&eta->eta);
+               eta_fn(&eta->eta);
                free(eta);
        }
 }
 
 static void remove_reply_cmd(struct fio_client *client, struct fio_net_cmd *cmd)
 {
-       struct fio_net_int_cmd *icmd = NULL;
+       struct fio_net_cmd_reply *reply = NULL;
        struct flist_head *entry;
 
        flist_for_each(entry, &client->cmd_list) {
-               icmd = flist_entry(entry, struct fio_net_int_cmd, list);
+               reply = flist_entry(entry, struct fio_net_cmd_reply, list);
 
-               if (cmd->tag == (uintptr_t) icmd)
+               if (cmd->tag == (uintptr_t) reply)
                        break;
 
-               icmd = NULL;
+               reply = NULL;
        }
 
-       if (!icmd) {
-               log_err("fio: client: unable to find matching tag\n");
+       if (!reply) {
+               log_err("fio: client: unable to find matching tag (%lx)\n", cmd->tag);
                return;
        }
 
-       flist_del(&icmd->list);
-       cmd->tag = icmd->saved_tag;
-       free(icmd);
+       flist_del(&reply->list);
+       cmd->tag = reply->saved_tag;
+       free(reply);
+}
+
+int fio_client_wait_for_reply(struct fio_client *client, uint64_t tag)
+{
+       do {
+               struct fio_net_cmd_reply *reply = NULL;
+               struct flist_head *entry;
+
+               flist_for_each(entry, &client->cmd_list) {
+                       reply = flist_entry(entry, struct fio_net_cmd_reply, list);
+
+                       if (tag == (uintptr_t) reply)
+                               break;
+
+                       reply = NULL;
+               }
+
+               if (!reply)
+                       break;
+
+               usleep(1000);
+       } while (1);
+
+       return 0;
 }
 
 static void handle_eta(struct fio_client *client, struct fio_net_cmd *cmd)
@@ -835,9 +980,11 @@ static void handle_eta(struct fio_client *client, struct fio_net_cmd *cmd)
        client->eta_in_flight = NULL;
        flist_del_init(&client->eta_list);
 
-       convert_jobs_eta(je);
-       sum_jobs_eta(&eta->eta, je);
-       dec_jobs_eta(eta);
+       if (client->ops->jobs_eta)
+               client->ops->jobs_eta(client, je);
+
+       fio_client_sum_jobs_eta(&eta->eta, je);
+       fio_client_dec_jobs_eta(eta, client->ops->eta);
 }
 
 static void handle_probe(struct fio_client *client, struct fio_net_cmd *cmd)
@@ -869,22 +1016,112 @@ static void handle_start(struct fio_client *client, struct fio_net_cmd *cmd)
        struct cmd_start_pdu *pdu = (struct cmd_start_pdu *) cmd->payload;
 
        client->state = Client_started;
-       client->jobs = le32_to_cpu(pdu->jobs);
+       client->jobs = pdu->jobs;
 }
 
 static void handle_stop(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       if (client->error)
+               log_info("client <%s>: exited with error %d\n", client->hostname, client->error);
+}
+
+static void convert_stop(struct fio_net_cmd *cmd)
 {
        struct cmd_end_pdu *pdu = (struct cmd_end_pdu *) cmd->payload;
 
-       client->state = Client_stopped;
-       client->error = le32_to_cpu(pdu->error);
+       pdu->error = le32_to_cpu(pdu->error);
+}
 
-       if (client->error)
-               log_info("client <%s>: exited with error %d\n", client->hostname, client->error);
+static void convert_text(struct fio_net_cmd *cmd)
+{
+       struct cmd_text_pdu *pdu = (struct cmd_text_pdu *) cmd->payload;
+
+       pdu->level      = le32_to_cpu(pdu->level);
+       pdu->buf_len    = le32_to_cpu(pdu->buf_len);
+       pdu->log_sec    = le64_to_cpu(pdu->log_sec);
+       pdu->log_usec   = le64_to_cpu(pdu->log_usec);
 }
 
-static int handle_client(struct fio_client *client)
+/*
+ * This has been compressed on the server side, since it can be big.
+ * Uncompress here.
+ */
+static struct cmd_iolog_pdu *convert_iolog(struct fio_net_cmd *cmd)
 {
+       struct cmd_iolog_pdu *pdu = (struct cmd_iolog_pdu *) cmd->payload;
+       struct cmd_iolog_pdu *ret;
+       uint32_t nr_samples;
+       unsigned long total;
+       z_stream stream;
+       void *p;
+       int i;
+
+       stream.zalloc = Z_NULL;
+       stream.zfree = Z_NULL;
+       stream.opaque = Z_NULL;
+       stream.avail_in = 0;
+       stream.next_in = Z_NULL;
+
+       if (inflateInit(&stream) != Z_OK)
+               return NULL;
+
+       /*
+        * Get header first, it's not compressed
+        */
+       nr_samples = le32_to_cpu(pdu->nr_samples);
+
+       total = nr_samples * sizeof(struct io_sample);
+       ret = malloc(total + sizeof(*pdu));
+       ret->thread_number = le32_to_cpu(pdu->thread_number);
+       ret->nr_samples = nr_samples;
+       ret->log_type = le32_to_cpu(pdu->log_type);
+       strcpy((char *) ret->name, (char *) pdu->name);
+
+       p = (void *) ret + sizeof(*pdu);
+
+       stream.avail_in = cmd->pdu_len - sizeof(*pdu);
+       stream.next_in = (void *) pdu + sizeof(*pdu);
+       while (stream.avail_in) {
+               unsigned int this_chunk = 65536;
+               unsigned int this_len;
+               int err;
+
+               if (this_chunk > total)
+                       this_chunk = total;
+
+               stream.avail_out = this_chunk;
+               stream.next_out = p;
+               err = inflate(&stream, Z_NO_FLUSH);
+               /* may be Z_OK, or Z_STREAM_END */
+               if (err < 0) {
+                       log_err("fio: inflate error %d\n", err);
+                       free(ret);
+                       ret = NULL;
+                       goto out;
+               }
+
+               this_len = this_chunk - stream.avail_out;
+               p += this_len;
+               total -= this_len;
+       }
+
+       for (i = 0; i < ret->nr_samples; i++) {
+               struct io_sample *s = &ret->samples[i];
+
+               s->time = le64_to_cpu(s->time);
+               s->val  = le64_to_cpu(s->val);
+               s->ddir = le32_to_cpu(s->ddir);
+               s->bs   = le32_to_cpu(s->bs);
+       }
+
+out:
+       inflateEnd(&stream);
+       return ret;
+}
+
+int fio_handle_client(struct fio_client *client)
+{
+       struct client_ops *ops = client->ops;
        struct fio_net_cmd *cmd;
 
        dprint(FD_NET, "client: handle %s\n", client->hostname);
@@ -893,61 +1130,112 @@ static int handle_client(struct fio_client *client)
        if (!cmd)
                return 0;
 
-       dprint(FD_NET, "client: got cmd op %s from %s\n",
-                               fio_server_op(cmd->opcode), client->hostname);
+       dprint(FD_NET, "client: got cmd op %s from %s (pdu=%u)\n",
+               fio_server_op(cmd->opcode), client->hostname, cmd->pdu_len);
 
        switch (cmd->opcode) {
        case FIO_NET_CMD_QUIT:
+               if (ops->quit)
+                       ops->quit(client, cmd);
                remove_client(client);
                free(cmd);
                break;
-       case FIO_NET_CMD_TEXT: {
-               const char *buf = (const char *) cmd->payload;
-               const char *name;
-               int fio_unused ret;
-
-               name = client->name ? client->name : client->hostname;
-
-               if (!client->skip_newline)
-                       fprintf(f_out, "<%s> ", name);
-               ret = fwrite(buf, cmd->pdu_len, 1, f_out);
-               fflush(f_out);
-               client->skip_newline = strchr(buf, '\n') == NULL;
+       case FIO_NET_CMD_TEXT:
+               convert_text(cmd);
+               ops->text(client, cmd);
                free(cmd);
                break;
-               }
-       case FIO_NET_CMD_DU:
-               handle_du(client, cmd);
+       case FIO_NET_CMD_DU: {
+               struct cmd_du_pdu *du = (struct cmd_du_pdu *) cmd->payload;
+
+               convert_dus(&du->dus);
+               convert_agg(&du->agg);
+
+               ops->disk_util(client, cmd);
                free(cmd);
                break;
-       case FIO_NET_CMD_TS:
-               handle_ts(cmd);
+               }
+       case FIO_NET_CMD_TS: {
+               struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+
+               convert_ts(&p->ts, &p->ts);
+               convert_gs(&p->rs, &p->rs);
+
+               ops->thread_status(client, cmd);
                free(cmd);
                break;
-       case FIO_NET_CMD_GS:
-               handle_gs(cmd);
+               }
+       case FIO_NET_CMD_GS: {
+               struct group_run_stats *gs = (struct group_run_stats *) cmd->payload;
+
+               convert_gs(gs, gs);
+
+               ops->group_stats(client, cmd);
                free(cmd);
                break;
-       case FIO_NET_CMD_ETA:
+               }
+       case FIO_NET_CMD_ETA: {
+               struct jobs_eta *je = (struct jobs_eta *) cmd->payload;
+
                remove_reply_cmd(client, cmd);
+               convert_jobs_eta(je);
                handle_eta(client, cmd);
                free(cmd);
                break;
+               }
        case FIO_NET_CMD_PROBE:
                remove_reply_cmd(client, cmd);
-               handle_probe(client, cmd);
+               ops->probe(client, cmd);
                free(cmd);
                break;
-       case FIO_NET_CMD_RUN:
+       case FIO_NET_CMD_SERVER_START:
                client->state = Client_running;
+               if (ops->job_start)
+                       ops->job_start(client, cmd);
+               free(cmd);
+               break;
+       case FIO_NET_CMD_START: {
+               struct cmd_start_pdu *pdu = (struct cmd_start_pdu *) cmd->payload;
+
+               pdu->jobs = le32_to_cpu(pdu->jobs);
+               ops->start(client, cmd);
                free(cmd);
                break;
-       case FIO_NET_CMD_START:
-               handle_start(client, cmd);
+               }
+       case FIO_NET_CMD_STOP: {
+               struct cmd_end_pdu *pdu = (struct cmd_end_pdu *) cmd->payload;
+
+               convert_stop(cmd);
+               client->state = Client_stopped;
+               client->error = le32_to_cpu(pdu->error);
+               client->signal = le32_to_cpu(pdu->signal);
+               ops->stop(client, cmd);
+               free(cmd);
+               break;
+               }
+       case FIO_NET_CMD_ADD_JOB: {
+               struct cmd_add_job_pdu *pdu = (struct cmd_add_job_pdu *) cmd->payload;
+
+               client->thread_number = le32_to_cpu(pdu->thread_number);
+               client->groupid = le32_to_cpu(pdu->groupid);
+
+               if (ops->add_job)
+                       ops->add_job(client, cmd);
+               free(cmd);
+               break;
+               }
+       case FIO_NET_CMD_IOLOG:
+               if (ops->iolog) {
+                       struct cmd_iolog_pdu *pdu;
+
+                       pdu = convert_iolog(cmd);
+                       ops->iolog(client, pdu);
+               }
                free(cmd);
                break;
-       case FIO_NET_CMD_STOP:
-               handle_stop(client, cmd);
+       case FIO_NET_CMD_UPDATE_JOB:
+               ops->update_job(client, cmd);
+               remove_reply_cmd(client, cmd);
                free(cmd);
                break;
        default:
@@ -959,7 +1247,7 @@ static int handle_client(struct fio_client *client)
        return 1;
 }
 
-static void request_client_etas(void)
+static void request_client_etas(struct client_ops *ops)
 {
        struct fio_client *client;
        struct flist_head *entry;
@@ -990,7 +1278,7 @@ static void request_client_etas(void)
        }
 
        while (skipped--)
-               dec_jobs_eta(eta);
+               fio_client_dec_jobs_eta(eta, ops->eta);
 
        dprint(FD_NET, "client: requested eta tag %p\n", eta);
 }
@@ -998,27 +1286,27 @@ static void request_client_etas(void)
 static int client_check_cmd_timeout(struct fio_client *client,
                                    struct timeval *now)
 {
-       struct fio_net_int_cmd *cmd;
+       struct fio_net_cmd_reply *reply;
        struct flist_head *entry, *tmp;
        int ret = 0;
 
        flist_for_each_safe(entry, tmp, &client->cmd_list) {
-               cmd = flist_entry(entry, struct fio_net_int_cmd, list);
+               reply = flist_entry(entry, struct fio_net_cmd_reply, list);
 
-               if (mtime_since(&cmd->tv, now) < FIO_NET_CLIENT_TIMEOUT)
+               if (mtime_since(&reply->tv, now) < FIO_NET_CLIENT_TIMEOUT)
                        continue;
 
                log_err("fio: client %s, timeout on cmd %s\n", client->hostname,
-                                               fio_server_op(cmd->cmd.opcode));
-               flist_del(&cmd->list);
-               free(cmd);
+                                               fio_server_op(reply->opcode));
+               flist_del(&reply->list);
+               free(reply);
                ret = 1;
        }
 
        return flist_empty(&client->cmd_list) && ret;
 }
 
-static int fio_client_timed_out(void)
+static int fio_check_clients_timed_out(void)
 {
        struct fio_client *client;
        struct flist_head *entry, *tmp;
@@ -1036,7 +1324,11 @@ static int fio_client_timed_out(void)
                if (!client_check_cmd_timeout(client, &tv))
                        continue;
 
-               log_err("fio: client %s timed out\n", client->hostname);
+               if (client->ops->timed_out)
+                       client->ops->timed_out(client);
+               else
+                       log_err("fio: client %s timed out\n", client->hostname);
+
                remove_client(client);
                ret = 1;
        }
@@ -1044,7 +1336,7 @@ static int fio_client_timed_out(void)
        return ret;
 }
 
-int fio_handle_clients(void)
+int fio_handle_clients(struct client_ops *ops)
 {
        struct pollfd *pfds;
        int i, ret = 0, retval = 0;
@@ -1065,7 +1357,7 @@ int fio_handle_clients(void)
                flist_for_each_safe(entry, tmp, &client_list) {
                        client = flist_entry(entry, struct fio_client, list);
 
-                       if (!client->sent_job &&
+                       if (!client->sent_job && !client->ops->stay_connected &&
                            flist_empty(&client->cmd_list)) {
                                remove_client(client);
                                continue;
@@ -1085,15 +1377,15 @@ int fio_handle_clients(void)
                        struct timeval tv;
 
                        gettimeofday(&tv, NULL);
-                       if (mtime_since(&eta_tv, &tv) >= 900) {
-                               request_client_etas();
+                       if (mtime_since(&eta_tv, &tv) >= ops->eta_msec) {
+                               request_client_etas(ops);
                                memcpy(&eta_tv, &tv, sizeof(tv));
 
-                               if (fio_client_timed_out())
+                               if (fio_check_clients_timed_out())
                                        break;
                        }
 
-                       ret = poll(pfds, nr_clients, 100);
+                       ret = poll(pfds, nr_clients, ops->eta_msec);
                        if (ret < 0) {
                                if (errno == EINTR)
                                        continue;
@@ -1112,14 +1404,14 @@ int fio_handle_clients(void)
                                log_err("fio: unknown client fd %d\n", pfds[i].fd);
                                continue;
                        }
-                       if (!handle_client(client)) {
+                       if (!fio_handle_client(client)) {
                                log_info("client: host=%s disconnected\n",
                                                client->hostname);
                                remove_client(client);
                                retval = 1;
                        } else if (client->error)
                                retval = 1;
-                       put_client(client);
+                       fio_put_client(client);
                }
        }
 
diff --git a/client.h b/client.h
new file mode 100644 (file)
index 0000000..341d260
--- /dev/null
+++ b/client.h
@@ -0,0 +1,142 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "stat.h"
+
+struct fio_net_cmd;
+struct client_ops;
+
+enum {
+       Client_created          = 0,
+       Client_connected        = 1,
+       Client_started          = 2,
+       Client_running          = 3,
+       Client_stopped          = 4,
+       Client_exited           = 5,
+};
+
+struct fio_client {
+       struct flist_head list;
+       struct flist_head hash_list;
+       struct flist_head arg_list;
+       union {
+               struct sockaddr_in addr;
+               struct sockaddr_in6 addr6;
+               struct sockaddr_un addr_un;
+       };
+       char *hostname;
+       int port;
+       int fd;
+       unsigned int refs;
+
+       char *name;
+
+       int state;
+
+       int skip_newline;
+       int is_sock;
+       int disk_stats_shown;
+       unsigned int jobs;
+       int error;
+       int signal;
+       int ipv6;
+       int sent_job;
+       uint32_t type;
+
+       uint32_t thread_number;
+       uint32_t groupid;
+
+       struct flist_head eta_list;
+       struct client_eta *eta_in_flight;
+
+       struct flist_head cmd_list;
+
+       uint16_t argc;
+       char **argv;
+
+       struct client_ops *ops;
+       void *client_data;
+
+       char **ini_file;
+       unsigned int nr_ini_file;
+};
+
+struct cmd_iolog_pdu;
+typedef void (client_cmd_op)(struct fio_client *, struct fio_net_cmd *);
+typedef void (client_eta_op)(struct jobs_eta *je);
+typedef void (client_timed_out_op)(struct fio_client *);
+typedef void (client_jobs_eta_op)(struct fio_client *client, struct jobs_eta *je);
+typedef void (client_iolog_op)(struct fio_client *client, struct cmd_iolog_pdu *);
+
+struct client_ops {
+       client_cmd_op           *text;
+       client_cmd_op           *disk_util;
+       client_cmd_op           *thread_status;
+       client_cmd_op           *group_stats;
+       client_jobs_eta_op      *jobs_eta;
+       client_eta_op           *eta;
+       client_cmd_op           *probe;
+       client_cmd_op           *quit;
+       client_cmd_op           *add_job;
+       client_cmd_op           *update_job;
+       client_timed_out_op     *timed_out;
+       client_cmd_op           *stop;
+       client_cmd_op           *start;
+       client_cmd_op           *job_start;
+       client_iolog_op         *iolog;
+       client_timed_out_op     *removed;
+
+       unsigned int eta_msec;
+       int stay_connected;
+       uint32_t client_type;
+};
+
+extern struct client_ops fio_client_ops;
+
+struct client_eta {
+       struct jobs_eta eta;
+       unsigned int pending;
+};
+
+extern int fio_handle_client(struct fio_client *);
+extern void fio_client_dec_jobs_eta(struct client_eta *eta, client_eta_op fn);
+extern void fio_client_sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je);
+
+enum {
+       Fio_client_ipv4 = 1,
+       Fio_client_ipv6,
+       Fio_client_socket,
+};
+
+extern int fio_client_connect(struct fio_client *);
+extern int fio_clients_connect(void);
+extern int fio_start_client(struct fio_client *);
+extern int fio_start_all_clients(void);
+extern int fio_client_send_ini(struct fio_client *, const char *);
+extern int fio_clients_send_ini(const char *);
+extern int fio_handle_clients(struct client_ops *);
+extern int fio_client_add(struct client_ops *, const char *, void **);
+extern struct fio_client *fio_client_add_explicit(struct client_ops *, const char *, int, int);
+extern void fio_client_add_cmd_option(void *, const char *);
+extern void fio_client_add_ini_file(void *, const char *);
+extern int fio_client_terminate(struct fio_client *);
+extern void fio_clients_terminate(void);
+extern struct fio_client *fio_get_client(struct fio_client *);
+extern void fio_put_client(struct fio_client *);
+extern int fio_client_update_options(struct fio_client *, struct thread_options *, uint64_t *);
+extern int fio_client_wait_for_reply(struct fio_client *, uint64_t);
+
+#define FIO_CLIENT_DEF_ETA_MSEC                900
+
+enum {
+       FIO_CLIENT_TYPE_CLI             = 1,
+       FIO_CLIENT_TYPE_GUI             = 2,
+};
+
+#endif
+
diff --git a/debug.h b/debug.h
index af71d625fc10c20433cc5a53285de61c70ad02dc..b55a1e4dc7d76d3c17e660288441ffb255960942 100644 (file)
--- a/debug.h
+++ b/debug.h
@@ -27,6 +27,7 @@ extern unsigned int fio_debug_jobno, *fio_debug_jobp;
 #ifdef FIO_INC_DEBUG
 struct debug_level {
        const char *name;
+       const char *help;
        unsigned long shift;
        unsigned int jobno;
 };
index 8bc9fd5c3c8c8919d4618bf6f149fb5b142093a6..c798f1884e8dff8df9b079d87d61c8c4259ae10c 100644 (file)
@@ -7,32 +7,73 @@
  */
 #include "../fio.h"
 
+struct cpu_options {
+       struct thread_data *td;
+       unsigned int cpuload;
+       unsigned int cpucycle;
+};
+
+static struct fio_option options[] = {
+       {
+               .name   = "cpuload",
+               .lname  = "CPU load",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct cpu_options, cpuload),
+               .help   = "Use this percentage of CPU",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "cpuchunks",
+               .lname  = "CPU chunk",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct cpu_options, cpucycle),
+               .help   = "Length of the CPU burn cycles (usecs)",
+               .def    = "50000",
+               .parent = "cpuload",
+               .hide   = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = NULL,
+       },
+};
+
+
 static int fio_cpuio_queue(struct thread_data *td, struct io_u fio_unused *io_u)
 {
-       usec_spin(td->o.cpucycle);
+       struct cpu_options *co = td->eo;
+
+       usec_spin(co->cpucycle);
        return FIO_Q_COMPLETED;
 }
 
 static int fio_cpuio_init(struct thread_data *td)
 {
        struct thread_options *o = &td->o;
+       struct cpu_options *co = td->eo;
 
-       if (!o->cpuload) {
+       if (!co->cpuload) {
                td_vmsg(td, EINVAL, "cpu thread needs rate (cpuload=)","cpuio");
                return 1;
        }
 
-       if (o->cpuload > 100)
-               o->cpuload = 100;
+       if (co->cpuload > 100)
+               co->cpuload = 100;
 
        /*
         * set thinktime_sleep and thinktime_spin appropriately
         */
        o->thinktime_blocks = 1;
        o->thinktime_spin = 0;
-       o->thinktime = (o->cpucycle * (100 - o->cpuload)) / o->cpuload;
+       o->thinktime = (co->cpucycle * (100 - co->cpuload)) / co->cpuload;
 
        o->nr_files = o->open_files = 1;
+
+       log_info("%s: ioengine=cpu, cpuload=%u, cpucycle=%u\n", td->o.name,
+                                               co->cpuload, co->cpucycle);
+
        return 0;
 }
 
@@ -49,6 +90,8 @@ static struct ioengine_ops ioengine = {
        .init           = fio_cpuio_init,
        .open_file      = fio_cpuio_open,
        .flags          = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOIO,
+       .options                = options,
+       .option_struct_size     = sizeof(struct cpu_options),
 };
 
 static void fio_init fio_cpuio_register(void)
index bc459da1fcbaf5a310305cc4e4d2b63136c873a8..748233c268e1dad4a106c00c4c1497ad62010c0f 100644 (file)
@@ -32,9 +32,11 @@ struct libaio_options {
 static struct fio_option options[] = {
        {
                .name   = "userspace_reap",
+               .lname  = "Libaio userspace reaping",
                .type   = FIO_OPT_STR_SET,
                .off1   = offsetof(struct libaio_options, userspace_reap),
                .help   = "Use alternative user-space reap implementation",
+               .category = FIO_OPT_C_IO,
        },
        {
                .name   = NULL,
index c5337994fa41509acad7a4cf91003b0ac1422503..468bc7c81746a8f73935903e8c99d3990c2286e1 100644 (file)
@@ -53,25 +53,31 @@ static int str_hostname_cb(void *data, const char *input);
 static struct fio_option options[] = {
        {
                .name   = "hostname",
+               .lname  = "net engine hostname",
                .type   = FIO_OPT_STR_STORE,
                .cb     = str_hostname_cb,
                .help   = "Hostname for net IO engine",
+               .category = FIO_OPT_C_IO,
        },
        {
                .name   = "port",
+               .lname  = "net engine port",
                .type   = FIO_OPT_INT,
                .off1   = offsetof(struct netio_options, port),
                .minval = 1,
                .maxval = 65535,
                .help   = "Port to use for TCP or UDP net connections",
+               .category = FIO_OPT_C_IO,
        },
        {
                .name   = "protocol",
+               .lname  = "net engine protocol",
                .alias  = "proto",
                .type   = FIO_OPT_STR,
                .off1   = offsetof(struct netio_options, proto),
                .help   = "Network protocol to use",
                .def    = "tcp",
+               .category = FIO_OPT_C_IO,
                .posval = {
                          { .ival = "tcp",
                            .oval = FIO_TYPE_TCP,
@@ -89,9 +95,11 @@ static struct fio_option options[] = {
        },
        {
                .name   = "listen",
+               .lname  = "net engine listen",
                .type   = FIO_OPT_STR_SET,
                .off1   = offsetof(struct netio_options, listen),
                .help   = "Listen for incoming TCP connections",
+               .category = FIO_OPT_C_IO,
        },
        {
                .name   = NULL,
diff --git a/eta.c b/eta.c
index 075ce8c81a010b1e57e0a7d31d5c8f7259b69a47..600b046ced8c86e16ff90b1d2dd05ac59e637d5a 100644 (file)
--- a/eta.c
+++ b/eta.c
@@ -93,7 +93,7 @@ static void check_str_update(struct thread_data *td)
 /*
  * Convert seconds to a printable string.
  */
-static void eta_to_str(char *str, unsigned long eta_sec)
+void eta_to_str(char *str, unsigned long eta_sec)
 {
        unsigned int d, h, m, s;
        int disp_hour = 0;
@@ -296,22 +296,22 @@ int calc_thread_status(struct jobs_eta *je, int force)
                    || td->runstate == TD_PRE_READING) {
                        je->nr_running++;
                        if (td_read(td)) {
-                               je->t_rate += td->o.rate[DDIR_READ];
-                               je->t_iops += td->o.rate_iops[DDIR_READ];
-                               je->m_rate += td->o.ratemin[DDIR_READ];
-                               je->m_iops += td->o.rate_iops_min[DDIR_READ];
+                               je->t_rate[0] += td->o.rate[DDIR_READ];
+                               je->t_iops[0] += td->o.rate_iops[DDIR_READ];
+                               je->m_rate[0] += td->o.ratemin[DDIR_READ];
+                               je->m_iops[0] += td->o.rate_iops_min[DDIR_READ];
                        }
                        if (td_write(td)) {
-                               je->t_rate += td->o.rate[DDIR_WRITE];
-                               je->t_iops += td->o.rate_iops[DDIR_WRITE];
-                               je->m_rate += td->o.ratemin[DDIR_WRITE];
-                               je->m_iops += td->o.rate_iops_min[DDIR_WRITE];
+                               je->t_rate[1] += td->o.rate[DDIR_WRITE];
+                               je->t_iops[1] += td->o.rate_iops[DDIR_WRITE];
+                               je->m_rate[1] += td->o.ratemin[DDIR_WRITE];
+                               je->m_iops[1] += td->o.rate_iops_min[DDIR_WRITE];
                        }
                        if (td_trim(td)) {
-                               je->t_rate += td->o.rate[DDIR_TRIM];
-                               je->t_iops += td->o.rate_iops[DDIR_TRIM];
-                               je->m_rate += td->o.ratemin[DDIR_TRIM];
-                               je->m_iops += td->o.rate_iops_min[DDIR_TRIM];
+                               je->t_rate[2] += td->o.rate[DDIR_TRIM];
+                               je->t_iops[2] += td->o.rate_iops[DDIR_TRIM];
+                               je->m_rate[2] += td->o.ratemin[DDIR_TRIM];
+                               je->m_iops[2] += td->o.rate_iops_min[DDIR_TRIM];
                        }
 
                        je->files_open += td->nr_open_files;
@@ -401,16 +401,19 @@ void display_thread_status(struct jobs_eta *je)
        }
 
        p += sprintf(p, "Jobs: %d (f=%d)", je->nr_running, je->files_open);
-       if (je->m_rate || je->t_rate) {
+       if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
                char *tr, *mr;
 
-               mr = num2str(je->m_rate, 4, 0, je->is_pow2);
-               tr = num2str(je->t_rate, 4, 0, je->is_pow2);
+               mr = num2str(je->m_rate[0] + je->m_rate[1], 4, 0, je->is_pow2);
+               tr = num2str(je->t_rate[0] + je->t_rate[1], 4, 0, je->is_pow2);
                p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
                free(tr);
                free(mr);
-       } else if (je->m_iops || je->t_iops)
-               p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
+       } else if (je->m_iops[0] || je->m_iops[1] || je->t_iops[0] || je->t_iops[1]) {
+               p += sprintf(p, ", CR=%d/%d IOPS",
+                                       je->t_iops[0] + je->t_iops[1],
+                                       je->m_iops[0] + je->m_iops[1]);
+       }
        if (je->eta_sec != INT_MAX && je->nr_running) {
                char perc_str[32];
                char *iops_str[DDIR_RWDIR_CNT];
diff --git a/examples/1mbs_clients b/examples/1mbs_clients
deleted file mode 100644 (file)
index 505cd87..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-; Keep adding 1024kb/s reading clients at 4 seconds
-[global]
-size=32m
-rw=read
-directory=tmp
-rate=1250
-ratemin=1024
-
-[file1]
-startdelay=0
-
-[file2]
-startdelay=4
-
-[file3]
-startdelay=8
-
-[file4]
-startdelay=12
-
-[file5]
-startdelay=16
-
-[file6]
-startdelay=20
-
-[file7]
-startdelay=24
-
-[file8]
-startdelay=28
-
-[file9]
-startdelay=32
-
-[file10]
-startdelay=36
-
-[file11]
-startdelay=40
-
-[file12]
-startdelay=44
-
-[file13]
-startdelay=48
-
-[file14]
-startdelay=52
-
-[file15]
-startdelay=56
-
-[file16]
-startdelay=60
-
-[file17]
-startdelay=64
-
-[file18]
-startdelay=68
-
-[file19]
-startdelay=72
-
-[file20]
-startdelay=76
-
-[file21]
-startdelay=80
-
-[file22]
-startdelay=84
-
-[file23]
-startdelay=88
-
-[file24]
-startdelay=92
-
-[file25]
-startdelay=96
-
-[file26]
-startdelay=100
-
-[file27]
-startdelay=104
-
-[file28]
-startdelay=108
-
-[file29]
-startdelay=112
-
-[file30]
-startdelay=116
-
-[file31]
-startdelay=120
-
-[file32]
-startdelay=124
-
diff --git a/examples/1mbs_clients.fio b/examples/1mbs_clients.fio
new file mode 100644 (file)
index 0000000..505cd87
--- /dev/null
@@ -0,0 +1,104 @@
+; Keep adding 1024kb/s reading clients at 4 seconds
+[global]
+size=32m
+rw=read
+directory=tmp
+rate=1250
+ratemin=1024
+
+[file1]
+startdelay=0
+
+[file2]
+startdelay=4
+
+[file3]
+startdelay=8
+
+[file4]
+startdelay=12
+
+[file5]
+startdelay=16
+
+[file6]
+startdelay=20
+
+[file7]
+startdelay=24
+
+[file8]
+startdelay=28
+
+[file9]
+startdelay=32
+
+[file10]
+startdelay=36
+
+[file11]
+startdelay=40
+
+[file12]
+startdelay=44
+
+[file13]
+startdelay=48
+
+[file14]
+startdelay=52
+
+[file15]
+startdelay=56
+
+[file16]
+startdelay=60
+
+[file17]
+startdelay=64
+
+[file18]
+startdelay=68
+
+[file19]
+startdelay=72
+
+[file20]
+startdelay=76
+
+[file21]
+startdelay=80
+
+[file22]
+startdelay=84
+
+[file23]
+startdelay=88
+
+[file24]
+startdelay=92
+
+[file25]
+startdelay=96
+
+[file26]
+startdelay=100
+
+[file27]
+startdelay=104
+
+[file28]
+startdelay=108
+
+[file29]
+startdelay=112
+
+[file30]
+startdelay=116
+
+[file31]
+startdelay=120
+
+[file32]
+startdelay=124
+
diff --git a/examples/aio-read b/examples/aio-read
deleted file mode 100644 (file)
index 173a4e1..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-; Read 4 files with aio at different depths
-[global]
-ioengine=libaio
-buffered=0
-rw=randread
-bs=128k
-size=512m
-directory=/data1
-
-[file1]
-iodepth=4
-
-[file2]
-iodepth=32
-
-[file3]
-iodepth=8
-
-[file4]
-iodepth=16
diff --git a/examples/aio-read.fio b/examples/aio-read.fio
new file mode 100644 (file)
index 0000000..173a4e1
--- /dev/null
@@ -0,0 +1,20 @@
+; Read 4 files with aio at different depths
+[global]
+ioengine=libaio
+buffered=0
+rw=randread
+bs=128k
+size=512m
+directory=/data1
+
+[file1]
+iodepth=4
+
+[file2]
+iodepth=32
+
+[file3]
+iodepth=8
+
+[file4]
+iodepth=16
diff --git a/examples/disk-zone-profile b/examples/disk-zone-profile
deleted file mode 100644 (file)
index 96e5669..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-; Read disk in zones of 128m/2g, generating a plot of that afterwards
-; should give a nice picture of the zoning of this drive
-
-[global]
-bs=64k
-direct=1
-rw=read
-ioengine=libaio
-iodepth=2
-zonesize=256m
-zoneskip=2g
-write_bw_log
-
-[/dev/sdb]
diff --git a/examples/disk-zone-profile.fio b/examples/disk-zone-profile.fio
new file mode 100644 (file)
index 0000000..96e5669
--- /dev/null
@@ -0,0 +1,14 @@
+; Read disk in zones of 128m/2g, generating a plot of that afterwards
+; should give a nice picture of the zoning of this drive
+
+[global]
+bs=64k
+direct=1
+rw=read
+ioengine=libaio
+iodepth=2
+zonesize=256m
+zoneskip=2g
+write_bw_log
+
+[/dev/sdb]
diff --git a/examples/flow b/examples/flow
deleted file mode 100644 (file)
index 4b078cf..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-# Example usage of flows. The below will have roughly a 1:8 difference
-# between job2 and job1.
-[global]
-norandommap
-thread
-time_based
-runtime=30
-direct=1
-ioengine=libaio
-iodepth=256
-size=100g
-bs=8k
-filename=/tmp/testfile
-flow_watermark=100
-flow_sleep=1000
-
-[job2]
-numjobs=1
-rw=write
-flow=-8
-
-[job1]
-numjobs=1
-rw=randread
-flow=1
diff --git a/examples/flow.fio b/examples/flow.fio
new file mode 100644 (file)
index 0000000..4b078cf
--- /dev/null
@@ -0,0 +1,25 @@
+# Example usage of flows. The below will have roughly a 1:8 difference
+# between job2 and job1.
+[global]
+norandommap
+thread
+time_based
+runtime=30
+direct=1
+ioengine=libaio
+iodepth=256
+size=100g
+bs=8k
+filename=/tmp/testfile
+flow_watermark=100
+flow_sleep=1000
+
+[job2]
+numjobs=1
+rw=write
+flow=-8
+
+[job1]
+numjobs=1
+rw=randread
+flow=1
diff --git a/examples/fsx b/examples/fsx
deleted file mode 100644 (file)
index 6b48c6f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-; This job file works pretty works similarly to running fsx-linux
-; with -r 4096 -w 4096 -Z -N 500000
-[file]
-ioengine=libaio
-iodepth=1
-rw=randrw
-size=256k
-bs=4k
-norandommap
-direct=1
-loops=500000
-rwmixcycle=40
diff --git a/examples/fsx.fio b/examples/fsx.fio
new file mode 100644 (file)
index 0000000..6b48c6f
--- /dev/null
@@ -0,0 +1,12 @@
+; This job file works pretty works similarly to running fsx-linux
+; with -r 4096 -w 4096 -Z -N 500000
+[file]
+ioengine=libaio
+iodepth=1
+rw=randrw
+size=256k
+bs=4k
+norandommap
+direct=1
+loops=500000
+rwmixcycle=40
diff --git a/examples/iometer-file-access-server b/examples/iometer-file-access-server
deleted file mode 100644 (file)
index 291bace..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# This job file tries to mimic the Intel IOMeter File Server Access Pattern
-[global]
-description=Emulation of Intel IOmeter File Server Access Pattern
-
-[iometer]
-bssplit=512/10:1k/5:2k/5:4k/60:8k/2:16k/4:32k/4:64k/10
-rw=randrw
-rwmixread=80
-direct=1
-size=4g
-ioengine=libaio
-# IOMeter defines the server loads as the following:
-# iodepth=1    Linear
-# iodepth=4    Very Light
-# iodepth=8    Light
-# iodepth=64   Moderate
-# iodepth=256  Heavy
-iodepth=64
diff --git a/examples/iometer-file-access-server.fio b/examples/iometer-file-access-server.fio
new file mode 100644 (file)
index 0000000..291bace
--- /dev/null
@@ -0,0 +1,18 @@
+# This job file tries to mimic the Intel IOMeter File Server Access Pattern
+[global]
+description=Emulation of Intel IOmeter File Server Access Pattern
+
+[iometer]
+bssplit=512/10:1k/5:2k/5:4k/60:8k/2:16k/4:32k/4:64k/10
+rw=randrw
+rwmixread=80
+direct=1
+size=4g
+ioengine=libaio
+# IOMeter defines the server loads as the following:
+# iodepth=1    Linear
+# iodepth=4    Very Light
+# iodepth=8    Light
+# iodepth=64   Moderate
+# iodepth=256  Heavy
+iodepth=64
diff --git a/examples/netio b/examples/netio
deleted file mode 100644 (file)
index 3b1a7cd..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-# Example network job, just defines two clients that send/recv data
-[global]
-ioengine=net
-#Use hostname=/tmp.fio.sock for local unix domain sockets
-port=8888
-#Use =udp for UDP, =unix for local unix domain socket
-protocol=tcp
-bs=4k
-size=10g
-#set the below option to enable end-to-end data integrity tests
-#verify=md5
-
-[receiver]
-listen
-rw=read
-
-[sender]
-hostname=localhost
-startdelay=1
-rw=write
diff --git a/examples/netio.fio b/examples/netio.fio
new file mode 100644 (file)
index 0000000..0c5c77c
--- /dev/null
@@ -0,0 +1,20 @@
+# Example network job, just defines two clients that send/recv data
+[global]
+ioengine=net
+#Use hostname=/tmp.fio.sock for local unix domain sockets
+port=8888
+#Use =udp for UDP, =unix for local unix domain socket
+protocol=tcp
+bs=4k
+size=100g
+#set the below option to enable end-to-end data integrity tests
+#verify=md5
+
+[receiver]
+listen
+rw=read
+
+[sender]
+hostname=localhost
+startdelay=1
+rw=write
diff --git a/examples/null b/examples/null
deleted file mode 100644 (file)
index 9d2f3e0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-[global]
-bs=4k
-gtod_reduce=1
-
-[null]
-ioengine=null
-size=100g
-rw=randread
-norandommap
-time_based=0
diff --git a/examples/null.fio b/examples/null.fio
new file mode 100644 (file)
index 0000000..9d2f3e0
--- /dev/null
@@ -0,0 +1,10 @@
+[global]
+bs=4k
+gtod_reduce=1
+
+[null]
+ioengine=null
+size=100g
+rw=randread
+norandommap
+time_based=0
diff --git a/examples/rdmaio-client b/examples/rdmaio-client
deleted file mode 100644 (file)
index 7c660c9..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Example rdma client job
-[global]
-ioengine=rdma
-filename=[ip_addr]/[port]/[RDMA_WRITE/RDMA_READ/SEND]
-bs=1m
-size=100g
-
-[sender]
-rw=write
-iodepth=1
-iodepth_batch_complete=1
\ No newline at end of file
diff --git a/examples/rdmaio-client.fio b/examples/rdmaio-client.fio
new file mode 100644 (file)
index 0000000..7c660c9
--- /dev/null
@@ -0,0 +1,11 @@
+# Example rdma client job
+[global]
+ioengine=rdma
+filename=[ip_addr]/[port]/[RDMA_WRITE/RDMA_READ/SEND]
+bs=1m
+size=100g
+
+[sender]
+rw=write
+iodepth=1
+iodepth_batch_complete=1
\ No newline at end of file
diff --git a/examples/rdmaio-server b/examples/rdmaio-server
deleted file mode 100644 (file)
index 9348859..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-# Example rdma server job
-[global]
-ioengine=rdma
-filename=[ip_addr]/[port]
-bs=1m
-size=100g
-
-[receiver]
-rw=read
-iodepth=16
\ No newline at end of file
diff --git a/examples/rdmaio-server.fio b/examples/rdmaio-server.fio
new file mode 100644 (file)
index 0000000..9348859
--- /dev/null
@@ -0,0 +1,10 @@
+# Example rdma server job
+[global]
+ioengine=rdma
+filename=[ip_addr]/[port]
+bs=1m
+size=100g
+
+[receiver]
+rw=read
+iodepth=16
\ No newline at end of file
diff --git a/examples/ssd-test b/examples/ssd-test
deleted file mode 100644 (file)
index c84cf50..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-# Do some important numbers on SSD drives, to gauge what kind of
-# performance you might get out of them.
-#
-# Sequential read and write speeds are tested, these are expected to be
-# high. Random reads should also be fast, random writes are where crap
-# drives are usually separated from the good drives.
-#
-# This uses a queue depth of 4. New SATA SSD's will support up to 32
-# in flight commands, so it may also be interesting to increase the queue
-# depth and compare. Note that most real-life usage will not see that
-# large of a queue depth, so 4 is more representative of normal use.
-#
-[global]
-bs=4k
-ioengine=libaio
-iodepth=4
-size=1g
-direct=1
-runtime=60
-directory=/mount-point-of-ssd
-filename=ssd.test.file
-
-[seq-read]
-rw=read
-stonewall
-
-[rand-read]
-rw=randread
-stonewall
-
-[seq-write]
-rw=write
-stonewall
-
-[rand-write]
-rw=randwrite
-stonewall
diff --git a/examples/ssd-test.fio b/examples/ssd-test.fio
new file mode 100644 (file)
index 0000000..c84cf50
--- /dev/null
@@ -0,0 +1,37 @@
+# Do some important numbers on SSD drives, to gauge what kind of
+# performance you might get out of them.
+#
+# Sequential read and write speeds are tested, these are expected to be
+# high. Random reads should also be fast, random writes are where crap
+# drives are usually separated from the good drives.
+#
+# This uses a queue depth of 4. New SATA SSD's will support up to 32
+# in flight commands, so it may also be interesting to increase the queue
+# depth and compare. Note that most real-life usage will not see that
+# large of a queue depth, so 4 is more representative of normal use.
+#
+[global]
+bs=4k
+ioengine=libaio
+iodepth=4
+size=1g
+direct=1
+runtime=60
+directory=/mount-point-of-ssd
+filename=ssd.test.file
+
+[seq-read]
+rw=read
+stonewall
+
+[rand-read]
+rw=randread
+stonewall
+
+[seq-write]
+rw=write
+stonewall
+
+[rand-write]
+rw=randwrite
+stonewall
diff --git a/examples/surface-scan b/examples/surface-scan
deleted file mode 100644 (file)
index dc3373a..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-; writes 512 byte verification blocks until the disk is full,
-; then verifies written data
-[global]
-thread=1
-bs=64k
-direct=1
-ioengine=sync
-verify=meta
-verify_pattern=0xaa555aa5
-verify_interval=512
-
-[write-phase]
-filename=datafile.tmp  ; or use a full disk, for example /dev/sda
-rw=write
-fill_device=1
-do_verify=0
-
-[verify-phase]
-stonewall
-create_serialize=0
-filename=datafile.tmp
-rw=read
-do_verify=1
diff --git a/examples/surface-scan.fio b/examples/surface-scan.fio
new file mode 100644 (file)
index 0000000..dc3373a
--- /dev/null
@@ -0,0 +1,23 @@
+; writes 512 byte verification blocks until the disk is full,
+; then verifies written data
+[global]
+thread=1
+bs=64k
+direct=1
+ioengine=sync
+verify=meta
+verify_pattern=0xaa555aa5
+verify_interval=512
+
+[write-phase]
+filename=datafile.tmp  ; or use a full disk, for example /dev/sda
+rw=write
+fill_device=1
+do_verify=0
+
+[verify-phase]
+stonewall
+create_serialize=0
+filename=datafile.tmp
+rw=read
+do_verify=1
diff --git a/examples/tiobench-example b/examples/tiobench-example
deleted file mode 100644 (file)
index 5a4493e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-; tiobench like setup, add more fX files between the stonewalls to
-; create more threads
-
-[global]
-direct=1
-size=512m
-bsrange=4k-4k
-timeout=60
-numjobs=4      ; 4 simultaneous threads for each job
-
-[f1]
-rw=write
-
-[f2]
-stonewall
-rw=randwrite
-
-[f3]
-stonewall
-rw=read
-
-[f4]
-stonewall
-rw=randread
diff --git a/examples/tiobench-example.fio b/examples/tiobench-example.fio
new file mode 100644 (file)
index 0000000..5a4493e
--- /dev/null
@@ -0,0 +1,24 @@
+; tiobench like setup, add more fX files between the stonewalls to
+; create more threads
+
+[global]
+direct=1
+size=512m
+bsrange=4k-4k
+timeout=60
+numjobs=4      ; 4 simultaneous threads for each job
+
+[f1]
+rw=write
+
+[f2]
+stonewall
+rw=randwrite
+
+[f3]
+stonewall
+rw=read
+
+[f4]
+stonewall
+rw=randread
index 9679c88d28245f287d64f13092a72631f7121fca..79e29da872cb85ebbb8eab682c2f935c82ce0606 100644 (file)
@@ -94,9 +94,9 @@ static int extend_file(struct thread_data *td, struct fio_file *f)
 
                        r = fallocate(f->fd, FALLOC_FL_KEEP_SIZE, 0,
                                        f->real_file_size);
-                       if (r != 0) {
+                       if (r != 0)
                                td_verror(td, errno, "fallocate");
-                       }
+
                        break;
 #endif /* FIO_HAVE_LINUX_FALLOCATE */
                default:
diff --git a/fio.1 b/fio.1
index fad0ae4ffbf7f70197d1669a8e745bdb9e88bc22..13abb94fd27715cae1377c250d643ba6c3e58953 100644 (file)
--- a/fio.1
+++ b/fio.1
@@ -1048,6 +1048,12 @@ Some parameters are only valid when a specific ioengine is in use. These are
 used identically to normal parameters, with the caveat that when used on the
 command line, the must come after the ioengine that defines them is selected.
 .TP
+.BI (cpu)cpuload \fR=\fPint
+Attempt to use the specified percentage of CPU cycles.
+.TP
+.BI (cpu)cpuchunks \fR=\fPint
+Split the load into cycles of the given time. In microseconds.
+.TP
 .BI (libaio)userspace_reap
 Normally, with the libaio engine in use, fio will use
 the io_getevents system call to reap newly returned events.
diff --git a/fio.c b/fio.c
index ac026fba8117ed09995ba4cfd398ab1f10bf19d3..9a6c31a65d912ba39a44f2cae1ce9ec8378146a6 100644 (file)
--- a/fio.c
+++ b/fio.c
 #include "profile.h"
 #include "lib/rand.h"
 #include "memalign.h"
+#include "client.h"
 #include "server.h"
 
-uintptr_t page_mask;
-uintptr_t page_size;
-
-static int endian_check(void)
-{
-       union {
-               uint8_t c[8];
-               uint64_t v;
-       } u;
-       int le = 0, be = 0;
-
-       u.v = 0x12;
-       if (u.c[7] == 0x12)
-               be = 1;
-       else if (u.c[0] == 0x12)
-               le = 1;
-
-#if defined(FIO_LITTLE_ENDIAN)
-       if (be)
-               return 1;
-#elif defined(FIO_BIG_ENDIAN)
-       if (le)
-               return 1;
-#else
-       return 1;
-#endif
-
-       if (!le && !be)
-               return 1;
-
-       return 0;
-}
-
 int main(int argc, char *argv[], char *envp[])
 {
-       long ps;
-
-       if (endian_check()) {
-               log_err("fio: endianness settings appear wrong.\n");
-               log_err("fio: please report this to fio@vger.kernel.org\n");
+       if (initialize_fio(envp))
                return 1;
-       }
-
-       arch_init(envp);
-
-       sinit();
-
-       /*
-        * We need locale for number printing, if it isn't set then just
-        * go with the US format.
-        */
-       if (!getenv("LC_NUMERIC"))
-               setlocale(LC_NUMERIC, "en_US");
-
-       ps = sysconf(_SC_PAGESIZE);
-       if (ps < 0) {
-               log_err("Failed to get page size\n");
-               return 1;
-       }
-
-       page_size = ps;
-       page_mask = ps - 1;
-
-       fio_keywords_init();
 
        if (parse_options(argc, argv))
                return 1;
 
-       if (nr_clients)
-               return fio_handle_clients();
-       else
+       if (nr_clients) {
+               if (fio_start_all_clients())
+                       return 1;
+               return fio_handle_clients(&fio_client_ops);
+       } else
                return fio_backend();
 }
diff --git a/fio.h b/fio.h
index 8bb5b0346eddde2f13aa17b87f8408d4ae281d8c..7f11861d074bb6855d6690fc4c0a8b1f765ea7e9 100644 (file)
--- a/fio.h
+++ b/fio.h
 struct thread_data;
 
 #include "compiler/compiler.h"
+#include "thread_options.h"
 #include "flist.h"
 #include "fifo.h"
-#include "rbtree.h"
+#include "lib/rbtree.h"
 #include "arch/arch.h"
 #include "os/os.h"
 #include "mutex.h"
@@ -36,6 +37,7 @@ struct thread_data;
 #include "gettime.h"
 #include "lib/getopt.h"
 #include "lib/rand.h"
+#include "client.h"
 #include "server.h"
 #include "stat.h"
 #include "flow.h"
@@ -48,17 +50,6 @@ struct thread_data;
 #include <sys/asynch.h>
 #endif
 
-/*
- * What type of allocation to use for io buffers
- */
-enum fio_memtype {
-       MEM_MALLOC = 0, /* ordinary malloc */
-       MEM_SHM,        /* use shared memory segments */
-       MEM_SHMHUGE,    /* use shared memory segments with huge pages */
-       MEM_MMAP,       /* use anonynomous mmap */
-       MEM_MMAPHUGE,   /* memory mapped huge file */
-};
-
 /*
  * offset generator types
  */
@@ -67,222 +58,6 @@ enum {
        RW_SEQ_IDENT,
 };
 
-/*
- * What type of errors to continue on when continue_on_error is used
- */
-enum error_type_bit {
-       ERROR_TYPE_READ_BIT = 0,
-       ERROR_TYPE_WRITE_BIT = 1,
-       ERROR_TYPE_VERIFY_BIT = 2,
-       ERROR_TYPE_CNT = 3,
-};
-
-enum error_type {
-        ERROR_TYPE_NONE = 0,
-        ERROR_TYPE_READ = 1 << ERROR_TYPE_READ_BIT,
-        ERROR_TYPE_WRITE = 1 << ERROR_TYPE_WRITE_BIT,
-        ERROR_TYPE_VERIFY = 1 << ERROR_TYPE_VERIFY_BIT,
-        ERROR_TYPE_ANY = 0xffff,
-};
-
-struct bssplit {
-       unsigned int bs;
-       unsigned char perc;
-};
-
-struct thread_options {
-       int pad;
-       char *description;
-       char *name;
-       char *directory;
-       char *filename;
-       char *opendir;
-       char *ioengine;
-       enum td_ddir td_ddir;
-       unsigned int rw_seq;
-       unsigned int kb_base;
-       unsigned int ddir_seq_nr;
-       long ddir_seq_add;
-       unsigned int iodepth;
-       unsigned int iodepth_low;
-       unsigned int iodepth_batch;
-       unsigned int iodepth_batch_complete;
-
-       unsigned long long size;
-       unsigned int size_percent;
-       unsigned int fill_device;
-       unsigned long long file_size_low;
-       unsigned long long file_size_high;
-       unsigned long long start_offset;
-
-       unsigned int bs[DDIR_RWDIR_CNT];
-       unsigned int ba[DDIR_RWDIR_CNT];
-       unsigned int min_bs[DDIR_RWDIR_CNT];
-       unsigned int max_bs[DDIR_RWDIR_CNT];
-       struct bssplit *bssplit[DDIR_RWDIR_CNT];
-       unsigned int bssplit_nr[DDIR_RWDIR_CNT];
-
-       int *ignore_error[ERROR_TYPE_CNT];
-       unsigned int ignore_error_nr[ERROR_TYPE_CNT];
-       unsigned int error_dump;
-
-       unsigned int nr_files;
-       unsigned int open_files;
-       enum file_lock_mode file_lock_mode;
-       unsigned int lockfile_batch;
-
-       unsigned int odirect;
-       unsigned int invalidate_cache;
-       unsigned int create_serialize;
-       unsigned int create_fsync;
-       unsigned int create_on_open;
-       unsigned int create_only;
-       unsigned int end_fsync;
-       unsigned int pre_read;
-       unsigned int sync_io;
-       unsigned int verify;
-       unsigned int do_verify;
-       unsigned int verifysort;
-       unsigned int verify_interval;
-       unsigned int verify_offset;
-       char verify_pattern[MAX_PATTERN_SIZE];
-       unsigned int verify_pattern_bytes;
-       unsigned int verify_fatal;
-       unsigned int verify_dump;
-       unsigned int verify_async;
-       unsigned long long verify_backlog;
-       unsigned int verify_batch;
-       unsigned int use_thread;
-       unsigned int unlink;
-       unsigned int do_disk_util;
-       unsigned int override_sync;
-       unsigned int rand_repeatable;
-       unsigned int use_os_rand;
-       unsigned int write_lat_log;
-       unsigned int write_bw_log;
-       unsigned int write_iops_log;
-       unsigned int log_avg_msec;
-       unsigned int norandommap;
-       unsigned int softrandommap;
-       unsigned int bs_unaligned;
-       unsigned int fsync_on_close;
-
-       unsigned int hugepage_size;
-       unsigned int rw_min_bs;
-       unsigned int thinktime;
-       unsigned int thinktime_spin;
-       unsigned int thinktime_blocks;
-       unsigned int fsync_blocks;
-       unsigned int fdatasync_blocks;
-       unsigned int barrier_blocks;
-       unsigned long long start_delay;
-       unsigned long long timeout;
-       unsigned long long ramp_time;
-       unsigned int overwrite;
-       unsigned int bw_avg_time;
-       unsigned int iops_avg_time;
-       unsigned int loops;
-       unsigned long long zone_range;
-       unsigned long long zone_size;
-       unsigned long long zone_skip;
-       enum fio_memtype mem_type;
-       unsigned int mem_align;
-
-       unsigned int stonewall;
-       unsigned int new_group;
-       unsigned int numjobs;
-       os_cpu_mask_t cpumask;
-       unsigned int cpumask_set;
-       os_cpu_mask_t verify_cpumask;
-       unsigned int verify_cpumask_set;
-       unsigned int iolog;
-       unsigned int rwmixcycle;
-       unsigned int rwmix[2];
-       unsigned int nice;
-       unsigned int file_service_type;
-       unsigned int group_reporting;
-       unsigned int fadvise_hint;
-       enum fio_fallocate_mode fallocate_mode;
-       unsigned int zero_buffers;
-       unsigned int refill_buffers;
-       unsigned int scramble_buffers;
-       unsigned int compress_percentage;
-       unsigned int compress_chunk;
-       unsigned int time_based;
-       unsigned int disable_lat;
-       unsigned int disable_clat;
-       unsigned int disable_slat;
-       unsigned int disable_bw;
-       unsigned int gtod_reduce;
-       unsigned int gtod_cpu;
-       unsigned int gtod_offload;
-       enum fio_cs clocksource;
-       unsigned int no_stall;
-       unsigned int trim_percentage;
-       unsigned int trim_batch;
-       unsigned int trim_zero;
-       unsigned long long trim_backlog;
-       unsigned int clat_percentiles;
-       unsigned int overwrite_plist;
-       fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
-
-       char *read_iolog_file;
-       char *write_iolog_file;
-       char *bw_log_file;
-       char *lat_log_file;
-       char *iops_log_file;
-       char *replay_redirect;
-
-       /*
-        * Pre-run and post-run shell
-        */
-       char *exec_prerun;
-       char *exec_postrun;
-
-       unsigned int rate[DDIR_RWDIR_CNT];
-       unsigned int ratemin[DDIR_RWDIR_CNT];
-       unsigned int ratecycle;
-       unsigned int rate_iops[DDIR_RWDIR_CNT];
-       unsigned int rate_iops_min[DDIR_RWDIR_CNT];
-
-       char *ioscheduler;
-
-       /*
-        * CPU "io" cycle burner
-        */
-       unsigned int cpuload;
-       unsigned int cpucycle;
-
-       /*
-        * I/O Error handling
-        */
-       enum error_type continue_on_error;
-
-       /*
-        * Benchmark profile type
-        */
-       char *profile;
-
-       /*
-        * blkio cgroup support
-        */
-       char *cgroup;
-       unsigned int cgroup_weight;
-       unsigned int cgroup_nodelete;
-
-       unsigned int uid;
-       unsigned int gid;
-
-       int flow_id;
-       int flow;
-       int flow_watermark;
-       unsigned int flow_sleep;
-
-       unsigned long long offset_increment;
-
-       unsigned int sync_file_range;
-};
-
 /*
  * This describes a single thread/process executing a fio job.
  */
@@ -291,10 +66,12 @@ struct thread_data {
        void *eo;
        char verror[FIO_VERROR_SIZE];
        pthread_t thread;
-       int thread_number;
-       int groupid;
+       unsigned int thread_number;
+       unsigned int groupid;
        struct thread_stat ts;
 
+       int client_type;
+
        struct io_log *slat_log;
        struct io_log *clat_log;
        struct io_log *lat_log;
@@ -329,12 +106,9 @@ struct thread_data {
        size_t orig_buffer_size;
        volatile int terminate;
        volatile int runstate;
-       unsigned int ioprio;
-       unsigned int ioprio_set;
        unsigned int last_was_sync;
        enum fio_ddir last_ddir;
 
-       char *mmapfile;
        int mmapfd;
 
        void *iolog_buf;
@@ -500,6 +274,8 @@ struct thread_data {
         */
        struct prof_io_ops prof_io_ops;
        void *prof_data;
+
+       void *pinned_mem;
 };
 
 /*
@@ -534,12 +310,10 @@ enum {
 
 extern int exitall_on_terminate;
 extern unsigned int thread_number;
-extern unsigned int nr_process, nr_thread;
 extern int shm_id;
 extern int groupid;
 extern int output_format;
 extern int temp_stall_ts;
-extern unsigned long long mlock_size;
 extern uintptr_t page_mask, page_size;
 extern int read_only;
 extern int eta_print;
@@ -620,9 +394,10 @@ static inline int should_fsync(struct thread_data *td)
 /*
  * Init/option functions
  */
+extern int __must_check fio_init_options(void);
 extern int __must_check parse_options(int, char **);
-extern int parse_jobs_ini(char *, int, int);
-extern int parse_cmd_line(int, char **);
+extern int parse_jobs_ini(char *, int, int, int);
+extern int parse_cmd_line(int, char **, int);
 extern int fio_backend(void);
 extern void reset_fio_state(void);
 extern void clear_io_state(struct thread_data *);
@@ -637,10 +412,14 @@ extern void fio_options_dup_and_init(struct option *);
 extern void fio_options_mem_dupe(struct thread_data *);
 extern void options_mem_dupe(void *data, struct fio_option *options);
 extern void td_fill_rand_seeds(struct thread_data *);
-extern void add_job_opts(const char **);
+extern void add_job_opts(const char **, int);
 extern char *num2str(unsigned long, int, int, int);
 extern int ioengine_load(struct thread_data *);
 
+extern unsigned long page_mask;
+extern unsigned long page_size;
+extern int initialize_fio(char *envp[]);
+
 #define FIO_GETOPT_JOB         0x89000000
 #define FIO_GETOPT_IOENGINE    0x98000000
 #define FIO_NR_OPTIONS         (FIO_MAX_OPTS + 128)
@@ -650,6 +429,7 @@ extern int ioengine_load(struct thread_data *);
  */
 extern void print_thread_status(void);
 extern void print_status_init(int);
+extern char *fio_uint_to_kmg(unsigned int val);
 
 /*
  * Thread life cycle. Once a thread has a runstate beyond TD_INITIALIZED, it
@@ -677,10 +457,11 @@ extern void fio_terminate_threads(int);
 /*
  * Memory helpers
  */
-extern int __must_check fio_pin_memory(void);
-extern void fio_unpin_memory(void);
+extern int __must_check fio_pin_memory(struct thread_data *);
+extern void fio_unpin_memory(struct thread_data *);
 extern int __must_check allocate_io_mem(struct thread_data *);
 extern void free_io_mem(struct thread_data *);
+extern void free_threads_shm(void);
 
 /*
  * Reset stats after ramp time completes
@@ -789,6 +570,8 @@ static inline void td_io_u_free_notify(struct thread_data *td)
 extern const char *fio_get_arch_string(int);
 extern const char *fio_get_os_string(int);
 
+#define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0])))
+
 enum {
        FIO_OUTPUT_TERSE        = 0,
        FIO_OUTPUT_JSON,
diff --git a/gclient.c b/gclient.c
new file mode 100644 (file)
index 0000000..b8c681a
--- /dev/null
+++ b/gclient.c
@@ -0,0 +1,1401 @@
+#include <malloc.h>
+#include <string.h>
+
+#include <glib.h>
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+#include "fio.h"
+#include "gfio.h"
+#include "ghelpers.h"
+#include "goptions.h"
+#include "gerror.h"
+#include "graph.h"
+#include "gclient.h"
+#include "printing.h"
+
+static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
+                           struct group_run_stats *rs);
+
+static gboolean results_window_delete(GtkWidget *w, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+
+       gtk_widget_destroy(w);
+       ge->results_window = NULL;
+       ge->results_notebook = NULL;
+       return TRUE;
+}
+
+static void results_close(GtkWidget *w, gpointer *data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+
+       gtk_widget_destroy(ge->results_window);
+}
+
+static void results_print(GtkWidget *w, gpointer *data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+
+       gfio_print_results(ge);
+}
+
+static GtkActionEntry results_menu_items[] = {
+       { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
+       { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
+       { "PrintFile", GTK_STOCK_PRINT, "Print", "<Control>P", NULL, G_CALLBACK(results_print) },
+       { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(results_close) },
+};
+static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]);
+
+static const gchar *results_ui_string = " \
+       <ui> \
+               <menubar name=\"MainMenu\"> \
+                       <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
+                               <menuitem name=\"Print\" action=\"PrintFile\" /> \
+                               <menuitem name=\"Close\" action=\"CloseFile\" /> \
+                       </menu> \
+                       <menu name=\"GraphMenu\" action=\"GraphMenuAction\"> \
+                       </menu>\
+               </menubar> \
+       </ui> \
+";
+
+static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge)
+{
+       GtkActionGroup *action_group;
+       GtkWidget *widget;
+       GError *error = 0;
+
+       ge->results_uimanager = gtk_ui_manager_new();
+
+       action_group = gtk_action_group_new("ResultsMenu");
+       gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge);
+
+       gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0);
+       gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error);
+
+       gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager));
+
+       widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu");
+       return widget;
+}
+
+static GtkWidget *get_results_window(struct gui_entry *ge)
+{
+       GtkWidget *win, *notebook, *vbox;
+
+       if (ge->results_window)
+               return ge->results_notebook;
+
+       win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_title(GTK_WINDOW(win), "Results");
+       gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
+       g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
+       g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
+
+       vbox = gtk_vbox_new(FALSE, 0);
+       gtk_container_add(GTK_CONTAINER(win), vbox);
+
+       ge->results_menu = get_results_menubar(win, ge);
+       gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0);
+
+       notebook = gtk_notebook_new();
+       gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
+       gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
+       gtk_container_add(GTK_CONTAINER(vbox), notebook);
+
+       ge->results_window = win;
+       ge->results_notebook = notebook;
+       return ge->results_notebook;
+}
+
+static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       struct gui_entry *ge = gc->ge;
+       struct gui *ui = ge->ui;
+       GtkTreeIter iter;
+       struct tm *tm;
+       time_t sec;
+       char tmp[64], timebuf[80];
+
+       sec = p->log_sec;
+       tm = localtime(&sec);
+       strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm);
+       sprintf(timebuf, "%s.%03ld", tmp, (long) p->log_usec / 1000);
+
+       gdk_threads_enter();
+
+       gtk_list_store_append(ui->log_model, &iter);
+       gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
+       gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
+       gtk_list_store_set(ui->log_model, &iter, 2, log_get_level(p->level), -1);
+       gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
+
+       if (p->level == FIO_LOG_ERR)
+               gfio_view_log(ui);
+
+       gdk_threads_leave();
+}
+
+static void disk_util_destroy(GtkWidget *w, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+
+       ge->disk_util_vbox = NULL;
+       gtk_widget_destroy(w);
+}
+
+static GtkWidget *gfio_disk_util_get_vbox(struct gui_entry *ge)
+{
+       GtkWidget *vbox, *box, *scroll, *res_notebook;
+
+       if (ge->disk_util_vbox)
+               return ge->disk_util_vbox;
+
+       scroll = get_scrolled_window(5);
+       vbox = gtk_vbox_new(FALSE, 3);
+       box = gtk_hbox_new(FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
+
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
+       res_notebook = get_results_window(ge);
+
+       gtk_notebook_append_page(GTK_NOTEBOOK(res_notebook), scroll, gtk_label_new("Disk utilization"));
+       ge->disk_util_vbox = box;
+       g_signal_connect(vbox, "destroy", G_CALLBACK(disk_util_destroy), ge);
+
+       return ge->disk_util_vbox;
+}
+
+static int __gfio_disk_util_show(GtkWidget *res_notebook,
+                                struct gfio_client *gc, struct cmd_du_pdu *p)
+{
+       GtkWidget *box, *frame, *entry, *vbox, *util_vbox;
+       struct gui_entry *ge = gc->ge;
+       double util;
+       char tmp[16];
+
+       util_vbox = gfio_disk_util_get_vbox(ge);
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(util_vbox), vbox);
+
+       frame = gtk_frame_new((char *) p->dus.name);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
+
+       box = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       frame = gtk_frame_new("Read");
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
+       vbox = gtk_hbox_new(TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       entry = new_info_entry_in_frame(vbox, "IOs");
+       entry_set_int_value(entry, p->dus.ios[0]);
+       entry = new_info_entry_in_frame(vbox, "Merges");
+       entry_set_int_value(entry, p->dus.merges[0]);
+       entry = new_info_entry_in_frame(vbox, "Sectors");
+       entry_set_int_value(entry, p->dus.sectors[0]);
+       entry = new_info_entry_in_frame(vbox, "Ticks");
+       entry_set_int_value(entry, p->dus.ticks[0]);
+
+       frame = gtk_frame_new("Write");
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
+       vbox = gtk_hbox_new(TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       entry = new_info_entry_in_frame(vbox, "IOs");
+       entry_set_int_value(entry, p->dus.ios[1]);
+       entry = new_info_entry_in_frame(vbox, "Merges");
+       entry_set_int_value(entry, p->dus.merges[1]);
+       entry = new_info_entry_in_frame(vbox, "Sectors");
+       entry_set_int_value(entry, p->dus.sectors[1]);
+       entry = new_info_entry_in_frame(vbox, "Ticks");
+       entry_set_int_value(entry, p->dus.ticks[1]);
+
+       frame = gtk_frame_new("Shared");
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
+       vbox = gtk_hbox_new(TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       entry = new_info_entry_in_frame(vbox, "IO ticks");
+       entry_set_int_value(entry, p->dus.io_ticks);
+       entry = new_info_entry_in_frame(vbox, "Time in queue");
+       entry_set_int_value(entry, p->dus.time_in_queue);
+
+       util = 0.0;
+       if (p->dus.msec)
+               util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec;
+       if (util > 100.0)
+               util = 100.0;
+
+       sprintf(tmp, "%3.2f%%", util);
+       entry = new_info_entry_in_frame(vbox, "Disk utilization");
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+
+       gtk_widget_show_all(ge->results_window);
+       return 0;
+}
+
+static int gfio_disk_util_show(struct gfio_client *gc)
+{
+       struct gui_entry *ge = gc->ge;
+       GtkWidget *res_notebook;
+       int i;
+
+       if (!gc->nr_du)
+               return 1;
+
+       res_notebook = get_results_window(ge);
+
+       for (i = 0; i < gc->nr_du; i++) {
+               struct cmd_du_pdu *p = &gc->du[i];
+
+               __gfio_disk_util_show(res_notebook, gc, p);
+       }
+
+       gtk_widget_show_all(ge->results_window);
+       return 0;
+}
+
+static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       struct gui_entry *ge = gc->ge;
+       unsigned int nr = gc->nr_du;
+
+       gc->du = realloc(gc->du, (nr + 1) * sizeof(struct cmd_du_pdu));
+       memcpy(&gc->du[nr], p, sizeof(*p));
+       gc->nr_du++;
+
+       gdk_threads_enter();
+       if (ge->results_window)
+               __gfio_disk_util_show(ge->results_notebook, gc, p);
+       else
+               gfio_disk_util_show(gc);
+       gdk_threads_leave();
+}
+
+extern int sum_stat_clients;
+extern struct thread_stat client_ts;
+extern struct group_run_stats client_gs;
+
+static int sum_stat_nr;
+
+static void gfio_thread_status_op(struct fio_client *client,
+                                 struct fio_net_cmd *cmd)
+{
+       struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+
+       gfio_display_ts(client, &p->ts, &p->rs);
+
+       if (sum_stat_clients == 1)
+               return;
+
+       sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
+       sum_group_stats(&client_gs, &p->rs);
+
+       client_ts.members++;
+       client_ts.thread_number = p->ts.thread_number;
+       client_ts.groupid = p->ts.groupid;
+
+       if (++sum_stat_nr == sum_stat_clients) {
+               strcpy(client_ts.name, "All clients");
+               gfio_display_ts(client, &client_ts, &client_gs);
+       }
+}
+
+static void gfio_group_stats_op(struct fio_client *client,
+                               struct fio_net_cmd *cmd)
+{
+       /* We're ignoring group stats for now */
+}
+
+static void gfio_update_thread_status(struct gui_entry *ge,
+                                     char *status_message, double perc)
+{
+       static char message[100];
+       const char *m = message;
+
+       strncpy(message, status_message, sizeof(message) - 1);
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
+       gtk_widget_queue_draw(ge->ui->window);
+}
+
+static void gfio_update_thread_status_all(struct gui *ui, char *status_message,
+                                         double perc)
+{
+       static char message[100];
+       const char *m = message;
+
+       strncpy(message, status_message, sizeof(message) - 1);
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
+       gtk_widget_queue_draw(ui->window);
+}
+
+/*
+ * Client specific ETA
+ */
+static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
+{
+       struct gfio_client *gc = client->client_data;
+       struct gui_entry *ge = gc->ge;
+       static int eta_good;
+       char eta_str[128];
+       char output[256];
+       char tmp[32];
+       double perc = 0.0;
+       int i2p = 0;
+
+       gdk_threads_enter();
+
+       eta_str[0] = '\0';
+       output[0] = '\0';
+
+       if (je->eta_sec != INT_MAX && je->elapsed_sec) {
+               perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
+               eta_to_str(eta_str, je->eta_sec);
+       }
+
+       sprintf(tmp, "%u", je->nr_running);
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
+       sprintf(tmp, "%u", je->files_open);
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
+
+#if 0
+       if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
+       if (je->m_rate || je->t_rate) {
+               char *tr, *mr;
+
+               mr = num2str(je->m_rate, 4, 0, i2p);
+               tr = num2str(je->t_rate, 4, 0, i2p);
+               gtk_entry_set_text(GTK_ENTRY(ge->eta);
+               p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
+               free(tr);
+               free(mr);
+       } else if (je->m_iops || je->t_iops)
+               p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
+
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
+#endif
+
+       if (je->eta_sec != INT_MAX && je->nr_running) {
+               char *iops_str[DDIR_RWDIR_CNT];
+               char *rate_str[DDIR_RWDIR_CNT];
+               int i;
+
+               if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
+                       strcpy(output, "-.-% done");
+               else {
+                       eta_good = 1;
+                       perc *= 100.0;
+                       sprintf(output, "%3.1f%% done", perc);
+               }
+
+               rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
+               rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
+               rate_str[2] = num2str(je->rate[2], 5, 10, i2p);
+
+               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+               iops_str[1] = num2str(je->iops[1], 4, 1, 0);
+               iops_str[2] = num2str(je->iops[2], 4, 1, 0);
+
+               gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
+               gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
+               gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
+               gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
+               gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_bw), rate_str[2]);
+               gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_iops), iops_str[2]);
+
+               graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]);
+               graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]);
+               graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.trim_iops, je->elapsed_sec, je->iops[2], iops_str[2]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.trim_bw, je->elapsed_sec, je->rate[2], rate_str[2]);
+
+               for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+                       free(rate_str[i]);
+                       free(iops_str[i]);
+               }
+       }
+
+       if (eta_str[0]) {
+               char *dst = output + strlen(output);
+
+               sprintf(dst, " - %s", eta_str);
+       }
+
+       gfio_update_thread_status(ge, output, perc);
+       gdk_threads_leave();
+}
+
+/*
+ * Update ETA in main window for all clients
+ */
+static void gfio_update_all_eta(struct jobs_eta *je)
+{
+       struct gui *ui = &main_ui;
+       static int eta_good;
+       char eta_str[128];
+       char output[256];
+       double perc = 0.0;
+       int i, i2p = 0;
+
+       gdk_threads_enter();
+
+       eta_str[0] = '\0';
+       output[0] = '\0';
+
+       if (je->eta_sec != INT_MAX && je->elapsed_sec) {
+               perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
+               eta_to_str(eta_str, je->eta_sec);
+       }
+
+#if 0
+       if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
+       if (je->m_rate || je->t_rate) {
+               char *tr, *mr;
+
+               mr = num2str(je->m_rate, 4, 0, i2p);
+               tr = num2str(je->t_rate, 4, 0, i2p);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta);
+               p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
+               free(tr);
+               free(mr);
+       } else if (je->m_iops || je->t_iops)
+               p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
+
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
+#endif
+
+       entry_set_int_value(ui->eta.jobs, je->nr_running);
+
+       if (je->eta_sec != INT_MAX && je->nr_running) {
+               char *iops_str[3];
+               char *rate_str[3];
+
+               if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
+                       strcpy(output, "-.-% done");
+               else {
+                       eta_good = 1;
+                       perc *= 100.0;
+                       sprintf(output, "%3.1f%% done", perc);
+               }
+
+               rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
+               rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
+               rate_str[2] = num2str(je->rate[2], 5, 10, i2p);
+
+               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+               iops_str[1] = num2str(je->iops[1], 4, 1, 0);
+               iops_str[2] = num2str(je->iops[2], 4, 1, 0);
+
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_bw), rate_str[2]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_iops), iops_str[2]);
+
+               graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]);
+               graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]);
+               graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.trim_iops, je->elapsed_sec, je->iops[2], iops_str[2]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.trim_bw, je->elapsed_sec, je->rate[2], rate_str[2]);
+
+               for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+                       free(rate_str[i]);
+                       free(iops_str[i]);
+               }
+       }
+
+       if (eta_str[0]) {
+               char *dst = output + strlen(output);
+
+               sprintf(dst, " - %s", eta_str);
+       }
+
+       gfio_update_thread_status_all(ui, output, perc);
+       gdk_threads_leave();
+}
+
+static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       struct gui_entry *ge = gc->ge;
+       const char *os, *arch;
+
+       os = fio_get_os_string(probe->os);
+       if (!os)
+               os = "unknown";
+
+       arch = fio_get_arch_string(probe->arch);
+       if (!arch)
+               os = "unknown";
+
+       if (!client->name)
+               client->name = strdup((char *) probe->hostname);
+
+       gc->client_cpus = le32_to_cpu(probe->cpus);
+       gc->client_flags = le64_to_cpu(probe->flags);
+
+       gdk_threads_enter();
+
+       gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
+       gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
+       gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
+       gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), (char *) probe->fio_version);
+
+       gfio_set_state(ge, GE_STATE_CONNECTED);
+
+       gdk_threads_leave();
+}
+
+static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct gfio_client *gc = client->client_data;
+
+       gdk_threads_enter();
+       gfio_set_state(gc->ge, GE_STATE_NEW);
+       gdk_threads_leave();
+}
+
+static struct thread_options *gfio_client_add_job(struct gfio_client *gc,
+                       struct thread_options_pack *top)
+{
+       struct gfio_client_options *gco;
+
+       gco = calloc(1, sizeof(*gco));
+       convert_thread_options_to_cpu(&gco->o, top);
+       INIT_FLIST_HEAD(&gco->list);
+       flist_add_tail(&gco->list, &gc->o_list);
+       gc->o_list_nr = 1;
+       return &gco->o;
+}
+
+static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       struct gui_entry *ge = gc->ge;
+       struct thread_options *o;
+       char *c1, *c2, *c3, *c4;
+       char tmp[80];
+
+       p->thread_number = le32_to_cpu(p->thread_number);
+       p->groupid = le32_to_cpu(p->groupid);
+       o = gfio_client_add_job(gc, &p->top);
+
+       gdk_threads_enter();
+
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ge->eta.names), (gchar *) o->name);
+       gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
+
+       sprintf(tmp, "%s %s", o->odirect ? "direct" : "buffered", ddir_str(o->td_ddir));
+       multitext_add_entry(&ge->eta.iotype, tmp);
+
+       c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
+       c2 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
+       c3 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
+       c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
+       sprintf(tmp, "%s-%s/%s-%s", c1, c2, c3, c4);
+       free(c1);
+       free(c2);
+       free(c3);
+       free(c4);
+       multitext_add_entry(&ge->eta.bs, tmp);
+
+       multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
+
+       sprintf(tmp, "%u", o->iodepth);
+       multitext_add_entry(&ge->eta.iodepth, tmp);
+
+       multitext_set_entry(&ge->eta.iotype, 0);
+       multitext_set_entry(&ge->eta.bs, 0);
+       multitext_set_entry(&ge->eta.ioengine, 0);
+       multitext_set_entry(&ge->eta.iodepth, 0);
+
+       gfio_set_state(ge, GE_STATE_JOB_SENT);
+
+       gdk_threads_leave();
+}
+
+static void gfio_update_job_op(struct fio_client *client,
+                              struct fio_net_cmd *cmd)
+{
+       uint32_t *pdu_error = (uint32_t *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+
+       gc->update_job_status = le32_to_cpu(*pdu_error);
+       gc->update_job_done = 1;
+}
+
+static void gfio_client_timed_out(struct fio_client *client)
+{
+       struct gfio_client *gc = client->client_data;
+       char buf[256];
+
+       gdk_threads_enter();
+
+       gfio_set_state(gc->ge, GE_STATE_NEW);
+       clear_ge_ui_info(gc->ge);
+
+       sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
+       gfio_report_info(gc->ge->ui, "Network timeout", buf);
+
+       gdk_threads_leave();
+}
+
+static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct gfio_client *gc = client->client_data;
+
+       gdk_threads_enter();
+
+       gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
+
+       if (gc->err_entry)
+               entry_set_int_value(gc->err_entry, client->error);
+
+       gdk_threads_leave();
+}
+
+static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct gfio_client *gc = client->client_data;
+
+       gdk_threads_enter();
+       gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
+       gdk_threads_leave();
+}
+
+static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct gfio_client *gc = client->client_data;
+
+       gdk_threads_enter();
+       gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
+       gdk_threads_leave();
+}
+
+static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu)
+{
+       printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples);
+       free(pdu);
+}
+
+static void gfio_add_total_depths_tree(GtkListStore *model,
+                                      struct thread_stat *ts, unsigned int len)
+{
+       double io_u_dist[FIO_IO_U_MAP_NR];
+       GtkTreeIter iter;
+       /* Bits 1-6, and 8 */
+       const int add_mask = 0x17e;
+       int i, j;
+
+       stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
+
+       gtk_list_store_append(model, &iter);
+
+       gtk_list_store_set(model, &iter, 0, "Total", -1);
+
+       for (i = 1, j = 0; i < len; i++) {
+               char fbuf[32];
+
+               if (!(add_mask & (1UL << (i - 1))))
+                       sprintf(fbuf, "0.0%%");
+               else {
+                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
+                       j++;
+               }
+
+               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       }
+
+}
+
+static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts,
+                                struct group_run_stats *rs)
+{
+       unsigned int nr = gc->nr_results;
+
+       gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results));
+       memcpy(&gc->results[nr].ts, ts, sizeof(*ts));
+       memcpy(&gc->results[nr].gs, rs, sizeof(*rs));
+       gc->nr_results++;
+}
+
+static void gfio_add_sc_depths_tree(GtkListStore *model,
+                                   struct thread_stat *ts, unsigned int len,
+                                   int submit)
+{
+       double io_u_dist[FIO_IO_U_MAP_NR];
+       GtkTreeIter iter;
+       /* Bits 0, and 3-8 */
+       const int add_mask = 0x1f9;
+       int i, j;
+
+       if (submit)
+               stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
+       else
+               stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
+
+       gtk_list_store_append(model, &iter);
+
+       gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
+
+       for (i = 1, j = 0; i < len; i++) {
+               char fbuf[32];
+
+               if (!(add_mask & (1UL << (i - 1))))
+                       sprintf(fbuf, "0.0%%");
+               else {
+                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
+                       j++;
+               }
+
+               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       }
+
+}
+
+static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
+{
+       GtkWidget *frame, *box, *tree_view = NULL;
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       int i;
+       const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
+       const int nr_labels = ARRAY_SIZE(labels);
+       GType types[nr_labels];
+
+       frame = gtk_frame_new("IO depths");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       for (i = 0; i < nr_labels; i++)
+               types[i] = G_TYPE_STRING;
+
+       model = gtk_list_store_newv(nr_labels, types);
+
+       tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+       gtk_widget_set_can_focus(tree_view, FALSE);
+
+       g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
+       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+       gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+
+       for (i = 0; i < nr_labels; i++)
+               tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
+
+       gfio_add_total_depths_tree(model, ts, nr_labels);
+       gfio_add_sc_depths_tree(model, ts, nr_labels, 1);
+       gfio_add_sc_depths_tree(model, ts, nr_labels, 0);
+
+       gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, TRUE, 3);
+}
+
+static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
+{
+       GtkWidget *box, *frame, *entry;
+       double usr_cpu, sys_cpu;
+       unsigned long runtime;
+       char tmp[32];
+
+       runtime = ts->total_run_time;
+       if (runtime) {
+               double runt = (double) runtime;
+
+               usr_cpu = (double) ts->usr_time * 100 / runt;
+               sys_cpu = (double) ts->sys_time * 100 / runt;
+       } else {
+               usr_cpu = 0;
+               sys_cpu = 0;
+       }
+
+       frame = gtk_frame_new("OS resources");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       entry = new_info_entry_in_frame(box, "User CPU");
+       sprintf(tmp, "%3.2f%%", usr_cpu);
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+       entry = new_info_entry_in_frame(box, "System CPU");
+       sprintf(tmp, "%3.2f%%", sys_cpu);
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+       entry = new_info_entry_in_frame(box, "Context switches");
+       entry_set_int_value(entry, ts->ctx);
+       entry = new_info_entry_in_frame(box, "Major faults");
+       entry_set_int_value(entry, ts->majf);
+       entry = new_info_entry_in_frame(box, "Minor faults");
+       entry_set_int_value(entry, ts->minf);
+}
+
+static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels,
+                                         int num)
+{
+       GtkWidget *tree_view;
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       GtkTreeIter iter;
+       GType *types;
+       int i;
+
+       types = malloc(num * sizeof(GType));
+
+       for (i = 0; i < num; i++)
+               types[i] = G_TYPE_STRING;
+
+       model = gtk_list_store_newv(num, types);
+       free(types);
+       types = NULL;
+
+       tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+       gtk_widget_set_can_focus(tree_view, FALSE);
+
+       g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
+       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+       gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+
+       for (i = 0; i < num; i++)
+               tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
+
+       gtk_list_store_append(model, &iter);
+
+       for (i = 0; i < num; i++) {
+               char fbuf[32];
+
+               if (lat[i] <= 0.0)
+                       sprintf(fbuf, "0.00");
+               else
+                       sprintf(fbuf, "%3.2f%%", lat[i]);
+
+               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       }
+
+       return tree_view;
+}
+
+static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
+                                           const char **labels,
+                                           unsigned int len,
+                                           double xdim, double ydim)
+{
+       struct graph *g;
+       int i;
+
+       g = graph_new(xdim, ydim, gfio_graph_font);
+       graph_title(g, title);
+       graph_x_title(g, "Buckets");
+       graph_y_title(g, "Percent");
+
+       for (i = 0; i < len; i++) {
+               graph_label_t l;
+
+               l = graph_add_label(g, labels[i]);
+               graph_add_data(g, l, lat[i]);
+       }
+
+       return g;
+}
+
+static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
+{
+       struct graph *g = p;
+       cairo_t *cr;
+
+       cr = gdk_cairo_create(gtk_widget_get_window(w));
+#if 0
+       if (graph_has_tooltips(g)) {
+               g_object_set(w, "has-tooltip", TRUE, NULL);
+               g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g);
+       }
+#endif
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       bar_graph_draw(g, cr);
+       cairo_destroy(cr);
+
+       return FALSE;
+}
+
+static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event,
+                                      gpointer data)
+{
+       guint width = gtk_widget_get_allocated_width(w);
+       guint height = gtk_widget_get_allocated_height(w);
+       struct graph *g = data;
+
+       graph_set_size(g, width, height);
+       graph_set_size(g, width, height);
+       graph_set_position(g, 0, 0);
+       return TRUE;
+}
+
+static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox,
+                                     struct thread_stat *ts)
+{
+       double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR];
+       const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u",
+                                "250u", "500u", "750u", "1m", "2m",
+                                "4m", "10m", "20m", "50m", "100m",
+                                "250m", "500m", "750m", "1s", "2s", ">= 2s" };
+       int start, end, i;
+       const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR;
+       GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area;
+       struct gui_entry *ge = gc->ge;
+
+       stat_calc_lat_u(ts, io_u_lat);
+       stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]);
+
+       /*
+        * Found out which first bucket has entries, and which last bucket
+        */
+       start = end = -1U;
+       for (i = 0; i < total; i++) {
+               if (io_u_lat[i] == 0.00)
+                       continue;
+
+               if (start == -1U)
+                       start = i;
+               end = i;
+       }
+
+       /*
+        * No entries...
+        */
+       if (start == -1U)
+               return;
+
+       tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1);
+       ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0);
+
+       frame = gtk_frame_new("Latency buckets");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       completion_vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
+
+       drawing_area = gtk_drawing_area_new();
+       gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
+       gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
+       gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
+       g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph);
+       g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph);
+
+       gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3);
+}
+
+static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
+                         unsigned long max, double mean, double dev)
+{
+       const char *base = "(usec)";
+       GtkWidget *hbox, *label, *frame;
+       char *minp, *maxp;
+       char tmp[64];
+
+       if (!usec_to_msec(&min, &max, &mean, &dev))
+               base = "(msec)";
+
+       minp = num2str(min, 6, 1, 0);
+       maxp = num2str(max, 6, 1, 0);
+
+       sprintf(tmp, "%s %s", name, base);
+       frame = gtk_frame_new(tmp);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+       label = new_info_label_in_frame(hbox, "Minimum");
+       gtk_label_set_text(GTK_LABEL(label), minp);
+       label = new_info_label_in_frame(hbox, "Maximum");
+       gtk_label_set_text(GTK_LABEL(label), maxp);
+       label = new_info_label_in_frame(hbox, "Average");
+       sprintf(tmp, "%5.02f", mean);
+       gtk_label_set_text(GTK_LABEL(label), tmp);
+       label = new_info_label_in_frame(hbox, "Standard deviation");
+       sprintf(tmp, "%5.02f", dev);
+       gtk_label_set_text(GTK_LABEL(label), tmp);
+
+       free(minp);
+       free(maxp);
+}
+
+static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
+                                              fio_fp64_t *plist,
+                                              unsigned int len,
+                                              const char *base,
+                                              unsigned int scale)
+{
+       GType types[FIO_IO_U_LIST_MAX_LEN];
+       GtkWidget *tree_view;
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       GtkTreeIter iter;
+       int i;
+
+       for (i = 0; i < len; i++)
+               types[i] = G_TYPE_INT;
+
+       model = gtk_list_store_newv(len, types);
+
+       tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+       gtk_widget_set_can_focus(tree_view, FALSE);
+
+       g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
+       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+       gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+
+       for (i = 0; i < len; i++) {
+               char fbuf[8];
+
+               sprintf(fbuf, "%2.2f%%", plist[i].u.f);
+               tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
+       }
+
+       gtk_list_store_append(model, &iter);
+
+       for (i = 0; i < len; i++) {
+               if (scale)
+                       ovals[i] = (ovals[i] + 999) / 1000;
+               gtk_list_store_set(model, &iter, i, ovals[i], -1);
+       }
+
+       return tree_view;
+}
+
+static struct graph *setup_clat_graph(char *title, unsigned int *ovals,
+                                     fio_fp64_t *plist,
+                                     unsigned int len,
+                                     double xdim, double ydim)
+{
+       struct graph *g;
+       int i;
+
+       g = graph_new(xdim, ydim, gfio_graph_font);
+       graph_title(g, title);
+       graph_x_title(g, "Percentile");
+       graph_y_title(g, "Time");
+
+       for (i = 0; i < len; i++) {
+               graph_label_t l;
+               char fbuf[8];
+
+               sprintf(fbuf, "%2.2f%%", plist[i].u.f);
+               l = graph_add_label(g, fbuf);
+               graph_add_data(g, l, (double) ovals[i]);
+       }
+
+       return g;
+}
+
+static void gfio_show_clat_percentiles(struct gfio_client *gc,
+                                      GtkWidget *vbox, struct thread_stat *ts,
+                                      int ddir)
+{
+       unsigned int *io_u_plat = ts->io_u_plat[ddir];
+       unsigned long nr = ts->clat_stat[ddir].samples;
+       fio_fp64_t *plist = ts->percentile_list;
+       unsigned int *ovals, len, minv, maxv, scale_down;
+       const char *base;
+       GtkWidget *tree_view, *frame, *hbox, *drawing_area, *completion_vbox;
+       struct gui_entry *ge = gc->ge;
+       char tmp[64];
+
+       len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
+       if (!len)
+               goto out;
+
+       /*
+        * We default to usecs, but if the value range is such that we
+        * should scale down to msecs, do that.
+        */
+       if (minv > 2000 && maxv > 99999) {
+               scale_down = 1;
+               base = "msec";
+       } else {
+               scale_down = 0;
+               base = "usec";
+       }
+
+       sprintf(tmp, "Completion percentiles (%s)", base);
+       tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
+       ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0);
+
+       frame = gtk_frame_new(tmp);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       completion_vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
+       drawing_area = gtk_drawing_area_new();
+       gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
+       gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
+       gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
+       g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->clat_graph);
+       g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->clat_graph);
+
+       gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3);
+out:
+       if (ovals)
+               free(ovals);
+}
+
+#define GFIO_CLAT      1
+#define GFIO_SLAT      2
+#define GFIO_LAT       4
+
+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" };
+       GtkWidget *frame, *label, *box, *vbox, *main_vbox;
+       unsigned long min[3], max[3], runt;
+       unsigned long long bw, iops;
+       unsigned int flags = 0;
+       double mean[3], dev[3];
+       char *io_p, *bw_p, *iops_p;
+       int i2p;
+
+       if (!ts->runtime[ddir])
+               return;
+
+       i2p = is_power_of_2(rs->kb_base);
+       runt = ts->runtime[ddir];
+
+       bw = (1000 * ts->io_bytes[ddir]) / runt;
+       io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
+       bw_p = num2str(bw, 6, 1, i2p);
+
+       iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
+       iops_p = num2str(iops, 6, 1, 0);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
+
+       frame = gtk_frame_new(ddir_label[ddir]);
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 5);
+
+       main_vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), main_vbox);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
+
+       label = new_info_label_in_frame(box, "IO");
+       gtk_label_set_text(GTK_LABEL(label), io_p);
+       label = new_info_label_in_frame(box, "Bandwidth");
+       gtk_label_set_text(GTK_LABEL(label), bw_p);
+       label = new_info_label_in_frame(box, "IOPS");
+       gtk_label_set_text(GTK_LABEL(label), iops_p);
+       label = new_info_label_in_frame(box, "Runtime (msec)");
+       label_set_int_value(label, ts->runtime[ddir]);
+
+       if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
+               double p_of_agg = 100.0;
+               const char *bw_str = "KB";
+               char tmp[32];
+
+               if (rs->agg[ddir]) {
+                       p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
+                       if (p_of_agg > 100.0)
+                               p_of_agg = 100.0;
+               }
+
+               if (mean[0] > 999999.9) {
+                       min[0] /= 1000.0;
+                       max[0] /= 1000.0;
+                       mean[0] /= 1000.0;
+                       dev[0] /= 1000.0;
+                       bw_str = "MB";
+               }
+
+               sprintf(tmp, "Bandwidth (%s)", bw_str);
+               frame = gtk_frame_new(tmp);
+               gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
+
+               box = gtk_hbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), box);
+
+               label = new_info_label_in_frame(box, "Minimum");
+               label_set_int_value(label, min[0]);
+               label = new_info_label_in_frame(box, "Maximum");
+               label_set_int_value(label, max[0]);
+               label = new_info_label_in_frame(box, "Percentage of jobs");
+               sprintf(tmp, "%3.2f%%", p_of_agg);
+               gtk_label_set_text(GTK_LABEL(label), tmp);
+               label = new_info_label_in_frame(box, "Average");
+               sprintf(tmp, "%5.02f", mean[0]);
+               gtk_label_set_text(GTK_LABEL(label), tmp);
+               label = new_info_label_in_frame(box, "Standard deviation");
+               sprintf(tmp, "%5.02f", dev[0]);
+               gtk_label_set_text(GTK_LABEL(label), tmp);
+       }
+
+       if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
+               flags |= GFIO_SLAT;
+       if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
+               flags |= GFIO_CLAT;
+       if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
+               flags |= GFIO_LAT;
+
+       if (flags) {
+               frame = gtk_frame_new("Latency");
+               gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
+
+               vbox = gtk_vbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+               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 (ts->clat_percentiles)
+               gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
+
+       free(io_p);
+       free(bw_p);
+       free(iops_p);
+}
+
+static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc,
+                                      struct thread_stat *ts,
+                                      struct group_run_stats *rs)
+{
+       GtkWidget *box, *vbox, *entry, *scroll;
+       int i;
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
+       gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       vbox = gtk_vbox_new(FALSE, 3);
+
+       box = gtk_hbox_new(FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
+
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
+
+       gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name));
+
+       entry = new_info_entry_in_frame(box, "Name");
+       gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
+       if (strlen(ts->description)) {
+               entry = new_info_entry_in_frame(box, "Description");
+               gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
+       }
+       entry = new_info_entry_in_frame(box, "Group ID");
+       entry_set_int_value(entry, ts->groupid);
+       entry = new_info_entry_in_frame(box, "Jobs");
+       entry_set_int_value(entry, ts->members);
+       gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
+       entry_set_int_value(entry, ts->error);
+       entry = new_info_entry_in_frame(box, "PID");
+       entry_set_int_value(entry, ts->pid);
+
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               if (ts->io_bytes[i])
+                       gfio_show_ddir_status(gc, vbox, rs, ts, i);
+       }
+
+       gfio_show_latency_buckets(gc, vbox, ts);
+       gfio_show_cpu_usage(vbox, ts);
+       gfio_show_io_depths(vbox, ts);
+}
+
+void gfio_display_end_results(struct gfio_client *gc)
+{
+       struct gui_entry *ge = gc->ge;
+       GtkWidget *res_notebook;
+       int i;
+
+       res_notebook = get_results_window(ge);
+
+       for (i = 0; i < gc->nr_results; i++) {
+               struct end_results *e = &gc->results[i];
+
+               __gfio_display_end_results(res_notebook, gc, &e->ts, &e->gs);
+       }
+
+       if (gfio_disk_util_show(gc))
+               gtk_widget_show_all(ge->results_window);
+}
+
+static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
+                           struct group_run_stats *rs)
+{
+       struct gfio_client *gc = client->client_data;
+       struct gui_entry *ge = gc->ge;
+
+       gfio_add_end_results(gc, ts, rs);
+
+       gdk_threads_enter();
+       if (ge->results_window)
+               __gfio_display_end_results(ge->results_notebook, gc, ts, rs);
+       else
+               gfio_display_end_results(gc);
+       gdk_threads_leave();
+}
+
+static void gfio_client_removed(struct fio_client *client)
+{
+       struct gfio_client *gc = client->client_data;
+
+       assert(gc->client == client);
+       fio_put_client(gc->client);
+       gc->client = NULL;
+}
+
+struct client_ops gfio_client_ops = {
+       .text                   = gfio_text_op,
+       .disk_util              = gfio_disk_util_op,
+       .thread_status          = gfio_thread_status_op,
+       .group_stats            = gfio_group_stats_op,
+       .jobs_eta               = gfio_update_client_eta,
+       .eta                    = gfio_update_all_eta,
+       .probe                  = gfio_probe_op,
+       .quit                   = gfio_quit_op,
+       .add_job                = gfio_add_job_op,
+       .update_job             = gfio_update_job_op,
+       .timed_out              = gfio_client_timed_out,
+       .stop                   = gfio_client_stop,
+       .start                  = gfio_client_start,
+       .job_start              = gfio_client_job_start,
+       .iolog                  = gfio_client_iolog,
+       .removed                = gfio_client_removed,
+       .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
+       .stay_connected         = 1,
+       .client_type            = FIO_CLIENT_TYPE_GUI,
+};
diff --git a/gclient.h b/gclient.h
new file mode 100644 (file)
index 0000000..4038365
--- /dev/null
+++ b/gclient.h
@@ -0,0 +1,18 @@
+#ifndef GFIO_CLIENT_H
+#define GFIO_CLIENT_H
+
+extern struct client_ops gfio_client_ops;
+
+extern void gfio_display_end_results(struct gfio_client *);
+
+#define GFIO_READ_R    0.13
+#define GFIO_READ_G    0.54
+#define GFIO_READ_B    0.13
+#define GFIO_WRITE_R   1.00
+#define GFIO_WRITE_G   0.00
+#define GFIO_WRITE_B   0.00
+#define GFIO_TRIM_R    0.24
+#define GFIO_TRIM_G    0.18
+#define GFIO_TRIM_B    0.52
+
+#endif
diff --git a/gcompat.c b/gcompat.c
new file mode 100644 (file)
index 0000000..5944df0
--- /dev/null
+++ b/gcompat.c
@@ -0,0 +1,59 @@
+#include <gtk/gtk.h>
+
+#include "gcompat.h"
+
+#if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 24
+
+GtkWidget *gtk_combo_box_text_new(void)
+{
+       return gtk_combo_box_new();
+}
+
+void gtk_combo_box_text_append_text(GtkComboBoxText *combo_box,
+                                   const gchar *text)
+{
+       gtk_combo_box_append_text(GTK_COMBO_BOX(combo_box), text);
+}
+
+void gtk_combo_box_text_insert_text(GtkComboBoxText *combo_box, gint position,
+                                   const gchar *text)
+{
+       gtk_combo_box_insert_text(GTK_COMBO_BOX(combo_box), position, text);
+}
+
+void gtk_combo_box_text_prepend_text(GtkComboBoxText *combo_box,
+                                    const gchar *text)
+{
+       gtk_combo_box_prepend_text(GTK_COMBO_BOX(combo_box), text);
+}
+
+gchar *gtk_combo_box_text_get_active_text(GtkComboBoxText *combo_box)
+{
+       return gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo_box));
+}
+
+#endif
+
+#if GTK_MAJOR_VERSION < 3
+
+guint gtk_widget_get_allocated_width(GtkWidget *w)
+{
+       return w->allocation.width;
+}
+
+guint gtk_widget_get_allocated_height(GtkWidget *w)
+{
+       return w->allocation.height;
+}
+
+#endif
+
+#if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 18
+void gtk_widget_set_can_focus(GtkWidget *widget, gboolean can_focus)
+{
+       if (can_focus)
+               GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS);
+       else
+               GTK_WIDGET_UNSET_FLAGS(widget, GTK_CAN_FOCUS);
+}
+#endif
diff --git a/gcompat.h b/gcompat.h
new file mode 100644 (file)
index 0000000..e0f7444
--- /dev/null
+++ b/gcompat.h
@@ -0,0 +1,46 @@
+#ifndef GFIO_GTK_COMPAT
+#define GFIO_GTK_COMPAT
+
+#include <gtk/gtk.h>
+
+#if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 24
+struct GtkComboBoxText;
+typedef GtkComboBox GtkComboBoxText;
+GtkWidget *gtk_combo_box_text_new(void);
+GtkWidget *gtk_combo_box_text_new_with_entry(void);
+void gtk_combo_box_text_append_text(GtkComboBoxText *combo_box, const gchar *text);
+void gtk_combo_box_text_insert_text(GtkComboBoxText *combo_box, gint position, const gchar *text);
+void gtk_combo_box_text_prepend_text(GtkComboBoxText *combo_box, const gchar *text);
+void gtk_combo_box_text_remove(GtkComboBoxText *combo_box, gint position);
+gchar *gtk_combo_box_text_get_active_text(GtkComboBoxText *combo_box);
+
+#define GTK_COMBO_BOX_TEXT     GTK_COMBO_BOX
+#endif /* GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 24 */
+
+#if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 14
+static inline GtkWidget *gtk_dialog_get_content_area(GtkDialog *dialog)
+{
+       return dialog->vbox;
+}
+static inline GdkWindow *gtk_widget_get_window(GtkWidget *w)
+{
+       return w->window;
+}
+#endif
+
+#if GTK_MAJOR_VERSION < 3
+guint gtk_widget_get_allocated_width(GtkWidget *w);
+guint gtk_widget_get_allocated_height(GtkWidget *w);
+#endif
+
+#if GTK_MAJOR_VERSION == 3
+#define GFIO_DRAW_EVENT                "draw"
+#elif GTK_MAJOR_VERSION == 2
+#define GFIO_DRAW_EVENT                "expose_event"
+#endif
+
+#if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 18
+void gtk_widget_set_can_focus(GtkWidget *widget, gboolean can_focus);
+#endif
+
+#endif
diff --git a/gerror.c b/gerror.c
new file mode 100644 (file)
index 0000000..3036738
--- /dev/null
+++ b/gerror.c
@@ -0,0 +1,74 @@
+#include <locale.h>
+#include <malloc.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gtk/gtk.h>
+
+#include "gfio.h"
+#include "gerror.h"
+
+static void on_info_bar_response(GtkWidget *widget, gint response,
+                                gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+
+       if (response == GTK_RESPONSE_OK) {
+               gtk_widget_destroy(widget);
+               ui->error_info_bar = NULL;
+       }
+}
+
+static void report_error(struct gui_entry *ge, GError *error)
+{
+       struct gui *ui = ge->ui;
+
+       if (ui->error_info_bar == NULL) {
+               ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
+                                               GTK_RESPONSE_OK, NULL);
+               g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), ui);
+               gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
+                                               GTK_MESSAGE_ERROR);
+
+               ui->error_label = gtk_label_new(error->message);
+               GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
+               gtk_container_add(GTK_CONTAINER(container), ui->error_label);
+
+               gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
+               gtk_widget_show_all(ui->vbox);
+       } else {
+               char buffer[256];
+               snprintf(buffer, sizeof(buffer), "Failed to open file.");
+               gtk_label_set_text(GTK_LABEL(ui->error_label), buffer);
+       }
+}
+
+void gfio_report_error(struct gui_entry *ge, const char *format, ...)
+{
+       va_list args;
+       GError *error;
+
+       va_start(args, format);
+       error = g_error_new_valist(g_quark_from_string("fio"), 1, format, args);
+       va_end(args);
+
+       report_error(ge, error);
+       g_error_free(error);
+}
+
+void gfio_report_info(struct gui *ui, const char *title, const char *message)
+{
+       GtkWidget *dialog, *content, *label;
+
+       dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window),
+                       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+
+       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       label = gtk_label_new(message);
+       gtk_container_add(GTK_CONTAINER(content), label);
+       gtk_widget_show_all(dialog);
+       gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+       gtk_dialog_run(GTK_DIALOG(dialog));
+       gtk_widget_destroy(dialog);
+}
diff --git a/gerror.h b/gerror.h
new file mode 100644 (file)
index 0000000..3767f92
--- /dev/null
+++ b/gerror.h
@@ -0,0 +1,7 @@
+#ifndef GFIO_ERROR_H
+#define GFIO_ERROR_H
+
+extern void gfio_report_error(struct gui_entry *ge, const char *format, ...);
+extern void gfio_report_info(struct gui *ui, const char *title, const char *message);
+
+#endif
diff --git a/gfio.c b/gfio.c
new file mode 100644 (file)
index 0000000..185dbd5
--- /dev/null
+++ b/gfio.c
@@ -0,0 +1,1748 @@
+/*
+ * gfio - gui front end for fio - the flexible io tester
+ *
+ * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
+ * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
+ *
+ * The license below covers all files distributed with fio unless otherwise
+ * noted in the file itself.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include <locale.h>
+#include <malloc.h>
+#include <string.h>
+
+#include <glib.h>
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+#include "fio.h"
+#include "gfio.h"
+#include "ghelpers.h"
+#include "goptions.h"
+#include "gerror.h"
+#include "gclient.h"
+#include "graph.h"
+
+static int gfio_server_running;
+static unsigned int gfio_graph_limit = 100;
+
+GdkColor gfio_color_white;
+GdkColor gfio_color_lightyellow;
+const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
+
+typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
+
+static void connect_clicked(GtkWidget *widget, gpointer data);
+static void start_job_clicked(GtkWidget *widget, gpointer data);
+static void send_clicked(GtkWidget *widget, gpointer data);
+
+static struct button_spec {
+       const char *buttontext;
+       clickfunction f;
+       const char *tooltiptext[2];
+       const int start_sensitive;
+} buttonspeclist[] = {
+       {
+         .buttontext           = "Connect",
+         .f                    = connect_clicked,
+         .tooltiptext          = { "Disconnect from host", "Connect to host" },
+         .start_sensitive      = 1,
+       },
+       {
+         .buttontext           = "Send",
+         .f                    = send_clicked,
+         .tooltiptext          = { "Send job description to host", NULL },
+         .start_sensitive      = 0,
+       },
+       {
+         .buttontext           = "Start Job",
+         .f                    = start_job_clicked,
+         .tooltiptext          = { "Start the current job on the server", NULL },
+         .start_sensitive      = 0,
+       },
+};
+
+static void setup_iops_graph(struct gfio_graphs *gg)
+{
+       struct graph *g;
+
+       g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
+       graph_title(g, "IOPS (IOs/sec)");
+       graph_x_title(g, "Time (secs)");
+       gg->read_iops = graph_add_label(g, "Read IOPS");
+       gg->write_iops = graph_add_label(g, "Write IOPS");
+       gg->trim_iops = graph_add_label(g, "Trim IOPS");
+       graph_set_color(g, gg->read_iops, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
+       graph_set_color(g, gg->write_iops, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
+       graph_set_color(g, gg->trim_iops, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
+       line_graph_set_data_count_limit(g, gfio_graph_limit);
+       graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
+       graph_set_graph_all_zeroes(g, 0);
+       gg->iops_graph = g;
+}
+
+static void setup_bandwidth_graph(struct gfio_graphs *gg)
+{
+       struct graph *g;
+
+       g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
+       graph_title(g, "Bandwidth (bytes/sec)");
+       graph_x_title(g, "Time (secs)");
+       gg->read_bw = graph_add_label(g, "Read Bandwidth");
+       gg->write_bw = graph_add_label(g, "Write Bandwidth");
+       gg->trim_bw = graph_add_label(g, "Trim Bandwidth");
+       graph_set_color(g, gg->read_bw, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
+       graph_set_color(g, gg->write_bw, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
+       graph_set_color(g, gg->trim_bw, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
+       graph_set_base_offset(g, 1);
+       line_graph_set_data_count_limit(g, 100);
+       graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
+       graph_set_graph_all_zeroes(g, 0);
+       gg->bandwidth_graph = g;
+}
+
+static void setup_graphs(struct gfio_graphs *g)
+{
+       setup_iops_graph(g);
+       setup_bandwidth_graph(g);
+}
+
+void clear_ge_ui_info(struct gui_entry *ge)
+{
+       gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
+       gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
+       gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
+       gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
+#if 0
+       /* should we empty it... */
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
+#endif
+       multitext_update_entry(&ge->eta.iotype, 0, "");
+       multitext_update_entry(&ge->eta.bs, 0, "");
+       multitext_update_entry(&ge->eta.ioengine, 0, "");
+       multitext_update_entry(&ge->eta.iodepth, 0, "");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
+       gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
+}
+
+static void set_menu_entry_text(struct gui *ui, const char *path,
+                               const char *text)
+{
+       GtkWidget *w;
+
+       w = gtk_ui_manager_get_widget(ui->uimanager, path);
+       if (w)
+               gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
+       else
+               fprintf(stderr, "gfio: can't find path %s\n", path);
+}
+
+
+static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
+{
+       GtkWidget *w;
+
+       w = gtk_ui_manager_get_widget(ui->uimanager, path);
+       if (w)
+               gtk_widget_set_sensitive(w, show);
+       else
+               fprintf(stderr, "gfio: can't find path %s\n", path);
+}
+
+static void set_job_menu_visible(struct gui *ui, int visible)
+{
+       set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
+}
+
+static void set_view_results_visible(struct gui *ui, int visible)
+{
+       set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
+}
+
+static const char *get_button_tooltip(struct button_spec *s, int sensitive)
+{
+       if (s->tooltiptext[sensitive])
+               return s->tooltiptext[sensitive];
+
+       return s->tooltiptext[0];
+}
+
+static GtkWidget *add_button(GtkWidget *buttonbox,
+                            struct button_spec *buttonspec, gpointer data)
+{
+       GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
+       gboolean sens = buttonspec->start_sensitive;
+
+       g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
+       gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
+
+       sens = buttonspec->start_sensitive;
+       gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
+       gtk_widget_set_sensitive(button, sens);
+
+       return button;
+}
+
+static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
+                       int nbuttons)
+{
+       int i;
+
+       for (i = 0; i < nbuttons; i++)
+               ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
+}
+
+/*
+ * Update sensitivity of job buttons and job menu items, based on the
+ * state of the client.
+ */
+static void update_button_states(struct gui *ui, struct gui_entry *ge)
+{
+       unsigned int connect_state, send_state, start_state, edit_state;
+       const char *connect_str = NULL;
+
+       switch (ge->state) {
+       default:
+               gfio_report_error(ge, "Bad client state: %u\n", ge->state);
+               /* fall through to new state */
+       case GE_STATE_NEW:
+               connect_state = 1;
+               edit_state = 1;
+               connect_str = "Connect";
+               send_state = 0;
+               start_state = 0;
+               break;
+       case GE_STATE_CONNECTED:
+               connect_state = 1;
+               edit_state = 1;
+               connect_str = "Disconnect";
+               send_state = 1;
+               start_state = 0;
+               break;
+       case GE_STATE_JOB_SENT:
+               connect_state = 1;
+               edit_state = 1;
+               connect_str = "Disconnect";
+               send_state = 0;
+               start_state = 1;
+               break;
+       case GE_STATE_JOB_STARTED:
+               connect_state = 1;
+               edit_state = 1;
+               connect_str = "Disconnect";
+               send_state = 0;
+               start_state = 1;
+               break;
+       case GE_STATE_JOB_RUNNING:
+               connect_state = 1;
+               edit_state = 0;
+               connect_str = "Disconnect";
+               send_state = 0;
+               start_state = 0;
+               break;
+       case GE_STATE_JOB_DONE:
+               connect_state = 1;
+               edit_state = 0;
+               connect_str = "Connect";
+               send_state = 0;
+               start_state = 0;
+               break;
+       }
+
+       gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
+       gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
+       gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
+       gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
+       gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
+
+       set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
+       set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
+
+       set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
+       set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
+       set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
+
+       if (ge->client && ge->client->nr_results)
+               set_view_results_visible(ui, 1);
+       else
+               set_view_results_visible(ui, 0);
+}
+
+void gfio_set_state(struct gui_entry *ge, unsigned int state)
+{
+       ge->state = state;
+       update_button_states(ge->ui, ge);
+}
+
+static void gfio_ui_setup_log(struct gui *ui)
+{
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       GtkWidget *tree_view;
+
+       model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+       tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+       gtk_widget_set_can_focus(tree_view, FALSE);
+
+       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+       gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+       g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
+       tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
+       tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
+       tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
+       tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
+
+       ui->log_model = model;
+       ui->log_tree = tree_view;
+}
+
+static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
+                                  gpointer data)
+{
+       guint width = gtk_widget_get_allocated_width(w);
+       guint height = gtk_widget_get_allocated_height(w);
+       struct gfio_graphs *g = data;
+
+       graph_set_size(g->iops_graph, width / 2.0, height);
+       graph_set_position(g->iops_graph, width / 2.0, 0.0);
+       graph_set_size(g->bandwidth_graph, width / 2.0, height);
+       graph_set_position(g->bandwidth_graph, 0, 0);
+       return TRUE;
+}
+
+static void draw_graph(struct graph *g, cairo_t *cr)
+{
+       line_graph_draw(g, cr);
+       cairo_stroke(cr);
+}
+
+static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
+                             gboolean keyboard_mode, GtkTooltip *tooltip,
+                             gpointer data)
+{
+       struct gfio_graphs *g = data;
+       const char *text = NULL;
+
+       if (graph_contains_xy(g->iops_graph, x, y))
+               text = graph_find_tooltip(g->iops_graph, x, y);
+       else if (graph_contains_xy(g->bandwidth_graph, x, y))
+               text = graph_find_tooltip(g->bandwidth_graph, x, y);
+
+       if (text) {
+               gtk_tooltip_set_text(tooltip, text);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
+{
+       struct gfio_graphs *g = p;
+       cairo_t *cr;
+
+       cr = gdk_cairo_create(gtk_widget_get_window(w));
+
+       if (graph_has_tooltips(g->iops_graph) ||
+           graph_has_tooltips(g->bandwidth_graph)) {
+               g_object_set(w, "has-tooltip", TRUE, NULL);
+               g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
+       }
+
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       draw_graph(g->iops_graph, cr);
+       draw_graph(g->bandwidth_graph, cr);
+       cairo_destroy(cr);
+
+       return FALSE;
+}
+
+/*
+ * FIXME: need more handling here
+ */
+static void ge_destroy(struct gui_entry *ge)
+{
+       struct gfio_client *gc = ge->client;
+
+       if (gc) {
+               if (gc->client) {
+                       if (ge->state >= GE_STATE_CONNECTED)
+                               fio_client_terminate(gc->client);
+
+                       fio_put_client(gc->client);
+               }
+               free(gc);
+       }
+
+       g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
+
+       free(ge->job_file);
+       free(ge->host);
+       free(ge);
+}
+
+static void ge_widget_destroy(GtkWidget *w, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+
+       ge_destroy(ge);
+}
+
+static void gfio_quit(struct gui *ui)
+{
+       gtk_main_quit();
+}
+
+static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
+                        gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+
+       gfio_quit(ui);
+}
+
+static void *job_thread(void *arg)
+{
+       struct gui *ui = arg;
+
+       ui->handler_running = 1;
+       fio_handle_clients(&gfio_client_ops);
+       ui->handler_running = 0;
+       return NULL;
+}
+
+static int send_job_file(struct gui_entry *ge)
+{
+       struct gfio_client *gc = ge->client;
+       int ret = 0;
+
+       /*
+        * Prune old options, we are expecting the return options
+        * when the job file is parsed remotely and returned to us.
+        */
+       while (!flist_empty(&gc->o_list)) {
+               struct gfio_client_options *gco;
+
+               gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
+               flist_del(&gco->list);
+               free(gco);
+       }
+
+       ret = fio_client_send_ini(gc->client, ge->job_file);
+       if (!ret)
+               return 0;
+
+       gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
+       return 1;
+}
+
+static void *server_thread(void *arg)
+{
+       is_backend = 1;
+       gfio_server_running = 1;
+       fio_start_server(NULL);
+       gfio_server_running = 0;
+       return NULL;
+}
+
+static void gfio_start_server(struct gui *ui)
+{
+       if (!gfio_server_running) {
+               gfio_server_running = 1;
+               pthread_create(&ui->server_t, NULL, server_thread, NULL);
+               pthread_detach(ui->server_t);
+       }
+}
+
+static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
+                             gpointer data)
+{
+       struct gui_entry *ge = data;
+       struct gfio_client *gc = ge->client;
+
+       if (gc)
+               fio_start_client(gc->client);
+}
+
+static void file_open(GtkWidget *w, gpointer data);
+
+struct connection_widgets
+{
+       GtkWidget *hentry;
+       GtkWidget *combo;
+       GtkWidget *button;
+};
+
+static void hostname_cb(GtkEntry *entry, gpointer data)
+{
+       struct connection_widgets *cw = data;
+       int uses_net = 0, is_localhost = 0;
+       const gchar *text;
+       gchar *ctext;
+
+       /*
+        * Check whether to display the 'auto start backend' box
+        * or not. Show it if we are a localhost and using network,
+        * or using a socket.
+        */
+       ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
+       if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
+               uses_net = 1;
+       g_free(ctext);
+
+       if (uses_net) {
+               text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
+               if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
+                   !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
+                   !strcmp(text, "ip6-loopback"))
+                       is_localhost = 1;
+       }
+
+       if (!uses_net || is_localhost) {
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
+               gtk_widget_set_sensitive(cw->button, 1);
+       } else {
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
+               gtk_widget_set_sensitive(cw->button, 0);
+       }
+}
+
+static int get_connection_details(struct gui_entry *ge)
+{
+       GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
+       struct connection_widgets cw;
+       struct gui *ui = ge->ui;
+       char *typeentry;
+
+       if (ge->host)
+               return 0;
+
+       dialog = gtk_dialog_new_with_buttons("Connection details",
+                       GTK_WINDOW(ui->window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+
+       frame = gtk_frame_new("Hostname / socket name");
+       vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       box = gtk_vbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 10);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       cw.hentry = gtk_entry_new();
+       gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
+       gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
+
+       frame = gtk_frame_new("Port");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
+
+       frame = gtk_frame_new("Type");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+
+       cw.combo = gtk_combo_box_text_new();
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
+       gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
+
+       gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
+
+       frame = gtk_frame_new("Options");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+
+       cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
+       gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
+       gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
+
+       /*
+        * Connect edit signal, so we can show/not-show the auto start button
+        */
+       g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
+       g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
+
+       gtk_widget_show_all(dialog);
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+               gtk_widget_destroy(dialog);
+               return 1;
+       }
+
+       ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
+       ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
+
+       typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
+       if (!typeentry || !strncmp(typeentry, "IPv4", 4))
+               ge->type = Fio_client_ipv4;
+       else if (!strncmp(typeentry, "IPv6", 4))
+               ge->type = Fio_client_ipv6;
+       else
+               ge->type = Fio_client_socket;
+       g_free(typeentry);
+
+       ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
+
+       gtk_widget_destroy(dialog);
+       return 0;
+}
+
+static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
+{
+       gc->client = fio_get_client(client);
+       client->client_data = gc;
+}
+
+static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
+{
+       struct gfio_client_options *gco;
+       struct gfio_client *gc;
+
+       gc = calloc(1, sizeof(*gc));
+       INIT_FLIST_HEAD(&gc->o_list);
+       gc->ge = ge;
+       ge->client = gc;
+       gfio_set_client(gc, client);
+
+       /*
+        * Just add a default set of options, need to consider how best
+        * to handle this
+        */
+       gco = calloc(1, sizeof(*gco));
+       INIT_FLIST_HEAD(&gco->list);
+       options_default_fill(&gco->o);
+       flist_add_tail(&gco->list, &gc->o_list);
+       gc->o_list_nr++;
+}
+
+static void gfio_clear_graph_data(struct gfio_graphs *g)
+{
+       graph_clear_values(g->iops_graph);
+       graph_clear_values(g->bandwidth_graph);
+}
+
+static void connect_clicked(GtkWidget *widget, gpointer data)
+{
+       struct gui_entry *ge = data;
+       struct gfio_client *gc = ge->client;
+
+       if (ge->state == GE_STATE_NEW) {
+               int ret;
+
+               if (!ge->job_file)
+                       file_open(widget, ge->ui);
+               if (!ge->job_file)
+                       return;
+
+               gc = ge->client;
+
+               if (!gc->client) {
+                       struct fio_client *client;
+
+                       if (get_connection_details(ge)) {
+                               gfio_report_error(ge, "Failed to get connection details\n");
+                               return;
+                       }
+
+                       client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
+                       if (!client) {
+                               gfio_report_error(ge, "Failed to add client %s\n", ge->host);
+                               free(ge->host);
+                               ge->host = NULL;
+                               return;
+                       }
+                       gfio_set_client(gc, client);
+               }
+
+               gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
+               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
+               ret = fio_client_connect(gc->client);
+               if (!ret) {
+                       if (!ge->ui->handler_running)
+                               pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
+                       gfio_set_state(ge, GE_STATE_CONNECTED);
+                       gfio_clear_graph_data(&ge->graphs);
+               } else {
+                       gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
+               }
+       } else {
+               fio_client_terminate(gc->client);
+               gfio_set_state(ge, GE_STATE_NEW);
+               clear_ge_ui_info(ge);
+       }
+}
+
+static void send_clicked(GtkWidget *widget, gpointer data)
+{
+       struct gui_entry *ge = data;
+
+       if (send_job_file(ge))
+               gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
+}
+
+static GtkWidget *new_client_page(struct gui_entry *ge);
+
+static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
+{
+       struct gui_entry *ge;
+
+       ge = malloc(sizeof(*ge));
+       memset(ge, 0, sizeof(*ge));
+       ge->state = GE_STATE_NEW;
+       ge->ui = ui;
+       return ge;
+}
+
+static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
+{
+       struct gui_entry *ge;
+
+       ge = alloc_new_gui_entry(ui);
+
+       ge->vbox = new_client_page(ge);
+       g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
+
+       ge->page_label = gtk_label_new(name);
+       ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
+
+       g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
+
+       gtk_widget_show_all(ui->window);
+       return ge;
+}
+
+static void file_new(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       ge = get_new_ge_with_tab(ui, "Untitled");
+       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+}
+
+/*
+ * Return the 'ge' corresponding to the tab. If the active tab is the
+ * main tab, open a new tab.
+ */
+static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
+                                         int *created)
+{
+       if (!cur_page) {
+               if (created)
+                       *created = 1;
+               return get_new_ge_with_tab(ui, "Untitled");
+       }
+
+       if (created)
+               *created = 0;
+
+       return g_hash_table_lookup(ui->ge_hash, &cur_page);
+}
+
+static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
+{
+       gint cur_page;
+
+       /*
+        * Main tab is tab 0, so any current page other than 0 holds
+        * a ge entry.
+        */
+       cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
+       if (cur_page)
+               return get_ge_from_page(ui, cur_page, NULL);
+
+       return NULL;
+}
+
+static void file_close(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       /*
+        * Can't close the main tab
+        */
+       ge = get_ge_from_cur_tab(ui);
+       if (ge) {
+               gtk_widget_destroy(ge->vbox);
+               return;
+       }
+
+       if (g_hash_table_size(ui->ge_hash)) {
+               gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
+               return;
+       }
+
+       gfio_quit(ui);
+}
+
+static void file_add_recent(struct gui *ui, const gchar *uri)
+{
+       GtkRecentData grd;
+
+       memset(&grd, 0, sizeof(grd));
+       grd.display_name = strdup("gfio");
+       grd.description = strdup("Fio job file");
+       grd.mime_type = strdup(GFIO_MIME);
+       grd.app_name = strdup(g_get_application_name());
+       grd.app_exec = strdup("gfio %f/%u");
+
+       gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
+}
+
+static gchar *get_filename_from_uri(const gchar *uri)
+{
+       if (strncmp(uri, "file://", 7))
+               return strdup(uri);
+
+       return strdup(uri + 7);
+}
+
+static int do_file_open(struct gui_entry *ge, const gchar *uri)
+{
+       struct fio_client *client;
+
+       assert(!ge->job_file);
+
+       ge->job_file = get_filename_from_uri(uri);
+
+       client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
+       if (client) {
+               char *label = strdup(uri);
+
+               basename(label);
+               gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
+               free(label);
+
+               gfio_client_added(ge, client);
+               file_add_recent(ge->ui, uri);
+               return 0;
+       }
+
+       gfio_report_error(ge, "Failed to add client %s\n", ge->host);
+       free(ge->host);
+       ge->host = NULL;
+       free(ge->job_file);
+       ge->job_file = NULL;
+       return 1;
+}
+
+static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
+{
+       struct gui_entry *ge;
+       gint cur_page;
+       int ret, ge_is_new = 0;
+
+       /*
+        * Creates new tab if current tab is the main window, or the
+        * current tab already has a client.
+        */
+       cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
+       ge = get_ge_from_page(ui, cur_page, &ge_is_new);
+       if (ge->client) {
+               ge = get_new_ge_with_tab(ui, "Untitled");
+               ge_is_new = 1;
+       }
+
+       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+
+       if (get_connection_details(ge)) {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+
+               return 1;
+       }
+
+       ret = do_file_open(ge, uri);
+
+       if (!ret) {
+               if (ge->server_start)
+                       gfio_start_server(ui);
+       } else {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+       }
+
+       return ret;
+}
+
+static void recent_open(GtkAction *action, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       GtkRecentInfo *info;
+       const gchar *uri;
+
+       info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
+       uri = gtk_recent_info_get_uri(info);
+
+       do_file_open_with_tab(ui, uri);
+}
+
+static void file_open(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = data;
+       GtkWidget *dialog;
+       GtkFileFilter *filter;
+       gchar *filename;
+
+       dialog = gtk_file_chooser_dialog_new("Open File",
+               GTK_WINDOW(ui->window),
+               GTK_FILE_CHOOSER_ACTION_OPEN,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+               GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+               NULL);
+       gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+
+       filter = gtk_file_filter_new();
+       gtk_file_filter_add_pattern(filter, "*.fio");
+       gtk_file_filter_add_pattern(filter, "*.job");
+       gtk_file_filter_add_pattern(filter, "*.ini");
+       gtk_file_filter_add_mime_type(filter, GFIO_MIME);
+       gtk_file_filter_set_name(filter, "Fio job file");
+       gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+               gtk_widget_destroy(dialog);
+               return;
+       }
+
+       filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+
+       gtk_widget_destroy(dialog);
+
+       do_file_open_with_tab(ui, filename);
+       g_free(filename);
+}
+
+static void file_save(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = data;
+       GtkWidget *dialog;
+
+       dialog = gtk_file_chooser_dialog_new("Save File",
+               GTK_WINDOW(ui->window),
+               GTK_FILE_CHOOSER_ACTION_SAVE,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+               GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+               NULL);
+
+       gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
+       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+               char *filename;
+
+               filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+               // save_job_file(filename);
+               g_free(filename);
+       }
+       gtk_widget_destroy(dialog);
+}
+
+static void view_log_destroy(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+
+       g_object_ref(G_OBJECT(ui->log_tree));
+       gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
+       gtk_widget_destroy(w);
+       ui->log_view = NULL;
+}
+
+void gfio_view_log(struct gui *ui)
+{
+       GtkWidget *win, *scroll, *vbox, *box;
+
+       if (ui->log_view)
+               return;
+
+       ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_title(GTK_WINDOW(win), "Log");
+       gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
+
+       gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
+
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       box = gtk_hbox_new(TRUE, 0);
+       gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
+       g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
+
+       vbox = gtk_vbox_new(TRUE, 5);
+       gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
+
+       gtk_container_add(GTK_CONTAINER(win), vbox);
+       gtk_widget_show_all(win);
+}
+
+static void view_log(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+
+       gfio_view_log(ui);
+}
+
+static void connect_job_entry(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       ge = get_ge_from_cur_tab(ui);
+       if (ge)
+               connect_clicked(w, ge);
+}
+
+static void send_job_entry(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       ge = get_ge_from_cur_tab(ui);
+       if (ge)
+               send_clicked(w, ge);
+}
+
+static void edit_job_entry(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       ge = get_ge_from_cur_tab(ui);
+       if (ge && ge->client)
+               gopt_get_options_window(ui->window, ge->client);
+}
+
+static void start_job_entry(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       ge = get_ge_from_cur_tab(ui);
+       if (ge)
+               start_job_clicked(w, ge);
+}
+
+static void view_results(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gfio_client *gc;
+       struct gui_entry *ge;
+
+       ge = get_ge_from_cur_tab(ui);
+       if (!ge)
+               return;
+
+       if (ge->results_window)
+               return;
+
+       gc = ge->client;
+       if (gc && gc->nr_results)
+               gfio_display_end_results(gc);
+}
+
+static void __update_graph_settings(struct gfio_graphs *g)
+{
+       line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
+       graph_set_font(g->iops_graph, gfio_graph_font);
+       line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
+       graph_set_font(g->bandwidth_graph, gfio_graph_font);
+}
+
+static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) value;
+       GdkEvent *ev;
+
+       __update_graph_settings(&ge->graphs);
+
+       ev = gdk_event_new(GDK_EXPOSE);
+       g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
+       gdk_event_free(ev);
+}
+
+static void update_graph_limits(void)
+{
+       struct gui *ui = &main_ui;
+       GdkEvent *ev;
+
+       __update_graph_settings(&ui->graphs);
+
+       ev = gdk_event_new(GDK_EXPOSE);
+       g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
+       gdk_event_free(ev);
+
+       g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
+}
+
+static void preferences(GtkWidget *w, gpointer data)
+{
+       GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
+       GtkWidget *hbox, *spin, *entry, *spin_int;
+       struct gui *ui = (struct gui *) data;
+       int i;
+
+       dialog = gtk_dialog_new_with_buttons("Preferences",
+               GTK_WINDOW(ui->window),
+               GTK_DIALOG_DESTROY_WITH_PARENT,
+               GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+               NULL);
+
+       frame = gtk_frame_new("Graphing");
+       vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       vbox = gtk_vbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+       hbox = gtk_hbox_new(FALSE, 5);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
+       entry = gtk_label_new("Font face to use for graph labels");
+       gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
+
+       font = gtk_font_button_new_with_font(gfio_graph_font);
+       gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
+
+       box = gtk_vbox_new(FALSE, 6);
+       gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
+
+       hbox = gtk_hbox_new(FALSE, 5);
+       gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
+       entry = gtk_label_new("Maximum number of data points in graph (seconds)");
+       gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+
+       spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
+
+       box = gtk_vbox_new(FALSE, 6);
+       gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
+
+       hbox = gtk_hbox_new(FALSE, 5);
+       gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
+       entry = gtk_label_new("Client ETA request interval (msec)");
+       gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+
+       spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
+       frame = gtk_frame_new("Debug logging");
+       vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       vbox = gtk_vbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+       box = gtk_hbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(vbox), box);
+
+       buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
+
+       for (i = 0; i < FD_DEBUG_MAX; i++) {
+               if (i == 7) {
+                       box = gtk_hbox_new(FALSE, 6);
+                       gtk_container_add(GTK_CONTAINER(vbox), box);
+               }
+
+
+               buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
+               gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
+               gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
+       }
+
+       gtk_widget_show_all(dialog);
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+               gtk_widget_destroy(dialog);
+               return;
+       }
+
+       for (i = 0; i < FD_DEBUG_MAX; i++) {
+               int set;
+
+               set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
+               if (set)
+                       fio_debug |= (1UL << i);
+       }
+
+       gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
+       gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
+       update_graph_limits();
+       gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
+
+       gtk_widget_destroy(dialog);
+}
+
+static void about_dialog(GtkWidget *w, gpointer data)
+{
+       const char *authors[] = {
+               "Jens Axboe <axboe@kernel.dk>",
+               "Stephen Carmeron <stephenmcameron@gmail.com>",
+               NULL
+       };
+       const char *license[] = {
+               "Fio is free software; you can redistribute it and/or modify "
+               "it under the terms of the GNU General Public License as published by "
+               "the Free Software Foundation; either version 2 of the License, or "
+               "(at your option) any later version.\n",
+               "Fio is distributed in the hope that it will be useful, "
+               "but WITHOUT ANY WARRANTY; without even the implied warranty of "
+               "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
+               "GNU General Public License for more details.\n",
+               "You should have received a copy of the GNU General Public License "
+               "along with Fio; if not, write to the Free Software Foundation, Inc., "
+               "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
+       };
+       char *license_trans;
+
+       license_trans = g_strconcat(license[0], "\n", license[1], "\n",
+                                    license[2], "\n", NULL);
+
+       gtk_show_about_dialog(NULL,
+               "program-name", "gfio",
+               "comments", "Gtk2 UI for fio",
+               "license", license_trans,
+               "website", "http://git.kernel.dk/?p=fio.git;a=summary",
+               "authors", authors,
+               "version", fio_version_string,
+               "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
+               "logo-icon-name", "fio",
+               /* Must be last: */
+               "wrap-license", TRUE,
+               NULL);
+
+       g_free(license_trans);
+}
+
+static GtkActionEntry menu_items[] = {
+       { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
+       { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
+       { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
+       { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
+       { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
+       { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
+       { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
+       { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
+       { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
+       { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
+       { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
+       { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
+       { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
+       { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
+       { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
+       { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
+       { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
+};
+static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
+
+static const gchar *ui_string = " \
+       <ui> \
+               <menubar name=\"MainMenu\"> \
+                       <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
+                               <menuitem name=\"New\" action=\"NewFile\" /> \
+                               <menuitem name=\"Open\" action=\"OpenFile\" /> \
+                               <menuitem name=\"Close\" action=\"CloseFile\" /> \
+                               <separator name=\"Separator1\"/> \
+                               <menuitem name=\"Save\" action=\"SaveFile\" /> \
+                               <separator name=\"Separator2\"/> \
+                               <menuitem name=\"Preferences\" action=\"Preferences\" /> \
+                               <separator name=\"Separator3\"/> \
+                               <placeholder name=\"FileRecentFiles\"/> \
+                               <separator name=\"Separator4\"/> \
+                               <menuitem name=\"Quit\" action=\"Quit\" /> \
+                       </menu> \
+                       <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
+                               <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
+                               <separator name=\"Separator5\"/> \
+                               <menuitem name=\"Edit job\" action=\"EditJob\" /> \
+                               <menuitem name=\"Send job\" action=\"SendJob\" /> \
+                               <separator name=\"Separator6\"/> \
+                               <menuitem name=\"Start job\" action=\"StartJob\" /> \
+                       </menu>\
+                       <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
+                               <menuitem name=\"Results\" action=\"ViewResults\" /> \
+                               <separator name=\"Separator7\"/> \
+                               <menuitem name=\"Log\" action=\"ViewLog\" /> \
+                       </menu>\
+                       <menu name=\"Help\" action=\"HelpMenuAction\"> \
+                               <menuitem name=\"About\" action=\"About\" /> \
+                       </menu> \
+               </menubar> \
+       </ui> \
+";
+
+static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
+                                  struct gui *ui)
+{
+       GtkActionGroup *action_group;
+       GError *error = 0;
+
+       action_group = gtk_action_group_new("Menu");
+       gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
+
+       gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
+       gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
+
+       gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
+
+       return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
+}
+
+void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
+                  GtkWidget *vbox, GtkUIManager *ui_manager)
+{
+       gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
+}
+
+static void combo_entry_changed(GtkComboBox *box, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+       gint index;
+
+       index = gtk_combo_box_get_active(box);
+
+       multitext_set_entry(&ge->eta.iotype, index);
+       multitext_set_entry(&ge->eta.bs, index);
+       multitext_set_entry(&ge->eta.ioengine, index);
+       multitext_set_entry(&ge->eta.iodepth, index);
+}
+
+static void combo_entry_destroy(GtkWidget *widget, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) data;
+
+       multitext_free(&ge->eta.iotype);
+       multitext_free(&ge->eta.bs);
+       multitext_free(&ge->eta.ioengine);
+       multitext_free(&ge->eta.iodepth);
+}
+
+static GtkWidget *new_client_page(struct gui_entry *ge)
+{
+       GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
+       GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
+
+       main_vbox = gtk_vbox_new(FALSE, 3);
+
+       top_align = gtk_alignment_new(0, 0, 1, 0);
+       top_vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
+       gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
+
+       probe = gtk_frame_new("Job");
+       gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
+       probe_frame = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(probe), probe_frame);
+
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
+       ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
+       ge->probe.os = new_info_label_in_frame(probe_box, "OS");
+       ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
+       ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
+
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
+
+       ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
+       g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
+       g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
+       ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
+       ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
+       ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
+       ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
+       ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
+       ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
+
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
+       ge->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
+       ge->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
+       ge->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
+       ge->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
+       ge->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
+       ge->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
+
+       /*
+        * Only add this if we have a commit rate
+        */
+#if 0
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
+
+       ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
+       ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
+
+       ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
+       ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
+#endif
+
+       /*
+        * Set up a drawing area and IOPS and bandwidth graphs
+        */
+       ge->graphs.drawing_area = gtk_drawing_area_new();
+       gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
+               DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
+       gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
+       g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
+                               G_CALLBACK(on_expose_drawing_area), &ge->graphs);
+       g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
+                               G_CALLBACK(on_config_drawing_area), &ge->graphs);
+       scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
+                                       ge->graphs.drawing_area);
+       gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
+
+       setup_graphs(&ge->graphs);
+
+       /*
+        * Set up alignments for widgets at the bottom of ui,
+        * align bottom left, expand horizontally but not vertically
+        */
+       bottom_align = gtk_alignment_new(0, 1, 1, 0);
+       ge->buttonbox = gtk_hbox_new(FALSE, 0);
+       gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
+       gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
+
+       add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
+
+       /*
+        * Set up thread status progress bar
+        */
+       ge->thread_status_pb = gtk_progress_bar_new();
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
+       gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
+
+
+       return main_vbox;
+}
+
+static GtkWidget *new_main_page(struct gui *ui)
+{
+       GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
+       GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
+
+       main_vbox = gtk_vbox_new(FALSE, 3);
+
+       /*
+        * Set up alignments for widgets at the top of ui,
+        * align top left, expand horizontally but not vertically
+        */
+       top_align = gtk_alignment_new(0, 0, 1, 0);
+       top_vbox = gtk_vbox_new(FALSE, 0);
+       gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
+       gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
+
+       probe = gtk_frame_new("Run statistics");
+       gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
+       probe_frame = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(probe), probe_frame);
+
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
+       ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
+       ui->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
+       ui->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
+       ui->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
+       ui->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
+       ui->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
+       ui->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
+
+       /*
+        * Only add this if we have a commit rate
+        */
+#if 0
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
+
+       ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
+       ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
+
+       ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
+       ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
+#endif
+
+       /*
+        * Set up a drawing area and IOPS and bandwidth graphs
+        */
+       ui->graphs.drawing_area = gtk_drawing_area_new();
+       gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
+               DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
+       gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
+       g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
+                       G_CALLBACK(on_expose_drawing_area), &ui->graphs);
+       g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
+                       G_CALLBACK(on_config_drawing_area), &ui->graphs);
+       scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+                                       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
+                                       ui->graphs.drawing_area);
+       gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
+                       TRUE, TRUE, 0);
+
+       setup_graphs(&ui->graphs);
+
+       /*
+        * Set up alignments for widgets at the bottom of ui,
+        * align bottom left, expand horizontally but not vertically
+        */
+       bottom_align = gtk_alignment_new(0, 1, 1, 0);
+       ui->buttonbox = gtk_hbox_new(FALSE, 0);
+       gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
+       gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
+
+       /*
+        * Set up thread status progress bar
+        */
+       ui->thread_status_pb = gtk_progress_bar_new();
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
+       gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
+
+       return main_vbox;
+}
+
+static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
+                                    guint page, gpointer data)
+
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       if (!page) {
+               set_job_menu_visible(ui, 0);
+               set_view_results_visible(ui, 0);
+               return TRUE;
+       }
+
+       set_job_menu_visible(ui, 1);
+       ge = get_ge_from_page(ui, page, NULL);
+       if (ge)
+               update_button_states(ui, ge);
+
+       return TRUE;
+}
+
+static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
+{
+       time_t time_a = gtk_recent_info_get_visited(a);
+       time_t time_b = gtk_recent_info_get_visited(b);
+
+       return time_b - time_a;
+}
+
+static void add_recent_file_items(struct gui *ui)
+{
+       const gchar *gfio = g_get_application_name();
+       GList *items, *item;
+       int i = 0;
+
+       if (ui->recent_ui_id) {
+               gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
+               gtk_ui_manager_ensure_update(ui->uimanager);
+       }
+       ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
+
+       if (ui->actiongroup) {
+               gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
+               g_object_unref(ui->actiongroup);
+       }
+       ui->actiongroup = gtk_action_group_new("RecentFileActions");
+
+       gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
+
+       items = gtk_recent_manager_get_items(ui->recentmanager);
+       items = g_list_sort(items, (GCompareFunc) compare_recent_items);
+
+       for (item = items; item && item->data; item = g_list_next(item)) {
+               GtkRecentInfo *info = (GtkRecentInfo *) item->data;
+               gchar *action_name;
+               const gchar *label;
+               GtkAction *action;
+
+               if (!gtk_recent_info_has_application(info, gfio))
+                       continue;
+
+               /*
+                * We only support local files for now
+                */
+               if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
+                       continue;
+
+               action_name = g_strdup_printf("RecentFile%u", i++);
+               label = gtk_recent_info_get_display_name(info);
+
+               action = g_object_new(GTK_TYPE_ACTION,
+                                       "name", action_name,
+                                       "label", label, NULL);
+
+               g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
+                                       gtk_recent_info_ref(info),
+                                       (GDestroyNotify) gtk_recent_info_unref);
+
+
+               g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
+
+               gtk_action_group_add_action(ui->actiongroup, action);
+               g_object_unref(action);
+
+               gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
+                                       "/MainMenu/FileMenu/FileRecentFiles",
+                                       label, action_name,
+                                       GTK_UI_MANAGER_MENUITEM, FALSE);
+
+               g_free(action_name);
+
+               if (i == 8)
+                       break;
+       }
+
+       g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
+       g_list_free(items);
+}
+
+static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
+                                  gint x, gint y, GtkSelectionData *seldata,
+                                  guint info, guint time, gpointer *data)
+{
+       struct gui *ui = (struct gui *) data;
+       gchar **uris;
+       GtkWidget *source;
+
+       source = gtk_drag_get_source_widget(ctx);
+       if (source && widget == gtk_widget_get_toplevel(source)) {
+               gtk_drag_finish(ctx, FALSE, FALSE, time);
+               return;
+       }
+
+       uris = gtk_selection_data_get_uris(seldata);
+       if (!uris) {
+               gtk_drag_finish(ctx, FALSE, FALSE, time);
+               return;
+       }
+
+       if (uris[0])
+               do_file_open_with_tab(ui, uris[0]);
+
+       gtk_drag_finish(ctx, TRUE, FALSE, time);
+       g_strfreev(uris);
+}
+
+static void init_ui(int *argc, char **argv[], struct gui *ui)
+{
+       GtkSettings *settings;
+       GtkWidget *vbox;
+
+       /* Magical g*thread incantation, you just need this thread stuff.
+        * Without it, the update that happens in gfio_update_thread_status
+        * doesn't really happen in a timely fashion, you need expose events
+        */
+       if (!g_thread_supported())
+               g_thread_init(NULL);
+       gdk_threads_init();
+
+       gtk_init(argc, argv);
+       settings = gtk_settings_get_default();
+       gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
+       g_type_init();
+       gdk_color_parse("#fffff4", &gfio_color_lightyellow);
+       gdk_color_parse("white", &gfio_color_white);
+
+       ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
+       gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
+
+       g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
+       g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
+
+       ui->vbox = gtk_vbox_new(FALSE, 0);
+       gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
+
+       ui->uimanager = gtk_ui_manager_new();
+       ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
+       gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
+
+       ui->recentmanager = gtk_recent_manager_get_default();
+       add_recent_file_items(ui);
+
+       ui->notebook = gtk_notebook_new();
+       g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
+       gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
+       gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
+       gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
+
+       vbox = new_main_page(ui);
+       gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
+       gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
+       g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
+
+       gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
+
+       gfio_ui_setup_log(ui);
+
+       gtk_widget_show_all(ui->window);
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+       if (initialize_fio(envp))
+               return 1;
+       if (fio_init_options())
+               return 1;
+
+       gopt_init();
+
+       memset(&main_ui, 0, sizeof(main_ui));
+       main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
+
+       init_ui(&argc, &argv, &main_ui);
+
+       gdk_threads_enter();
+       gtk_main();
+       gdk_threads_leave();
+
+       g_hash_table_destroy(main_ui.ge_hash);
+
+       gopt_exit();
+       return 0;
+}
diff --git a/gfio.h b/gfio.h
new file mode 100644 (file)
index 0000000..aa14e3c
--- /dev/null
+++ b/gfio.h
@@ -0,0 +1,179 @@
+#ifndef GFIO_H
+#define GFIO_H
+
+#include <gtk/gtk.h>
+
+#include "gcompat.h"
+#include "stat.h"
+#include "thread_options.h"
+#include "ghelpers.h"
+#include "graph.h"
+
+struct probe_widget {
+       GtkWidget *hostname;
+       GtkWidget *os;
+       GtkWidget *arch;
+       GtkWidget *fio_ver;
+};
+
+struct eta_widget {
+       GtkWidget *names;
+       struct multitext_widget iotype;
+       struct multitext_widget bs;
+       struct multitext_widget ioengine;
+       struct multitext_widget iodepth;
+       GtkWidget *jobs;
+       GtkWidget *files;
+       GtkWidget *read_bw;
+       GtkWidget *read_iops;
+       GtkWidget *cr_bw;
+       GtkWidget *cr_iops;
+       GtkWidget *write_bw;
+       GtkWidget *write_iops;
+       GtkWidget *cw_bw;
+       GtkWidget *cw_iops;
+       GtkWidget *trim_bw;
+       GtkWidget *trim_iops;
+};
+
+struct gfio_graphs {
+#define DRAWING_AREA_XDIM 1000
+#define DRAWING_AREA_YDIM 400
+       GtkWidget *drawing_area;
+       struct graph *iops_graph;
+       graph_label_t read_iops;
+       graph_label_t write_iops;
+       graph_label_t trim_iops;
+       struct graph *bandwidth_graph;
+       graph_label_t read_bw;
+       graph_label_t write_bw;
+       graph_label_t trim_bw;
+};
+
+/*
+ * Main window widgets and data
+ */
+struct gui {
+       GtkUIManager *uimanager;
+       GtkRecentManager *recentmanager;
+       GtkActionGroup *actiongroup;
+       guint recent_ui_id;
+       GtkWidget *menu;
+       GtkWidget *window;
+       GtkWidget *vbox;
+       GtkWidget *thread_status_pb;
+       GtkWidget *buttonbox;
+       GtkWidget *notebook;
+       GtkWidget *error_info_bar;
+       GtkWidget *error_label;
+       GtkListStore *log_model;
+       GtkWidget *log_tree;
+       GtkWidget *log_view;
+       struct gfio_graphs graphs;
+       struct probe_widget probe;
+       struct eta_widget eta;
+       pthread_t server_t;
+
+       pthread_t t;
+       int handler_running;
+
+       GHashTable *ge_hash;
+} main_ui;
+
+enum {
+       GE_STATE_NEW = 1,
+       GE_STATE_CONNECTED,
+       GE_STATE_JOB_SENT,
+       GE_STATE_JOB_STARTED,
+       GE_STATE_JOB_RUNNING,
+       GE_STATE_JOB_DONE,
+};
+
+enum {
+       GFIO_BUTTON_CONNECT = 0,
+       GFIO_BUTTON_SEND,
+       GFIO_BUTTON_START,
+       GFIO_BUTTON_NR,
+};
+
+/*
+ * Notebook entry
+ */
+struct gui_entry {
+       struct gui *ui;
+
+       GtkWidget *vbox;
+       GtkWidget *job_notebook;
+       GtkWidget *thread_status_pb;
+       GtkWidget *buttonbox;
+       GtkWidget *button[GFIO_BUTTON_NR];
+       GtkWidget *notebook;
+       GtkWidget *error_info_bar;
+       GtkWidget *error_label;
+       GtkWidget *results_window;
+       GtkWidget *results_notebook;
+       GtkUIManager *results_uimanager;
+       GtkWidget *results_menu;
+       GtkWidget *disk_util_vbox;
+       GtkListStore *log_model;
+       GtkWidget *log_tree;
+       GtkWidget *log_view;
+       struct gfio_graphs graphs;
+       struct probe_widget probe;
+       struct eta_widget eta;
+       GtkWidget *page_label;
+       gint page_num;
+       unsigned int state;
+
+       struct graph *clat_graph;
+       struct graph *lat_bucket_graph;
+
+       struct gfio_client *client;
+       char *job_file;
+       char *host;
+       int port;
+       int type;
+       int server_start;
+};
+
+struct end_results {
+       struct group_run_stats gs;
+       struct thread_stat ts;
+};
+
+struct gfio_client_options {
+       struct flist_head list;
+       struct thread_options o;
+};
+
+struct gfio_client {
+       struct gui_entry *ge;
+       struct fio_client *client;
+       GtkWidget *err_entry;
+       uint32_t client_cpus;
+       uint64_t client_flags;
+
+       struct flist_head o_list;
+       unsigned int o_list_nr;
+
+       struct end_results *results;
+       unsigned int nr_results;
+
+       uint32_t update_job_status;
+       volatile uint32_t update_job_done;
+
+       struct cmd_du_pdu *du;
+       unsigned int nr_du;
+};
+
+#define GFIO_MIME      "text/fio"
+
+extern void gfio_view_log(struct gui *ui);
+extern void gfio_set_state(struct gui_entry *ge, unsigned int state);
+extern void clear_ge_ui_info(struct gui_entry *ge);
+
+extern const char *gfio_graph_font;
+extern GdkColor gfio_color_white;
+extern GdkColor gfio_color_lightyellow;
+
+#endif
diff --git a/ghelpers.c b/ghelpers.c
new file mode 100644 (file)
index 0000000..7acf588
--- /dev/null
@@ -0,0 +1,201 @@
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#include "gcompat.h"
+#include "ghelpers.h"
+
+GtkWidget *new_combo_entry_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *entry, *frame;
+
+       frame = gtk_frame_new(label);
+       entry = gtk_combo_box_text_new();
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), entry);
+
+       return entry;
+}
+
+GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *entry, *frame;
+
+       frame = gtk_frame_new(label);
+       entry = gtk_entry_new();
+       gtk_editable_set_editable(GTK_EDITABLE(entry), 0);
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), entry);
+
+       return entry;
+}
+
+static void fill_color_from_rgb(GdkColor *c, gfloat r, gfloat g, gfloat b)
+{
+       gint R, G, B;
+       gchar tmp[8];
+
+       memset(c, 0, sizeof(*c));
+       R = r * 255;
+       G = g * 255;
+       B = b * 255;
+       snprintf(tmp, sizeof(tmp), "#%02x%02x%02x", R, G, B);
+       gdk_color_parse(tmp, c);
+}
+
+GtkWidget *new_info_entry_in_frame_rgb(GtkWidget *box, const char *label,
+                                       gfloat r, gfloat g, gfloat b)
+{
+       GtkWidget *entry;
+       GdkColor c;
+
+       entry = new_info_entry_in_frame(box, label);
+       fill_color_from_rgb(&c, r, g, b);
+       gtk_widget_modify_text(entry, GTK_STATE_NORMAL, &c);
+       return entry;
+}
+
+GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *label_widget;
+       GtkWidget *frame;
+
+       frame = gtk_frame_new(label);
+       label_widget = gtk_label_new(NULL);
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), label_widget);
+
+       return label_widget;
+}
+
+GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
+{
+       GtkWidget *button, *box;
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(hbox), box);
+
+       button = gtk_spin_button_new_with_range(min, max, 1.0);
+       gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
+
+       gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
+
+       return button;
+}
+
+void label_set_int_value(GtkWidget *entry, unsigned int val)
+{
+       char tmp[80];
+
+       sprintf(tmp, "%u", val);
+       gtk_label_set_text(GTK_LABEL(entry), tmp);
+}
+
+void entry_set_int_value(GtkWidget *entry, unsigned int val)
+{
+       char tmp[80];
+
+       sprintf(tmp, "%u", val);
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+}
+
+GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags)
+{
+       GtkCellRenderer *renderer;
+       GtkTreeViewColumn *col;
+       double xalign = 0.0; /* left as default */
+       PangoAlignment align;
+       gboolean visible;
+
+       align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
+               (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
+               PANGO_ALIGN_CENTER;
+       visible = !(flags & INVISIBLE);
+
+       renderer = gtk_cell_renderer_text_new();
+       col = gtk_tree_view_column_new();
+
+       gtk_tree_view_column_set_title(col, title);
+       if (!(flags & UNSORTABLE))
+               gtk_tree_view_column_set_sort_column_id(col, index);
+       gtk_tree_view_column_set_resizable(col, TRUE);
+       gtk_tree_view_column_pack_start(col, renderer, TRUE);
+       gtk_tree_view_column_set_expand(col, TRUE);
+       gtk_tree_view_column_add_attribute(col, renderer, "text", index);
+       g_object_set(G_OBJECT(renderer), "alignment", align, NULL);
+       switch (align) {
+       case PANGO_ALIGN_LEFT:
+               xalign = 0.0;
+               break;
+       case PANGO_ALIGN_CENTER:
+               xalign = 0.5;
+               break;
+       case PANGO_ALIGN_RIGHT:
+               xalign = 1.0;
+               break;
+       }
+       gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
+       gtk_tree_view_column_set_visible(col, visible);
+       gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
+       return col;
+}
+
+void multitext_add_entry(struct multitext_widget *mt, const char *text)
+{
+       mt->text = realloc(mt->text, (mt->max_text + 1) * sizeof(char *));
+       mt->text[mt->max_text] = strdup(text);
+       mt->max_text++;
+}
+
+void multitext_set_entry(struct multitext_widget *mt, unsigned int index)
+{
+       if (index >= mt->max_text)
+               return;
+       if (!mt->text || !mt->text[index])
+               return;
+
+       mt->cur_text = index;
+       gtk_entry_set_text(GTK_ENTRY(mt->entry), mt->text[index]);
+}
+
+void multitext_update_entry(struct multitext_widget *mt, unsigned int index,
+                           const char *text)
+{
+       if (!mt->text)
+               return;
+
+       if (mt->text[index])
+               free(mt->text[index]);
+
+       mt->text[index] = strdup(text);
+       if (mt->cur_text == index)
+               gtk_entry_set_text(GTK_ENTRY(mt->entry), mt->text[index]);
+}
+
+void multitext_free(struct multitext_widget *mt)
+{
+       int i;
+
+       gtk_entry_set_text(GTK_ENTRY(mt->entry), "");
+
+       for (i = 0; i < mt->max_text; i++) {
+               if (mt->text[i])
+                       free(mt->text[i]);
+       }
+
+       free(mt->text);
+       mt->cur_text = -1;
+       mt->max_text = 0;
+}
+
+GtkWidget *get_scrolled_window(gint border_width)
+{
+       GtkWidget *scroll;
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
+       gtk_container_set_border_width(GTK_CONTAINER(scroll), border_width);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       return scroll;
+}
diff --git a/ghelpers.h b/ghelpers.h
new file mode 100644 (file)
index 0000000..39a994b
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef GFIO_HELPERS_H
+#define GFIO_HELPERS_H
+
+GtkWidget *new_combo_entry_in_frame(GtkWidget *box, const char *label);
+GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label);
+GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label);
+GtkWidget *new_info_entry_in_frame_rgb(GtkWidget *box, const char *label,
+                                       gfloat r, gfloat g, gfloat b);
+GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval);
+void label_set_int_value(GtkWidget *entry, unsigned int val);
+void entry_set_int_value(GtkWidget *entry, unsigned int val);
+
+GtkWidget *get_scrolled_window(gint border_width);
+
+struct multitext_widget {
+       GtkWidget *entry;
+       char **text;
+       unsigned int cur_text;
+       unsigned int max_text;
+};
+
+void multitext_add_entry(struct multitext_widget *mt, const char *text);
+void multitext_set_entry(struct multitext_widget *mt, unsigned int index);
+void multitext_update_entry(struct multitext_widget *mt, unsigned int index,
+                           const char *text);
+void multitext_free(struct multitext_widget *mt);
+
+#define ALIGN_LEFT 1
+#define ALIGN_RIGHT 2
+#define INVISIBLE 4
+#define UNSORTABLE 8
+
+GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags);
+
+#endif
diff --git a/goptions.c b/goptions.c
new file mode 100644 (file)
index 0000000..b78accd
--- /dev/null
@@ -0,0 +1,1618 @@
+#include <locale.h>
+#include <malloc.h>
+#include <string.h>
+
+#include <glib.h>
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+#include "fio.h"
+#include "gfio.h"
+#include "ghelpers.h"
+#include "gerror.h"
+#include "parse.h"
+
+struct gopt {
+       GtkWidget *box;
+       unsigned int opt_index;
+       unsigned int opt_type;
+       gulong sig_handler;
+       struct gopt_job_view *gjv;
+       struct flist_head changed_list;
+};
+
+struct gopt_combo {
+       struct gopt gopt;
+       GtkWidget *combo;
+};
+
+struct gopt_int {
+       struct gopt gopt;
+       unsigned long long lastval;
+       GtkWidget *spin;
+};
+
+struct gopt_bool {
+       struct gopt gopt;
+       GtkWidget *check;
+};
+
+struct gopt_str {
+       struct gopt gopt;
+       GtkWidget *entry;
+};
+
+struct gopt_str_val {
+       struct gopt gopt;
+       GtkWidget *spin;
+       GtkWidget *combo;
+       unsigned int maxindex;
+};
+
+#define GOPT_RANGE_SPIN        4
+
+struct gopt_range {
+       struct gopt gopt;
+       GtkWidget *spins[GOPT_RANGE_SPIN];
+};
+
+struct gopt_str_multi {
+       struct gopt gopt;
+       GtkWidget *checks[PARSE_MAX_VP];
+};
+
+enum {
+       GOPT_COMBO_INT = 1,
+       GOPT_COMBO_STR,
+       GOPT_INT,
+       GOPT_BOOL,
+       GOPT_STR,
+       GOPT_STR_VAL,
+       GOPT_RANGE,
+       GOPT_STR_MULTI,
+};
+
+struct gopt_frame_widget {
+       GtkWidget *vbox[2];
+       unsigned int nr;
+};
+
+struct gopt_job_view {
+       struct gopt_frame_widget g_widgets[__FIO_OPT_G_NR];
+       GtkWidget *vboxes[__FIO_OPT_C_NR];
+       struct gopt *gopts[FIO_MAX_OPTS];
+       GtkWidget *dialog;
+       GtkWidget *job_combo;
+       struct gfio_client *client;
+       struct flist_head changed_list;
+       struct thread_options *o;
+       int in_job_switch;
+};
+
+static GNode *gopt_dep_tree;
+
+static GtkWidget *gopt_get_group_frame(struct gopt_job_view *gjv,
+                                      GtkWidget *box, unsigned int groupmask)
+{
+       unsigned int mask, group;
+       struct opt_group *og;
+       GtkWidget *frame, *hbox;
+       struct gopt_frame_widget *gfw;
+
+       if (!groupmask)
+               return 0;
+
+       mask = groupmask;
+       og = opt_group_cat_from_mask(&mask);
+       if (!og)
+               return NULL;
+
+       group = ffz(~groupmask);
+       gfw = &gjv->g_widgets[group];
+       if (!gfw->vbox[0]) {
+               frame = gtk_frame_new(og->name);
+               gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3);
+               hbox = gtk_hbox_new(FALSE, 0);
+               gtk_container_add(GTK_CONTAINER(frame), hbox);
+               gfw->vbox[0] = gtk_vbox_new(TRUE, 5);
+               gfw->vbox[1] = gtk_vbox_new(TRUE, 5);
+               gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[0], TRUE, TRUE, 5);
+               gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[1], TRUE, TRUE, 5);
+       }
+
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(gfw->vbox[gfw->nr++ & 1]), hbox, FALSE, FALSE, 5);
+       return hbox;
+}
+
+/*
+ * Mark children as invisible, if needed.
+ */
+static void gopt_set_children_visible(struct gopt_job_view *gjv,
+                                     struct fio_option *parent,
+                                     gboolean visible)
+{
+       GNode *child, *node;
+
+       if (parent->hide_on_set)
+               visible = !visible;
+
+       node = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
+       child = g_node_first_child(node);
+       while (child) {
+               struct fio_option *o = child->data;
+               struct gopt *g = o->gui_data;
+               GtkWidget *widget = g->box;
+
+               /*
+                * Recurse into child, if it also has children
+                */
+               if (g_node_n_children(child))
+                       gopt_set_children_visible(gjv, o, visible);
+
+               gtk_widget_set_sensitive(widget, visible);
+               child = g_node_next_sibling(child);
+       }
+}
+
+static void gopt_mark_index(struct gopt_job_view *gjv, struct gopt *gopt,
+                           unsigned int idx, int type)
+{
+       INIT_FLIST_HEAD(&gopt->changed_list);
+
+       assert(!gjv->gopts[idx]);
+       gopt->opt_index = idx;
+       gopt->opt_type = type;
+       gopt->gjv = gjv;
+       gjv->gopts[idx] = gopt;
+}
+
+static void gopt_dialog_update_apply_button(struct gopt_job_view *gjv)
+{
+       GtkDialog *dialog = GTK_DIALOG(gjv->dialog);
+       gboolean set;
+
+       set = !flist_empty(&gjv->changed_list);
+       gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_APPLY, set);
+
+       if (set) {
+               gtk_widget_set_sensitive(gjv->job_combo, 0);
+               gtk_widget_set_tooltip_text(gjv->job_combo, "Apply option changes before switching to a new job");
+       } else {
+               gtk_widget_set_sensitive(gjv->job_combo, 1);
+               gtk_widget_set_tooltip_text(gjv->job_combo, "Change current job");
+       }
+}
+
+static void gopt_changed(struct gopt *gopt)
+{
+       struct gopt_job_view *gjv = gopt->gjv;
+
+       if (gjv->in_job_switch)
+               return;
+
+       /*
+        * Add to changed list. This also prevents the option from being
+        * freed when the widget is destroyed.
+        */
+       if (flist_empty(&gopt->changed_list)) {
+               flist_add_tail(&gopt->changed_list, &gjv->changed_list);
+               gopt_dialog_update_apply_button(gjv);
+       }
+}
+
+static void gopt_str_changed(GtkEntry *entry, gpointer data)
+{
+       struct gopt_str *s = (struct gopt_str *) data;
+       struct fio_option *o = &fio_options[s->gopt.opt_index];
+       const gchar *text;
+       int set;
+
+       gopt_changed(&s->gopt);
+
+       text = gtk_entry_get_text(GTK_ENTRY(s->entry));
+       set = strcmp(text, "") != 0;
+
+       gopt_set_children_visible(s->gopt.gjv, o, set);
+}
+
+static void gopt_str_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_str *s = (struct gopt_str *) data;
+
+       free(s);
+       gtk_widget_destroy(w);
+}
+
+static void gopt_str_store_set_val(struct gopt_str *s, const char *text)
+{
+       if (text)
+               gtk_entry_set_text(GTK_ENTRY(s->entry), text);
+}
+
+static struct gopt *gopt_new_str_store(struct gopt_job_view *gjv,
+                                      struct fio_option *o, const char *text,
+                                      unsigned int idx)
+{
+       struct gopt_str *s;
+       GtkWidget *label;
+
+       s = calloc(1, sizeof(*s));
+
+       s->gopt.box = gtk_hbox_new(FALSE, 3);
+       if (!o->lname)
+               label = gtk_label_new(o->name);
+       else
+               label = gtk_label_new(o->lname);
+
+       s->entry = gtk_entry_new();
+       gopt_mark_index(gjv, &s->gopt, idx, GOPT_STR);
+       gtk_editable_set_editable(GTK_EDITABLE(s->entry), 1);
+
+       if (text)
+               gopt_str_store_set_val(s, text);
+       else if (o->def)
+               gopt_str_store_set_val(s, o->def);
+
+       s->gopt.sig_handler = g_signal_connect(G_OBJECT(s->entry), "changed", G_CALLBACK(gopt_str_changed), s);
+       g_signal_connect(G_OBJECT(s->entry), "destroy", G_CALLBACK(gopt_str_destroy), s);
+
+       gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0);
+       return &s->gopt;
+}
+
+static void gopt_combo_changed(GtkComboBox *box, gpointer data)
+{
+       struct gopt_combo *c = (struct gopt_combo *) data;
+       struct fio_option *o = &fio_options[c->gopt.opt_index];
+       unsigned int index;
+
+       gopt_changed(&c->gopt);
+
+       index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
+
+       gopt_set_children_visible(c->gopt.gjv, o, index);
+}
+
+static void gopt_combo_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_combo *c = (struct gopt_combo *) data;
+
+       free(c);
+       gtk_widget_destroy(w);
+}
+
+static struct gopt_combo *__gopt_new_combo(struct gopt_job_view *gjv,
+                                          struct fio_option *o,
+                                          unsigned int idx, int type)
+{
+       struct gopt_combo *c;
+       GtkWidget *label;
+
+       c = calloc(1, sizeof(*c));
+
+       c->gopt.box = gtk_hbox_new(FALSE, 3);
+       if (!o->lname)
+               label = gtk_label_new(o->name);
+       else
+               label = gtk_label_new(o->lname);
+
+       c->combo = gtk_combo_box_text_new();
+       gopt_mark_index(gjv, &c->gopt, idx, type);
+       g_signal_connect(G_OBJECT(c->combo), "destroy", G_CALLBACK(gopt_combo_destroy), c);
+
+       gtk_box_pack_start(GTK_BOX(c->gopt.box), c->combo, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(c->gopt.box), label, FALSE, FALSE, 0);
+
+       return c;
+}
+
+static void gopt_combo_str_set_val(struct gopt_combo *c, const char *text)
+{
+       struct fio_option *o = &fio_options[c->gopt.opt_index];
+       struct value_pair *vp;
+       int i;
+
+       i = 0;
+       vp = &o->posval[0];
+       while (vp->ival) {
+               if (!strcmp(vp->ival, text)) {
+                       gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), i);
+                       break;
+               }
+               vp++;
+               i++;
+       }
+}
+
+static struct gopt *gopt_new_combo_str(struct gopt_job_view *gjv,
+                                      struct fio_option *o, const char *text,
+                                      unsigned int idx)
+{
+       struct gopt_combo *c;
+       struct value_pair *vp;
+       int i, active = 0;
+
+       c = __gopt_new_combo(gjv, o, idx, GOPT_COMBO_STR);
+
+       i = 0;
+       vp = &o->posval[0];
+       while (vp->ival) {
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c->combo), vp->ival);
+               if (o->def && !strcmp(vp->ival, o->def))
+                       active = i;
+               vp++;
+               i++;
+       }
+
+       gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
+       if (text)
+               gopt_combo_str_set_val(c, text);
+       c->gopt.sig_handler = g_signal_connect(G_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
+       return &c->gopt;
+}
+
+static void gopt_combo_int_set_val(struct gopt_combo *c, unsigned int ip)
+{
+       struct fio_option *o = &fio_options[c->gopt.opt_index];
+       struct value_pair *vp;
+       int i;
+
+       i = 0;
+       vp = &o->posval[0];
+       while (vp->ival) {
+               if (vp->oval == ip) {
+                       gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), i);
+                       break;
+               }
+               vp++;
+               i++;
+       }
+}
+
+static struct gopt *gopt_new_combo_int(struct gopt_job_view *gjv,
+                                      struct fio_option *o, unsigned int *ip,
+                                      unsigned int idx)
+{
+       struct gopt_combo *c;
+       struct value_pair *vp;
+       int i, active = 0;
+
+       c = __gopt_new_combo(gjv, o, idx, GOPT_COMBO_INT);
+
+       i = 0;
+       vp = &o->posval[0];
+       while (vp->ival) {
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c->combo), vp->ival);
+               if (ip && vp->oval == *ip)
+                       active = i;
+               vp++;
+               i++;
+       }
+
+       gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active);
+       if (ip)
+               gopt_combo_int_set_val(c, *ip);
+       c->gopt.sig_handler = g_signal_connect(G_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c);
+       return &c->gopt;
+}
+
+static void gopt_str_multi_toggled(GtkToggleButton *button, gpointer data)
+{
+       struct gopt_str_multi *m = (struct gopt_str_multi *) data;
+
+       gopt_changed(&m->gopt);
+}
+
+static void gopt_str_multi_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_str_multi *m = (struct gopt_str_multi *) data;
+
+       free(m);
+       gtk_widget_destroy(w);
+}
+
+static void gopt_str_multi_set_val(struct gopt_str_multi *m, int val)
+{
+}
+
+static struct gopt *gopt_new_str_multi(struct gopt_job_view *gjv,
+                                      struct fio_option *o, unsigned int idx)
+{
+       struct gopt_str_multi *m;
+       struct value_pair *vp;
+       GtkWidget *frame, *hbox;
+       int i;
+
+       m = calloc(1, sizeof(*m));
+       m->gopt.box = gtk_hbox_new(FALSE, 3);
+       gopt_mark_index(gjv, &m->gopt, idx, GOPT_STR_MULTI);
+
+       if (!o->lname)
+               frame = gtk_frame_new(o->name);
+       else
+               frame = gtk_frame_new(o->lname);
+       gtk_box_pack_start(GTK_BOX(m->gopt.box), frame, FALSE, FALSE, 3);
+
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+       i = 0;
+       vp = &o->posval[0];
+       while (vp->ival) {
+               m->checks[i] = gtk_check_button_new_with_label(vp->ival);
+               gtk_widget_set_tooltip_text(m->checks[i], vp->help);
+               gtk_box_pack_start(GTK_BOX(hbox), m->checks[i], FALSE, FALSE, 3);
+               g_signal_connect(G_OBJECT(m->checks[i]), "toggled", G_CALLBACK(gopt_str_multi_toggled), m);
+               vp++;
+               i++;
+       }
+
+       gopt_str_multi_set_val(m, 0);
+       g_signal_connect(G_OBJECT(m->gopt.box), "destroy", G_CALLBACK(gopt_str_multi_destroy), m);
+       return &m->gopt;
+}
+
+static void gopt_int_changed(GtkSpinButton *spin, gpointer data)
+{
+       struct gopt_int *i = (struct gopt_int *) data;
+       struct fio_option *o = &fio_options[i->gopt.opt_index];
+       GtkAdjustment *adj;
+       int value, delta;
+
+       gopt_changed(&i->gopt);
+
+       adj = gtk_spin_button_get_adjustment(spin);
+       value = gtk_adjustment_get_value(adj);
+       delta = value - i->lastval;
+       i->lastval = value;
+
+       if (o->inv_opt) {
+               struct gopt *b_inv = o->inv_opt->gui_data;
+               struct gopt_int *i_inv = container_of(b_inv, struct gopt_int, gopt);
+               int cur_val;
+
+               assert(o->type == o->inv_opt->type);
+
+               cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin));
+               cur_val -= delta;
+               g_signal_handler_block(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
+               gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val);
+               g_signal_handler_unblock(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler);
+       }
+}
+
+static void gopt_int_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_int *i = (struct gopt_int *) data;
+
+       free(i);
+       gtk_widget_destroy(w);
+}
+
+static void gopt_int_set_val(struct gopt_int *i, unsigned long long p)
+{
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), p);
+       i->lastval = p;
+}
+
+static struct gopt_int *__gopt_new_int(struct gopt_job_view *gjv,
+                                      struct fio_option *o,
+                                      unsigned long long *p, unsigned int idx)
+{
+       unsigned long long defval;
+       struct gopt_int *i;
+       guint maxval, interval;
+       GtkWidget *label;
+
+       i = calloc(1, sizeof(*i));
+       i->gopt.box = gtk_hbox_new(FALSE, 3);
+       if (!o->lname)
+               label = gtk_label_new(o->name);
+       else
+               label = gtk_label_new(o->lname);
+
+       maxval = o->maxval;
+       if (!maxval)
+               maxval = UINT_MAX;
+
+       defval = 0;
+       if (p)
+               defval = *p;
+       else if (o->def) {
+               long long val;
+
+               check_str_bytes(o->def, &val, o);
+               defval = val;
+       }
+
+       interval = 1.0;
+       if (o->interval)
+               interval = o->interval;
+
+       i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval);
+       gopt_mark_index(gjv, &i->gopt, idx, GOPT_INT);
+       gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID);
+       if (p)
+               gopt_int_set_val(i, *p);
+       else
+               gopt_int_set_val(i, defval);
+       i->gopt.sig_handler = g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i);
+       g_signal_connect(G_OBJECT(i->spin), "destroy", G_CALLBACK(gopt_int_destroy), i);
+
+       gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0);
+
+       return i;
+}
+
+static struct gopt *gopt_new_int(struct gopt_job_view *gjv,
+                                struct fio_option *o, unsigned int *ip,
+                                unsigned int idx)
+{
+       unsigned long long ullp;
+       struct gopt_int *i;
+
+       if (ip) {
+               ullp = *ip;
+               i = __gopt_new_int(gjv, o, &ullp, idx);
+       } else
+               i = __gopt_new_int(gjv, o, NULL, idx);
+
+       return &i->gopt;
+}
+
+static struct gopt *gopt_new_ullong(struct gopt_job_view *gjv,
+                                   struct fio_option *o, unsigned long long *p,
+                                   unsigned int idx)
+{
+       struct gopt_int *i;
+
+       i = __gopt_new_int(gjv, o, p, idx);
+       return &i->gopt;
+}
+
+static void gopt_bool_toggled(GtkToggleButton *button, gpointer data)
+{
+       struct gopt_bool *b = (struct gopt_bool *) data;
+       struct fio_option *o = &fio_options[b->gopt.opt_index];
+       gboolean set;
+
+       gopt_changed(&b->gopt);
+
+       set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
+
+       if (o->inv_opt) {
+               struct gopt *g_inv = o->inv_opt->gui_data;
+               struct gopt_bool *b_inv = container_of(g_inv, struct gopt_bool, gopt);
+
+               assert(o->type == o->inv_opt->type);
+
+               g_signal_handler_block(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_inv->check), !set);
+               g_signal_handler_unblock(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler);
+       }
+
+       gopt_set_children_visible(b->gopt.gjv, o, set);
+}
+
+static void gopt_bool_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_bool *b = (struct gopt_bool *) data;
+
+       free(b);
+       gtk_widget_destroy(w);
+}
+
+static void gopt_bool_set_val(struct gopt_bool *b, unsigned int val)
+{
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), val);
+}
+
+static struct gopt *gopt_new_bool(struct gopt_job_view *gjv,
+                                 struct fio_option *o, unsigned int *val,
+                                 unsigned int idx)
+{
+       struct gopt_bool *b;
+       GtkWidget *label;
+       int defstate = 0;
+
+       b = calloc(1, sizeof(*b));
+       b->gopt.box = gtk_hbox_new(FALSE, 3);
+       if (!o->lname)
+               label = gtk_label_new(o->name);
+       else
+               label = gtk_label_new(o->lname);
+
+       b->check = gtk_check_button_new();
+       gopt_mark_index(gjv, &b->gopt, idx, GOPT_BOOL);
+       if (o->def && !strcmp(o->def, "1"))
+               defstate = 1;
+
+       if (o->neg)
+               defstate = !defstate;
+
+       if (val)
+               gopt_bool_set_val(b, *val);
+       else
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate);
+       b->gopt.sig_handler = g_signal_connect(G_OBJECT(b->check), "toggled", G_CALLBACK(gopt_bool_toggled), b);
+       g_signal_connect(G_OBJECT(b->check), "destroy", G_CALLBACK(gopt_bool_destroy), b);
+
+       gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0);
+       return &b->gopt;
+}
+
+/*
+ * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values.
+ * If the max is made smaller than min, adjust min down.
+ * If the min is made larger than max, adjust the max.
+ */
+static void range_value_changed(GtkSpinButton *spin, gpointer data)
+{
+       struct gopt_range *r = (struct gopt_range *) data;
+       int changed = -1, i;
+       gint val, mval;
+
+       gopt_changed(&r->gopt);
+
+       for (i = 0; i < GOPT_RANGE_SPIN; i++) {
+               if (GTK_SPIN_BUTTON(r->spins[i]) == spin) {
+                       changed = i;
+                       break;
+               }
+       }
+
+       assert(changed != -1);
+
+       /*
+        * Min changed
+        */
+       if (changed == 0 || changed == 2) {
+               GtkWidget *mspin = r->spins[changed + 1];
+
+               val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
+               mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
+               if (val > mval)
+                       gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
+       } else {
+               GtkWidget *mspin = r->spins[changed - 1];
+
+               val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed]));
+               mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin));
+               if (val < mval)
+                       gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val);
+       }
+}
+
+static void gopt_range_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_range *r = (struct gopt_range *) data;
+
+       free(r);
+       gtk_widget_destroy(w);
+}
+
+static void gopt_int_range_set_val(struct gopt_range *r, unsigned int *vals)
+{
+       int i;
+
+       for (i = 0; i < GOPT_RANGE_SPIN; i++)
+               gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), vals[i]);
+}
+
+static struct gopt *gopt_new_int_range(struct gopt_job_view *gjv,
+                                      struct fio_option *o, unsigned int **ip,
+                                      unsigned int idx)
+{
+       struct gopt_range *r;
+       GtkWidget *label;
+       guint interval;
+       unsigned int defvals[GOPT_RANGE_SPIN];
+       gint maxval;
+       int i;
+
+       r = calloc(1, sizeof(*r));
+       r->gopt.box = gtk_hbox_new(FALSE, 3);
+       gopt_mark_index(gjv, &r->gopt, idx, GOPT_RANGE);
+       if (!o->lname)
+               label = gtk_label_new(o->name);
+       else
+               label = gtk_label_new(o->lname);
+
+       maxval = o->maxval;
+       if (!maxval)
+               maxval = INT_MAX;
+
+       memset(defvals, 0, sizeof(defvals));
+       if (o->def) {
+               long long val;
+
+               check_str_bytes(o->def, &val, o);
+               for (i = 0; i < GOPT_RANGE_SPIN; i++)
+                       defvals[i] = val;
+       }
+
+       interval = 1.0;
+       if (o->interval)
+               interval = o->interval;
+
+       for (i = 0; i < GOPT_RANGE_SPIN; i++) {
+               r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval);
+               gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID);
+               gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0);
+       }
+
+       if (ip)
+               gopt_int_range_set_val(r, *ip);
+       else
+               gopt_int_range_set_val(r, defvals);
+
+       for (i = 0; i < GOPT_RANGE_SPIN; i++)
+               g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r);
+
+       gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(r->gopt.box), "destroy", G_CALLBACK(gopt_range_destroy), r);
+       return &r->gopt;
+}
+
+static void gopt_str_val_destroy(GtkWidget *w, gpointer data)
+{
+       struct gopt_str_val *g = (struct gopt_str_val *) data;
+
+       free(g);
+       gtk_widget_destroy(w);
+}
+
+static void gopt_str_val_spin_wrapped(GtkSpinButton *spin, gpointer data)
+{
+       struct gopt_str_val *g = (struct gopt_str_val *) data;
+       unsigned int val;
+       GtkAdjustment *adj;
+       gint index;
+
+       adj = gtk_spin_button_get_adjustment(spin);
+       val = gtk_adjustment_get_value(adj);
+
+       /*
+        * Can't rely on exact value, as fast changes increment >= 1
+        */
+       if (!val) {
+               index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
+               if (index + 1 <= g->maxindex) {
+                       val = 1;
+                       gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), ++index);
+               } else
+                       val = 1023;
+               gtk_spin_button_set_value(spin, val);
+       } else {
+               index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo));
+               if (index) {
+                       gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), --index);
+                       gtk_spin_button_set_value(spin, 1023);
+               } else
+                       gtk_spin_button_set_value(spin, 0);
+       }
+}
+
+static void gopt_str_val_changed(GtkSpinButton *spin, gpointer data)
+{
+       struct gopt_str_val *g = (struct gopt_str_val *) data;
+
+       gopt_changed(&g->gopt);
+}
+
+static void gopt_str_val_set_val(struct gopt_str_val *g, unsigned long long val)
+{
+       int i = 0;
+
+       do {
+               if (!val || (val % 1024))
+                       break;
+
+               i++;
+               val /= 1024;
+       } while (1);
+
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), val);
+       gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), i);
+}
+
+static struct gopt *gopt_new_str_val(struct gopt_job_view *gjv,
+                                    struct fio_option *o,
+                                    unsigned long long *p, unsigned int idx)
+{
+       struct gopt_str_val *g;
+       const gchar *postfix[] = { "B", "KB", "MB", "GB", "PB", "TB", "" };
+       GtkWidget *label;
+       int i;
+
+       g = calloc(1, sizeof(*g));
+       g->gopt.box = gtk_hbox_new(FALSE, 3);
+       if (!o->lname)
+               label = gtk_label_new(o->name);
+       else
+               label = gtk_label_new(o->lname);
+       gopt_mark_index(gjv, &g->gopt, idx, GOPT_STR_VAL);
+
+       g->spin = gtk_spin_button_new_with_range(0.0, 1023.0, 1.0);
+       gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(g->spin), GTK_UPDATE_IF_VALID);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), 0);
+       gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(g->spin), 1);
+       gtk_box_pack_start(GTK_BOX(g->gopt.box), g->spin, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(g->spin), "wrapped", G_CALLBACK(gopt_str_val_spin_wrapped), g);
+       g_signal_connect(G_OBJECT(g->spin), "changed", G_CALLBACK(gopt_str_val_changed), g);
+
+       g->combo = gtk_combo_box_text_new();
+       i = 0;
+       while (strlen(postfix[i])) {
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(g->combo), postfix[i]);
+               i++;
+       }
+       g->maxindex = i - 1;
+       gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), 0);
+       gtk_box_pack_start(GTK_BOX(g->gopt.box), g->combo, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(g->gopt.box), label, FALSE, FALSE, 3);
+
+       if (p)
+               gopt_str_val_set_val(g, *p);
+
+       g_signal_connect(G_OBJECT(g->combo), "changed", G_CALLBACK(gopt_str_val_changed), g);
+
+       g_signal_connect(G_OBJECT(g->gopt.box), "destroy", G_CALLBACK(gopt_str_val_destroy), g);
+       return &g->gopt;
+}
+
+static void gopt_set_option(struct gopt_job_view *gjv, struct fio_option *o,
+                           struct gopt *gopt, struct thread_options *to)
+{
+       switch (o->type) {
+       case FIO_OPT_STR_VAL: {
+               unsigned long long *ullp = NULL;
+               struct gopt_str_val *g;
+
+               if (o->off1)
+                       ullp = td_var(to, o->off1);
+
+               g = container_of(gopt, struct gopt_str_val, gopt);
+               if (ullp)
+                       gopt_str_val_set_val(g, *ullp);
+               break;
+               }
+       case FIO_OPT_STR_VAL_TIME: {
+               unsigned long long *ullp = NULL;
+               struct gopt_int *i;
+
+               if (o->off1)
+                       ullp = td_var(to, o->off1);
+
+               i = container_of(gopt, struct gopt_int, gopt);
+               if (ullp)
+                       gopt_int_set_val(i, *ullp);
+               break;
+               }
+       case FIO_OPT_INT: {
+               unsigned int *ip = NULL;
+               struct gopt_int *i;
+
+               if (o->off1)
+                       ip = td_var(to, o->off1);
+
+               i = container_of(gopt, struct gopt_int, gopt);
+               if (ip)
+                       gopt_int_set_val(i, *ip);
+               break;
+               }
+       case FIO_OPT_STR_SET:
+       case FIO_OPT_BOOL: {
+               unsigned int *ip = NULL;
+               struct gopt_bool *b;
+
+               if (o->off1)
+                       ip = td_var(to, o->off1);
+
+               b = container_of(gopt, struct gopt_bool, gopt);
+               if (ip)
+                       gopt_bool_set_val(b, *ip);
+               break;
+               }
+       case FIO_OPT_STR: {
+               if (o->posval[0].ival) {
+                       unsigned int *ip = NULL;
+                       struct gopt_combo *c;
+
+                       if (o->off1)
+                               ip = td_var(to, o->off1);
+
+                       c = container_of(gopt, struct gopt_combo, gopt);
+                       if (ip)
+                               gopt_combo_int_set_val(c, *ip);
+               } else {
+                       struct gopt_str *s;
+                       char *text = NULL;
+
+                       if (o->off1) {
+                               char **p = td_var(to, o->off1);
+
+                               text = *p;
+                       }
+
+                       s = container_of(gopt, struct gopt_str, gopt);
+                       gopt_str_store_set_val(s, text);
+               }
+
+               break;
+               }
+       case FIO_OPT_STR_STORE: {
+               struct gopt_combo *c;
+               char *text = NULL;
+
+               if (o->off1) {
+                       char **p = td_var(to, o->off1);
+                       text = *p;
+               }
+
+               if (!o->posval[0].ival) {
+                       struct gopt_str *s;
+
+                       s = container_of(gopt, struct gopt_str, gopt);
+                       gopt_str_store_set_val(s, text);
+                       break;
+               }
+
+               c = container_of(gopt, struct gopt_combo, gopt);
+               if (text)
+                       gopt_combo_str_set_val(c, text);
+               break;
+               }
+       case FIO_OPT_STR_MULTI:
+               /* HANDLE ME */
+               break;
+       case FIO_OPT_RANGE: {
+               struct gopt_range *r;
+               unsigned int *ip[4] = { td_var(to, o->off1),
+                                       td_var(to, o->off2),
+                                       td_var(to, o->off3),
+                                       td_var(to, o->off4) };
+
+               r = container_of(gopt, struct gopt_range, gopt);
+               gopt_int_range_set_val(r, *ip);
+               break;
+               }
+       /* still need to handle this one */
+       case FIO_OPT_FLOAT_LIST:
+               break;
+       case FIO_OPT_DEPRECATED:
+               break;
+       default:
+               printf("ignore type %u\n", o->type);
+               break;
+       }
+}
+
+static void gopt_add_option(struct gopt_job_view *gjv, GtkWidget *hbox,
+                           struct fio_option *o, unsigned int opt_index,
+                           struct thread_options *to)
+{
+       struct gopt *go = NULL;
+
+       switch (o->type) {
+       case FIO_OPT_STR_VAL: {
+               unsigned long long *ullp = NULL;
+
+               if (o->off1)
+                       ullp = td_var(to, o->off1);
+
+               go = gopt_new_str_val(gjv, o, ullp, opt_index);
+               break;
+               }
+       case FIO_OPT_STR_VAL_TIME: {
+               unsigned long long *ullp = NULL;
+
+               if (o->off1)
+                       ullp = td_var(to, o->off1);
+
+               go = gopt_new_ullong(gjv, o, ullp, opt_index);
+               break;
+               }
+       case FIO_OPT_INT: {
+               unsigned int *ip = NULL;
+
+               if (o->off1)
+                       ip = td_var(to, o->off1);
+
+               go = gopt_new_int(gjv, o, ip, opt_index);
+               break;
+               }
+       case FIO_OPT_STR_SET:
+       case FIO_OPT_BOOL: {
+               unsigned int *ip = NULL;
+
+               if (o->off1)
+                       ip = td_var(to, o->off1);
+
+               go = gopt_new_bool(gjv, o, ip, opt_index);
+               break;
+               }
+       case FIO_OPT_STR: {
+               if (o->posval[0].ival) {
+                       unsigned int *ip = NULL;
+
+                       if (o->off1)
+                               ip = td_var(to, o->off1);
+
+                       go = gopt_new_combo_int(gjv, o, ip, opt_index);
+               } else {
+                       /* TODO: usually ->cb, or unsigned int pointer */
+                       go = gopt_new_str_store(gjv, o, NULL, opt_index);
+               }
+
+               break;
+               }
+       case FIO_OPT_STR_STORE: {
+               char *text = NULL;
+
+               if (o->off1) {
+                       char **p = td_var(to, o->off1);
+                       text = *p;
+               }
+
+               if (!o->posval[0].ival) {
+                       go = gopt_new_str_store(gjv, o, text, opt_index);
+                       break;
+               }
+
+               go = gopt_new_combo_str(gjv, o, text, opt_index);
+               break;
+               }
+       case FIO_OPT_STR_MULTI:
+               go = gopt_new_str_multi(gjv, o, opt_index);
+               break;
+       case FIO_OPT_RANGE: {
+               unsigned int *ip[4] = { td_var(to, o->off1),
+                                       td_var(to, o->off2),
+                                       td_var(to, o->off3),
+                                       td_var(to, o->off4) };
+
+               go = gopt_new_int_range(gjv, o, ip, opt_index);
+               break;
+               }
+       /* still need to handle this one */
+       case FIO_OPT_FLOAT_LIST:
+               break;
+       case FIO_OPT_DEPRECATED:
+               break;
+       default:
+               printf("ignore type %u\n", o->type);
+               break;
+       }
+
+       if (go) {
+               GtkWidget *dest;
+
+               if (o->help)
+                       gtk_widget_set_tooltip_text(go->box, o->help);
+
+               o->gui_data = go;
+
+               dest = gopt_get_group_frame(gjv, hbox, o->group);
+               if (!dest)
+                       gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5);
+               else
+                       gtk_box_pack_start(GTK_BOX(dest), go->box, FALSE, FALSE, 5);
+       }
+}
+
+static void gopt_add_options(struct gopt_job_view *gjv,
+                            struct thread_options *to)
+{
+       GtkWidget *hbox = NULL;
+       int i;
+
+       /*
+        * First add all options
+        */
+       for (i = 0; fio_options[i].name; i++) {
+               struct fio_option *o = &fio_options[i];
+               unsigned int mask = o->category;
+               struct opt_group *og;
+
+               while ((og = opt_group_from_mask(&mask)) != NULL) {
+                       GtkWidget *vbox = gjv->vboxes[ffz(~og->mask)];
+
+                       hbox = gtk_hbox_new(FALSE, 3);
+                       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
+                       gopt_add_option(gjv, hbox, o, i, to);
+               }
+       }
+}
+
+static void gopt_set_options(struct gopt_job_view *gjv,
+                            struct thread_options *to)
+{
+       int i;
+
+       for (i = 0; fio_options[i].name; i++) {
+               struct fio_option *o = &fio_options[i];
+               struct gopt *gopt = gjv->gopts[i];
+
+               gopt_set_option(gjv, o, gopt, to);
+       }
+}
+
+static GtkWidget *gopt_add_tab(GtkWidget *notebook, const char *name)
+{
+       GtkWidget *box, *vbox, *scroll;
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
+       gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       box = gtk_hbox_new(FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
+       gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scroll, gtk_label_new(name));
+       return vbox;
+}
+
+static GtkWidget *gopt_add_group_tab(GtkWidget *notebook, struct opt_group *og)
+{
+       return gopt_add_tab(notebook, og->name);
+}
+
+static void gopt_add_group_tabs(GtkWidget *notebook, struct gopt_job_view *gjv)
+{
+       struct opt_group *og;
+       unsigned int i;
+
+       i = 0;
+       do {
+               unsigned int mask = (1U << i);
+
+               og = opt_group_from_mask(&mask);
+               if (!og)
+                       break;
+               gjv->vboxes[i] = gopt_add_group_tab(notebook, og);
+               i++;
+       } while (1);
+}
+
+static void gopt_handle_str_multi_changed(struct gopt_job_view *gjv,
+                                         struct gopt_str_multi *m,
+                                         struct fio_option *o)
+{
+       unsigned int *ip = td_var(gjv->o, o->off1);
+       struct value_pair *vp;
+       gboolean set;
+       guint val = 0;
+       int i;
+
+       i = 0;
+       vp = &o->posval[0];
+       while (vp->ival) {
+               if (!m->checks[i])
+                       break;
+               set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->checks[i]));
+               if (set) {
+                       if (vp->or)
+                               val |= vp->oval;
+                       else
+                               val = vp->oval;
+               }
+               i++;
+               vp++;
+       }
+
+       if (o->off1)
+               *ip = val;
+}
+
+static void gopt_handle_range_changed(struct gopt_job_view *gjv,
+                                     struct gopt_range *r,
+                                     struct fio_option *o)
+{
+       unsigned int *ip[4] = { td_var(gjv->o, o->off1),
+                               td_var(gjv->o, o->off2),
+                               td_var(gjv->o, o->off3),
+                               td_var(gjv->o, o->off4) };
+       gint val;
+       int i;
+
+       for (i = 0; i < GOPT_RANGE_SPIN; i++) {
+               val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[i]));
+               *ip[i] = val;
+       }
+}
+
+static void gopt_handle_str_val_changed(struct gopt_job_view *gjv,
+                                       struct gopt_str_val *s,
+                                       struct fio_option *o)
+{
+       unsigned long long *ullp = td_var(gjv->o, o->off1);
+       GtkAdjustment *adj;
+       gint index;
+
+       if (!ullp)
+               return;
+
+       /*
+        * Numerical value
+        */
+       adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(s->spin));
+       *ullp = gtk_adjustment_get_value(adj);
+
+       /*
+        * Multiplier
+        */
+       index = gtk_combo_box_get_active(GTK_COMBO_BOX(s->combo));
+       while (index--)
+               *ullp *= 1024ULL;
+}
+
+static void gopt_handle_str_changed(struct gopt_job_view *gjv,
+                                   struct gopt_str *s, struct fio_option *o)
+{
+       char **p = td_var(gjv->o, o->off1);
+
+       if (*p)
+               free(*p);
+
+       *p = strdup(gtk_entry_get_text(GTK_ENTRY(s->entry)));
+}
+
+static void gopt_handle_bool_changed(struct gopt_job_view *gjv,
+                                    struct gopt_bool *b, struct fio_option *o)
+{
+       unsigned int *ip = td_var(gjv->o, o->off1);
+       gboolean set;
+
+       set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check));
+       *ip = set;
+}
+
+static void gopt_handle_int_changed(struct gopt_job_view *gjv,
+                                   struct gopt_int *i, struct fio_option *o)
+{
+       unsigned int *ip = td_var(gjv->o, o->off1);
+       GtkAdjustment *adj;
+       guint val;
+
+       adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(i->spin));
+       val = gtk_adjustment_get_value(adj);
+       *ip = val;
+}
+
+static void gopt_handle_combo_str_changed(struct gopt_job_view *gjv,
+                                         struct gopt_combo *c,
+                                         struct fio_option *o)
+{
+       char **p = td_var(gjv->o, o->off1);
+
+       if (*p)
+               free(*p);
+
+       *p = strdup(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(c->combo)));
+}
+
+static void gopt_handle_combo_int_changed(struct gopt_job_view *gjv,
+                                         struct gopt_combo *c,
+                                         struct fio_option *o)
+{
+       unsigned int *ip = td_var(gjv->o, o->off1);
+       gint index;
+
+       index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo));
+       *ip = o->posval[index].oval;
+}
+
+static void gopt_handle_changed(struct gopt *gopt)
+{
+       struct fio_option *o = &fio_options[gopt->opt_index];
+       struct gopt_job_view *gjv = gopt->gjv;
+
+       switch (gopt->opt_type) {
+       case GOPT_COMBO_INT: {
+               struct gopt_combo *c;
+
+               c = container_of(gopt, struct gopt_combo, gopt);
+               gopt_handle_combo_int_changed(gjv, c, o);
+               break;
+               }
+       case GOPT_COMBO_STR: {
+               struct gopt_combo *c;
+
+               c = container_of(gopt, struct gopt_combo, gopt);
+               gopt_handle_combo_str_changed(gjv, c, o);
+               break;
+               }
+       case GOPT_INT: {
+               struct gopt_int *i;
+
+               i = container_of(gopt, struct gopt_int, gopt);
+               gopt_handle_int_changed(gjv, i, o);
+               break;
+               }
+       case GOPT_BOOL: {
+               struct gopt_bool *b;
+
+               b = container_of(gopt, struct gopt_bool, gopt);
+               gopt_handle_bool_changed(gjv, b, o);
+               break;
+               }
+       case GOPT_STR: {
+               struct gopt_str *s;
+
+               s = container_of(gopt, struct gopt_str, gopt);
+               gopt_handle_str_changed(gjv, s, o);
+               break;
+               }
+       case GOPT_STR_VAL: {
+               struct gopt_str_val *s;
+
+               s = container_of(gopt, struct gopt_str_val, gopt);
+               gopt_handle_str_val_changed(gjv, s, o);
+               break;
+               }
+       case GOPT_RANGE: {
+               struct gopt_range *r;
+
+               r = container_of(gopt, struct gopt_range, gopt);
+               gopt_handle_range_changed(gjv, r, o);
+               break;
+               }
+       case GOPT_STR_MULTI: {
+               struct gopt_str_multi *m;
+
+               m = container_of(gopt, struct gopt_str_multi, gopt);
+               gopt_handle_str_multi_changed(gjv, m, o);
+               break;
+               }
+       default:
+               log_err("gfio: bad option type %s/%d\n", gopt->opt_type);
+               return;
+       }
+}
+
+static void gopt_report_update_status(struct gopt_job_view *gjv)
+{
+       struct gfio_client *gc = gjv->client;
+       char tmp[80];
+
+       sprintf(tmp, "\nCompleted with error: %d\n", gc->update_job_status);
+       gfio_report_info(gc->ge->ui, "Update job", tmp);
+}
+
+static int gopt_handle_changed_options(struct gopt_job_view *gjv)
+{
+       struct gfio_client *gc = gjv->client;
+       struct flist_head *entry;
+       uint64_t waitid = 0;
+       struct gopt *gopt;
+       int ret;
+
+       flist_for_each(entry, &gjv->changed_list) {
+               gopt = flist_entry(entry, struct gopt, changed_list);
+               gopt_handle_changed(gopt);
+       }
+
+       gc->update_job_status = 0;
+       gc->update_job_done = 0;
+
+       ret = fio_client_update_options(gc->client, gjv->o, &waitid);
+       if (ret)
+               goto done;
+
+       ret = fio_client_wait_for_reply(gc->client, waitid);
+       if (ret)
+               goto done;
+
+       assert(gc->update_job_done);
+       if (gc->update_job_status)
+               goto done;
+
+       while (!flist_empty(&gjv->changed_list)) {
+               gopt = flist_entry(gjv->changed_list.next, struct gopt, changed_list);
+               flist_del_init(&gopt->changed_list);
+       }
+
+done:
+       gopt_dialog_update_apply_button(gjv);
+       return ret;
+}
+
+static gint gopt_dialog_cancel(gint response)
+{
+       switch (response) {
+       case GTK_RESPONSE_NONE:
+       case GTK_RESPONSE_REJECT:
+       case GTK_RESPONSE_DELETE_EVENT:
+       case GTK_RESPONSE_CANCEL:
+       case GTK_RESPONSE_NO:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static gint gopt_dialog_done(gint response)
+{
+       switch (response) {
+       case GTK_RESPONSE_ACCEPT:
+       case GTK_RESPONSE_OK:
+       case GTK_RESPONSE_YES:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static void gopt_handle_option_dialog(struct gopt_job_view *gjv)
+{
+       gint response;
+
+       do {
+               response = gtk_dialog_run(GTK_DIALOG(gjv->dialog));
+
+               if (gopt_dialog_cancel(response) ||
+                   gopt_dialog_done(response))
+                       break;
+
+               /*
+                * Apply
+                */
+               gopt_handle_changed_options(gjv);
+               gopt_report_update_status(gjv);
+       } while (1);
+
+       if (gopt_dialog_cancel(response))
+               return;
+
+       gopt_handle_changed_options(gjv);
+}
+
+static void gopt_job_changed(GtkComboBox *box, gpointer data)
+{
+       struct gopt_job_view *gjv = (struct gopt_job_view *) data;
+       struct gfio_client_options *gco = NULL;
+       struct gfio_client *gc = gjv->client;
+       struct flist_head *entry;
+       gchar *job;
+
+       /*
+        * The switch act should be sensitized appropriately, so that we
+        * never get here with modified options.
+        */
+       if (!flist_empty(&gjv->changed_list)) {
+               gfio_report_info(gc->ge->ui, "Internal Error", "Modified options on job switch.\nThat should not be possible!\n");
+               return;
+       }
+
+       job = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gjv->job_combo));
+       flist_for_each(entry, &gc->o_list) {
+               const char *name;
+
+               gco = flist_entry(entry, struct gfio_client_options, list);
+               name = gco->o.name;
+               if (!name || !strlen(name))
+                       name = "Default job";
+
+               if (!strcmp(name, job))
+                       break;
+
+               gco = NULL;
+       }
+
+       if (!gco) {
+               gfio_report_info(gc->ge->ui, "Internal Error", "Could not find job description.\nThat should not be possible!\n");
+               return;
+       }
+
+       gjv->in_job_switch = 1;
+       gopt_set_options(gjv, &gco->o);
+       gjv->in_job_switch = 0;
+}
+
+void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc)
+{
+       GtkWidget *dialog, *notebook, *vbox, *topvbox, *combo;
+       struct gfio_client_options *gco;
+       struct flist_head *entry;
+       struct gopt_job_view *gjv;
+
+       dialog = gtk_dialog_new_with_buttons("Fio options",
+                       GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                       GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+
+       combo = gtk_combo_box_text_new();
+       flist_for_each(entry, &gc->o_list) {
+               struct thread_options *o;
+               const char *name;
+
+               gco = flist_entry(entry, struct gfio_client_options, list);
+               o = &gco->o;
+               name = o->name;
+               if (!name || !strlen(name))
+                       name = "Default job";
+
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), name);
+       }
+       gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
+
+       gtk_widget_set_size_request(GTK_WIDGET(dialog), 1024, 768);
+
+       topvbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       gtk_box_pack_start(GTK_BOX(topvbox), combo, FALSE, FALSE, 5);
+
+       vbox = gtk_vbox_new(TRUE, 5);
+       gtk_box_pack_start(GTK_BOX(topvbox), vbox, TRUE, TRUE, 5);
+
+       notebook = gtk_notebook_new();
+       gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
+       gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
+       gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 5);
+
+       gjv = calloc(1, sizeof(*gjv));
+       INIT_FLIST_HEAD(&gjv->changed_list);
+       gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
+       gjv->o = &gco->o;
+       gjv->dialog = dialog;
+       gjv->client = gc;
+       gjv->job_combo = combo;
+       gopt_add_group_tabs(notebook, gjv);
+       gopt_add_options(gjv, &gco->o);
+       gopt_dialog_update_apply_button(gjv);
+
+       g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(gopt_job_changed), gjv);
+
+       gtk_widget_show_all(dialog);
+
+       gopt_handle_option_dialog(gjv);
+
+       gtk_widget_destroy(dialog);
+       free(gjv);
+}
+
+/*
+ * Build n-ary option dependency tree
+ */
+void gopt_init(void)
+{
+       int i;
+
+       gopt_dep_tree = g_node_new(NULL);
+
+       for (i = 0; fio_options[i].name; i++) {
+               struct fio_option *o = &fio_options[i];
+               GNode *node, *nparent;
+
+               /*
+                * Insert node with either the root parent, or an
+                * option parent.
+                */
+               node = g_node_new(o);
+               nparent = gopt_dep_tree;
+               if (o->parent) {
+                       struct fio_option *parent;
+
+                       parent = fio_option_find(o->parent);
+                       nparent = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent);
+                       if (!nparent) {
+                               log_err("fio: did not find parent %s for opt %s\n", o->name, o->parent);
+                               nparent = gopt_dep_tree;
+                       }
+               }
+
+               g_node_insert(nparent, -1, node);
+       }
+}
+
+void gopt_exit(void)
+{
+       g_node_destroy(gopt_dep_tree);
+       gopt_dep_tree = NULL;
+}
diff --git a/goptions.h b/goptions.h
new file mode 100644 (file)
index 0000000..a225a8d
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef GFIO_OPTIONS_H
+#define GFIO_OPTIONS_H
+
+void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc);
+void gopt_init(void);
+void gopt_exit(void);
+
+#endif
diff --git a/graph.c b/graph.c
new file mode 100644 (file)
index 0000000..7a7c792
--- /dev/null
+++ b/graph.c
@@ -0,0 +1,1033 @@
+/*
+ * gfio - gui front end for fio - the flexible io tester
+ *
+ * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
+ *
+ * The license below covers all files distributed with fio unless otherwise
+ * noted in the file itself.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include <string.h>
+#include <malloc.h>
+#include <math.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+#include "tickmarks.h"
+#include "graph.h"
+#include "flist.h"
+#include "lib/prio_tree.h"
+#include "cairo_text_helpers.h"
+
+/*
+ * Allowable difference to show tooltip
+ */
+#define TOOLTIP_DELTA  0.08
+
+struct xyvalue {
+       double x, y;
+};
+
+enum {
+       GV_F_ON_PRIO    = 1,
+       GV_F_PRIO_SKIP  = 2,
+};
+
+struct graph_value {
+       struct flist_head list;
+       struct prio_tree_node node;
+       struct flist_head alias;
+       unsigned int flags;
+       char *tooltip;
+       void *value;
+};
+
+struct graph_label {
+       struct flist_head list;
+       char *label;
+       struct flist_head value_list;
+       struct prio_tree_root prio_tree;
+       double r, g, b;
+       int hide;
+       int value_count;
+       struct graph *parent;
+};
+
+struct tick_value {
+       unsigned int offset;
+       double value;
+};
+
+struct graph {
+       char *title;
+       char *xtitle;
+       char *ytitle;
+       unsigned int xdim, ydim;
+       double xoffset, yoffset;
+       struct flist_head label_list;
+       int per_label_limit;
+       const char *font;
+       graph_axis_unit_change_callback x_axis_unit_change_callback;
+       graph_axis_unit_change_callback y_axis_unit_change_callback;
+       unsigned int base_offset;
+       unsigned int dont_graph_all_zeroes;
+       double left_extra;
+       double right_extra;
+       double top_extra;
+       double bottom_extra;
+
+       double xtick_zero;
+       double xtick_delta;
+       double xtick_zero_val;
+       double xtick_one_val;
+       double ytick_zero;
+       double ytick_delta;
+       double ytick_zero_val;
+       double ytick_one_val;
+};
+
+void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim)
+{
+       g->xdim = xdim;
+       g->ydim = ydim;
+}
+
+void graph_set_position(struct graph *g, double xoffset, double yoffset)
+{
+       g->xoffset = xoffset;
+       g->yoffset = yoffset;
+}
+
+struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font)
+{
+       struct graph *g;
+
+       g = calloc(1, sizeof(*g));
+       INIT_FLIST_HEAD(&g->label_list);
+       graph_set_size(g, xdim, ydim);
+       g->per_label_limit = -1;
+       g->font = font;
+       if (!g->font)
+               g->font = GRAPH_DEFAULT_FONT;
+       return g;
+}
+
+void graph_set_font(struct graph *g, const char *font)
+{
+       g->font = font;
+}
+
+void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f)
+{
+       g->x_axis_unit_change_callback = f;
+}
+
+void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f)
+{
+       g->y_axis_unit_change_callback = f;
+}
+
+static int count_labels(struct graph *g)
+{
+       struct flist_head *entry;
+       int count = 0;
+
+       flist_for_each(entry, &g->label_list)
+               count++;
+
+       return count;
+}
+
+static int count_values(struct graph_label *l)
+{
+       struct flist_head *entry;
+       int count = 0;
+
+       flist_for_each(entry, &l->value_list)
+               count++;
+
+       return count;
+}
+
+typedef double (*double_comparator)(double a, double b);
+
+static double mindouble(double a, double b)
+{
+       return a < b ? a : b;
+}
+
+static double maxdouble(double a, double b)
+{
+       return a < b ? b : a;
+}
+
+static double find_double_values(struct graph_label *l, double_comparator cmp)
+{
+       struct flist_head *entry;
+       double answer, tmp;
+       int first = 1;
+
+       if (flist_empty(&l->value_list))
+               return 0.0;
+
+       flist_for_each(entry, &l->value_list) {
+               struct graph_value *i;
+
+               i = flist_entry(entry, struct graph_value, list);
+               tmp = *(double *) i->value;
+               if (first) {
+                       answer = tmp;
+                       first = 0;
+               } else {
+                       answer = cmp(answer, tmp);
+               }
+       }
+       return answer;
+}
+
+static double find_double_data(struct graph *g, double_comparator cmp)
+{
+       struct flist_head *entry;
+       struct graph_label *i;
+       int first = 1;
+       double answer, tmp;
+
+       if (flist_empty(&g->label_list))
+               return 0.0;
+
+       flist_for_each(entry, &g->label_list) {
+               i = flist_entry(entry, struct graph_label, list);
+               tmp = find_double_values(i, cmp);
+               if (first) {
+                       answer = tmp;
+                       first = 0;
+               } else {
+                       answer = cmp(tmp, answer);
+               }
+       }
+       return answer;
+}
+
+static double find_min_data(struct graph *g)
+{
+       return find_double_data(g, mindouble);
+}
+
+static double find_max_data(struct graph *g)
+{
+       return find_double_data(g, maxdouble);
+}
+
+static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb,
+                       double label_offset, double bar_width,
+                       double mindata, double maxdata)
+{
+       struct flist_head *entry;
+       double x1, y1, x2, y2;
+       int bar_num = 0;
+       double domain, range, v;
+
+       domain = (maxdata - mindata);
+       range = (double) bg->ydim * 0.80; /* FIXME */
+       cairo_stroke(cr);
+       flist_for_each(entry, &lb->value_list) {
+               struct graph_value *i;
+
+               i = flist_entry(entry, struct graph_value, list);
+
+               x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05);
+               x2 = x1 + bar_width * 0.90;
+               y2 = bg->ydim * 0.90;
+               v = *(double *) i->value;
+               y1 = y2 - (((v - mindata) / domain) * range);
+               cairo_move_to(cr, x1, y1);
+               cairo_line_to(cr, x1, y2);
+               cairo_line_to(cr, x2, y2);
+               cairo_line_to(cr, x2, y1);
+               cairo_close_path(cr);
+               cairo_fill(cr);
+               cairo_stroke(cr);
+               bar_num++;
+       }
+}
+
+static void graph_draw_common(struct graph *g, cairo_t *cr, double *x1,
+                             double *y1, double *x2, double *y2)
+{
+       const double shade_col[3][3] = { { 0.55, 0.54, 0.54 },
+                                        { 0.80, 0.78, 0.78 },
+                                        { 0.93, 0.91, 0.91 } };
+       int i;
+
+       *x1 = 0.10 * g->xdim;
+       *x2 = 0.95 * g->xdim;
+       *y1 = 0.10 * g->ydim;
+       *y2 = 0.90 * g->ydim;
+
+       /*
+        * Add shade
+        */
+       cairo_set_line_width(cr, 1.0);
+       for (i = 0; i < 3; i++) {
+               float offset = i + 1.0;
+
+               cairo_set_source_rgb(cr, shade_col[i][0], shade_col[i][1], shade_col[i][2]);
+               cairo_move_to(cr, offset + *x1, *y1 - offset);
+               cairo_line_to(cr, *x2 + offset, *y1 - offset);
+               cairo_line_to(cr, *x2 + offset, *y2 - offset);
+               cairo_stroke(cr);
+       }
+
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       cairo_set_line_width(cr, 1.2);
+
+       cairo_move_to(cr, *x1, *y1);
+       cairo_line_to(cr, *x1, *y2);
+       cairo_line_to(cr, *x2, *y2);
+       cairo_line_to(cr, *x2, *y1);
+       cairo_line_to(cr, *x1, *y1);
+       cairo_stroke(cr);
+
+       draw_centered_text(cr, g->font, g->xdim / 2, g->ydim / 20, 20.0, g->title);
+       draw_centered_text(cr, g->font, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle);
+       draw_vertical_centered_text(cr, g->font, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle);
+       cairo_stroke(cr);
+}
+
+static void graph_draw_x_ticks(struct graph *g, cairo_t *cr,
+       double x1, double y1, double x2, double y2,
+       double minx, double maxx, int nticks, int add_tm_text)
+{
+       struct tickmark *tm;
+       double tx;
+       int i, power_of_ten;
+       static double dash[] = { 1.0, 2.0 };
+
+       nticks = calc_tickmarks(minx, maxx, nticks, &tm, &power_of_ten,
+               g->x_axis_unit_change_callback == NULL, g->base_offset);
+       if (g->x_axis_unit_change_callback)
+               g->x_axis_unit_change_callback(g, power_of_ten);
+
+       for (i = 0; i < nticks; i++) {
+               tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1;
+
+               /*
+                * Update tick delta
+                */
+               if (!i) {
+                       g->xtick_zero = tx;
+                       g->xtick_zero_val = tm[0].value;
+               } else if (i == 1) {
+                       g->xtick_delta = (tm[1].value - tm[0].value) / (tx - g->xtick_zero);
+                       g->xtick_one_val = tm[1].value;
+               }
+
+               /* really tx < yx || tx > x2, but protect against rounding */
+               if (x1 - tx > 0.01 || tx - x2 > 0.01)
+                       continue;
+
+               /* Draw tick mark */
+               cairo_set_line_width(cr, 1.0);
+               cairo_move_to(cr, tx, y2);
+               cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03);
+               cairo_stroke(cr);
+
+               /* draw grid lines */
+               cairo_save(cr);
+               cairo_set_dash(cr, dash, 2, 0.66);
+               cairo_set_line_width(cr, 0.33);
+               cairo_move_to(cr, tx, y1);
+               cairo_line_to(cr, tx, y2);
+               cairo_stroke(cr);
+               cairo_restore(cr);
+
+               if (!add_tm_text)
+                       continue;
+
+               /* draw tickmark label */
+               draw_centered_text(cr, g->font, tx, y2 * 1.04, 12.0, tm[i].string);
+               cairo_stroke(cr);
+       }
+}
+
+static double graph_draw_y_ticks(struct graph *g, cairo_t *cr,
+       double x1, double y1, double x2, double y2,
+       double miny, double maxy, int nticks, int add_tm_text)
+{
+       struct tickmark *tm;
+       double ty;
+       int i, power_of_ten;
+       static double dash[] = { 1.0, 2.0 };
+
+       nticks = calc_tickmarks(miny, maxy, nticks, &tm, &power_of_ten,
+               g->y_axis_unit_change_callback == NULL, g->base_offset);
+       if (g->y_axis_unit_change_callback)
+               g->y_axis_unit_change_callback(g, power_of_ten);
+
+       /*
+        * Use highest tickmark as top of graph, not highest value. Otherwise
+        * it's impossible to see what the max value is, if the graph is
+        * fairly flat.
+        */
+       maxy = tm[nticks - 1].value;
+
+       for (i = 0; i < nticks; i++) {
+               ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1);
+
+               /*
+                * Update tick delta
+                */
+               if (!i) {
+                       g->ytick_zero = ty;
+                       g->ytick_zero_val = tm[0].value;
+               } else if (i == 1) {
+                       g->ytick_delta = (tm[1].value - tm[0].value) / (ty - g->ytick_zero);
+                       g->ytick_one_val = tm[1].value;
+               }
+
+               /* really ty < y1 || ty > y2, but protect against rounding */
+               if (y1 - ty > 0.01 || ty - y2 > 0.01)
+                       continue;
+
+               /* draw tick mark */
+               cairo_move_to(cr, x1, ty);
+               cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty);
+               cairo_stroke(cr);
+
+               /* draw grid lines */
+               cairo_save(cr);
+               cairo_set_dash(cr, dash, 2, 0.66);
+               cairo_set_line_width(cr, 0.33);
+               cairo_move_to(cr, x1, ty);
+               cairo_line_to(cr, x2, ty);
+               cairo_stroke(cr);
+               cairo_restore(cr);
+
+               if (!add_tm_text)
+                       continue;
+
+               /* draw tickmark label */
+               draw_right_justified_text(cr, g->font, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string);
+               cairo_stroke(cr);
+       }
+
+       /*
+        * Return new max to use
+        */
+       return maxy;
+}
+
+void bar_graph_draw(struct graph *bg, cairo_t *cr)
+{
+       double x1, y1, x2, y2;
+       double space_per_label, bar_width;
+       double label_offset, mindata, maxdata;
+       int i, nlabels;
+       struct graph_label *lb;
+       struct flist_head *entry;
+
+       cairo_save(cr);
+       cairo_translate(cr, bg->xoffset, bg->yoffset);
+       graph_draw_common(bg, cr, &x1, &y1, &x2, &y2);
+
+       nlabels = count_labels(bg);
+       space_per_label = (x2 - x1) / (double) nlabels;
+
+       /*
+        * Start bars at 0 unless we have negative values, otherwise we
+        * present a skewed picture comparing label X and X+1.
+        */
+       mindata = find_min_data(bg);
+       if (mindata > 0)
+               mindata = 0;
+
+       maxdata = find_max_data(bg);
+
+       if (fabs(maxdata - mindata) < 1e-20) {
+               draw_centered_text(cr, bg->font,
+                       x1 + (x2 - x1) / 2.0,
+                       y1 + (y2 - y1) / 2.0, 20.0, "No good data");
+               return;
+       }
+
+       maxdata = graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10, 1);
+       i = 0;
+       flist_for_each(entry, &bg->label_list) {
+               int nvalues;
+
+               lb = flist_entry(entry, struct graph_label, list);
+               nvalues = count_values(lb);
+               bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues;
+               label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1;
+               draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata);
+               // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93,
+               draw_centered_text(cr, bg->font, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93,
+                       12.0, lb->label);
+               i++;
+       }
+       cairo_stroke(cr);
+       cairo_restore(cr);
+}
+
+typedef double (*xy_value_extractor)(struct graph_value *v);
+
+static double getx(struct graph_value *v)
+{
+       struct xyvalue *xy = v->value;
+       return xy->x;
+}
+
+static double gety(struct graph_value *v)
+{
+       struct xyvalue *xy = v->value;
+       return xy->y;
+}
+
+static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp)
+{
+       double tmp, answer = 0.0;
+       struct graph_label *i;
+       struct graph_value *j;
+       struct flist_head *jentry, *entry;
+       int first = 1;
+
+       flist_for_each(entry, &g->label_list) {
+               i = flist_entry(entry, struct graph_label, list);
+
+               flist_for_each(jentry, &i->value_list) {
+                       j = flist_entry(jentry, struct graph_value, list);
+                       tmp = getvalue(j);
+                       if (first) {
+                               first = 0;
+                               answer = tmp;
+                       }
+                       answer = cmp(tmp, answer);
+               }
+       }
+
+       return answer;
+}
+
+void line_graph_draw(struct graph *g, cairo_t *cr)
+{
+       double x1, y1, x2, y2;
+       double minx, miny, maxx, maxy, gminx, gminy, gmaxx, gmaxy;
+       double tx, ty, top_extra, bottom_extra, left_extra, right_extra;
+       struct graph_label *i;
+       struct graph_value *j;
+       int good_data = 1, first = 1;
+       struct flist_head *entry, *lentry;
+
+       cairo_save(cr);
+       cairo_translate(cr, g->xoffset, g->yoffset);
+       graph_draw_common(g, cr, &x1, &y1, &x2, &y2);
+
+       minx = find_xy_value(g, getx, mindouble);
+       maxx = find_xy_value(g, getx, maxdouble);
+       miny = find_xy_value(g, gety, mindouble);
+
+       /*
+        * Start graphs at zero, unless we have a value below. Otherwise
+        * it's hard to visually compare the read and write graph, since
+        * the lowest valued one will be the floor of the graph view.
+        */
+       if (miny > 0)
+               miny = 0;
+
+       maxy = find_xy_value(g, gety, maxdouble);
+
+       if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) {
+               good_data = 0;
+               minx = 0.0;
+               miny = 0.0;
+               maxx = 10.0;
+               maxy = 100.0;
+       }
+
+       top_extra = 0.0;
+       bottom_extra = 0.0;
+       left_extra = 0.0;
+       right_extra = 0.0;
+
+       if (g->top_extra > 0.001)
+               top_extra = fabs(maxy - miny) * g->top_extra;
+       if (g->bottom_extra > 0.001)
+               bottom_extra = fabs(maxy - miny) * g->bottom_extra;
+       if (g->left_extra > 0.001)
+               left_extra = fabs(maxx - minx) * g->left_extra;
+       if (g->right_extra > 0.001)
+               right_extra = fabs(maxx - minx) * g->right_extra;
+
+       gminx = minx - left_extra;
+       gmaxx = maxx + right_extra;
+       gminy = miny - bottom_extra;
+       gmaxy = maxy + top_extra;
+
+       graph_draw_x_ticks(g, cr, x1, y1, x2, y2, gminx, gmaxx, 10, good_data);
+       gmaxy = graph_draw_y_ticks(g, cr, x1, y1, x2, y2, gminy, gmaxy, 10, good_data);
+
+       if (!good_data)
+               goto skip_data;
+
+       cairo_set_line_width(cr, 1.5);
+       cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
+
+       flist_for_each(lentry, &g->label_list) {
+               i = flist_entry(lentry, struct graph_label, list);
+               first = 1;
+               if (i->hide || i->r < 0) /* invisible data */
+                       continue;
+
+               cairo_set_source_rgb(cr, i->r, i->g, i->b);
+               flist_for_each(entry, &i->value_list) {
+                       j = flist_entry(entry, struct graph_value, list);
+                       tx = ((getx(j) - gminx) / (gmaxx - gminx)) * (x2 - x1) + x1;
+                       ty = y2 - ((gety(j) - gminy) / (gmaxy - gminy)) * (y2 - y1);
+                       if (first) {
+                               cairo_move_to(cr, tx, ty);
+                               first = 0;
+                       } else
+                               cairo_line_to(cr, tx, ty);
+               }
+               cairo_stroke(cr);
+       }
+
+skip_data:
+       cairo_restore(cr);
+}
+
+static void setstring(char **str, const char *value)
+{
+       free(*str);
+       *str = strdup(value);
+}
+
+void graph_title(struct graph *bg, const char *title)
+{
+       setstring(&bg->title, title);
+}
+
+void graph_x_title(struct graph *bg, const char *title)
+{
+       setstring(&bg->xtitle, title);
+}
+
+void graph_y_title(struct graph *bg, const char *title)
+{
+       setstring(&bg->ytitle, title);
+}
+
+static struct graph_label *graph_find_label(struct graph *bg,
+                               const char *label)
+{
+       struct flist_head *entry;
+       struct graph_label *i;
+
+       flist_for_each(entry, &bg->label_list) {
+               i = flist_entry(entry, struct graph_label, list);
+
+               if (strcmp(label, i->label) == 0)
+                       return i;
+       }
+
+       return NULL;
+}
+
+graph_label_t graph_add_label(struct graph *bg, const char *label)
+{
+       struct graph_label *i;
+
+       i = graph_find_label(bg, label);
+       if (i)
+               return i; /* already present. */
+       i = calloc(1, sizeof(*i));
+       INIT_FLIST_HEAD(&i->value_list);
+       i->parent = bg;
+       setstring(&i->label, label);
+       flist_add_tail(&i->list, &bg->label_list);
+       INIT_PRIO_TREE_ROOT(&i->prio_tree);
+       return i;
+}
+
+static void __graph_value_drop(struct graph_label *l, struct graph_value *v)
+{
+       flist_del_init(&v->list);
+       if (v->tooltip)
+               free(v->tooltip);
+       free(v->value);
+       free(v);
+       l->value_count--;
+}
+
+static void graph_value_drop(struct graph_label *l, struct graph_value *v)
+{
+       if (v->flags & GV_F_PRIO_SKIP) {
+               __graph_value_drop(l, v);
+               return;
+       }
+
+       /*
+        * Find head, the guy that's on the prio tree
+        */
+       while (!(v->flags & GV_F_ON_PRIO)) {
+               assert(!flist_empty(&v->alias));
+               v = flist_entry(v->alias.next, struct graph_value, alias);
+       }
+
+       prio_tree_remove(&l->prio_tree, &v->node);
+
+       /*
+        * Free aliases
+        */
+       while (!flist_empty(&v->alias)) {
+               struct graph_value *a;
+
+               a = flist_entry(v->alias.next, struct graph_value, alias);
+               flist_del_init(&a->alias);
+
+               __graph_value_drop(l, a);
+       }
+
+       __graph_value_drop(l, v);
+}
+
+static void graph_label_add_value(struct graph_label *i, void *value,
+                                 const char *tooltip)
+{
+       struct graph *g = i->parent;
+       struct graph_value *x;
+
+       x = malloc(sizeof(*x));
+       memset(x, 0, sizeof(*x));
+       INIT_FLIST_HEAD(&x->alias);
+       INIT_FLIST_HEAD(&x->list);
+       flist_add_tail(&x->list, &i->value_list);
+       i->value_count++;
+       x->value = value;
+
+       if (tooltip) {
+               double xval = getx(x);
+               double minx = xval - (g->xtick_one_val * TOOLTIP_DELTA);
+               double maxx = xval + (g->xtick_one_val * TOOLTIP_DELTA);
+               struct prio_tree_node *ret;
+
+               /*
+                * use msec to avoid dropping too much precision when
+                * storing as an integer.
+                */
+               minx = minx * 1000.0;
+               maxx = maxx * 1000.0;
+
+               INIT_PRIO_TREE_NODE(&x->node);
+               x->node.start = minx;
+               x->node.last = maxx;
+               x->tooltip = strdup(tooltip);
+               if (x->node.last == x->node.start) {
+                       x->node.last += fabs(g->xtick_delta);
+                       if (x->node.last == x->node.start)
+                               x->node.last++;
+               }
+
+               /*
+                * If ret != &x->node, we have an alias. Since the values
+                * should be identical, we can drop it
+                */
+               ret = prio_tree_insert(&i->prio_tree, &x->node);
+               if (ret != &x->node) {
+                       struct graph_value *alias;
+
+                       alias = container_of(ret, struct graph_value, node);
+                       flist_add_tail(&x->alias, &alias->alias);
+               } else
+                       x->flags = GV_F_ON_PRIO;
+       } else
+               x->flags = GV_F_PRIO_SKIP;
+
+       if (g->per_label_limit != -1 &&
+               i->value_count > g->per_label_limit) {
+               int to_drop = 1;
+
+               /*
+                * If the limit was dynamically reduced, making us more
+                * than 1 entry ahead after adding this one, drop two
+                * entries. This will make us (eventually) reach the
+                * specified limit.
+                */
+               if (i->value_count - g->per_label_limit >= 2)
+                       to_drop = 2;
+
+               while (to_drop-- && !flist_empty(&i->value_list)) {
+                       x = flist_entry(i->value_list.next, struct graph_value, list);
+                       graph_value_drop(i, x);
+
+                       /*
+                        * If we have aliases, we could drop > 1 above.
+                        */
+                       if (i->value_count <= g->per_label_limit)
+                               break;
+               }
+       }
+}
+
+int graph_add_data(struct graph *bg, graph_label_t label, const double value)
+{
+       struct graph_label *i = label;
+       double *d;
+
+       d = malloc(sizeof(*d));
+       *d = value;
+
+       graph_label_add_value(i, d, NULL);
+       return 0;
+}
+
+static int graph_nonzero_y(struct graph_label *l)
+{
+       struct flist_head *entry;
+
+       flist_for_each(entry, &l->value_list) {
+               struct graph_value *v;
+
+               v = flist_entry(entry, struct graph_value, list);
+               if (gety(v) != 0.0)
+                       return 1;
+       }
+
+       return 0;
+}
+
+int graph_add_xy_data(struct graph *bg, graph_label_t label,
+                     const double x, const double y, const char *tooltip)
+{
+       struct graph_label *i = label;
+       struct xyvalue *xy;
+
+       if (bg->dont_graph_all_zeroes && y == 0.0 && !graph_nonzero_y(i))
+               i->hide = 1;
+       else
+               i->hide = 0;
+
+       xy = malloc(sizeof(*xy));
+       xy->x = x;
+       xy->y = y;
+
+       graph_label_add_value(i, xy, tooltip);
+       return 0;
+}
+
+static void graph_free_values(struct graph_label *l)
+{
+       struct graph_value *i;
+
+       while (!flist_empty(&l->value_list)) {
+               i = flist_entry(l->value_list.next, struct graph_value, list);
+               graph_value_drop(l, i);
+       }
+}
+
+static void graph_free_labels(struct graph *g)
+{
+       struct graph_label *i;
+
+       while (!flist_empty(&g->label_list)) {
+               i = flist_entry(g->label_list.next, struct graph_label, list);
+               flist_del(&i->list);
+               graph_free_values(i);
+               free(i);
+       }
+}
+
+void graph_clear_values(struct graph *g)
+{
+       struct flist_head *node;
+       struct graph_label *i;
+
+       flist_for_each(node, &g->label_list) {
+               i = flist_entry(node, struct graph_label, list);
+               graph_free_values(i);
+       }
+}
+
+void graph_set_color(struct graph *gr, graph_label_t label, double red,
+                    double green, double blue)
+{
+       struct graph_label *i = label;
+       double r, g, b;
+
+       if (red < 0.0) { /* invisible color */
+               r = -1.0;
+               g = -1.0;
+               b = -1.0;
+       } else {
+               r = fabs(red);
+               g = fabs(green);
+               b = fabs(blue);
+
+               if (r > 1.0)
+                       r = 1.0;
+               if (g > 1.0)
+                       g = 1.0;
+               if (b > 1.0)
+                       b = 1.0;
+       }
+
+       i->r = r;
+       i->g = g;
+       i->b = b;
+}
+
+void graph_free(struct graph *bg)
+{
+       free(bg->title);
+       free(bg->xtitle);
+       free(bg->ytitle);
+       graph_free_labels(bg);
+}
+
+/* For each line in the line graph, up to per_label_limit segments may
+ * be added.  After that, adding more data to the end of the line
+ * causes data to drop off of the front of the line.
+ */
+void line_graph_set_data_count_limit(struct graph *g, int per_label_limit)
+{
+       g->per_label_limit = per_label_limit;
+}
+
+void graph_add_extra_space(struct graph *g, double left_percent,
+                          double right_percent, double top_percent,
+                          double bottom_percent)
+{
+       g->left_extra = left_percent;
+       g->right_extra = right_percent;
+       g->top_extra = top_percent;
+       g->bottom_extra = bottom_percent;
+}
+
+/*
+ * Normally values are logged in a base unit of 0, but for other purposes
+ * it makes more sense to log in higher unit. For instance for bandwidth
+ * purposes, you may want to log in KB/sec (or MB/sec) rather than bytes/sec.
+ */
+void graph_set_base_offset(struct graph *g, unsigned int base_offset)
+{
+       g->base_offset = base_offset;
+}
+
+int graph_has_tooltips(struct graph *g)
+{
+       struct flist_head *entry;
+       struct graph_label *i;
+
+       flist_for_each(entry, &g->label_list) {
+               i = flist_entry(entry, struct graph_label, list);
+
+               if (!prio_tree_empty(&i->prio_tree))
+                       return 1;
+       }
+
+       return 0;
+}
+
+int graph_contains_xy(struct graph *g, int x, int y)
+{
+       int first_x = g->xoffset;
+       int last_x = g->xoffset + g->xdim;
+       int first_y = g->yoffset;
+       int last_y = g->yoffset + g->ydim;
+
+       return (x >= first_x && x <= last_x) && (y >= first_y && y <= last_y);
+}
+
+const char *graph_find_tooltip(struct graph *g, int ix, int iy)
+{
+       double x = ix, y = iy;
+       struct prio_tree_iter iter;
+       struct prio_tree_node *n;
+       struct graph_value *best = NULL;
+       struct flist_head *entry;
+       double best_delta;
+       double maxy, miny;
+
+       x -= g->xoffset;
+       y -= g->yoffset;
+
+       x = g->xtick_zero_val + ((x - g->xtick_zero) * g->xtick_delta);
+       y = g->ytick_zero_val + ((y - g->ytick_zero) * g->ytick_delta);
+
+       x = x * 1000.0;
+       maxy = y + (g->ytick_one_val * TOOLTIP_DELTA);
+       miny = y - (g->ytick_one_val * TOOLTIP_DELTA);
+       best_delta = UINT_MAX;
+       flist_for_each(entry, &g->label_list) {
+               struct graph_label *i;
+
+               i = flist_entry(entry, struct graph_label, list);
+               if (i->hide)
+                       continue;
+
+               INIT_PRIO_TREE_ITER(&iter);
+               prio_tree_iter_init(&iter, &i->prio_tree, x, x);
+
+               n = prio_tree_next(&iter);
+               if (!n)
+                       continue;
+
+               do {
+                       struct graph_value *v, *rootv;
+                       double yval, ydiff;
+
+                       v = container_of(n, struct graph_value, node);
+                       rootv = v;
+                       do {
+                               yval = gety(v);
+                               ydiff = fabs(yval - y);
+
+                               /*
+                                * zero delta, or within or match critera, break
+                                */
+                               if (ydiff < best_delta) {
+                                       best_delta = ydiff;
+                                       if (!best_delta ||
+                                           (yval >= miny && yval <= maxy)) {
+                                               best = v;
+                                               break;
+                                       }
+                               }
+                               if (!flist_empty(&v->alias))
+                                       v = flist_entry(v->alias.next, struct graph_value, alias);
+                       } while (v != rootv);
+               } while ((n = prio_tree_next(&iter)) != NULL);
+
+               /*
+                * If we got matches in one label, don't check others.
+                */
+               if (best)
+                       break;
+       }
+
+       if (best)
+               return best->tooltip;
+
+       return NULL;
+}
+
+void graph_set_graph_all_zeroes(struct graph *g, unsigned int set)
+{
+       g->dont_graph_all_zeroes = !set;
+}
diff --git a/graph.h b/graph.h
new file mode 100644 (file)
index 0000000..078e50c
--- /dev/null
+++ b/graph.h
@@ -0,0 +1,99 @@
+#ifndef GRAPH_H
+#define GRAPH_H
+
+struct graph;
+struct graph_label;
+
+typedef struct graph_label * graph_label_t;
+
+#define GRAPH_DEFAULT_FONT     "Sans 12"
+
+struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font);
+/* graph_new() Returns a new graph structure of the given dimensions and font */
+void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim);
+/* graph_set_size() Changes the size of a graph to the given dimensions. */ 
+void graph_set_position(struct graph *g, double xoffset, double yoffset);
+/* graph_set_position() sets the x- and y-offset to translate the graph */
+void bar_graph_draw(struct graph *g, cairo_t *cr);
+/* bar_graph_draw() draws the given graph as a bar graph */
+void line_graph_draw(struct graph *g, cairo_t *cr);
+/* line_graph_draw draws the given graph as a line graph */
+void line_graph_set_data_count_limit(struct graph *g, int per_label_limit);
+/* line_graph_set_data_count_limit() limits the amount of data which can
+ * be added to a line graph.  Once the limit is reached, the oldest data 
+ * is discarded as new data is added
+ */
+void graph_set_font(struct graph *g, const char *font);
+void graph_title(struct graph *g, const char *title);
+/* graph_title() sets the main title of the graph to the given string */
+void graph_x_title(struct graph *g, const char *title);
+/* graph_x_title() sets the title of the x axis to the given string */
+void graph_y_title(struct graph *g, const char *title);
+/* graph_y_title() sets the title of the y axis to the given string */
+graph_label_t graph_add_label(struct graph *g, const char *label);
+/* graph_add_label() adds a new "stream" of data to be graphed.
+ * For line charts, each label is a separate line on the graph.
+ * For bar charts, each label is a grouping of columns on the x-axis
+ * For example:
+ *
+ *  |  *                          | **
+ *  |   *      xxxxxxxx           | **
+ *  |    ***  x                   | **              **
+ *  |       *x       ****         | **      **      **
+ *  |    xxxx*  *****             | ** xx   ** xx   **
+ *  |   x     **                  | ** xx   ** xx   ** xx
+ *  |  x                          | ** xx   ** xx   ** xx
+ *  -----------------------       -------------------------
+ *                                    A       B       C
+ *
+ * For a line graph, the 'x's     For a bar graph, 
+ * would be on one "label", and   'A', 'B', and 'C'
+ * the '*'s would be on another   are the labels.
+ * label.
+ */
+
+int graph_add_data(struct graph *g, graph_label_t label, const double value);
+/* graph_add_data() is used to add data to the labels of a bar graph */
+int graph_add_xy_data(struct graph *g, graph_label_t label,
+               const double x, const double y, const char *tooltip);
+/* graph_add_xy_data is used to add data to the labels of a line graph */
+
+void graph_set_color(struct graph *g, graph_label_t label,
+               double red, double green, double blue);
+#define INVISIBLE_COLOR (-1.0)
+/* graph_set_color is used to set the color used to plot the data in
+ * a line graph.  INVISIBLE_COLOR can be used to plot the data invisibly.
+ * Invisible data will have the same effect on the scaling of the axes
+ * as visible data.
+ */
+
+void graph_free(struct graph *bg);
+/* free a graph allocated by graph_new() */
+
+typedef void (*graph_axis_unit_change_callback)(struct graph *g, int power_of_ten);
+void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f);
+void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f);
+/* The labels used on the x and y axes may be shortened.  You can register for callbacks
+ * so that you can know how the labels are shorted, typically used to adjust the axis
+ * titles to display the proper units.  The power_of_ten parameter indicates what power
+ * of ten the labels have been divided by (9, 6, 3, or 0, corresponding to billions,
+ * millions, thousands and ones. 
+ */ 
+
+void graph_add_extra_space(struct graph *g, double left_percent, double right_percent,
+                               double top_percent, double bottom_percent);
+/* graph_add_extra_space() adds extra space to edges of the the graph
+ * so that the data doesn't go to the very edges.
+ */
+
+extern int graph_has_tooltips(struct graph *g);
+extern const char *graph_find_tooltip(struct graph *g, int x, int y);
+extern int graph_contains_xy(struct graph *p, int x, int y);
+
+extern void graph_set_base_offset(struct graph *g, unsigned int base_offset);
+extern void graph_set_graph_all_zeroes(struct graph *g, unsigned int set);
+
+extern void graph_clear_values(struct graph *g);
+
+#endif
+
diff --git a/init.c b/init.c
index 23be863141a3ff670d84780eccb52b924ab0cb66..488101bfa3b9098491fbac2dccb045951873615b 100644 (file)
--- a/init.c
+++ b/init.c
@@ -38,7 +38,6 @@ struct thread_data *threads = NULL;
 int exitall_on_terminate = 0;
 int output_format = FIO_OUTPUT_NORMAL;
 int eta_print;
-unsigned long long mlock_size = 0;
 FILE *f_out = NULL;
 FILE *f_err = NULL;
 char **job_sections = NULL;
@@ -210,7 +209,7 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
        },
 };
 
-static void free_shm(void)
+void free_threads_shm(void)
 {
        struct shmid_ds sbuf;
 
@@ -218,11 +217,19 @@ static void free_shm(void)
                void *tp = threads;
 
                threads = NULL;
+               shmdt(tp);
+               shmctl(shm_id, IPC_RMID, &sbuf);
+               shm_id = -1;
+       }
+}
+
+void free_shm(void)
+{
+       if (threads) {
                file_hash_exit();
                flow_exit();
                fio_debug_jobp = NULL;
-               shmdt(tp);
-               shmctl(shm_id, IPC_RMID, &sbuf);
+               free_threads_shm();
        }
 
        scleanup();
@@ -598,7 +605,7 @@ static int fixup_options(struct thread_data *td)
 /*
  * This function leaks the buffer
  */
-static char *to_kmg(unsigned int val)
+char *fio_uint_to_kmg(unsigned int val)
 {
        char *buf = malloc(32);
        char post[] = { 0, 'K', 'M', 'G', 'P', 'E', 0 };
@@ -759,11 +766,9 @@ int ioengine_load(struct thread_data *td)
  * to make sure we don't have conflicts, and initializes various
  * members of td.
  */
-static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
+static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
+                  int recursed, int client_type)
 {
-       const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
-                                  "randread", "randwrite", "randrw",
-                                  "trim", NULL, NULL, NULL, "randtrim" };
        unsigned int i;
        char fname[PATH_MAX];
        int numjobs, file_alloced;
@@ -782,17 +787,14 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
                return 0;
        }
 
+       td->client_type = client_type;
+
        if (profile_td_init(td))
                goto err;
 
        if (ioengine_load(td))
                goto err;
 
-       if (td->o.use_thread)
-               nr_thread++;
-       else
-               nr_process++;
-
        if (td->o.odirect)
                td->io_ops->flags |= FIO_RAWIO;
 
@@ -862,40 +864,38 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
        if (setup_rate(td))
                goto err;
 
-       if (td->o.write_lat_log) {
-               setup_log(&td->lat_log, td->o.log_avg_msec);
-               setup_log(&td->slat_log, td->o.log_avg_msec);
-               setup_log(&td->clat_log, td->o.log_avg_msec);
+       if (td->o.lat_log_file) {
+               setup_log(&td->lat_log, td->o.log_avg_msec, IO_LOG_TYPE_LAT);
+               setup_log(&td->slat_log, td->o.log_avg_msec, IO_LOG_TYPE_SLAT);
+               setup_log(&td->clat_log, td->o.log_avg_msec, IO_LOG_TYPE_CLAT);
        }
-       if (td->o.write_bw_log)
-               setup_log(&td->bw_log, td->o.log_avg_msec);
-       if (td->o.write_iops_log)
-               setup_log(&td->iops_log, td->o.log_avg_msec);
+       if (td->o.bw_log_file)
+               setup_log(&td->bw_log, td->o.log_avg_msec, IO_LOG_TYPE_BW);
+       if (td->o.iops_log_file)
+               setup_log(&td->iops_log, td->o.log_avg_msec, IO_LOG_TYPE_IOPS);
 
        if (!td->o.name)
                td->o.name = strdup(jobname);
 
        if (output_format == FIO_OUTPUT_NORMAL) {
                if (!job_add_num) {
-                       if (!strcmp(td->io_ops->name, "cpuio")) {
-                               log_info("%s: ioengine=cpu, cpuload=%u,"
-                                        " cpucycle=%u\n", td->o.name,
-                                                       td->o.cpuload,
-                                                       td->o.cpucycle);
-                       } else {
+                       if (is_backend && !recursed)
+                               fio_server_send_add_job(td);
+
+                       if (!(td->io_ops->flags & FIO_NOIO)) {
                                char *c1, *c2, *c3, *c4, *c5, *c6;
 
-                               c1 = to_kmg(td->o.min_bs[DDIR_READ]);
-                               c2 = to_kmg(td->o.max_bs[DDIR_READ]);
-                               c3 = to_kmg(td->o.min_bs[DDIR_WRITE]);
-                               c4 = to_kmg(td->o.max_bs[DDIR_WRITE]);
-                               c5 = to_kmg(td->o.min_bs[DDIR_TRIM]);
-                               c6 = to_kmg(td->o.max_bs[DDIR_TRIM]);
+                               c1 = fio_uint_to_kmg(td->o.min_bs[DDIR_READ]);
+                               c2 = fio_uint_to_kmg(td->o.max_bs[DDIR_READ]);
+                               c3 = fio_uint_to_kmg(td->o.min_bs[DDIR_WRITE]);
+                               c4 = fio_uint_to_kmg(td->o.max_bs[DDIR_WRITE]);
+                               c5 = fio_uint_to_kmg(td->o.min_bs[DDIR_TRIM]);
+                               c6 = fio_uint_to_kmg(td->o.max_bs[DDIR_TRIM]);
 
                                log_info("%s: (g=%d): rw=%s, bs=%s-%s/%s-%s/%s-%s,"
                                         " ioengine=%s, iodepth=%u\n",
                                                td->o.name, td->groupid,
-                                               ddir_str[td->o.td_ddir],
+                                               ddir_str(td->o.td_ddir),
                                                c1, c2, c3, c4, c5, c6,
                                                td->io_ops->name,
                                                td->o.iodepth);
@@ -935,7 +935,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
 
                job_add_num = numjobs - 1;
 
-               if (add_job(td_new, jobname, job_add_num))
+               if (add_job(td_new, jobname, job_add_num, 1, client_type))
                        goto err;
        }
 
@@ -948,7 +948,7 @@ err:
 /*
  * Parse as if 'o' was a command line
  */
-void add_job_opts(const char **o)
+void add_job_opts(const char **o, int client_type)
 {
        struct thread_data *td, *td_parent;
        int i, in_global = 1;
@@ -960,7 +960,7 @@ void add_job_opts(const char **o)
                if (!strncmp(o[i], "name", 4)) {
                        in_global = 0;
                        if (td)
-                               add_job(td, jobname, 0);
+                               add_job(td, jobname, 0, 0, client_type);
                        td = NULL;
                        sprintf(jobname, "%s", o[i] + 5);
                }
@@ -979,7 +979,7 @@ void add_job_opts(const char **o)
        }
 
        if (td)
-               add_job(td, jobname, 0);
+               add_job(td, jobname, 0, 0, client_type);
 }
 
 static int skip_this_section(const char *name)
@@ -1017,7 +1017,7 @@ static int is_empty_or_comment(char *line)
 /*
  * This is our [ini] type file parser.
  */
-int parse_jobs_ini(char *file, int is_buf, int stonewall_flag)
+int parse_jobs_ini(char *file, int is_buf, int stonewall_flag, int type)
 {
        unsigned int global;
        struct thread_data *td;
@@ -1161,7 +1161,7 @@ int parse_jobs_ini(char *file, int is_buf, int stonewall_flag)
                                for (i = 0; i < num_opts; i++)
                                        log_info("--%s ", opts[i]);
 
-                       ret = add_job(td, name, 0);
+                       ret = add_job(td, name, 0, 0, type);
                } else {
                        log_err("fio: job %s dropped\n", name);
                        put_job(td);
@@ -1247,20 +1247,62 @@ static void usage(const char *name)
 
 #ifdef FIO_INC_DEBUG
 struct debug_level debug_levels[] = {
-       { .name = "process",    .shift = FD_PROCESS, },
-       { .name = "file",       .shift = FD_FILE, },
-       { .name = "io",         .shift = FD_IO, },
-       { .name = "mem",        .shift = FD_MEM, },
-       { .name = "blktrace",   .shift = FD_BLKTRACE },
-       { .name = "verify",     .shift = FD_VERIFY },
-       { .name = "random",     .shift = FD_RANDOM },
-       { .name = "parse",      .shift = FD_PARSE },
-       { .name = "diskutil",   .shift = FD_DISKUTIL },
-       { .name = "job",        .shift = FD_JOB },
-       { .name = "mutex",      .shift = FD_MUTEX },
-       { .name = "profile",    .shift = FD_PROFILE },
-       { .name = "time",       .shift = FD_TIME },
-       { .name = "net",        .shift = FD_NET },
+       { .name = "process",
+         .help = "Process creation/exit logging",
+         .shift = FD_PROCESS,
+       },
+       { .name = "file",
+         .help = "File related action logging",
+         .shift = FD_FILE,
+       },
+       { .name = "io",
+         .help = "IO and IO engine action logging (offsets, queue, completions, etc)",
+         .shift = FD_IO,
+       },
+       { .name = "mem",
+         .help = "Memory allocation/freeing logging",
+         .shift = FD_MEM,
+       },
+       { .name = "blktrace",
+         .help = "blktrace action logging",
+         .shift = FD_BLKTRACE,
+       },
+       { .name = "verify",
+         .help = "IO verification action logging",
+         .shift = FD_VERIFY,
+       },
+       { .name = "random",
+         .help = "Random generation logging",
+         .shift = FD_RANDOM,
+       },
+       { .name = "parse",
+         .help = "Parser logging",
+         .shift = FD_PARSE,
+       },
+       { .name = "diskutil",
+         .help = "Disk utility logging actions",
+         .shift = FD_DISKUTIL,
+       },
+       { .name = "job",
+         .help = "Logging related to creating/destroying jobs",
+         .shift = FD_JOB,
+       },
+       { .name = "mutex",
+         .help = "Mutex logging",
+         .shift = FD_MUTEX
+       },
+       { .name = "profile",
+         .help = "Logging related to profiles",
+         .shift = FD_PROFILE,
+       },
+       { .name = "time",
+         .help = "Logging related to time keeping functions",
+         .shift = FD_TIME,
+       },
+       { .name = "net",
+         .help = "Network logging",
+         .shift = FD_NET,
+       },
        { .name = NULL, },
 };
 
@@ -1367,7 +1409,7 @@ void parse_cmd_client(void *client, char *opt)
        fio_client_add_cmd_option(client, opt);
 }
 
-int parse_cmd_line(int argc, char *argv[])
+int parse_cmd_line(int argc, char *argv[], int client_type)
 {
        struct thread_data *td = NULL;
        int c, ini_idx = 0, lidx, ret = 0, do_exit = 0, exit_val = 0;
@@ -1497,7 +1539,7 @@ int parse_cmd_line(int argc, char *argv[])
                        char *val = optarg;
 
                        if (!strncmp(opt, "name", 4) && td) {
-                               ret = add_job(td, td->o.name ?: "fio", 0);
+                               ret = add_job(td, td->o.name ?: "fio", 0, 0, client_type);
                                if (ret)
                                        return 0;
                                td = NULL;
@@ -1569,7 +1611,7 @@ int parse_cmd_line(int argc, char *argv[])
                                exit_val = 1;
                                break;
                        }
-                       if (fio_client_add(optarg, &cur_client)) {
+                       if (fio_client_add(&fio_client_ops, optarg, &cur_client)) {
                                log_err("fio: failed adding client %s\n", optarg);
                                do_exit++;
                                exit_val = 1;
@@ -1613,7 +1655,7 @@ int parse_cmd_line(int argc, char *argv[])
 
        if (td) {
                if (!ret)
-                       ret = add_job(td, td->o.name ?: "fio", 0);
+                       ret = add_job(td, td->o.name ?: "fio", 0, 0, client_type);
        }
 
        while (!ret && optind < argc) {
@@ -1626,10 +1668,8 @@ int parse_cmd_line(int argc, char *argv[])
        return ini_idx;
 }
 
-int parse_options(int argc, char *argv[])
+int fio_init_options(void)
 {
-       int job_files, i;
-
        f_out = stdout;
        f_err = stderr;
 
@@ -1641,7 +1681,22 @@ int parse_options(int argc, char *argv[])
        if (fill_def_thread())
                return 1;
 
-       job_files = parse_cmd_line(argc, argv);
+       return 0;
+}
+
+extern int fio_check_options(struct thread_options *);
+
+int parse_options(int argc, char *argv[])
+{
+       const int type = FIO_CLIENT_TYPE_CLI;
+       int job_files, i;
+
+       if (fio_init_options())
+               return 1;
+       if (fio_test_cconv(&def_thread.o))
+               log_err("fio: failed internal cconv test\n");
+
+       job_files = parse_cmd_line(argc, argv, type);
 
        if (job_files > 0) {
                for (i = 0; i < job_files; i++) {
@@ -1652,7 +1707,7 @@ int parse_options(int argc, char *argv[])
                                        return 1;
                                free(ini_file[i]);
                        } else if (!is_backend) {
-                               if (parse_jobs_ini(ini_file[i], 0, i))
+                               if (parse_jobs_ini(ini_file[i], 0, i, type))
                                        return 1;
                                free(ini_file[i]);
                        }
@@ -1698,3 +1753,8 @@ int parse_options(int argc, char *argv[])
 
        return 0;
 }
+
+void options_default_fill(struct thread_options *o)
+{
+       memcpy(o, &def_thread.o, sizeof(*o));
+}
index df5abbbc1c45322e0625f0e77bf614ca25f8424d..f28f7555ceeadffbe819e54fdd2d2d386cc6b990 100644 (file)
--- a/io_ddir.h
+++ b/io_ddir.h
@@ -43,6 +43,15 @@ static inline int ddir_rw(enum fio_ddir ddir)
        return ddir == DDIR_READ || ddir == DDIR_WRITE || ddir == DDIR_TRIM;
 }
 
+static inline const char *ddir_str(enum fio_ddir ddir)
+{
+       const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
+                                  "randread", "randwrite", "randrw",
+                                  "trim", NULL, NULL, NULL, "randtrim" };
+
+       return ddir_str[ddir];
+}
+
 #define ddir_trim(ddir) ((ddir) == DDIR_TRIM)
 
 #define ddir_rw_sum(arr)       \
diff --git a/io_u.c b/io_u.c
index e047677e1b507d0923dfe781f5c354f63248a62c..347e1159fa5d5d69e48faebe31965ea508aef60c 100644 (file)
--- a/io_u.c
+++ b/io_u.c
@@ -78,7 +78,7 @@ static void mark_random_map(struct thread_data *td, struct io_u *io_u)
                                mask = -1UL;
                        else
                                mask = ((1UL << this_blocks) - 1) << bit;
-       
+
                        if (!(f->file_map[idx] & mask))
                                break;
 
@@ -310,7 +310,7 @@ static int get_next_block(struct thread_data *td, struct io_u *io_u,
                        ret = 1;
                }
        }
-       
+
        if (!ret) {
                if (offset != -1ULL)
                        io_u->offset = offset;
index 61cb396f5e0e792f87b862976a426695338e524b..b4bc22b8968b00039989734ebc720567ff72f340 100644 (file)
@@ -1,6 +1,11 @@
 #ifndef FIO_IOENGINE_H
 #define FIO_IOENGINE_H
 
+#include "compiler/compiler.h"
+#include "io_ddir.h"
+#include "debug.h"
+#include "file.h"
+
 #define FIO_IOOPS_VERSION      13
 
 enum {
@@ -14,6 +19,8 @@ enum {
        IO_U_F_VER_LIST         = 1 << 7,
 };
 
+struct thread_data;
+
 /*
  * The io unit
  */
index c2a64cb092786519746ef82111e19bd1a79300fb..8b71e13449638dfec259370a57b74d1f7f1258c2 100644 (file)
@@ -236,7 +236,7 @@ int td_io_getevents(struct thread_data *td, unsigned int min, unsigned int max,
 out:
        if (r >= 0) {
                /*
-                * Reflect that our submitted requests were retrieved with
+                * Reflect that our submitted requests were retrieved with
                 * whatever OS async calls are in the underlying engine.
                 */
                td->io_u_in_flight -= r;
@@ -361,14 +361,14 @@ int td_io_commit(struct thread_data *td)
        if (!td->cur_depth || !td->io_u_queued)
                return 0;
 
-       io_u_mark_depth(td, td->io_u_queued);   
+       io_u_mark_depth(td, td->io_u_queued);
 
        if (td->io_ops->commit) {
                ret = td->io_ops->commit(td);
                if (ret)
                        td_verror(td, -ret, "io commit");
        }
-       
+
        /*
         * Reflect that events were submitted as async IO requests.
         */
@@ -533,7 +533,7 @@ int do_io_u_trim(struct thread_data *td, struct io_u *io_u)
 
        ret = os_trim(f->fd, io_u->offset, io_u->xfer_buflen);
        if (!ret)
-               return io_u->xfer_buflen;;
+               return io_u->xfer_buflen;
 
        io_u->error = ret;
        return 0;
diff --git a/iolog.c b/iolog.c
index 12f09d0ec019b075625fd6a4f016337657c9e151..137c1e9889c8e025a59d8ab71df7378b767ef996 100644 (file)
--- a/iolog.c
+++ b/iolog.c
@@ -110,7 +110,7 @@ int read_iolog_get(struct thread_data *td, struct io_u *io_u)
 {
        struct io_piece *ipo;
        unsigned long elapsed;
-       
+
        while (!flist_empty(&td->io_log_list)) {
                int ret;
 
@@ -141,11 +141,10 @@ int read_iolog_get(struct thread_data *td, struct io_u *io_u)
                        elapsed = mtime_since_genesis();
                        if (ipo->delay > elapsed)
                                usec_sleep(td, (ipo->delay - elapsed) * 1000);
-                               
                }
 
                free(ipo);
-               
+
                if (io_u->ddir != DDIR_WAIT)
                        return 0;
        }
@@ -372,7 +371,7 @@ static int read_iolog2(struct thread_data *td, FILE *f)
                        ipo->fileno = fileno;
                        ipo->file_action = file_action;
                }
-                       
+
                queue_io_piece(td, ipo);
        }
 
@@ -494,13 +493,14 @@ int init_iolog(struct thread_data *td)
        return ret;
 }
 
-void setup_log(struct io_log **log, unsigned long avg_msec)
+void setup_log(struct io_log **log, unsigned long avg_msec, int log_type)
 {
        struct io_log *l = malloc(sizeof(*l));
 
        memset(l, 0, sizeof(*l));
        l->nr_samples = 0;
        l->max_samples = 1024;
+       l->log_type = log_type;
        l->log = malloc(l->max_samples * sizeof(struct io_sample));
        l->avg_msec = avg_msec;
        *log = l;
@@ -518,10 +518,10 @@ void __finish_log(struct io_log *log, const char *name)
        }
 
        for (i = 0; i < log->nr_samples; i++) {
-               fprintf(f, "%lu, %lu, %u, %u\n", log->log[i].time,
-                                               log->log[i].val,
-                                               log->log[i].ddir,
-                                               log->log[i].bs);
+               fprintf(f, "%lu, %lu, %u, %u\n",
+                               (unsigned long) log->log[i].time,
+                               (unsigned long) log->log[i].val,
+                               log->log[i].ddir, log->log[i].bs);
        }
 
        fclose(f);
@@ -536,7 +536,13 @@ void finish_log_named(struct thread_data *td, struct io_log *log,
 
        snprintf(file_name, 200, "%s_%s.log", prefix, postfix);
        p = basename(file_name);
-       __finish_log(log, p);
+
+       if (td->client_type == FIO_CLIENT_TYPE_GUI) {
+               fio_send_iolog(td, log, p);
+               free(log->log);
+               free(log);
+       } else
+               __finish_log(log, p);
 }
 
 void finish_log(struct thread_data *td, struct io_log *log, const char *name)
diff --git a/iolog.h b/iolog.h
index bb5c9b785813bd4b4dd0e18eb20c26f231872fd5..4ad4e79ebf2c66ee79597e42fc1d0411ccaf3332 100644 (file)
--- a/iolog.h
+++ b/iolog.h
@@ -1,7 +1,9 @@
 #ifndef FIO_IOLOG_H
 #define FIO_IOLOG_H
 
+#include "lib/rbtree.h"
 #include "lib/ieee754.h"
+#include "ioengine.h"
 
 /*
  * Use for maintaining statistics
@@ -19,10 +21,18 @@ struct io_stat {
  * A single data sample
  */
 struct io_sample {
-       unsigned long time;
-       unsigned long val;
-       enum fio_ddir ddir;
-       unsigned int bs;
+       uint64_t time;
+       uint64_t val;
+       uint32_t ddir;
+       uint32_t bs;
+};
+
+enum {
+       IO_LOG_TYPE_LAT = 1,
+       IO_LOG_TYPE_CLAT,
+       IO_LOG_TYPE_SLAT,
+       IO_LOG_TYPE_BW,
+       IO_LOG_TYPE_IOPS,
 };
 
 /*
@@ -36,6 +46,8 @@ struct io_log {
        unsigned long max_samples;
        struct io_sample *log;
 
+       unsigned int log_type;
+
        /*
         * Windowed average, for logging single entries average over some
         * period of time.
@@ -107,7 +119,7 @@ extern void add_bw_sample(struct thread_data *, enum fio_ddir, unsigned int,
 extern void add_iops_sample(struct thread_data *, enum fio_ddir, struct timeval *);
 extern void init_disk_util(struct thread_data *);
 extern void update_rusage_stat(struct thread_data *);
-extern void setup_log(struct io_log **, unsigned long);
+extern void setup_log(struct io_log **, unsigned long, int);
 extern void finish_log(struct thread_data *, struct io_log *, const char *);
 extern void finish_log_named(struct thread_data *, struct io_log *, const char *, const char *);
 extern void __finish_log(struct io_log *, const char *);
diff --git a/lib/prio_tree.c b/lib/prio_tree.c
new file mode 100644 (file)
index 0000000..b0e935c
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ * lib/prio_tree.c - priority search tree
+ *
+ * Copyright (C) 2004, Rajesh Venkatasubramanian <vrajesh@umich.edu>
+ *
+ * This file is released under the GPL v2.
+ *
+ * Based on the radix priority search tree proposed by Edward M. McCreight
+ * SIAM Journal of Computing, vol. 14, no.2, pages 257-276, May 1985
+ *
+ * 02Feb2004   Initial version
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include "../fio.h"
+#include "prio_tree.h"
+
+/*
+ * A clever mix of heap and radix trees forms a radix priority search tree (PST)
+ * which is useful for storing intervals, e.g, we can consider a vma as a closed
+ * interval of file pages [offset_begin, offset_end], and store all vmas that
+ * map a file in a PST. Then, using the PST, we can answer a stabbing query,
+ * i.e., selecting a set of stored intervals (vmas) that overlap with (map) a
+ * given input interval X (a set of consecutive file pages), in "O(log n + m)"
+ * time where 'log n' is the height of the PST, and 'm' is the number of stored
+ * intervals (vmas) that overlap (map) with the input interval X (the set of
+ * consecutive file pages).
+ *
+ * In our implementation, we store closed intervals of the form [radix_index,
+ * heap_index]. We assume that always radix_index <= heap_index. McCreight's PST
+ * is designed for storing intervals with unique radix indices, i.e., each
+ * interval have different radix_index. However, this limitation can be easily
+ * overcome by using the size, i.e., heap_index - radix_index, as part of the
+ * index, so we index the tree using [(radix_index,size), heap_index].
+ *
+ * When the above-mentioned indexing scheme is used, theoretically, in a 32 bit
+ * machine, the maximum height of a PST can be 64. We can use a balanced version
+ * of the priority search tree to optimize the tree height, but the balanced
+ * tree proposed by McCreight is too complex and memory-hungry for our purpose.
+ */
+
+static void get_index(const struct prio_tree_node *node,
+                     unsigned long *radix, unsigned long *heap)
+{
+       *radix = node->start;
+       *heap = node->last;
+}
+
+static unsigned long index_bits_to_maxindex[BITS_PER_LONG];
+
+void fio_init prio_tree_init(void)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(index_bits_to_maxindex) - 1; i++)
+               index_bits_to_maxindex[i] = (1UL << (i + 1)) - 1;
+       index_bits_to_maxindex[ARRAY_SIZE(index_bits_to_maxindex) - 1] = ~0UL;
+}
+
+/*
+ * Maximum heap_index that can be stored in a PST with index_bits bits
+ */
+static inline unsigned long prio_tree_maxindex(unsigned int bits)
+{
+       return index_bits_to_maxindex[bits - 1];
+}
+
+/*
+ * Extend a priority search tree so that it can store a node with heap_index
+ * max_heap_index. In the worst case, this algorithm takes O((log n)^2).
+ * However, this function is used rarely and the common case performance is
+ * not bad.
+ */
+static struct prio_tree_node *prio_tree_expand(struct prio_tree_root *root,
+               struct prio_tree_node *node, unsigned long max_heap_index)
+{
+       struct prio_tree_node *first = NULL, *prev, *last = NULL;
+
+       if (max_heap_index > prio_tree_maxindex(root->index_bits))
+               root->index_bits++;
+
+       while (max_heap_index > prio_tree_maxindex(root->index_bits)) {
+               root->index_bits++;
+
+               if (prio_tree_empty(root))
+                       continue;
+
+               if (first == NULL) {
+                       first = root->prio_tree_node;
+                       prio_tree_remove(root, root->prio_tree_node);
+                       INIT_PRIO_TREE_NODE(first);
+                       last = first;
+               } else {
+                       prev = last;
+                       last = root->prio_tree_node;
+                       prio_tree_remove(root, root->prio_tree_node);
+                       INIT_PRIO_TREE_NODE(last);
+                       prev->left = last;
+                       last->parent = prev;
+               }
+       }
+
+       INIT_PRIO_TREE_NODE(node);
+
+       if (first) {
+               node->left = first;
+               first->parent = node;
+       } else
+               last = node;
+
+       if (!prio_tree_empty(root)) {
+               last->left = root->prio_tree_node;
+               last->left->parent = last;
+       }
+
+       root->prio_tree_node = node;
+       return node;
+}
+
+/*
+ * Replace a prio_tree_node with a new node and return the old node
+ */
+struct prio_tree_node *prio_tree_replace(struct prio_tree_root *root,
+               struct prio_tree_node *old, struct prio_tree_node *node)
+{
+       INIT_PRIO_TREE_NODE(node);
+
+       if (prio_tree_root(old)) {
+               assert(root->prio_tree_node == old);
+               /*
+                * We can reduce root->index_bits here. However, it is complex
+                * and does not help much to improve performance (IMO).
+                */
+               node->parent = node;
+               root->prio_tree_node = node;
+       } else {
+               node->parent = old->parent;
+               if (old->parent->left == old)
+                       old->parent->left = node;
+               else
+                       old->parent->right = node;
+       }
+
+       if (!prio_tree_left_empty(old)) {
+               node->left = old->left;
+               old->left->parent = node;
+       }
+
+       if (!prio_tree_right_empty(old)) {
+               node->right = old->right;
+               old->right->parent = node;
+       }
+
+       return old;
+}
+
+/*
+ * Insert a prio_tree_node @node into a radix priority search tree @root. The
+ * algorithm typically takes O(log n) time where 'log n' is the number of bits
+ * required to represent the maximum heap_index. In the worst case, the algo
+ * can take O((log n)^2) - check prio_tree_expand.
+ *
+ * If a prior node with same radix_index and heap_index is already found in
+ * the tree, then returns the address of the prior node. Otherwise, inserts
+ * @node into the tree and returns @node.
+ */
+struct prio_tree_node *prio_tree_insert(struct prio_tree_root *root,
+               struct prio_tree_node *node)
+{
+       struct prio_tree_node *cur, *res = node;
+       unsigned long radix_index, heap_index;
+       unsigned long r_index, h_index, index, mask;
+       int size_flag = 0;
+
+       get_index(node, &radix_index, &heap_index);
+
+       if (prio_tree_empty(root) ||
+                       heap_index > prio_tree_maxindex(root->index_bits))
+               return prio_tree_expand(root, node, heap_index);
+
+       cur = root->prio_tree_node;
+       mask = 1UL << (root->index_bits - 1);
+
+       while (mask) {
+               get_index(cur, &r_index, &h_index);
+
+               if (r_index == radix_index && h_index == heap_index)
+                       return cur;
+
+                if (h_index < heap_index ||
+                   (h_index == heap_index && r_index > radix_index)) {
+                       struct prio_tree_node *tmp = node;
+                       node = prio_tree_replace(root, cur, node);
+                       cur = tmp;
+                       /* swap indices */
+                       index = r_index;
+                       r_index = radix_index;
+                       radix_index = index;
+                       index = h_index;
+                       h_index = heap_index;
+                       heap_index = index;
+               }
+
+               if (size_flag)
+                       index = heap_index - radix_index;
+               else
+                       index = radix_index;
+
+               if (index & mask) {
+                       if (prio_tree_right_empty(cur)) {
+                               INIT_PRIO_TREE_NODE(node);
+                               cur->right = node;
+                               node->parent = cur;
+                               return res;
+                       } else
+                               cur = cur->right;
+               } else {
+                       if (prio_tree_left_empty(cur)) {
+                               INIT_PRIO_TREE_NODE(node);
+                               cur->left = node;
+                               node->parent = cur;
+                               return res;
+                       } else
+                               cur = cur->left;
+               }
+
+               mask >>= 1;
+
+               if (!mask) {
+                       mask = 1UL << (BITS_PER_LONG - 1);
+                       size_flag = 1;
+               }
+       }
+       /* Should not reach here */
+       assert(0);
+       return NULL;
+}
+
+/*
+ * Remove a prio_tree_node @node from a radix priority search tree @root. The
+ * algorithm takes O(log n) time where 'log n' is the number of bits required
+ * to represent the maximum heap_index.
+ */
+void prio_tree_remove(struct prio_tree_root *root, struct prio_tree_node *node)
+{
+       struct prio_tree_node *cur;
+       unsigned long r_index, h_index_right, h_index_left;
+
+       cur = node;
+
+       while (!prio_tree_left_empty(cur) || !prio_tree_right_empty(cur)) {
+               if (!prio_tree_left_empty(cur))
+                       get_index(cur->left, &r_index, &h_index_left);
+               else {
+                       cur = cur->right;
+                       continue;
+               }
+
+               if (!prio_tree_right_empty(cur))
+                       get_index(cur->right, &r_index, &h_index_right);
+               else {
+                       cur = cur->left;
+                       continue;
+               }
+
+               /* both h_index_left and h_index_right cannot be 0 */
+               if (h_index_left >= h_index_right)
+                       cur = cur->left;
+               else
+                       cur = cur->right;
+       }
+
+       if (prio_tree_root(cur)) {
+               assert(root->prio_tree_node == cur);
+               INIT_PRIO_TREE_ROOT(root);
+               return;
+       }
+
+       if (cur->parent->right == cur)
+               cur->parent->right = cur->parent;
+       else
+               cur->parent->left = cur->parent;
+
+       while (cur != node)
+               cur = prio_tree_replace(root, cur->parent, cur);
+}
+
+/*
+ * Following functions help to enumerate all prio_tree_nodes in the tree that
+ * overlap with the input interval X [radix_index, heap_index]. The enumeration
+ * takes O(log n + m) time where 'log n' is the height of the tree (which is
+ * proportional to # of bits required to represent the maximum heap_index) and
+ * 'm' is the number of prio_tree_nodes that overlap the interval X.
+ */
+
+static struct prio_tree_node *prio_tree_left(struct prio_tree_iter *iter,
+               unsigned long *r_index, unsigned long *h_index)
+{
+       if (prio_tree_left_empty(iter->cur))
+               return NULL;
+
+       get_index(iter->cur->left, r_index, h_index);
+
+       if (iter->r_index <= *h_index) {
+               iter->cur = iter->cur->left;
+               iter->mask >>= 1;
+               if (iter->mask) {
+                       if (iter->size_level)
+                               iter->size_level++;
+               } else {
+                       if (iter->size_level) {
+                               assert(prio_tree_left_empty(iter->cur));
+                               assert(prio_tree_right_empty(iter->cur));
+                               iter->size_level++;
+                               iter->mask = ULONG_MAX;
+                       } else {
+                               iter->size_level = 1;
+                               iter->mask = 1UL << (BITS_PER_LONG - 1);
+                       }
+               }
+               return iter->cur;
+       }
+
+       return NULL;
+}
+
+static struct prio_tree_node *prio_tree_right(struct prio_tree_iter *iter,
+               unsigned long *r_index, unsigned long *h_index)
+{
+       unsigned long value;
+
+       if (prio_tree_right_empty(iter->cur))
+               return NULL;
+
+       if (iter->size_level)
+               value = iter->value;
+       else
+               value = iter->value | iter->mask;
+
+       if (iter->h_index < value)
+               return NULL;
+
+       get_index(iter->cur->right, r_index, h_index);
+
+       if (iter->r_index <= *h_index) {
+               iter->cur = iter->cur->right;
+               iter->mask >>= 1;
+               iter->value = value;
+               if (iter->mask) {
+                       if (iter->size_level)
+                               iter->size_level++;
+               } else {
+                       if (iter->size_level) {
+                               assert(prio_tree_left_empty(iter->cur));
+                               assert(prio_tree_right_empty(iter->cur));
+                               iter->size_level++;
+                               iter->mask = ULONG_MAX;
+                       } else {
+                               iter->size_level = 1;
+                               iter->mask = 1UL << (BITS_PER_LONG - 1);
+                       }
+               }
+               return iter->cur;
+       }
+
+       return NULL;
+}
+
+static struct prio_tree_node *prio_tree_parent(struct prio_tree_iter *iter)
+{
+       iter->cur = iter->cur->parent;
+       if (iter->mask == ULONG_MAX)
+               iter->mask = 1UL;
+       else if (iter->size_level == 1)
+               iter->mask = 1UL;
+       else
+               iter->mask <<= 1;
+       if (iter->size_level)
+               iter->size_level--;
+       if (!iter->size_level && (iter->value & iter->mask))
+               iter->value ^= iter->mask;
+       return iter->cur;
+}
+
+static inline int overlap(struct prio_tree_iter *iter,
+               unsigned long r_index, unsigned long h_index)
+{
+       return iter->h_index >= r_index && iter->r_index <= h_index;
+}
+
+/*
+ * prio_tree_first:
+ *
+ * Get the first prio_tree_node that overlaps with the interval [radix_index,
+ * heap_index]. Note that always radix_index <= heap_index. We do a pre-order
+ * traversal of the tree.
+ */
+static struct prio_tree_node *prio_tree_first(struct prio_tree_iter *iter)
+{
+       struct prio_tree_root *root;
+       unsigned long r_index, h_index;
+
+       INIT_PRIO_TREE_ITER(iter);
+
+       root = iter->root;
+       if (prio_tree_empty(root))
+               return NULL;
+
+       get_index(root->prio_tree_node, &r_index, &h_index);
+
+       if (iter->r_index > h_index)
+               return NULL;
+
+       iter->mask = 1UL << (root->index_bits - 1);
+       iter->cur = root->prio_tree_node;
+
+       while (1) {
+               if (overlap(iter, r_index, h_index))
+                       return iter->cur;
+
+               if (prio_tree_left(iter, &r_index, &h_index))
+                       continue;
+
+               if (prio_tree_right(iter, &r_index, &h_index))
+                       continue;
+
+               break;
+       }
+       return NULL;
+}
+
+/*
+ * prio_tree_next:
+ *
+ * Get the next prio_tree_node that overlaps with the input interval in iter
+ */
+struct prio_tree_node *prio_tree_next(struct prio_tree_iter *iter)
+{
+       unsigned long r_index, h_index;
+
+       if (iter->cur == NULL)
+               return prio_tree_first(iter);
+
+repeat:
+       while (prio_tree_left(iter, &r_index, &h_index))
+               if (overlap(iter, r_index, h_index))
+                       return iter->cur;
+
+       while (!prio_tree_right(iter, &r_index, &h_index)) {
+               while (!prio_tree_root(iter->cur) &&
+                               iter->cur->parent->right == iter->cur)
+                       prio_tree_parent(iter);
+
+               if (prio_tree_root(iter->cur))
+                       return NULL;
+
+               prio_tree_parent(iter);
+       }
+
+       if (overlap(iter, r_index, h_index))
+               return iter->cur;
+
+       goto repeat;
+}
diff --git a/lib/prio_tree.h b/lib/prio_tree.h
new file mode 100644 (file)
index 0000000..e1491db
--- /dev/null
@@ -0,0 +1,90 @@
+#ifndef _LINUX_PRIO_TREE_H
+#define _LINUX_PRIO_TREE_H
+
+#include <inttypes.h>
+#include "../hash.h"
+
+struct prio_tree_node {
+       struct prio_tree_node   *left;
+       struct prio_tree_node   *right;
+       struct prio_tree_node   *parent;
+       uint64_t                start;
+       uint64_t                last;   /* last location _in_ interval */
+};
+
+struct prio_tree_root {
+       struct prio_tree_node   *prio_tree_node;
+       unsigned short          index_bits;
+};
+
+struct prio_tree_iter {
+       struct prio_tree_node   *cur;
+       unsigned long           mask;
+       unsigned long           value;
+       int                     size_level;
+
+       struct prio_tree_root   *root;
+       uint64_t                r_index;
+       uint64_t                h_index;
+};
+
+static inline void prio_tree_iter_init(struct prio_tree_iter *iter,
+               struct prio_tree_root *root, uint64_t r_index, uint64_t h_index)
+{
+       iter->root = root;
+       iter->r_index = r_index;
+       iter->h_index = h_index;
+       iter->cur = NULL;
+}
+
+#define INIT_PRIO_TREE_ROOT(ptr)       \
+do {                                   \
+       (ptr)->prio_tree_node = NULL;   \
+       (ptr)->index_bits = 1;          \
+} while (0)
+
+#define INIT_PRIO_TREE_NODE(ptr)                               \
+do {                                                           \
+       (ptr)->left = (ptr)->right = (ptr)->parent = (ptr);     \
+} while (0)
+
+#define INIT_PRIO_TREE_ITER(ptr)       \
+do {                                   \
+       (ptr)->cur = NULL;              \
+       (ptr)->mask = 0UL;              \
+       (ptr)->value = 0UL;             \
+       (ptr)->size_level = 0;          \
+} while (0)
+
+#define prio_tree_entry(ptr, type, member) \
+       ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+static inline int prio_tree_empty(const struct prio_tree_root *root)
+{
+       return root->prio_tree_node == NULL;
+}
+
+static inline int prio_tree_root(const struct prio_tree_node *node)
+{
+       return node->parent == node;
+}
+
+static inline int prio_tree_left_empty(const struct prio_tree_node *node)
+{
+       return node->left == node;
+}
+
+static inline int prio_tree_right_empty(const struct prio_tree_node *node)
+{
+       return node->right == node;
+}
+
+
+struct prio_tree_node *prio_tree_replace(struct prio_tree_root *root,
+                struct prio_tree_node *old, struct prio_tree_node *node);
+struct prio_tree_node *prio_tree_insert(struct prio_tree_root *root,
+                struct prio_tree_node *node);
+void prio_tree_remove(struct prio_tree_root *root, struct prio_tree_node *node);
+struct prio_tree_node *prio_tree_next(struct prio_tree_iter *iter);
+
+#endif /* _LINUX_PRIO_TREE_H */
diff --git a/lib/rbtree.c b/lib/rbtree.c
new file mode 100644 (file)
index 0000000..7cff649
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+  (C) 2002  David Woodhouse <dwmw2@infradead.org>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/lib/rbtree.c
+*/
+
+#include "rbtree.h"
+
+static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *right = node->rb_right;
+       struct rb_node *parent = rb_parent(node);
+
+       if ((node->rb_right = right->rb_left))
+               rb_set_parent(right->rb_left, node);
+       right->rb_left = node;
+
+       rb_set_parent(right, parent);
+
+       if (parent)
+       {
+               if (node == parent->rb_left)
+                       parent->rb_left = right;
+               else
+                       parent->rb_right = right;
+       }
+       else
+               root->rb_node = right;
+       rb_set_parent(node, right);
+}
+
+static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *left = node->rb_left;
+       struct rb_node *parent = rb_parent(node);
+
+       if ((node->rb_left = left->rb_right))
+               rb_set_parent(left->rb_right, node);
+       left->rb_right = node;
+
+       rb_set_parent(left, parent);
+
+       if (parent)
+       {
+               if (node == parent->rb_right)
+                       parent->rb_right = left;
+               else
+                       parent->rb_left = left;
+       }
+       else
+               root->rb_node = left;
+       rb_set_parent(node, left);
+}
+
+void rb_insert_color(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *parent, *gparent;
+
+       while ((parent = rb_parent(node)) && rb_is_red(parent))
+       {
+               gparent = rb_parent(parent);
+
+               if (parent == gparent->rb_left)
+               {
+                       {
+                               register struct rb_node *uncle = gparent->rb_right;
+                               if (uncle && rb_is_red(uncle))
+                               {
+                                       rb_set_black(uncle);
+                                       rb_set_black(parent);
+                                       rb_set_red(gparent);
+                                       node = gparent;
+                                       continue;
+                               }
+                       }
+
+                       if (parent->rb_right == node)
+                       {
+                               register struct rb_node *tmp;
+                               __rb_rotate_left(parent, root);
+                               tmp = parent;
+                               parent = node;
+                               node = tmp;
+                       }
+
+                       rb_set_black(parent);
+                       rb_set_red(gparent);
+                       __rb_rotate_right(gparent, root);
+               } else {
+                       {
+                               register struct rb_node *uncle = gparent->rb_left;
+                               if (uncle && rb_is_red(uncle))
+                               {
+                                       rb_set_black(uncle);
+                                       rb_set_black(parent);
+                                       rb_set_red(gparent);
+                                       node = gparent;
+                                       continue;
+                               }
+                       }
+
+                       if (parent->rb_left == node)
+                       {
+                               register struct rb_node *tmp;
+                               __rb_rotate_right(parent, root);
+                               tmp = parent;
+                               parent = node;
+                               node = tmp;
+                       }
+
+                       rb_set_black(parent);
+                       rb_set_red(gparent);
+                       __rb_rotate_left(gparent, root);
+               }
+       }
+
+       rb_set_black(root->rb_node);
+}
+
+static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
+                            struct rb_root *root)
+{
+       struct rb_node *other;
+
+       while ((!node || rb_is_black(node)) && node != root->rb_node)
+       {
+               if (parent->rb_left == node)
+               {
+                       other = parent->rb_right;
+                       if (rb_is_red(other))
+                       {
+                               rb_set_black(other);
+                               rb_set_red(parent);
+                               __rb_rotate_left(parent, root);
+                               other = parent->rb_right;
+                       }
+                       if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+                           (!other->rb_right || rb_is_black(other->rb_right)))
+                       {
+                               rb_set_red(other);
+                               node = parent;
+                               parent = rb_parent(node);
+                       }
+                       else
+                       {
+                               if (!other->rb_right || rb_is_black(other->rb_right))
+                               {
+                                       struct rb_node *o_left;
+                                       if ((o_left = other->rb_left))
+                                               rb_set_black(o_left);
+                                       rb_set_red(other);
+                                       __rb_rotate_right(other, root);
+                                       other = parent->rb_right;
+                               }
+                               rb_set_color(other, rb_color(parent));
+                               rb_set_black(parent);
+                               if (other->rb_right)
+                                       rb_set_black(other->rb_right);
+                               __rb_rotate_left(parent, root);
+                               node = root->rb_node;
+                               break;
+                       }
+               }
+               else
+               {
+                       other = parent->rb_left;
+                       if (rb_is_red(other))
+                       {
+                               rb_set_black(other);
+                               rb_set_red(parent);
+                               __rb_rotate_right(parent, root);
+                               other = parent->rb_left;
+                       }
+                       if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+                           (!other->rb_right || rb_is_black(other->rb_right)))
+                       {
+                               rb_set_red(other);
+                               node = parent;
+                               parent = rb_parent(node);
+                       }
+                       else
+                       {
+                               if (!other->rb_left || rb_is_black(other->rb_left))
+                               {
+                                       register struct rb_node *o_right;
+                                       if ((o_right = other->rb_right))
+                                               rb_set_black(o_right);
+                                       rb_set_red(other);
+                                       __rb_rotate_left(other, root);
+                                       other = parent->rb_left;
+                               }
+                               rb_set_color(other, rb_color(parent));
+                               rb_set_black(parent);
+                               if (other->rb_left)
+                                       rb_set_black(other->rb_left);
+                               __rb_rotate_right(parent, root);
+                               node = root->rb_node;
+                               break;
+                       }
+               }
+       }
+       if (node)
+               rb_set_black(node);
+}
+
+void rb_erase(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *child, *parent;
+       int color;
+
+       if (!node->rb_left)
+               child = node->rb_right;
+       else if (!node->rb_right)
+               child = node->rb_left;
+       else
+       {
+               struct rb_node *old = node, *left;
+
+               node = node->rb_right;
+               while ((left = node->rb_left) != NULL)
+                       node = left;
+               child = node->rb_right;
+               parent = rb_parent(node);
+               color = rb_color(node);
+
+               if (child)
+                       rb_set_parent(child, parent);
+               if (parent == old) {
+                       parent->rb_right = child;
+                       parent = node;
+               } else
+                       parent->rb_left = child;
+
+               node->rb_parent_color = old->rb_parent_color;
+               node->rb_right = old->rb_right;
+               node->rb_left = old->rb_left;
+
+               if (rb_parent(old))
+               {
+                       if (rb_parent(old)->rb_left == old)
+                               rb_parent(old)->rb_left = node;
+                       else
+                               rb_parent(old)->rb_right = node;
+               } else
+                       root->rb_node = node;
+
+               rb_set_parent(old->rb_left, node);
+               if (old->rb_right)
+                       rb_set_parent(old->rb_right, node);
+               goto color;
+       }
+
+       parent = rb_parent(node);
+       color = rb_color(node);
+
+       if (child)
+               rb_set_parent(child, parent);
+       if (parent)
+       {
+               if (parent->rb_left == node)
+                       parent->rb_left = child;
+               else
+                       parent->rb_right = child;
+       }
+       else
+               root->rb_node = child;
+
+ color:
+       if (color == RB_BLACK)
+               __rb_erase_color(child, parent, root);
+}
+
+/*
+ * This function returns the first node (in sort order) of the tree.
+ */
+struct rb_node *rb_first(struct rb_root *root)
+{
+       struct rb_node  *n;
+
+       n = root->rb_node;
+       if (!n)
+               return NULL;
+       while (n->rb_left)
+               n = n->rb_left;
+       return n;
+}
diff --git a/lib/rbtree.h b/lib/rbtree.h
new file mode 100644 (file)
index 0000000..7563725
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/include/linux/rbtree.h
+
+  To use rbtrees you'll have to implement your own insert and search cores.
+  This will avoid us to use callbacks and to drop drammatically performances.
+  I know it's not the cleaner way,  but in C (not in C++) to get
+  performances and genericity...
+
+  Some example of insert and search follows here. The search is a plain
+  normal search over an ordered tree. The insert instead must be implemented
+  int two steps: as first thing the code must insert the element in
+  order as a red leaf in the tree, then the support library function
+  rb_insert_color() must be called. Such function will do the
+  not trivial work to rebalance the rbtree if necessary.
+
+-----------------------------------------------------------------------
+static inline struct page * rb_search_page_cache(struct inode * inode,
+                                                unsigned long offset)
+{
+       struct rb_node * n = inode->i_rb_page_cache.rb_node;
+       struct page * page;
+
+       while (n)
+       {
+               page = rb_entry(n, struct page, rb_page_cache);
+
+               if (offset < page->offset)
+                       n = n->rb_left;
+               else if (offset > page->offset)
+                       n = n->rb_right;
+               else
+                       return page;
+       }
+       return NULL;
+}
+
+static inline struct page * __rb_insert_page_cache(struct inode * inode,
+                                                  unsigned long offset,
+                                                  struct rb_node * node)
+{
+       struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
+       struct rb_node * parent = NULL;
+       struct page * page;
+
+       while (*p)
+       {
+               parent = *p;
+               page = rb_entry(parent, struct page, rb_page_cache);
+
+               if (offset < page->offset)
+                       p = &(*p)->rb_left;
+               else if (offset > page->offset)
+                       p = &(*p)->rb_right;
+               else
+                       return page;
+       }
+
+       rb_link_node(node, parent, p);
+
+       return NULL;
+}
+
+static inline struct page * rb_insert_page_cache(struct inode * inode,
+                                                unsigned long offset,
+                                                struct rb_node * node)
+{
+       struct page * ret;
+       if ((ret = __rb_insert_page_cache(inode, offset, node)))
+               goto out;
+       rb_insert_color(node, &inode->i_rb_page_cache);
+ out:
+       return ret;
+}
+-----------------------------------------------------------------------
+*/
+
+#ifndef        _LINUX_RBTREE_H
+#define        _LINUX_RBTREE_H
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+struct rb_node
+{
+       intptr_t rb_parent_color;
+#define        RB_RED          0
+#define        RB_BLACK        1
+       struct rb_node *rb_right;
+       struct rb_node *rb_left;
+} __attribute__((aligned(sizeof(long))));
+    /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+struct rb_root
+{
+       struct rb_node *rb_node;
+};
+
+
+#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
+#define rb_color(r)   ((r)->rb_parent_color & 1)
+#define rb_is_red(r)   (!rb_color(r))
+#define rb_is_black(r) rb_color(r)
+#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
+#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
+
+static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
+{
+       rb->rb_parent_color = (rb->rb_parent_color & 3) | (uintptr_t)p;
+}
+static inline void rb_set_color(struct rb_node *rb, int color)
+{
+       rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
+}
+
+#define RB_ROOT        (struct rb_root) { NULL, }
+#define        rb_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define RB_EMPTY_ROOT(root)    ((root)->rb_node == NULL)
+#define RB_EMPTY_NODE(node)    (rb_parent(node) == node)
+#define RB_CLEAR_NODE(node)    (rb_set_parent(node, node))
+
+extern void rb_insert_color(struct rb_node *, struct rb_root *);
+extern void rb_erase(struct rb_node *, struct rb_root *);
+
+/* Find logical next and previous nodes in a tree */
+extern struct rb_node *rb_first(struct rb_root *);
+
+static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
+                               struct rb_node ** rb_link)
+{
+       node->rb_parent_color = (uintptr_t)parent;
+       node->rb_left = node->rb_right = NULL;
+
+       *rb_link = node;
+}
+
+#endif /* _LINUX_RBTREE_H */
index 43e1a61aad8abbf217da11125ac6818defaaa2c8..f680be84c66acb59bf94960b62f8b074ae3187e5 100644 (file)
--- a/libfio.c
+++ b/libfio.c
 #include <string.h>
 #include <sys/types.h>
 #include <signal.h>
+#include <stdint.h>
+#include <locale.h>
+
 #include "fio.h"
+#include "smalloc.h"
+#include "os/os.h"
 
 /*
  * Just expose an empty list, if the OS does not support disk util stats
@@ -36,6 +41,9 @@ FLIST_HEAD(disk_list);
 
 unsigned long arch_flags = 0;
 
+uintptr_t page_mask;
+uintptr_t page_size;
+
 static const char *fio_os_strings[os_nr] = {
        "Invalid",
        "Linux",
@@ -127,8 +135,6 @@ void reset_fio_state(void)
 {
        groupid = 0;
        thread_number = 0;
-       nr_process = 0;
-       nr_thread = 0;
        done_secs = 0;
 }
 
@@ -189,4 +195,66 @@ void fio_terminate_threads(int group_id)
        }
 }
 
+static int endian_check(void)
+{
+       union {
+               uint8_t c[8];
+               uint64_t v;
+       } u;
+       int le = 0, be = 0;
+
+       u.v = 0x12;
+       if (u.c[7] == 0x12)
+               be = 1;
+       else if (u.c[0] == 0x12)
+               le = 1;
+
+#if defined(FIO_LITTLE_ENDIAN)
+       if (be)
+               return 1;
+#elif defined(FIO_BIG_ENDIAN)
+       if (le)
+               return 1;
+#else
+       return 1;
+#endif
+
+       if (!le && !be)
+               return 1;
+
+       return 0;
+}
+
+int initialize_fio(char *envp[])
+{
+       long ps;
+
+       if (endian_check()) {
+               log_err("fio: endianness settings appear wrong.\n");
+               log_err("fio: please report this to fio@vger.kernel.org\n");
+               return 1;
+       }
+
+       arch_init(envp);
 
+       sinit();
+
+       /*
+        * We need locale for number printing, if it isn't set then just
+        * go with the US format.
+        */
+       if (!getenv("LC_NUMERIC"))
+               setlocale(LC_NUMERIC, "en_US");
+
+       ps = sysconf(_SC_PAGESIZE);
+       if (ps < 0) {
+               log_err("Failed to get page size\n");
+               return 1;
+       }
+
+       page_size = ps;
+       page_mask = ps - 1;
+
+       fio_keywords_init();
+       return 0;
+}
diff --git a/log.c b/log.c
index af974f85b594abc1951b4bfb77ad625f4887b529..a26dee8775cb751eb786cb562cfafbcf4decdca5 100644 (file)
--- a/log.c
+++ b/log.c
@@ -60,7 +60,7 @@ int log_info(const char *format, ...)
        va_end(args);
 
        if (is_backend)
-               return fio_server_text_output(buffer, len);
+               return fio_server_text_output(FIO_LOG_INFO, buffer, len);
        else if (log_syslog) {
                syslog(LOG_INFO, "%s", buffer);
                return len;
@@ -79,7 +79,7 @@ int log_err(const char *format, ...)
        va_end(args);
 
        if (is_backend)
-               return fio_server_text_output(buffer, len);
+               return fio_server_text_output(FIO_LOG_ERR, buffer, len);
        else if (log_syslog) {
                syslog(LOG_INFO, "%s", buffer);
                return len;
@@ -93,3 +93,14 @@ int log_err(const char *format, ...)
                return fwrite(buffer, len, 1, f_err);
        }
 }
+
+const char *log_get_level(int level)
+{
+       static const char *levels[] = { "Unknown", "Debug", "Info", "Error",
+                                               "Unknown" };
+
+       if (level >= FIO_LOG_NR)
+               level = FIO_LOG_NR;
+
+       return levels[level];
+}
diff --git a/log.h b/log.h
index fdf3d7b19aed3d3981b166c32339fbd7bb596f79..ad9d08bbe6730fcf3750ec30e54d5d2b6265f330 100644 (file)
--- a/log.h
+++ b/log.h
@@ -13,4 +13,13 @@ extern int log_local(const char *format, ...);
 extern int log_valist(const char *str, va_list);
 extern int log_local_buf(const char *buf, size_t);
 
+enum {
+       FIO_LOG_DEBUG   = 1,
+       FIO_LOG_INFO    = 2,
+       FIO_LOG_ERR     = 3,
+       FIO_LOG_NR      = 4,
+};
+
+extern const char *log_get_level(int level);
+
 #endif
index 82a79bd7175b9a74ca0846a669d767e06d50b84d..f97749bc72c657f7b0d9f3739853dd7039797280 100644 (file)
--- a/memory.c
+++ b/memory.c
 
 #include "fio.h"
 
-static void *pinned_mem;
-
-void fio_unpin_memory(void)
+void fio_unpin_memory(struct thread_data *td)
 {
-       if (pinned_mem) {
-               dprint(FD_MEM, "unpinning %llu bytes\n", mlock_size);
-               if (munlock(pinned_mem, mlock_size) < 0)
+       if (td->pinned_mem) {
+               dprint(FD_MEM, "unpinning %llu bytes\n", td->o.lockmem);
+               if (munlock(td->pinned_mem, td->o.lockmem) < 0)
                        perror("munlock");
-               munmap(pinned_mem, mlock_size);
-               pinned_mem = NULL;
+               munmap(td->pinned_mem, td->o.lockmem);
+               td->pinned_mem = NULL;
        }
 }
 
-int fio_pin_memory(void)
+int fio_pin_memory(struct thread_data *td)
 {
        unsigned long long phys_mem;
 
-       if (!mlock_size)
+       if (!td->o.lockmem)
                return 0;
 
-       dprint(FD_MEM, "pinning %llu bytes\n", mlock_size);
+       dprint(FD_MEM, "pinning %llu bytes\n", td->o.lockmem);
 
        /*
         * Don't allow mlock of more than real_mem-128MB
         */
        phys_mem = os_phys_mem();
        if (phys_mem) {
-               if ((mlock_size + 128 * 1024 * 1024) > phys_mem) {
-                       mlock_size = phys_mem - 128 * 1024 * 1024;
+               if ((td->o.lockmem + 128 * 1024 * 1024) > phys_mem) {
+                       td->o.lockmem = phys_mem - 128 * 1024 * 1024;
                        log_info("fio: limiting mlocked memory to %lluMB\n",
-                                                       mlock_size >> 20);
+                                                       td->o.lockmem >> 20);
                }
        }
 
-       pinned_mem = mmap(NULL, mlock_size, PROT_READ | PROT_WRITE,
+       td->pinned_mem = mmap(NULL, td->o.lockmem, PROT_READ | PROT_WRITE,
                                MAP_PRIVATE | OS_MAP_ANON, -1, 0);
-       if (pinned_mem == MAP_FAILED) {
+       if (td->pinned_mem == MAP_FAILED) {
                perror("malloc locked mem");
-               pinned_mem = NULL;
+               td->pinned_mem = NULL;
                return 1;
        }
-       if (mlock(pinned_mem, mlock_size) < 0) {
+       if (mlock(td->pinned_mem, td->o.lockmem) < 0) {
                perror("mlock");
-               munmap(pinned_mem, mlock_size);
-               pinned_mem = NULL;
+               munmap(td->pinned_mem, td->o.lockmem);
+               td->pinned_mem = NULL;
                return 1;
        }
 
@@ -121,8 +119,8 @@ static int alloc_mem_mmap(struct thread_data *td, size_t total_mem)
 
        td->mmapfd = 1;
 
-       if (td->mmapfile) {
-               td->mmapfd = open(td->mmapfile, O_RDWR|O_CREAT, 0644);
+       if (td->o.mmapfile) {
+               td->mmapfd = open(td->o.mmapfile, O_RDWR|O_CREAT, 0644);
 
                if (td->mmapfd < 0) {
                        td_verror(td, errno, "open mmap file");
@@ -146,7 +144,7 @@ static int alloc_mem_mmap(struct thread_data *td, size_t total_mem)
                td->orig_buffer = NULL;
                if (td->mmapfd) {
                        close(td->mmapfd);
-                       unlink(td->mmapfile);
+                       unlink(td->o.mmapfile);
                }
 
                return 1;
@@ -159,10 +157,10 @@ static void free_mem_mmap(struct thread_data *td, size_t total_mem)
 {
        dprint(FD_MEM, "munmap %u %p\n", total_mem, td->orig_buffer);
        munmap(td->orig_buffer, td->orig_buffer_size);
-       if (td->mmapfile) {
+       if (td->o.mmapfile) {
                close(td->mmapfd);
-               unlink(td->mmapfile);
-               free(td->mmapfile);
+               unlink(td->o.mmapfile);
+               free(td->o.mmapfile);
        }
 }
 
index 84101d1a3752ce3e3c0f60b9587962edab4562a1..eb7c596a1b96d04f3e2d387fd3ee0eefc337cd66 100644 (file)
--- a/options.c
+++ b/options.c
@@ -37,7 +37,7 @@ static int converthexchartoint(char a)
 {
        int base;
 
-       switch(a) {
+       switch (a) {
        case '0'...'9':
                base = '0';
                break;
@@ -50,7 +50,7 @@ static int converthexchartoint(char a)
        default:
                base = 0;
        }
-       return (a - base);
+       return a - base;
 }
 
 static int bs_cmp(const void *p1, const void *p2)
@@ -61,7 +61,7 @@ static int bs_cmp(const void *p1, const void *p2)
        return bsp1->perc < bsp2->perc;
 }
 
-static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
+static int bssplit_ddir(struct thread_options *o, int ddir, char *str)
 {
        struct bssplit *bssplit;
        unsigned int i, perc, perc_missing;
@@ -69,7 +69,7 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
        long long val;
        char *fname;
 
-       td->o.bssplit_nr[ddir] = 4;
+       o->bssplit_nr[ddir] = 4;
        bssplit = malloc(4 * sizeof(struct bssplit));
 
        i = 0;
@@ -84,9 +84,9 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
                /*
                 * grow struct buffer, if needed
                 */
-               if (i == td->o.bssplit_nr[ddir]) {
-                       td->o.bssplit_nr[ddir] <<= 1;
-                       bssplit = realloc(bssplit, td->o.bssplit_nr[ddir]
+               if (i == o->bssplit_nr[ddir]) {
+                       o->bssplit_nr[ddir] <<= 1;
+                       bssplit = realloc(bssplit, o->bssplit_nr[ddir]
                                                  * sizeof(struct bssplit));
                }
 
@@ -102,9 +102,9 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
                } else
                        perc = -1;
 
-               if (str_to_decimal(fname, &val, 1, td)) {
+               if (str_to_decimal(fname, &val, 1, o)) {
                        log_err("fio: bssplit conversion failed\n");
-                       free(td->o.bssplit);
+                       free(o->bssplit);
                        return 1;
                }
 
@@ -118,13 +118,13 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
                i++;
        }
 
-       td->o.bssplit_nr[ddir] = i;
+       o->bssplit_nr[ddir] = i;
 
        /*
         * Now check if the percentages add up, and how much is missing
         */
        perc = perc_missing = 0;
-       for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+       for (i = 0; i < o->bssplit_nr[ddir]; i++) {
                struct bssplit *bsp = &bssplit[i];
 
                if (bsp->perc == (unsigned char) -1)
@@ -143,7 +143,7 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
         * them.
         */
        if (perc_missing) {
-               for (i = 0; i < td->o.bssplit_nr[ddir]; i++) {
+               for (i = 0; i < o->bssplit_nr[ddir]; i++) {
                        struct bssplit *bsp = &bssplit[i];
 
                        if (bsp->perc == (unsigned char) -1)
@@ -151,16 +151,15 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
                }
        }
 
-       td->o.min_bs[ddir] = min_bs;
-       td->o.max_bs[ddir] = max_bs;
+       o->min_bs[ddir] = min_bs;
+       o->max_bs[ddir] = max_bs;
 
        /*
         * now sort based on percentages, for ease of lookup
         */
-       qsort(bssplit, td->o.bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
-       td->o.bssplit[ddir] = bssplit;
+       qsort(bssplit, o->bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
+       o->bssplit[ddir] = bssplit;
        return 0;
-
 }
 
 static int str_bssplit_cb(void *data, const char *input)
@@ -178,36 +177,36 @@ static int str_bssplit_cb(void *data, const char *input)
        if (odir) {
                ddir = strchr(odir + 1, ',');
                if (ddir) {
-                       ret = bssplit_ddir(td, DDIR_TRIM, ddir + 1);
+                       ret = bssplit_ddir(&td->o, DDIR_TRIM, ddir + 1);
                        if (!ret)
                                *ddir = '\0';
                } else {
                        char *op;
 
                        op = strdup(odir + 1);
-                       ret = bssplit_ddir(td, DDIR_TRIM, op);
+                       ret = bssplit_ddir(&td->o, DDIR_TRIM, op);
 
                        free(op);
                }
-               if (!ret) 
-                       ret = bssplit_ddir(td, DDIR_WRITE, odir + 1);
+               if (!ret)
+                       ret = bssplit_ddir(&td->o, DDIR_WRITE, odir + 1);
                if (!ret) {
                        *odir = '\0';
-                       ret = bssplit_ddir(td, DDIR_READ, str);
+                       ret = bssplit_ddir(&td->o, DDIR_READ, str);
                }
        } else {
                char *op;
 
                op = strdup(str);
-               ret = bssplit_ddir(td, DDIR_WRITE, op);
+               ret = bssplit_ddir(&td->o, DDIR_WRITE, op);
                free(op);
 
                if (!ret) {
                        op = strdup(str);
-                       ret = bssplit_ddir(td, DDIR_TRIM, op);
+                       ret = bssplit_ddir(&td->o, DDIR_TRIM, op);
                        free(op);
                }
-               ret = bssplit_ddir(td, DDIR_READ, str);
+               ret = bssplit_ddir(&td->o, DDIR_READ, str);
        }
 
        free(p);
@@ -216,17 +215,17 @@ static int str_bssplit_cb(void *data, const char *input)
 
 static int str2error(char *str)
 {
-       const char * err[] = {"EPERM", "ENOENT", "ESRCH", "EINTR", "EIO",
+       const char *err[] = { "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO",
                            "ENXIO", "E2BIG", "ENOEXEC", "EBADF",
                            "ECHILD", "EAGAIN", "ENOMEM", "EACCES",
                            "EFAULT", "ENOTBLK", "EBUSY", "EEXIST",
                            "EXDEV", "ENODEV", "ENOTDIR", "EISDIR",
                            "EINVAL", "ENFILE", "EMFILE", "ENOTTY",
                            "ETXTBSY","EFBIG", "ENOSPC", "ESPIPE",
-                           "EROFS","EMLINK", "EPIPE", "EDOM", "ERANGE"};
+                           "EROFS","EMLINK", "EPIPE", "EDOM", "ERANGE" };
        int i = 0, num = sizeof(err) / sizeof(void *);
 
-       whilei < num) {
+       while (i < num) {
                if (!strcmp(err[i], str))
                        return i + 1;
                i++;
@@ -312,26 +311,27 @@ static int str_ignore_error_cb(void *data, const char *input)
 static int str_rw_cb(void *data, const char *str)
 {
        struct thread_data *td = data;
+       struct thread_options *o = &td->o;
        char *nr = get_opt_postfix(str);
 
-       td->o.ddir_seq_nr = 1;
-       td->o.ddir_seq_add = 0;
+       o->ddir_seq_nr = 1;
+       o->ddir_seq_add = 0;
 
        if (!nr)
                return 0;
 
        if (td_random(td))
-               td->o.ddir_seq_nr = atoi(nr);
+               o->ddir_seq_nr = atoi(nr);
        else {
                long long val;
 
-               if (str_to_decimal(nr, &val, 1, td)) {
+               if (str_to_decimal(nr, &val, 1, o)) {
                        log_err("fio: rw postfix parsing failed\n");
                        free(nr);
                        return 1;
                }
 
-               td->o.ddir_seq_add = val;
+               o->ddir_seq_add = val;
        }
 
        free(nr);
@@ -341,10 +341,11 @@ static int str_rw_cb(void *data, const char *str)
 static int str_mem_cb(void *data, const char *mem)
 {
        struct thread_data *td = data;
+       struct thread_options *o = &td->o;
 
-       if (td->o.mem_type == MEM_MMAPHUGE || td->o.mem_type == MEM_MMAP) {
-               td->mmapfile = get_opt_postfix(mem);
-               if (td->o.mem_type == MEM_MMAPHUGE && !td->mmapfile) {
+       if (o->mem_type == MEM_MMAPHUGE || o->mem_type == MEM_MMAP) {
+               o->mmapfile = get_opt_postfix(mem);
+               if (o->mem_type == MEM_MMAPHUGE && !o->mmapfile) {
                        log_err("fio: mmaphuge:/path/to/file\n");
                        return 1;
                }
@@ -353,18 +354,6 @@ static int str_mem_cb(void *data, const char *mem)
        return 0;
 }
 
-static int str_verify_cb(void *data, const char *mem)
-{
-       struct thread_data *td = data;
-
-       if (td->o.verify == VERIFY_CRC32C_INTEL ||
-           td->o.verify == VERIFY_CRC32C) {
-               crc32c_intel_probe();
-       }
-
-       return 0;
-}
-
 static int fio_clock_source_cb(void *data, const char *str)
 {
        struct thread_data *td = data;
@@ -374,12 +363,6 @@ static int fio_clock_source_cb(void *data, const char *str)
        return 0;
 }
 
-static int str_lockmem_cb(void fio_unused *data, unsigned long long *val)
-{
-       mlock_size = *val;
-       return 0;
-}
-
 static int str_rwmix_read_cb(void *data, unsigned long long *val)
 {
        struct thread_data *td = data;
@@ -398,40 +381,6 @@ static int str_rwmix_write_cb(void *data, unsigned long long *val)
        return 0;
 }
 
-#ifdef FIO_HAVE_IOPRIO
-static int str_prioclass_cb(void *data, unsigned long long *val)
-{
-       struct thread_data *td = data;
-       unsigned short mask;
-
-       /*
-        * mask off old class bits, str_prio_cb() may have set a default class
-        */
-       mask = (1 << IOPRIO_CLASS_SHIFT) - 1;
-       td->ioprio &= mask;
-
-       td->ioprio |= *val << IOPRIO_CLASS_SHIFT;
-       td->ioprio_set = 1;
-       return 0;
-}
-
-static int str_prio_cb(void *data, unsigned long long *val)
-{
-       struct thread_data *td = data;
-
-       td->ioprio |= *val;
-
-       /*
-        * If no class is set, assume BE
-        */
-       if ((td->ioprio >> IOPRIO_CLASS_SHIFT) == 0)
-               td->ioprio |= IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT;
-
-       td->ioprio_set = 1;
-       return 0;
-}
-#endif
-
 static int str_exitall_cb(void)
 {
        exitall_on_terminate = 1;
@@ -564,16 +513,6 @@ static int str_verify_cpus_allowed_cb(void *data, const char *input)
 }
 #endif
 
-#ifdef FIO_HAVE_TRIM
-static int str_verify_trim_cb(void *data, unsigned long long *val)
-{
-       struct thread_data *td = data;
-
-       td->o.trim_percentage = *val;
-       return 0;
-}
-#endif
-
 static int str_fst_cb(void *data, const char *str)
 {
        struct thread_data *td = data;
@@ -604,45 +543,6 @@ static int str_sfr_cb(void *data, const char *str)
 }
 #endif
 
-static int check_dir(struct thread_data *td, char *fname)
-{
-#if 0
-       char file[PATH_MAX], *dir;
-       int elen = 0;
-
-       if (td->o.directory) {
-               strcpy(file, td->o.directory);
-               strcat(file, "/");
-               elen = strlen(file);
-       }
-
-       sprintf(file + elen, "%s", fname);
-       dir = dirname(file);
-
-       {
-       struct stat sb;
-       /*
-        * We can't do this on FIO_DISKLESSIO engines. The engine isn't loaded
-        * yet, so we can't do this check right here...
-        */
-       if (lstat(dir, &sb) < 0) {
-               int ret = errno;
-
-               log_err("fio: %s is not a directory\n", dir);
-               td_verror(td, ret, "lstat");
-               return 1;
-       }
-
-       if (!S_ISDIR(sb.st_mode)) {
-               log_err("fio: %s is not a directory\n", dir);
-               return 1;
-       }
-       }
-#endif
-
-       return 0;
-}
-
 /*
  * Return next file in the string. Files are separated with ':'. If the ':'
  * is escaped with a '\', then that ':' is part of the filename and does not
@@ -705,10 +605,6 @@ static int str_filename_cb(void *data, const char *input)
        while ((fname = get_next_file_name(&str)) != NULL) {
                if (!strlen(fname))
                        break;
-               if (check_dir(td, fname)) {
-                       free(p);
-                       return 1;
-               }
                add_file(td, fname);
                td->o.nr_files++;
        }
@@ -747,25 +643,12 @@ static int str_opendir_cb(void *data, const char fio_unused *str)
        return add_dir_files(td, td->o.opendir);
 }
 
-static int str_verify_offset_cb(void *data, unsigned long long *off)
-{
-       struct thread_data *td = data;
-
-       if (*off && *off < sizeof(struct verify_header)) {
-               log_err("fio: verify_offset too small\n");
-               return 1;
-       }
-
-       td->o.verify_offset = *off;
-       return 0;
-}
-
 static int str_verify_pattern_cb(void *data, const char *input)
 {
        struct thread_data *td = data;
        long off;
        int i = 0, j = 0, len, k, base = 10;
-       char* loc1, * loc2;
+       char *loc1, *loc2;
 
        loc1 = strstr(input, "0x");
        loc2 = strstr(input, "0X");
@@ -841,39 +724,6 @@ static int str_lockfile_cb(void *data, const char *str)
        return 0;
 }
 
-static int str_write_bw_log_cb(void *data, const char *str)
-{
-       struct thread_data *td = data;
-
-       if (str)
-               td->o.bw_log_file = strdup(str);
-
-       td->o.write_bw_log = 1;
-       return 0;
-}
-
-static int str_write_lat_log_cb(void *data, const char *str)
-{
-       struct thread_data *td = data;
-
-       if (str)
-               td->o.lat_log_file = strdup(str);
-
-       td->o.write_lat_log = 1;
-       return 0;
-}
-
-static int str_write_iops_log_cb(void *data, const char *str)
-{
-       struct thread_data *td = data;
-
-       if (str)
-               td->o.iops_log_file = strdup(str);
-
-       td->o.write_iops_log = 1;
-       return 0;
-}
-
 static int str_gtod_reduce_cb(void *data, int *il)
 {
        struct thread_data *td = data;
@@ -955,54 +805,216 @@ static int kb_base_verify(struct fio_option *o, void *data)
        return 0;
 }
 
+/*
+ * Option grouping
+ */
+static struct opt_group fio_opt_groups[] = {
+       {
+               .name   = "General",
+               .mask   = FIO_OPT_C_GENERAL,
+       },
+       {
+               .name   = "I/O",
+               .mask   = FIO_OPT_C_IO,
+       },
+       {
+               .name   = "File",
+               .mask   = FIO_OPT_C_FILE,
+       },
+       {
+               .name   = "Statistics",
+               .mask   = FIO_OPT_C_STAT,
+       },
+       {
+               .name   = "Logging",
+               .mask   = FIO_OPT_C_LOG,
+       },
+       {
+               .name   = "Profiles",
+               .mask   = FIO_OPT_C_PROFILE,
+       },
+       {
+               .name   = NULL,
+       },
+};
+
+static struct opt_group *__opt_group_from_mask(struct opt_group *ogs, unsigned int *mask,
+                                              unsigned int inv_mask)
+{
+       struct opt_group *og;
+       int i;
+
+       if (*mask == inv_mask || !*mask)
+               return NULL;
+
+       for (i = 0; ogs[i].name; i++) {
+               og = &ogs[i];
+
+               if (*mask & og->mask) {
+                       *mask &= ~(og->mask);
+                       return og;
+               }
+       }
+
+       return NULL;
+}
+
+struct opt_group *opt_group_from_mask(unsigned int *mask)
+{
+       return __opt_group_from_mask(fio_opt_groups, mask, FIO_OPT_C_INVALID);
+}
+
+static struct opt_group fio_opt_cat_groups[] = {
+       {
+               .name   = "Rate",
+               .mask   = FIO_OPT_G_RATE,
+       },
+       {
+               .name   = "Zone",
+               .mask   = FIO_OPT_G_ZONE,
+       },
+       {
+               .name   = "Read/write mix",
+               .mask   = FIO_OPT_G_RWMIX,
+       },
+       {
+               .name   = "Verify",
+               .mask   = FIO_OPT_G_VERIFY,
+       },
+       {
+               .name   = "Trim",
+               .mask   = FIO_OPT_G_TRIM,
+       },
+       {
+               .name   = "I/O Logging",
+               .mask   = FIO_OPT_G_IOLOG,
+       },
+       {
+               .name   = "I/O Depth",
+               .mask   = FIO_OPT_G_IO_DEPTH,
+       },
+       {
+               .name   = "I/O Flow",
+               .mask   = FIO_OPT_G_IO_FLOW,
+       },
+       {
+               .name   = "Description",
+               .mask   = FIO_OPT_G_DESC,
+       },
+       {
+               .name   = "Filename",
+               .mask   = FIO_OPT_G_FILENAME,
+       },
+       {
+               .name   = "General I/O",
+               .mask   = FIO_OPT_G_IO_BASIC,
+       },
+       {
+               .name   = "Cgroups",
+               .mask   = FIO_OPT_G_CGROUP,
+       },
+       {
+               .name   = "Runtime",
+               .mask   = FIO_OPT_G_RUNTIME,
+       },
+       {
+               .name   = "Process",
+               .mask   = FIO_OPT_G_PROCESS,
+       },
+       {
+               .name   = "Job credentials / priority",
+               .mask   = FIO_OPT_G_CRED,
+       },
+       {
+               .name   = "Clock settings",
+               .mask   = FIO_OPT_G_CLOCK,
+       },
+       {
+               .name   = "I/O Type",
+               .mask   = FIO_OPT_G_IO_TYPE,
+       },
+       {
+               .name   = "I/O Thinktime",
+               .mask   = FIO_OPT_G_THINKTIME,
+       },
+       {
+               .name   = "Randomizations",
+               .mask   = FIO_OPT_G_RANDOM,
+       },
+       {
+               .name   = "I/O buffers",
+               .mask   = FIO_OPT_G_IO_BUF,
+       },
+       {
+               .name   = "Tiobench profile",
+               .mask   = FIO_OPT_G_TIOBENCH,
+       },
+
+       {
+               .name   = NULL,
+       }
+};
+
+struct opt_group *opt_group_cat_from_mask(unsigned int *mask)
+{
+       return __opt_group_from_mask(fio_opt_cat_groups, mask, FIO_OPT_G_INVALID);
+}
+
 /*
  * Map of job/command line options
  */
-static struct fio_option options[FIO_MAX_OPTS] = {
+struct fio_option fio_options[FIO_MAX_OPTS] = {
        {
                .name   = "description",
+               .lname  = "Description of job",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(description),
                .help   = "Text job description",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_DESC,
        },
        {
                .name   = "name",
+               .lname  = "Job name",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(name),
                .help   = "Name of this job",
-       },
-       {
-               .name   = "directory",
-               .type   = FIO_OPT_STR_STORE,
-               .off1   = td_var_offset(directory),
-               .cb     = str_directory_cb,
-               .help   = "Directory to store files in",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_DESC,
        },
        {
                .name   = "filename",
+               .lname  = "Filename(s)",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(filename),
                .cb     = str_filename_cb,
                .prio   = -1, /* must come after "directory" */
                .help   = "File(s) to use for the workload",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_FILENAME,
        },
        {
-               .name   = "kb_base",
-               .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(kb_base),
-               .verify = kb_base_verify,
-               .prio   = 1,
-               .def    = "1024",
-               .help   = "How many bytes per KB for reporting (1000 or 1024)",
+               .name   = "directory",
+               .lname  = "Directory",
+               .type   = FIO_OPT_STR_STORE,
+               .off1   = td_var_offset(directory),
+               .cb     = str_directory_cb,
+               .help   = "Directory to store files in",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_FILENAME,
        },
        {
                .name   = "lockfile",
+               .lname  = "Lockfile",
                .type   = FIO_OPT_STR,
                .cb     = str_lockfile_cb,
                .off1   = td_var_offset(file_lock_mode),
                .help   = "Lock file when doing IO to it",
                .parent = "filename",
+               .hide   = 0,
                .def    = "none",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_FILENAME,
                .posval = {
                          { .ival = "none",
                            .oval = FILE_LOCK_NONE,
@@ -1021,13 +1033,17 @@ static struct fio_option options[FIO_MAX_OPTS] = {
        },
        {
                .name   = "opendir",
+               .lname  = "Open directory",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(opendir),
                .cb     = str_opendir_cb,
                .help   = "Recursively add files from this directory and down",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_FILENAME,
        },
        {
                .name   = "rw",
+               .lname  = "Read/write",
                .alias  = "readwrite",
                .type   = FIO_OPT_STR,
                .cb     = str_rw_cb,
@@ -1035,6 +1051,8 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "IO direction",
                .def    = "read",
                .verify = rw_verify,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
                .posval = {
                          { .ival = "read",
                            .oval = TD_DDIR_READ,
@@ -1076,10 +1094,13 @@ static struct fio_option options[FIO_MAX_OPTS] = {
        },
        {
                .name   = "rw_sequencer",
+               .lname  = "RW Sequencer",
                .type   = FIO_OPT_STR,
                .off1   = td_var_offset(rw_seq),
                .help   = "IO offset generator modifier",
                .def    = "sequential",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
                .posval = {
                          { .ival = "sequential",
                            .oval = RW_SEQ_SEQ,
@@ -1094,10 +1115,13 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 
        {
                .name   = "ioengine",
+               .lname  = "IO Engine",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(ioengine),
                .help   = "IO engine to use",
                .def    = FIO_PREFERRED_ENGINE,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
                .posval = {
                          { .ival = "sync",
                            .help = "Use read/write",
@@ -1195,78 +1219,118 @@ static struct fio_option options[FIO_MAX_OPTS] = {
        },
        {
                .name   = "iodepth",
+               .lname  = "IO Depth",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(iodepth),
                .help   = "Number of IO buffers to keep in flight",
                .minval = 1,
+               .interval = 1,
                .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
        },
        {
                .name   = "iodepth_batch",
+               .lname  = "IO Depth batch",
                .alias  = "iodepth_batch_submit",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(iodepth_batch),
                .help   = "Number of IO buffers to submit in one go",
                .parent = "iodepth",
+               .hide   = 1,
                .minval = 1,
+               .interval = 1,
                .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
        },
        {
                .name   = "iodepth_batch_complete",
+               .lname  = "IO Depth batch complete",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(iodepth_batch_complete),
                .help   = "Number of IO buffers to retrieve in one go",
                .parent = "iodepth",
+               .hide   = 1,
                .minval = 0,
+               .interval = 1,
                .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
        },
        {
                .name   = "iodepth_low",
+               .lname  = "IO Depth batch low",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(iodepth_low),
                .help   = "Low water mark for queuing depth",
                .parent = "iodepth",
+               .hide   = 1,
+               .interval = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BASIC,
        },
        {
                .name   = "size",
+               .lname  = "Size",
                .type   = FIO_OPT_STR_VAL,
                .cb     = str_size_cb,
                .help   = "Total size of device or files",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "fill_device",
+               .lname  = "Fill device",
                .alias  = "fill_fs",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(fill_device),
                .help   = "Write until an ENOSPC error occurs",
                .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "filesize",
+               .lname  = "File size",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(file_size_low),
                .off2   = td_var_offset(file_size_high),
                .minval = 1,
                .help   = "Size of individual files",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "offset",
+               .lname  = "IO offset",
                .alias  = "fileoffset",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(start_offset),
                .help   = "Start IO from this offset",
                .def    = "0",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "offset_increment",
+               .lname  = "IO offset increment",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(offset_increment),
                .help   = "What is the increment from one offset to the next",
                .parent = "offset",
+               .hide   = 1,
                .def    = "0",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "bs",
+               .lname  = "Block size",
                .alias  = "blocksize",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(bs[DDIR_READ]),
@@ -1276,9 +1340,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Block size unit",
                .def    = "4k",
                .parent = "rw",
+               .hide   = 1,
+               .interval = 512,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "ba",
+               .lname  = "Block size align",
                .alias  = "blockalign",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(ba[DDIR_READ]),
@@ -1287,9 +1356,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .minval = 1,
                .help   = "IO block offset alignment",
                .parent = "rw",
+               .hide   = 1,
+               .interval = 512,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "bsrange",
+               .lname  = "Block size range",
                .alias  = "blocksize_range",
                .type   = FIO_OPT_RANGE,
                .off1   = td_var_offset(min_bs[DDIR_READ]),
@@ -1301,74 +1375,113 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .minval = 1,
                .help   = "Set block size range (in more detail than bs)",
                .parent = "rw",
+               .hide   = 1,
+               .interval = 4096,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "bssplit",
+               .lname  = "Block size split",
                .type   = FIO_OPT_STR,
                .cb     = str_bssplit_cb,
                .help   = "Set a specific mix of block sizes",
                .parent = "rw",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "bs_unaligned",
+               .lname  = "Block size unaligned",
                .alias  = "blocksize_unaligned",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(bs_unaligned),
                .help   = "Don't sector align IO buffer sizes",
                .parent = "rw",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "randrepeat",
+               .lname  = "Random repeatable",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(rand_repeatable),
                .help   = "Use repeatable random IO pattern",
                .def    = "1",
                .parent = "rw",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RANDOM,
        },
        {
                .name   = "use_os_rand",
+               .lname  = "Use OS random",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(use_os_rand),
                .help   = "Set to use OS random generator",
                .def    = "0",
                .parent = "rw",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RANDOM,
        },
        {
                .name   = "norandommap",
+               .lname  = "No randommap",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(norandommap),
                .help   = "Accept potential duplicate random blocks",
                .parent = "rw",
+               .hide   = 1,
+               .hide_on_set = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RANDOM,
        },
        {
                .name   = "softrandommap",
+               .lname  = "Soft randommap",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(softrandommap),
                .help   = "Set norandommap if randommap allocation fails",
                .parent = "norandommap",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RANDOM,
        },
        {
                .name   = "nrfiles",
+               .lname  = "Number of files",
                .alias  = "nr_files",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(nr_files),
                .help   = "Split job workload between this number of files",
                .def    = "1",
+               .interval = 1,
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "openfiles",
+               .lname  = "Number of open files",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(open_files),
                .help   = "Number of files to keep open at the same time",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "file_service_type",
+               .lname  = "File service type",
                .type   = FIO_OPT_STR,
                .cb     = str_fst_cb,
                .off1   = td_var_offset(file_service_type),
                .help   = "How to select which file to service next",
                .def    = "roundrobin",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
                .posval = {
                          { .ival = "random",
                            .oval = FIO_FSERVICE_RANDOM,
@@ -1384,14 +1497,18 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                          },
                },
                .parent = "nrfiles",
+               .hide   = 1,
        },
 #ifdef FIO_HAVE_FALLOCATE
        {
                .name   = "fallocate",
+               .lname  = "Fallocate",
                .type   = FIO_OPT_STR,
                .off1   = td_var_offset(fallocate_mode),
                .help   = "Whether pre-allocation is performed when laying out files",
                .def    = "posix",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
                .posval = {
                          { .ival = "none",
                            .oval = FIO_FALLOCATE_NONE,
@@ -1421,35 +1538,51 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 #endif /* FIO_HAVE_FALLOCATE */
        {
                .name   = "fadvise_hint",
+               .lname  = "Fadvise hint",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(fadvise_hint),
                .help   = "Use fadvise() to advise the kernel on IO pattern",
                .def    = "1",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "fsync",
+               .lname  = "Fsync",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(fsync_blocks),
                .help   = "Issue fsync for writes every given number of blocks",
                .def    = "0",
+               .interval = 1,
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "fdatasync",
+               .lname  = "Fdatasync",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(fdatasync_blocks),
                .help   = "Issue fdatasync for writes every given number of blocks",
                .def    = "0",
+               .interval = 1,
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "write_barrier",
+               .lname  = "Write barrier",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(barrier_blocks),
                .help   = "Make every Nth write a barrier write",
                .def    = "0",
+               .interval = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
 #ifdef FIO_HAVE_SYNC_FILE_RANGE
        {
                .name   = "sync_file_range",
+               .lname  = "Sync file range",
                .posval = {
                          { .ival = "wait_before",
                            .oval = SYNC_FILE_RANGE_WAIT_BEFORE,
@@ -1472,77 +1605,113 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_sfr_cb,
                .off1   = td_var_offset(sync_file_range),
                .help   = "Use sync_file_range()",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
 #endif
        {
                .name   = "direct",
+               .lname  = "Direct I/O",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(odirect),
                .help   = "Use O_DIRECT IO (negates buffered)",
                .def    = "0",
+               .inverse = "buffered",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_TYPE,
        },
        {
                .name   = "buffered",
+               .lname  = "Buffered I/O",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(odirect),
                .neg    = 1,
                .help   = "Use buffered IO (negates direct)",
                .def    = "1",
+               .inverse = "direct",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_TYPE,
        },
        {
                .name   = "overwrite",
+               .lname  = "Overwrite",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(overwrite),
                .help   = "When writing, set whether to overwrite current data",
                .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "loops",
+               .lname  = "Loops",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(loops),
                .help   = "Number of times to run the job",
                .def    = "1",
+               .interval = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
        },
        {
                .name   = "numjobs",
+               .lname  = "Number of jobs",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(numjobs),
                .help   = "Duplicate this job this many times",
                .def    = "1",
+               .interval = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
        },
        {
                .name   = "startdelay",
+               .lname  = "Start delay",
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(start_delay),
                .help   = "Only start job when this period has passed",
                .def    = "0",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
        },
        {
                .name   = "runtime",
+               .lname  = "Runtime",
                .alias  = "timeout",
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(timeout),
                .help   = "Stop workload when this amount of time has passed",
                .def    = "0",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
        },
        {
                .name   = "time_based",
+               .lname  = "Time based",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(time_based),
                .help   = "Keep running until runtime/timeout is met",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
        },
        {
                .name   = "ramp_time",
+               .lname  = "Ramp time",
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(ramp_time),
                .help   = "Ramp up time before measuring performance",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_RUNTIME,
        },
        {
                .name   = "clocksource",
+               .lname  = "Clock source",
                .type   = FIO_OPT_STR,
                .cb     = fio_clock_source_cb,
                .off1   = td_var_offset(clocksource),
                .help   = "What type of timing source to use",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CLOCK,
                .posval = {
                          { .ival = "gettimeofday",
                            .oval = CS_GTOD,
@@ -1563,11 +1732,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
        {
                .name   = "mem",
                .alias  = "iomem",
+               .lname  = "I/O Memory",
                .type   = FIO_OPT_STR,
                .cb     = str_mem_cb,
                .off1   = td_var_offset(mem_type),
                .help   = "Backing type for IO buffers",
                .def    = "malloc",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
                .posval = {
                          { .ival = "malloc",
                            .oval = MEM_MALLOC,
@@ -1598,20 +1770,26 @@ static struct fio_option options[FIO_MAX_OPTS] = {
        {
                .name   = "iomem_align",
                .alias  = "mem_align",
+               .lname  = "I/O memory alignment",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(mem_align),
                .minval = 0,
                .help   = "IO memory buffer offset alignment",
                .def    = "0",
                .parent = "iomem",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "verify",
+               .lname  = "Verify",
                .type   = FIO_OPT_STR,
                .off1   = td_var_offset(verify),
                .help   = "Verify data written",
-               .cb     = str_verify_cb,
                .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
                .posval = {
                          { .ival = "0",
                            .oval = VERIFY_NONE,
@@ -1670,535 +1848,765 @@ static struct fio_option options[FIO_MAX_OPTS] = {
        },
        {
                .name   = "do_verify",
+               .lname  = "Perform verify step",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(do_verify),
                .help   = "Run verification stage after write",
                .def    = "1",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verifysort",
+               .lname  = "Verify sort",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(verifysort),
                .help   = "Sort written verify blocks for read back",
                .def    = "1",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_interval",
+               .lname  = "Verify interval",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(verify_interval),
                .minval = 2 * sizeof(struct verify_header),
                .help   = "Store verify buffer header every N bytes",
                .parent = "verify",
+               .hide   = 1,
+               .interval = 2 * sizeof(struct verify_header),
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_offset",
+               .lname  = "Verify offset",
                .type   = FIO_OPT_INT,
                .help   = "Offset verify header location by N bytes",
-               .def    = "0",
-               .cb     = str_verify_offset_cb,
+               .off1   = td_var_offset(verify_offset),
+               .minval = sizeof(struct verify_header),
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_pattern",
+               .lname  = "Verify pattern",
                .type   = FIO_OPT_STR,
                .cb     = str_verify_pattern_cb,
                .help   = "Fill pattern for IO buffers",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_fatal",
+               .lname  = "Verify fatal",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(verify_fatal),
                .def    = "0",
                .help   = "Exit on a single verify failure, don't continue",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_dump",
+               .lname  = "Verify dump",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(verify_dump),
                .def    = "0",
                .help   = "Dump contents of good and bad blocks on failure",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_async",
+               .lname  = "Verify asynchronously",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(verify_async),
                .def    = "0",
                .help   = "Number of async verifier threads to use",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_backlog",
+               .lname  = "Verify backlog",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(verify_backlog),
                .help   = "Verify after this number of blocks are written",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_backlog_batch",
+               .lname  = "Verify backlog batch",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(verify_batch),
                .help   = "Verify this number of IO blocks",
                .parent = "verify",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
 #ifdef FIO_HAVE_CPU_AFFINITY
        {
                .name   = "verify_async_cpus",
+               .lname  = "Async verify CPUs",
                .type   = FIO_OPT_STR,
                .cb     = str_verify_cpus_allowed_cb,
                .help   = "Set CPUs allowed for async verify threads",
                .parent = "verify_async",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_VERIFY,
        },
 #endif
 #ifdef FIO_HAVE_TRIM
        {
                .name   = "trim_percentage",
+               .lname  = "Trim percentage",
                .type   = FIO_OPT_INT,
-               .cb     = str_verify_trim_cb,
+               .off1   = td_var_offset(trim_percentage),
+               .minval = 0,
                .maxval = 100,
                .help   = "Number of verify blocks to discard/trim",
                .parent = "verify",
                .def    = "0",
+               .interval = 1,
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_TRIM,
        },
        {
                .name   = "trim_verify_zero",
-               .type   = FIO_OPT_INT,
+               .lname  = "Verify trim zero",
+               .type   = FIO_OPT_BOOL,
                .help   = "Verify that trim/discarded blocks are returned as zeroes",
                .off1   = td_var_offset(trim_zero),
                .parent = "trim_percentage",
+               .hide   = 1,
                .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_TRIM,
        },
        {
                .name   = "trim_backlog",
+               .lname  = "Trim backlog",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(trim_backlog),
                .help   = "Trim after this number of blocks are written",
                .parent = "trim_percentage",
+               .hide   = 1,
+               .interval = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_TRIM,
        },
        {
                .name   = "trim_backlog_batch",
+               .lname  = "Trim backlog batch",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(trim_batch),
                .help   = "Trim this number of IO blocks",
                .parent = "trim_percentage",
+               .hide   = 1,
+               .interval = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_TRIM,
        },
 #endif
        {
                .name   = "write_iolog",
+               .lname  = "Write I/O log",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(write_iolog_file),
                .help   = "Store IO pattern to file",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IOLOG,
        },
        {
                .name   = "read_iolog",
+               .lname  = "Read I/O log",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(read_iolog_file),
                .help   = "Playback IO pattern from file",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IOLOG,
        },
        {
                .name   = "replay_no_stall",
-               .type   = FIO_OPT_INT,
+               .lname  = "Don't stall on replay",
+               .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(no_stall),
                .def    = "0",
                .parent = "read_iolog",
+               .hide   = 1,
                .help   = "Playback IO pattern file as fast as possible without stalls",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IOLOG,
        },
        {
                .name   = "replay_redirect",
+               .lname  = "Redirect device for replay",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(replay_redirect),
                .parent = "read_iolog",
+               .hide   = 1,
                .help   = "Replay all I/O onto this device, regardless of trace device",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IOLOG,
        },
        {
                .name   = "exec_prerun",
+               .lname  = "Pre-execute runnable",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(exec_prerun),
                .help   = "Execute this file prior to running job",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "exec_postrun",
+               .lname  = "Post-execute runnable",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(exec_postrun),
                .help   = "Execute this file after running job",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
        },
 #ifdef FIO_HAVE_IOSCHED_SWITCH
        {
                .name   = "ioscheduler",
+               .lname  = "I/O scheduler",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(ioscheduler),
                .help   = "Use this IO scheduler on the backing device",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
 #endif
        {
                .name   = "zonesize",
+               .lname  = "Zone size",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(zone_size),
                .help   = "Amount of data to read per zone",
                .def    = "0",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_ZONE,
        },
        {
                .name   = "zonerange",
+               .lname  = "Zone range",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(zone_range),
                .help   = "Give size of an IO zone",
                .def    = "0",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_ZONE,
        },
        {
                .name   = "zoneskip",
+               .lname  = "Zone skip",
                .type   = FIO_OPT_STR_VAL,
                .off1   = td_var_offset(zone_skip),
                .help   = "Space between IO zones",
                .def    = "0",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_ZONE,
        },
        {
                .name   = "lockmem",
+               .lname  = "Lock memory",
                .type   = FIO_OPT_STR_VAL,
-               .cb     = str_lockmem_cb,
+               .off1   = td_var_offset(lockmem),
                .help   = "Lock down this amount of memory",
                .def    = "0",
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "rwmixread",
+               .lname  = "Read/write mix read",
                .type   = FIO_OPT_INT,
                .cb     = str_rwmix_read_cb,
                .maxval = 100,
                .help   = "Percentage of mixed workload that is reads",
                .def    = "50",
+               .interval = 5,
+               .inverse = "rwmixwrite",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RWMIX,
        },
        {
                .name   = "rwmixwrite",
+               .lname  = "Read/write mix write",
                .type   = FIO_OPT_INT,
                .cb     = str_rwmix_write_cb,
                .maxval = 100,
                .help   = "Percentage of mixed workload that is writes",
                .def    = "50",
+               .interval = 5,
+               .inverse = "rwmixread",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RWMIX,
        },
        {
                .name   = "rwmixcycle",
+               .lname  = "Read/write mix cycle",
                .type   = FIO_OPT_DEPRECATED,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RWMIX,
        },
        {
                .name   = "nice",
+               .lname  = "Nice",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(nice),
                .help   = "Set job CPU nice value",
                .minval = -19,
                .maxval = 20,
                .def    = "0",
+               .interval = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
        },
 #ifdef FIO_HAVE_IOPRIO
        {
                .name   = "prio",
+               .lname  = "I/O nice priority",
                .type   = FIO_OPT_INT,
-               .cb     = str_prio_cb,
+               .off1   = td_var_offset(ioprio),
                .help   = "Set job IO priority value",
                .minval = 0,
                .maxval = 7,
+               .interval = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
        },
        {
                .name   = "prioclass",
+               .lname  = "I/O nice priority class",
                .type   = FIO_OPT_INT,
-               .cb     = str_prioclass_cb,
+               .off1   = td_var_offset(ioprio_class),
                .help   = "Set job IO priority class",
                .minval = 0,
                .maxval = 3,
+               .interval = 1,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
        },
 #endif
        {
                .name   = "thinktime",
+               .lname  = "Thinktime",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(thinktime),
                .help   = "Idle time between IO buffers (usec)",
                .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_THINKTIME,
        },
        {
                .name   = "thinktime_spin",
+               .lname  = "Thinktime spin",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(thinktime_spin),
                .help   = "Start think time by spinning this amount (usec)",
                .def    = "0",
                .parent = "thinktime",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_THINKTIME,
        },
        {
                .name   = "thinktime_blocks",
+               .lname  = "Thinktime blocks",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(thinktime_blocks),
                .help   = "IO buffer period between 'thinktime'",
                .def    = "1",
                .parent = "thinktime",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_THINKTIME,
        },
        {
                .name   = "rate",
+               .lname  = "I/O rate",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(rate[DDIR_READ]),
                .off2   = td_var_offset(rate[DDIR_WRITE]),
                .off3   = td_var_offset(rate[DDIR_TRIM]),
                .help   = "Set bandwidth rate",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "ratemin",
+               .lname  = "I/O min rate",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(ratemin[DDIR_READ]),
                .off2   = td_var_offset(ratemin[DDIR_WRITE]),
                .off3   = td_var_offset(ratemin[DDIR_TRIM]),
                .help   = "Job must meet this rate or it will be shutdown",
                .parent = "rate",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "rate_iops",
+               .lname  = "I/O rate IOPS",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(rate_iops[DDIR_READ]),
                .off2   = td_var_offset(rate_iops[DDIR_WRITE]),
                .off3   = td_var_offset(rate_iops[DDIR_TRIM]),
                .help   = "Limit IO used to this number of IO operations/sec",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "rate_iops_min",
+               .lname  = "I/O min rate IOPS",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(rate_iops_min[DDIR_READ]),
                .off2   = td_var_offset(rate_iops_min[DDIR_WRITE]),
                .off3   = td_var_offset(rate_iops_min[DDIR_TRIM]),
                .help   = "Job must meet this rate or it will be shut down",
                .parent = "rate_iops",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "ratecycle",
+               .lname  = "I/O rate cycle",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(ratecycle),
                .help   = "Window average for rate limits (msec)",
                .def    = "1000",
                .parent = "rate",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "invalidate",
+               .lname  = "Cache invalidate",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(invalidate_cache),
                .help   = "Invalidate buffer/page cache prior to running job",
                .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_TYPE,
        },
        {
                .name   = "sync",
+               .lname  = "Synchronous I/O",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(sync_io),
                .help   = "Use O_SYNC for buffered writes",
                .def    = "0",
                .parent = "buffered",
-       },
-       {
-               .name   = "bwavgtime",
-               .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(bw_avg_time),
-               .help   = "Time window over which to calculate bandwidth"
-                         " (msec)",
-               .def    = "500",
-               .parent = "write_bw_log",
-       },
-       {
-               .name   = "iopsavgtime",
-               .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(iops_avg_time),
-               .help   = "Time window over which to calculate IOPS (msec)",
-               .def    = "500",
-               .parent = "write_iops_log",
+               .hide   = 1,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_TYPE,
        },
        {
                .name   = "create_serialize",
+               .lname  = "Create serialize",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(create_serialize),
                .help   = "Serialize creating of job files",
                .def    = "1",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "create_fsync",
+               .lname  = "Create fsync",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(create_fsync),
                .help   = "fsync file after creation",
                .def    = "1",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "create_on_open",
+               .lname  = "Create on open",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(create_on_open),
                .help   = "Create files when they are opened for IO",
                .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "create_only",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(create_only),
                .help   = "Only perform file creation phase",
+               .category = FIO_OPT_C_FILE,
                .def    = "0",
        },
        {
                .name   = "pre_read",
+               .lname  = "Pre-read files",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(pre_read),
                .help   = "Pre-read files before starting official testing",
                .def    = "0",
-       },
-       {
-               .name   = "cpuload",
-               .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(cpuload),
-               .help   = "Use this percentage of CPU",
-       },
-       {
-               .name   = "cpuchunks",
-               .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(cpucycle),
-               .help   = "Length of the CPU burn cycles (usecs)",
-               .def    = "50000",
-               .parent = "cpuload",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
 #ifdef FIO_HAVE_CPU_AFFINITY
        {
                .name   = "cpumask",
+               .lname  = "CPU mask",
                .type   = FIO_OPT_INT,
                .cb     = str_cpumask_cb,
                .help   = "CPU affinity mask",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
        },
        {
                .name   = "cpus_allowed",
+               .lname  = "CPUs allowed",
                .type   = FIO_OPT_STR,
                .cb     = str_cpus_allowed_cb,
                .help   = "Set CPUs allowed",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
        },
 #endif
        {
                .name   = "end_fsync",
+               .lname  = "End fsync",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(end_fsync),
                .help   = "Include fsync at the end of job",
                .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "fsync_on_close",
+               .lname  = "Fsync on close",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(fsync_on_close),
                .help   = "fsync files on close",
                .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "unlink",
+               .lname  = "Unlink file",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(unlink),
                .help   = "Unlink created files after job has completed",
                .def    = "0",
+               .category = FIO_OPT_C_FILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "exitall",
+               .lname  = "Exit-all on terminate",
                .type   = FIO_OPT_STR_SET,
                .cb     = str_exitall_cb,
                .help   = "Terminate all jobs when one exits",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_PROCESS,
        },
        {
                .name   = "stonewall",
+               .lname  = "Wait for previous",
                .alias  = "wait_for_previous",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(stonewall),
                .help   = "Insert a hard barrier between this job and previous",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_PROCESS,
        },
        {
                .name   = "new_group",
+               .lname  = "New group",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(new_group),
                .help   = "Mark the start of a new group (for reporting)",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_PROCESS,
        },
        {
                .name   = "thread",
+               .lname  = "Thread",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(use_thread),
-               .help   = "Use threads instead of forks",
+               .help   = "Use threads instead of processes",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_PROCESS,
        },
        {
                .name   = "write_bw_log",
-               .type   = FIO_OPT_STR,
-               .off1   = td_var_offset(write_bw_log),
-               .cb     = str_write_bw_log_cb,
+               .lname  = "Write bandwidth log",
+               .type   = FIO_OPT_STR_STORE,
+               .off1   = td_var_offset(bw_log_file),
                .help   = "Write log of bandwidth during run",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "write_lat_log",
-               .type   = FIO_OPT_STR,
-               .off1   = td_var_offset(write_lat_log),
-               .cb     = str_write_lat_log_cb,
+               .lname  = "Write latency log",
+               .type   = FIO_OPT_STR_STORE,
+               .off1   = td_var_offset(lat_log_file),
                .help   = "Write log of latency during run",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "write_iops_log",
+               .lname  = "Write IOPS log",
                .type   = FIO_OPT_STR,
-               .off1   = td_var_offset(write_iops_log),
-               .cb     = str_write_iops_log_cb,
+               .off1   = td_var_offset(iops_log_file),
                .help   = "Write log of IOPS during run",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "log_avg_msec",
+               .lname  = "Log averaging (msec)",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(log_avg_msec),
                .help   = "Average bw/iops/lat logs over this period of time",
                .def    = "0",
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
-               .name   = "hugepage-size",
+               .name   = "bwavgtime",
+               .lname  = "Bandwidth average time",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(hugepage_size),
-               .help   = "When using hugepages, specify size of each page",
-               .def    = __fio_stringify(FIO_HUGE_PAGE),
+               .off1   = td_var_offset(bw_avg_time),
+               .help   = "Time window over which to calculate bandwidth"
+                         " (msec)",
+               .def    = "500",
+               .parent = "write_bw_log",
+               .hide   = 1,
+               .interval = 100,
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "iopsavgtime",
+               .lname  = "IOPS average time",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(iops_avg_time),
+               .help   = "Time window over which to calculate IOPS (msec)",
+               .def    = "500",
+               .parent = "write_iops_log",
+               .hide   = 1,
+               .interval = 100,
+               .category = FIO_OPT_C_LOG,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "group_reporting",
-               .type   = FIO_OPT_STR_SET,
+               .lname  = "Group reporting",
+               .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(group_reporting),
                .help   = "Do reporting on a per-group basis",
+               .def    = "1",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "zero_buffers",
+               .lname  = "Zero I/O buffers",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(zero_buffers),
                .help   = "Init IO buffers to all zeroes",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "refill_buffers",
+               .lname  = "Refill I/O buffers",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(refill_buffers),
                .help   = "Refill IO buffers on every IO submit",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "scramble_buffers",
+               .lname  = "Scramble I/O buffers",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(scramble_buffers),
                .help   = "Slightly scramble buffers on every IO submit",
                .def    = "1",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "buffer_compress_percentage",
+               .lname  = "Buffer compression percentage",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(compress_percentage),
                .maxval = 100,
                .minval = 1,
                .help   = "How compressible the buffer is (approximately)",
+               .interval = 5,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "buffer_compress_chunk",
+               .lname  = "Buffer compression chunk size",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(compress_chunk),
                .parent = "buffer_compress_percentage",
+               .hide   = 1,
                .help   = "Size of compressible region in buffer",
+               .interval = 256,
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "clat_percentiles",
+               .lname  = "Completion latency percentiles",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(clat_percentiles),
                .help   = "Enable the reporting of completion latency percentiles",
                .def    = "1",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "percentile_list",
+               .lname  = "Completion latency percentile list",
                .type   = FIO_OPT_FLOAT_LIST,
                .off1   = td_var_offset(percentile_list),
                .off2   = td_var_offset(overwrite_plist),
@@ -2206,69 +2614,100 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .maxlen = FIO_IO_U_LIST_MAX_LEN,
                .minfp  = 0.0,
                .maxfp  = 100.0,
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
 
 #ifdef FIO_HAVE_DISK_UTIL
        {
                .name   = "disk_util",
+               .lname  = "Disk utilization",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(do_disk_util),
                .help   = "Log disk utilization statistics",
                .def    = "1",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
 #endif
        {
                .name   = "gtod_reduce",
+               .lname  = "Reduce gettimeofday() calls",
                .type   = FIO_OPT_BOOL,
                .help   = "Greatly reduce number of gettimeofday() calls",
                .cb     = str_gtod_reduce_cb,
                .def    = "0",
+               .hide_on_set = 1,
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "disable_lat",
+               .lname  = "Disable all latency stats",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(disable_lat),
                .help   = "Disable latency numbers",
                .parent = "gtod_reduce",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "disable_clat",
+               .lname  = "Disable completion latency stats",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(disable_clat),
                .help   = "Disable completion latency numbers",
                .parent = "gtod_reduce",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "disable_slat",
+               .lname  = "Disable submission latency stats",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(disable_slat),
                .help   = "Disable submission latency numbers",
                .parent = "gtod_reduce",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "disable_bw_measurement",
+               .lname  = "Disable bandwidth stats",
                .type   = FIO_OPT_BOOL,
                .off1   = td_var_offset(disable_bw),
                .help   = "Disable bandwidth logging",
                .parent = "gtod_reduce",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_STAT,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "gtod_cpu",
+               .lname  = "Dedicated gettimeofday() CPU",
                .type   = FIO_OPT_INT,
                .cb     = str_gtod_cpu_cb,
                .help   = "Set up dedicated gettimeofday() thread on this CPU",
                .verify = gtod_cpu_verify,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CLOCK,
        },
        {
                .name   = "continue_on_error",
+               .lname  = "Continue on error",
                .type   = FIO_OPT_STR,
                .off1   = td_var_offset(continue_on_error),
                .help   = "Continue on non-fatal errors during IO",
                .def    = "none",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_ERR,
                .posval = {
                          { .ival = "none",
                            .oval = ERROR_TYPE_NONE,
@@ -2310,6 +2749,8 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_ignore_error_cb,
                .help   = "Set a specific list of errors to ignore",
                .parent = "rw",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_ERR,
        },
        {
                .name   = "error_dump",
@@ -2317,80 +2758,139 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(error_dump),
                .def    = "0",
                .help   = "Dump info on each error",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_ERR,
        },
-
        {
                .name   = "profile",
+               .lname  = "Profile",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(profile),
                .help   = "Select a specific builtin performance test",
+               .category = FIO_OPT_C_PROFILE,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "cgroup",
+               .lname  = "Cgroup",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(cgroup),
                .help   = "Add job to cgroup of this name",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CGROUP,
+       },
+       {
+               .name   = "cgroup_nodelete",
+               .lname  = "Cgroup no-delete",
+               .type   = FIO_OPT_BOOL,
+               .off1   = td_var_offset(cgroup_nodelete),
+               .help   = "Do not delete cgroups after job completion",
+               .def    = "0",
+               .parent = "cgroup",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CGROUP,
        },
        {
                .name   = "cgroup_weight",
+               .lname  = "Cgroup weight",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(cgroup_weight),
                .help   = "Use given weight for cgroup",
                .minval = 100,
                .maxval = 1000,
-       },
-       {
-               .name   = "cgroup_nodelete",
-               .type   = FIO_OPT_BOOL,
-               .off1   = td_var_offset(cgroup_nodelete),
-               .help   = "Do not delete cgroups after job completion",
-               .def    = "0",
+               .parent = "cgroup",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CGROUP,
        },
        {
                .name   = "uid",
+               .lname  = "User ID",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(uid),
                .help   = "Run job with this user ID",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
        },
        {
                .name   = "gid",
+               .lname  = "Group ID",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(gid),
                .help   = "Run job with this group ID",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CRED,
+       },
+       {
+               .name   = "kb_base",
+               .lname  = "KB Base",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(kb_base),
+               .verify = kb_base_verify,
+               .prio   = 1,
+               .def    = "1024",
+               .help   = "How many bytes per KB for reporting (1000 or 1024)",
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
+       },
+       {
+               .name   = "hugepage-size",
+               .lname  = "Hugepage size",
+               .type   = FIO_OPT_INT,
+               .off1   = td_var_offset(hugepage_size),
+               .help   = "When using hugepages, specify size of each page",
+               .def    = __fio_stringify(FIO_HUGE_PAGE),
+               .interval = 1024 * 1024,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_INVALID,
        },
        {
                .name   = "flow_id",
+               .lname  = "I/O flow ID",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(flow_id),
                .help   = "The flow index ID to use",
                .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_FLOW,
        },
        {
                .name   = "flow",
+               .lname  = "I/O flow weight",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(flow),
                .help   = "Weight for flow control of this job",
                .parent = "flow_id",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_FLOW,
        },
        {
                .name   = "flow_watermark",
+               .lname  = "I/O flow watermark",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(flow_watermark),
                .help   = "High watermark for flow control. This option"
                        " should be set to the same value for all threads"
                        " with non-zero flow.",
                .parent = "flow_id",
+               .hide   = 1,
                .def    = "1024",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_FLOW,
        },
        {
                .name   = "flow_sleep",
+               .lname  = "I/O flow sleep",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(flow_sleep),
                .help   = "How many microseconds to sleep after being held"
                        " back by the flow control mechanism",
                .parent = "flow_id",
+               .hide   = 1,
                .def    = "0",
+               .category = FIO_OPT_C_IO,
+               .group  = FIO_OPT_G_IO_FLOW,
        },
        {
                .name = NULL,
@@ -2454,13 +2954,13 @@ void fio_options_dup_and_init(struct option *long_options)
 {
        unsigned int i;
 
-       options_init(options);
+       options_init(fio_options);
 
        i = 0;
        while (long_options[i].name)
                i++;
 
-       options_to_lopts(options, long_options, i, FIO_GETOPT_JOB);
+       options_to_lopts(fio_options, long_options, i, FIO_GETOPT_JOB);
 }
 
 struct fio_keyword {
@@ -2543,14 +3043,12 @@ static char *bc_calc(char *str)
 
        sprintf(buf, "echo '%s' | %s", tmp, BC_APP);
        f = popen(buf, "r");
-       if (!f) {
+       if (!f)
                return NULL;
-       }
 
        ret = fread(&buf[tmp - str], 1, 128 - (tmp - str), f);
-       if (ret <= 0) {
+       if (ret <= 0)
                return NULL;
-       }
 
        pclose(f);
        buf[(tmp - str) + ret - 1] = '\0';
@@ -2683,13 +3181,13 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
        int i, ret, unknown;
        char **opts_copy;
 
-       sort_options(opts, options, num_opts);
+       sort_options(opts, fio_options, num_opts);
        opts_copy = dup_and_sub_options(opts, num_opts);
 
        for (ret = 0, i = 0, unknown = 0; i < num_opts; i++) {
                struct fio_option *o;
-               int newret = parse_option(opts_copy[i], opts[i], options, &o,
-                                         td);
+               int newret = parse_option(opts_copy[i], opts[i], fio_options,
+                                               &o, td);
 
                if (opts_copy[i]) {
                        if (newret && !o) {
@@ -2735,7 +3233,7 @@ int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
 
 int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val)
 {
-       return parse_cmd_option(opt, val, options, td);
+       return parse_cmd_option(opt, val, fio_options, td);
 }
 
 int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
@@ -2746,12 +3244,12 @@ int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
 
 void fio_fill_default_options(struct thread_data *td)
 {
-       fill_default_options(td, options);
+       fill_default_options(td, fio_options);
 }
 
 int fio_show_option_help(const char *opt)
 {
-       return show_cmd_help(options, opt);
+       return show_cmd_help(fio_options, opt);
 }
 
 void options_mem_dupe(void *data, struct fio_option *options)
@@ -2774,7 +3272,7 @@ void options_mem_dupe(void *data, struct fio_option *options)
  */
 void fio_options_mem_dupe(struct thread_data *td)
 {
-       options_mem_dupe(&td->o, options);
+       options_mem_dupe(&td->o, fio_options);
 
        if (td->eo && td->io_ops) {
                void *oldeo = td->eo;
@@ -2787,11 +3285,11 @@ void fio_options_mem_dupe(struct thread_data *td)
 
 unsigned int fio_get_kb_base(void *data)
 {
-       struct thread_data *td = data;
+       struct thread_options *o = data;
        unsigned int kb_base = 0;
 
-       if (td)
-               kb_base = td->o.kb_base;
+       if (o)
+               kb_base = o->kb_base;
        if (!kb_base)
                kb_base = 1024;
 
@@ -2803,13 +3301,13 @@ int add_option(struct fio_option *o)
        struct fio_option *__o;
        int opt_index = 0;
 
-       __o = options;
+       __o = fio_options;
        while (__o->name) {
                opt_index++;
                __o++;
        }
 
-       memcpy(&options[opt_index], o, sizeof(*o));
+       memcpy(&fio_options[opt_index], o, sizeof(*o));
        return 0;
 }
 
@@ -2817,7 +3315,7 @@ void invalidate_profile_options(const char *prof_name)
 {
        struct fio_option *o;
 
-       o = options;
+       o = fio_options;
        while (o->name) {
                if (o->prof_name && !strcmp(o->prof_name, prof_name)) {
                        o->type = FIO_OPT_INVALID;
@@ -2832,7 +3330,7 @@ void add_opt_posval(const char *optname, const char *ival, const char *help)
        struct fio_option *o;
        unsigned int i;
 
-       o = find_option(options, optname);
+       o = find_option(fio_options, optname);
        if (!o)
                return;
 
@@ -2851,7 +3349,7 @@ void del_opt_posval(const char *optname, const char *ival)
        struct fio_option *o;
        unsigned int i;
 
-       o = find_option(options, optname);
+       o = find_option(fio_options, optname);
        if (!o)
                return;
 
@@ -2868,10 +3366,16 @@ void del_opt_posval(const char *optname, const char *ival)
 
 void fio_options_free(struct thread_data *td)
 {
-       options_free(options, td);
+       options_free(fio_options, td);
        if (td->eo && td->io_ops && td->io_ops->options) {
                options_free(td->io_ops->options, td->eo);
                free(td->eo);
                td->eo = NULL;
        }
 }
+
+struct fio_option *fio_option_find(const char *name)
+{
+       return find_option(fio_options, name);
+}
+
index ed6b9c2da47a5699cb9b4d3f1808eba4b8ede019..330cc87665215769180941d773800496d19a4cff 100644 (file)
--- a/options.h
+++ b/options.h
@@ -18,6 +18,8 @@ void del_opt_posval(const char *, const char *);
 struct thread_data;
 void fio_options_free(struct thread_data *);
 
+extern struct fio_option fio_options[FIO_MAX_OPTS];
+
 static inline int o_match(struct fio_option *o, const char *opt)
 {
        if (!strcmp(o->name, opt))
@@ -40,4 +42,82 @@ static inline struct fio_option *find_option(struct fio_option *options,
        return NULL;
 }
 
+struct opt_group {
+       const char *name;
+       unsigned int mask;
+};
+
+enum opt_category {
+       __FIO_OPT_C_GENERAL     = 0,
+       __FIO_OPT_C_IO,
+       __FIO_OPT_C_FILE,
+       __FIO_OPT_C_STAT,
+       __FIO_OPT_C_LOG,
+       __FIO_OPT_C_PROFILE,
+       __FIO_OPT_C_NR,
+
+       FIO_OPT_C_GENERAL       = (1U << __FIO_OPT_C_GENERAL),
+       FIO_OPT_C_IO            = (1U << __FIO_OPT_C_IO),
+       FIO_OPT_C_FILE          = (1U << __FIO_OPT_C_FILE),
+       FIO_OPT_C_STAT          = (1U << __FIO_OPT_C_STAT),
+       FIO_OPT_C_LOG           = (1U << __FIO_OPT_C_LOG),
+       FIO_OPT_C_PROFILE       = (1U << __FIO_OPT_C_PROFILE),
+       FIO_OPT_C_INVALID       = (1U << __FIO_OPT_C_NR),
+};
+
+enum opt_category_group {
+       __FIO_OPT_G_RATE        = 0,
+       __FIO_OPT_G_ZONE,
+       __FIO_OPT_G_RWMIX,
+       __FIO_OPT_G_VERIFY,
+       __FIO_OPT_G_TRIM,
+       __FIO_OPT_G_IOLOG,
+       __FIO_OPT_G_IO_DEPTH,
+       __FIO_OPT_G_IO_FLOW,
+       __FIO_OPT_G_DESC,
+       __FIO_OPT_G_FILENAME,
+       __FIO_OPT_G_IO_BASIC,
+       __FIO_OPT_G_CGROUP,
+       __FIO_OPT_G_RUNTIME,
+       __FIO_OPT_G_PROCESS,
+       __FIO_OPT_G_CRED,
+       __FIO_OPT_G_CLOCK,
+       __FIO_OPT_G_IO_TYPE,
+       __FIO_OPT_G_THINKTIME,
+       __FIO_OPT_G_RANDOM,
+       __FIO_OPT_G_IO_BUF,
+       __FIO_OPT_G_TIOBENCH,
+       __FIO_OPT_G_ERR,
+       __FIO_OPT_G_NR,
+
+       FIO_OPT_G_RATE          = (1U << __FIO_OPT_G_RATE),
+       FIO_OPT_G_ZONE          = (1U << __FIO_OPT_G_ZONE),
+       FIO_OPT_G_RWMIX         = (1U << __FIO_OPT_G_RWMIX),
+       FIO_OPT_G_VERIFY        = (1U << __FIO_OPT_G_VERIFY),
+       FIO_OPT_G_TRIM          = (1U << __FIO_OPT_G_TRIM),
+       FIO_OPT_G_IOLOG         = (1U << __FIO_OPT_G_IOLOG),
+       FIO_OPT_G_IO_DEPTH      = (1U << __FIO_OPT_G_IO_DEPTH),
+       FIO_OPT_G_IO_FLOW       = (1U << __FIO_OPT_G_IO_FLOW),
+       FIO_OPT_G_DESC          = (1U << __FIO_OPT_G_DESC),
+       FIO_OPT_G_FILENAME      = (1U << __FIO_OPT_G_FILENAME),
+       FIO_OPT_G_IO_BASIC      = (1U << __FIO_OPT_G_IO_BASIC),
+       FIO_OPT_G_CGROUP        = (1U << __FIO_OPT_G_CGROUP),
+       FIO_OPT_G_RUNTIME       = (1U << __FIO_OPT_G_RUNTIME),
+       FIO_OPT_G_PROCESS       = (1U << __FIO_OPT_G_PROCESS),
+       FIO_OPT_G_CRED          = (1U << __FIO_OPT_G_CRED),
+       FIO_OPT_G_CLOCK         = (1U << __FIO_OPT_G_CLOCK),
+       FIO_OPT_G_IO_TYPE       = (1U << __FIO_OPT_G_IO_TYPE),
+       FIO_OPT_G_THINKTIME     = (1U << __FIO_OPT_G_THINKTIME),
+       FIO_OPT_G_RANDOM        = (1U << __FIO_OPT_G_RANDOM),
+       FIO_OPT_G_IO_BUF        = (1U << __FIO_OPT_G_IO_BUF),
+       FIO_OPT_G_TIOBENCH      = (1U << __FIO_OPT_G_TIOBENCH),
+       FIO_OPT_G_ERR           = (1U << __FIO_OPT_G_ERR),
+       FIO_OPT_G_INVALID       = (1U << __FIO_OPT_G_NR),
+};
+
+extern struct opt_group *opt_group_from_mask(unsigned int *mask);
+extern struct opt_group *opt_group_cat_from_mask(unsigned int *mask);
+extern struct fio_option *fio_option_find(const char *name);
+extern unsigned int fio_get_kb_base(void *);
+
 #endif
index 9b7ff29e50af7f07d656cf3e763e7fafe409a669..081f5d6c632b57ae322ff93e8d39d5c985b14e9e 100644 (file)
@@ -116,8 +116,31 @@ static inline int fio_cpuset_exit(os_cpu_mask_t *mask)
 
 #define FIO_MAX_CPUS                   CPU_SETSIZE
 
-static inline int ioprio_set(int which, int who, int ioprio)
+enum {
+       IOPRIO_CLASS_NONE,
+       IOPRIO_CLASS_RT,
+       IOPRIO_CLASS_BE,
+       IOPRIO_CLASS_IDLE,
+};
+
+enum {
+       IOPRIO_WHO_PROCESS = 1,
+       IOPRIO_WHO_PGRP,
+       IOPRIO_WHO_USER,
+};
+
+#define IOPRIO_BITS            16
+#define IOPRIO_CLASS_SHIFT     13
+
+static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio)
 {
+       /*
+        * If no class is set, assume BE
+        */
+       if (!ioprio_class)
+               ioprio_class = IOPRIO_CLASS_BE;
+
+       ioprio |= ioprio_class << IOPRIO_CLASS_SHIFT;
        return syscall(__NR_ioprio_set, which, who, ioprio);
 }
 
@@ -191,22 +214,6 @@ static inline long umem_add(unsigned long *uptr, unsigned long inc)
 }
 #endif /* FIO_HAVE_SYSLET */
 
-enum {
-       IOPRIO_CLASS_NONE,
-       IOPRIO_CLASS_RT,
-       IOPRIO_CLASS_BE,
-       IOPRIO_CLASS_IDLE,
-};
-
-enum {
-       IOPRIO_WHO_PROCESS = 1,
-       IOPRIO_WHO_PGRP,
-       IOPRIO_WHO_USER,
-};
-
-#define IOPRIO_BITS            16
-#define IOPRIO_CLASS_SHIFT     13
-
 #ifndef BLKGETSIZE64
 #define BLKGETSIZE64   _IOR(0x12,114,size_t)
 #endif
diff --git a/os/os.h b/os/os.h
index c6da0c3d9f0825efe6f078f59aa6566c6bc27258..e2c188f1235cc5c0e85d280582ba0ed0a4b18483 100644 (file)
--- a/os/os.h
+++ b/os/os.h
@@ -84,7 +84,7 @@ typedef unsigned long os_cpu_mask_t;
 #endif
 
 #ifndef FIO_HAVE_IOPRIO
-#define ioprio_set(which, who, prio)   (0)
+#define ioprio_set(which, who, prioclass, prio)        (0)
 #endif
 
 #ifndef FIO_HAVE_ODIRECT
diff --git a/parse.c b/parse.c
index 1a686e4cceb89a5ee8446d10a4472556d3445764..9a6494f32db811746ba46303b5f300a83e1ddfc6 100644 (file)
--- a/parse.c
+++ b/parse.c
@@ -15,8 +15,7 @@
 #include "debug.h"
 #include "options.h"
 
-static struct fio_option *fio_options;
-extern unsigned int fio_get_kb_base(void *);
+static struct fio_option *__fio_options;
 
 static int vp_cmp(const void *p1, const void *p2)
 {
@@ -47,7 +46,7 @@ static void posval_sort(struct fio_option *o, struct value_pair *vpmap)
 static void show_option_range(struct fio_option *o,
                                int (*logger)(const char *format, ...))
 {
-       if (o->type == FIO_OPT_FLOAT_LIST){
+       if (o->type == FIO_OPT_FLOAT_LIST) {
                if (isnan(o->minfp) && isnan(o->maxfp))
                        return;
 
@@ -153,32 +152,32 @@ static unsigned long long __get_mult_bytes(const char *p, void *data,
        for (i = 0; i < strlen(c); i++)
                c[i] = tolower(c[i]);
 
-       if (!strcmp("pib", c)) {
+       if (!strncmp("pib", c, 3)) {
                pow = 5;
                mult = 1000;
-       } else if (!strcmp("tib", c)) {
+       } else if (!strncmp("tib", c, 3)) {
                pow = 4;
                mult = 1000;
-       } else if (!strcmp("gib", c)) {
+       } else if (!strncmp("gib", c, 3)) {
                pow = 3;
                mult = 1000;
-       } else if (!strcmp("mib", c)) {
+       } else if (!strncmp("mib", c, 3)) {
                pow = 2;
                mult = 1000;
-       } else if (!strcmp("kib", c)) {
+       } else if (!strncmp("kib", c, 3)) {
                pow = 1;
                mult = 1000;
-       } else if (!strcmp("p", c) || !strcmp("pb", c))
+       } else if (!strncmp("p", c, 1) || !strncmp("pb", c, 2))
                pow = 5;
-       else if (!strcmp("t", c) || !strcmp("tb", c))
+       else if (!strncmp("t", c, 1) || !strncmp("tb", c, 2))
                pow = 4;
-       else if (!strcmp("g", c) || !strcmp("gb", c))
+       else if (!strncmp("g", c, 1) || !strncmp("gb", c, 2))
                pow = 3;
-       else if (!strcmp("m", c) || !strcmp("mb", c))
+       else if (!strncmp("m", c, 1) || !strncmp("mb", c, 2))
                pow = 2;
-       else if (!strcmp("k", c) || !strcmp("kb", c))
+       else if (!strncmp("k", c, 1) || !strncmp("kb", c, 2))
                pow = 1;
-       else if (!strcmp("%", c)) {
+       else if (!strncmp("%", c, 1)) {
                *percent = 1;
                free(c);
                return ret;
@@ -260,7 +259,7 @@ int str_to_decimal(const char *str, long long *val, int kilo, void *data)
        return 0;
 }
 
-static int check_str_bytes(const char *p, long long *val, void *data)
+int check_str_bytes(const char *p, long long *val, void *data)
 {
        return str_to_decimal(p, val, 1, data);
 }
@@ -356,7 +355,7 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
                           int first, int more, int curr)
 {
        int il, *ilp;
-       doubleflp;
+       double *flp;
        long long ull, *ullp;
        long ul1, ul2;
        double uf;
@@ -505,7 +504,7 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
                                        o->maxlen);
                        return 1;
                }
-               if(!str_to_float(ptr, &uf)){
+               if (!str_to_float(ptr, &uf)) {
                        log_err("not a floating point value: %s\n", ptr);
                        return 1;
                }
@@ -827,14 +826,14 @@ static int opt_cmp(const void *p1, const void *p2)
 
        if (*(char **)p1) {
                s = strdup(*((char **) p1));
-               o = get_option(s, fio_options, &foo);
+               o = get_option(s, __fio_options, &foo);
                if (o)
                        prio1 = o->prio;
                free(s);
        }
        if (*(char **)p2) {
                s = strdup(*((char **) p2));
-               o = get_option(s, fio_options, &foo);
+               o = get_option(s, __fio_options, &foo);
                if (o)
                        prio2 = o->prio;
                free(s);
@@ -845,9 +844,9 @@ static int opt_cmp(const void *p1, const void *p2)
 
 void sort_options(char **opts, struct fio_option *options, int num_opts)
 {
-       fio_options = options;
+       __fio_options = options;
        qsort(opts, num_opts, sizeof(char *), opt_cmp);
-       fio_options = NULL;
+       __fio_options = NULL;
 }
 
 int parse_cmd_option(const char *opt, const char *val,
@@ -890,9 +889,8 @@ int parse_option(char *opt, const char *input,
                return 1;
        }
 
-       if (!handle_option(*o, post, data)) {
+       if (!handle_option(*o, post, data))
                return 0;
-       }
 
        log_err("fio: failed parsing %s\n", input);
        return 1;
@@ -1108,6 +1106,10 @@ void option_init(struct fio_option *o)
                      (o->roff1 || o->roff2 || o->roff3 || o->roff4))) {
                log_err("Option %s: both cb and offset given\n", o->name);
        }
+       if (!o->category) {
+               log_info("Options %s: no category defined. Setting to misc\n", o->name);
+               o->category = FIO_OPT_C_GENERAL;
+       }
 }
 
 /*
@@ -1120,8 +1122,11 @@ void options_init(struct fio_option *options)
 
        dprint(FD_PARSE, "init options\n");
 
-       for (o = &options[0]; o->name; o++)
+       for (o = &options[0]; o->name; o++) {
                option_init(o);
+               if (o->inverse)
+                       o->inv_opt = find_option(options, o->inverse);
+       }
 }
 
 void options_free(struct fio_option *options, void *data)
diff --git a/parse.h b/parse.h
index 71190ea010e470bd7f1c7ad4b8846a2749c36da1..7fee4fa423d900a21296a4af37c96a014dcb983f 100644 (file)
--- a/parse.h
+++ b/parse.h
@@ -40,6 +40,7 @@ struct value_pair {
  */
 struct fio_option {
        const char *name;               /* option name */
+       const char *lname;              /* long option name */
        const char *alias;              /* possible old allowed name */
        enum fio_opt_type type;         /* option type */
        unsigned int off1;              /* potential parameters */
@@ -53,6 +54,7 @@ struct fio_option {
        int minval;
        double maxfp;                   /* max and min floating value */
        double minfp;
+       unsigned int interval;          /* client hint for suitable interval */
        unsigned int maxlen;            /* max length */
        int neg;                        /* negate value stored */
        int prio;
@@ -61,8 +63,15 @@ struct fio_option {
        const char *def;                /* default setting */
        struct value_pair posval[PARSE_MAX_VP];/* possible values */
        const char *parent;             /* parent option */
+       int hide;                       /* hide if parent isn't set */
+       int hide_on_set;                /* hide on set, not on unset */
+       const char *inverse;            /* if set, apply opposite action to this option */
+       struct fio_option *inv_opt;     /* cached lookup */
        int (*verify)(struct fio_option *, void *);
        const char *prof_name;          /* only valid for specific profile */
+       unsigned int category;          /* what type of option */
+       unsigned int group;             /* who to group with */
+       void *gui_data;
 };
 
 typedef int (str_cb_fn)(void *, char *);
@@ -79,6 +88,7 @@ extern void options_free(struct fio_option *, void *);
 extern void strip_blank_front(char **);
 extern void strip_blank_end(char *);
 extern int str_to_decimal(const char *, long long *, int, void *);
+extern int check_str_bytes(const char *p, long long *val, void *data);
 
 /*
  * Handlers for the options
diff --git a/printing.c b/printing.c
new file mode 100644 (file)
index 0000000..4dcc986
--- /dev/null
@@ -0,0 +1,139 @@
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+#include "gfio.h"
+#include "cairo_text_helpers.h"
+#include "printing.h"
+
+
+static struct printing_parameters {
+       gdouble width, height, xdpi, ydpi;
+       GtkPrintSettings *settings;
+       GtkPageSetup *page_setup;
+} print_params = { 0 };
+
+static void begin_print(GtkPrintOperation *operation,
+                       GtkPrintContext *context, gpointer data)
+{
+       print_params.page_setup = gtk_print_context_get_page_setup(context);
+
+       print_params.width = gtk_print_context_get_width(context);
+       print_params.height = gtk_print_context_get_height(context);
+       print_params.xdpi = gtk_print_context_get_dpi_x(context);
+       print_params.ydpi = gtk_print_context_get_dpi_y(context);
+
+       /* assume 1 page for now. */
+       gtk_print_operation_set_n_pages(operation, 1);
+}
+
+static void results_draw_page(GtkPrintOperation *operation,
+                             GtkPrintContext *context, gint page_nr,
+                             gpointer data)
+{
+       cairo_t *cr;
+       char str[20];
+       double x, y;
+
+       cr = gtk_print_context_get_cairo_context(context);
+
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       cairo_set_line_width(cr, 5.0);
+       cairo_move_to(cr, 0.0, 0.0);
+       cairo_line_to(cr, print_params.width, print_params.height);
+       cairo_move_to(cr, 0.0, print_params.height);
+       cairo_line_to(cr, print_params.width, 0.0);
+       cairo_stroke(cr);
+
+       x = print_params.width / 4.0;
+       y = print_params.height / 5.0;
+       sprintf(str, "(%g,%g)", x, y);
+       draw_right_justified_text(cr, "Sans", x, y, 12.0, str);
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       cairo_set_line_width(cr, 2.0);
+       cairo_move_to(cr, x, y - 30.0);
+       cairo_line_to(cr, x, y + 30.0);
+       cairo_move_to(cr, x - 30, y);
+       cairo_line_to(cr, x + 30, y);
+
+       y *= 4.0;
+       x *= 2.0;
+       sprintf(str, "(%g,%g)", x, y);
+       draw_right_justified_text(cr, "Sans", x, y, 12.0, str);
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       cairo_set_line_width(cr, 2.0);
+       cairo_move_to(cr, x, y - 30.0);
+       cairo_line_to(cr, x, y + 30.0);
+       cairo_move_to(cr, x - 30, y);
+       cairo_line_to(cr, x + 30, y);
+       cairo_stroke(cr);
+}
+
+static void printing_error_dialog(GtkWidget *window, GError *print_error)
+{
+       GtkWidget *error_dialog;
+
+       printf("printing_error_dialog called\n");
+       printf("error message = %s\n", print_error->message);
+       error_dialog = gtk_message_dialog_new(GTK_WINDOW(window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
+                       GTK_BUTTONS_CLOSE, "Print error:\n%s",
+                       print_error->message);
+       g_signal_connect(error_dialog, "response",
+                       G_CALLBACK(gtk_widget_destroy), NULL);
+       gtk_widget_show(error_dialog);
+}
+
+static void results_print_done(GtkPrintOperation *operation,
+                       GtkPrintOperationResult result, gpointer data)
+{
+       GError *print_error;
+       struct gui_entry *ge = data;
+
+       if (result != GTK_PRINT_OPERATION_RESULT_ERROR)
+               return;
+
+       gtk_print_operation_get_error(operation, &print_error);
+       printing_error_dialog(ge->results_window, print_error);
+       g_error_free(print_error);
+}
+
+void gfio_print_results(struct gui_entry *ge)
+{
+       GtkPrintOperation *print;
+       GtkPrintOperationResult res;
+       GError *print_error;
+
+       print = gtk_print_operation_new();
+       if (print_params.settings != NULL)
+               gtk_print_operation_set_print_settings(print, print_params.settings);
+
+       if (print_params.page_setup != NULL)
+               gtk_print_operation_set_default_page_setup(print, print_params.page_setup);
+
+       g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), NULL);
+       g_signal_connect(print, "draw_page", G_CALLBACK(results_draw_page), NULL);
+       g_signal_connect(print, "done", G_CALLBACK(results_print_done), NULL);
+       gtk_print_operation_set_allow_async(print, TRUE);
+       res = gtk_print_operation_run(print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+               GTK_WINDOW(ge->results_window), &print_error);
+
+       /*
+        * Something's not quite right about the error handling.  If I print
+        * to a file, and the file exists, and I don't have write permission
+        * on that file but attempt to replace it anyway, then it just kind of
+        * hangs and I don't get into any of this error handling stuff at all,
+        * neither here, nor in results_print_done().
+        */
+
+       if (res == GTK_PRINT_OPERATION_RESULT_ERROR) {
+               printing_error_dialog(ge->results_window, print_error);
+               g_error_free(print_error);
+       } else {
+               if (res == GTK_PRINT_OPERATION_RESULT_APPLY) {
+                       if (print_params.settings != NULL)
+                               g_object_unref(print_params.settings);
+                       print_params.settings = g_object_ref(gtk_print_operation_get_print_settings(print));
+               }
+       }
+       g_object_unref(print);
+}
diff --git a/printing.h b/printing.h
new file mode 100644 (file)
index 0000000..0079919
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef PRINTING_H
+#define PRINTING_H
+
+void gfio_print_results(struct gui_entry *ge);
+
+#endif
index 855dde3e6d1efb2c316d28265be80754d604283b..74f14facd6c6d0115bea1af3dc0c328fda1fdfd7 100644 (file)
--- a/profile.c
+++ b/profile.c
@@ -31,7 +31,7 @@ int load_profile(const char *profile)
        ops = find_profile(profile);
        if (ops) {
                ops->prep_cmd();
-               add_job_opts(ops->cmdline);
+               add_job_opts(ops->cmdline, FIO_CLIENT_TYPE_CLI);
                return 0;
        }
 
@@ -42,7 +42,7 @@ int load_profile(const char *profile)
 static int add_profile_options(struct profile_ops *ops)
 {
        struct fio_option *o;
-       
+
        if (!ops->options)
                return 0;
 
index f86a33787d9ca4e5f25fe2d096089c04b902154d..0279e1cafbec95d286f802889ec8045729a2c3cc 100644 (file)
@@ -23,34 +23,49 @@ static const char *tb_opts[] = {
 static struct fio_option options[] = {
        {
                .name   = "size",
+               .lname  = "Tiobench size",
                .type   = FIO_OPT_STR_VAL,
                .roff1  = &size,
                .help   = "Size in MB",
+               .category = FIO_OPT_C_PROFILE,
+               .group  = FIO_OPT_G_TIOBENCH,
        },
        {
                .name   = "block",
+               .lname  = "Tiobench block",
                .type   = FIO_OPT_INT,
                .roff1  = &bs,
                .help   = "Block size in bytes",
                .def    = "4k",
+               .category = FIO_OPT_C_PROFILE,
+               .group  = FIO_OPT_G_TIOBENCH,
        },
        {
                .name   = "numruns",
+               .lname  = "Tiobench numruns",
                .type   = FIO_OPT_INT,
                .roff1  = &loops,
                .help   = "Number of runs",
+               .category = FIO_OPT_C_PROFILE,
+               .group  = FIO_OPT_G_TIOBENCH,
        },
        {
                .name   = "dir",
+               .lname  = "Tiobench directory",
                .type   = FIO_OPT_STR_STORE,
                .roff1  = &dir,
                .help   = "Test directory",
+               .category = FIO_OPT_C_PROFILE,
+               .group  = FIO_OPT_G_TIOBENCH,
        },
        {
                .name   = "threads",
+               .lname  = "Tiobench threads",
                .type   = FIO_OPT_INT,
                .roff1  = &nthreads,
                .help   = "Number of Threads",
+               .category = FIO_OPT_C_PROFILE,
+               .group  = FIO_OPT_G_TIOBENCH,
        },
        {
                .name   = NULL,
diff --git a/rbtree.c b/rbtree.c
deleted file mode 100644 (file)
index 7cff649..0000000
--- a/rbtree.c
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
-  Red Black Trees
-  (C) 1999  Andrea Arcangeli <andrea@suse.de>
-  (C) 2002  David Woodhouse <dwmw2@infradead.org>
-  
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-  linux/lib/rbtree.c
-*/
-
-#include "rbtree.h"
-
-static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
-{
-       struct rb_node *right = node->rb_right;
-       struct rb_node *parent = rb_parent(node);
-
-       if ((node->rb_right = right->rb_left))
-               rb_set_parent(right->rb_left, node);
-       right->rb_left = node;
-
-       rb_set_parent(right, parent);
-
-       if (parent)
-       {
-               if (node == parent->rb_left)
-                       parent->rb_left = right;
-               else
-                       parent->rb_right = right;
-       }
-       else
-               root->rb_node = right;
-       rb_set_parent(node, right);
-}
-
-static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
-{
-       struct rb_node *left = node->rb_left;
-       struct rb_node *parent = rb_parent(node);
-
-       if ((node->rb_left = left->rb_right))
-               rb_set_parent(left->rb_right, node);
-       left->rb_right = node;
-
-       rb_set_parent(left, parent);
-
-       if (parent)
-       {
-               if (node == parent->rb_right)
-                       parent->rb_right = left;
-               else
-                       parent->rb_left = left;
-       }
-       else
-               root->rb_node = left;
-       rb_set_parent(node, left);
-}
-
-void rb_insert_color(struct rb_node *node, struct rb_root *root)
-{
-       struct rb_node *parent, *gparent;
-
-       while ((parent = rb_parent(node)) && rb_is_red(parent))
-       {
-               gparent = rb_parent(parent);
-
-               if (parent == gparent->rb_left)
-               {
-                       {
-                               register struct rb_node *uncle = gparent->rb_right;
-                               if (uncle && rb_is_red(uncle))
-                               {
-                                       rb_set_black(uncle);
-                                       rb_set_black(parent);
-                                       rb_set_red(gparent);
-                                       node = gparent;
-                                       continue;
-                               }
-                       }
-
-                       if (parent->rb_right == node)
-                       {
-                               register struct rb_node *tmp;
-                               __rb_rotate_left(parent, root);
-                               tmp = parent;
-                               parent = node;
-                               node = tmp;
-                       }
-
-                       rb_set_black(parent);
-                       rb_set_red(gparent);
-                       __rb_rotate_right(gparent, root);
-               } else {
-                       {
-                               register struct rb_node *uncle = gparent->rb_left;
-                               if (uncle && rb_is_red(uncle))
-                               {
-                                       rb_set_black(uncle);
-                                       rb_set_black(parent);
-                                       rb_set_red(gparent);
-                                       node = gparent;
-                                       continue;
-                               }
-                       }
-
-                       if (parent->rb_left == node)
-                       {
-                               register struct rb_node *tmp;
-                               __rb_rotate_right(parent, root);
-                               tmp = parent;
-                               parent = node;
-                               node = tmp;
-                       }
-
-                       rb_set_black(parent);
-                       rb_set_red(gparent);
-                       __rb_rotate_left(gparent, root);
-               }
-       }
-
-       rb_set_black(root->rb_node);
-}
-
-static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
-                            struct rb_root *root)
-{
-       struct rb_node *other;
-
-       while ((!node || rb_is_black(node)) && node != root->rb_node)
-       {
-               if (parent->rb_left == node)
-               {
-                       other = parent->rb_right;
-                       if (rb_is_red(other))
-                       {
-                               rb_set_black(other);
-                               rb_set_red(parent);
-                               __rb_rotate_left(parent, root);
-                               other = parent->rb_right;
-                       }
-                       if ((!other->rb_left || rb_is_black(other->rb_left)) &&
-                           (!other->rb_right || rb_is_black(other->rb_right)))
-                       {
-                               rb_set_red(other);
-                               node = parent;
-                               parent = rb_parent(node);
-                       }
-                       else
-                       {
-                               if (!other->rb_right || rb_is_black(other->rb_right))
-                               {
-                                       struct rb_node *o_left;
-                                       if ((o_left = other->rb_left))
-                                               rb_set_black(o_left);
-                                       rb_set_red(other);
-                                       __rb_rotate_right(other, root);
-                                       other = parent->rb_right;
-                               }
-                               rb_set_color(other, rb_color(parent));
-                               rb_set_black(parent);
-                               if (other->rb_right)
-                                       rb_set_black(other->rb_right);
-                               __rb_rotate_left(parent, root);
-                               node = root->rb_node;
-                               break;
-                       }
-               }
-               else
-               {
-                       other = parent->rb_left;
-                       if (rb_is_red(other))
-                       {
-                               rb_set_black(other);
-                               rb_set_red(parent);
-                               __rb_rotate_right(parent, root);
-                               other = parent->rb_left;
-                       }
-                       if ((!other->rb_left || rb_is_black(other->rb_left)) &&
-                           (!other->rb_right || rb_is_black(other->rb_right)))
-                       {
-                               rb_set_red(other);
-                               node = parent;
-                               parent = rb_parent(node);
-                       }
-                       else
-                       {
-                               if (!other->rb_left || rb_is_black(other->rb_left))
-                               {
-                                       register struct rb_node *o_right;
-                                       if ((o_right = other->rb_right))
-                                               rb_set_black(o_right);
-                                       rb_set_red(other);
-                                       __rb_rotate_left(other, root);
-                                       other = parent->rb_left;
-                               }
-                               rb_set_color(other, rb_color(parent));
-                               rb_set_black(parent);
-                               if (other->rb_left)
-                                       rb_set_black(other->rb_left);
-                               __rb_rotate_right(parent, root);
-                               node = root->rb_node;
-                               break;
-                       }
-               }
-       }
-       if (node)
-               rb_set_black(node);
-}
-
-void rb_erase(struct rb_node *node, struct rb_root *root)
-{
-       struct rb_node *child, *parent;
-       int color;
-
-       if (!node->rb_left)
-               child = node->rb_right;
-       else if (!node->rb_right)
-               child = node->rb_left;
-       else
-       {
-               struct rb_node *old = node, *left;
-
-               node = node->rb_right;
-               while ((left = node->rb_left) != NULL)
-                       node = left;
-               child = node->rb_right;
-               parent = rb_parent(node);
-               color = rb_color(node);
-
-               if (child)
-                       rb_set_parent(child, parent);
-               if (parent == old) {
-                       parent->rb_right = child;
-                       parent = node;
-               } else
-                       parent->rb_left = child;
-
-               node->rb_parent_color = old->rb_parent_color;
-               node->rb_right = old->rb_right;
-               node->rb_left = old->rb_left;
-
-               if (rb_parent(old))
-               {
-                       if (rb_parent(old)->rb_left == old)
-                               rb_parent(old)->rb_left = node;
-                       else
-                               rb_parent(old)->rb_right = node;
-               } else
-                       root->rb_node = node;
-
-               rb_set_parent(old->rb_left, node);
-               if (old->rb_right)
-                       rb_set_parent(old->rb_right, node);
-               goto color;
-       }
-
-       parent = rb_parent(node);
-       color = rb_color(node);
-
-       if (child)
-               rb_set_parent(child, parent);
-       if (parent)
-       {
-               if (parent->rb_left == node)
-                       parent->rb_left = child;
-               else
-                       parent->rb_right = child;
-       }
-       else
-               root->rb_node = child;
-
- color:
-       if (color == RB_BLACK)
-               __rb_erase_color(child, parent, root);
-}
-
-/*
- * This function returns the first node (in sort order) of the tree.
- */
-struct rb_node *rb_first(struct rb_root *root)
-{
-       struct rb_node  *n;
-
-       n = root->rb_node;
-       if (!n)
-               return NULL;
-       while (n->rb_left)
-               n = n->rb_left;
-       return n;
-}
diff --git a/rbtree.h b/rbtree.h
deleted file mode 100644 (file)
index 7563725..0000000
--- a/rbtree.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
-  Red Black Trees
-  (C) 1999  Andrea Arcangeli <andrea@suse.de>
-  
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-  linux/include/linux/rbtree.h
-
-  To use rbtrees you'll have to implement your own insert and search cores.
-  This will avoid us to use callbacks and to drop drammatically performances.
-  I know it's not the cleaner way,  but in C (not in C++) to get
-  performances and genericity...
-
-  Some example of insert and search follows here. The search is a plain
-  normal search over an ordered tree. The insert instead must be implemented
-  int two steps: as first thing the code must insert the element in
-  order as a red leaf in the tree, then the support library function
-  rb_insert_color() must be called. Such function will do the
-  not trivial work to rebalance the rbtree if necessary.
-
------------------------------------------------------------------------
-static inline struct page * rb_search_page_cache(struct inode * inode,
-                                                unsigned long offset)
-{
-       struct rb_node * n = inode->i_rb_page_cache.rb_node;
-       struct page * page;
-
-       while (n)
-       {
-               page = rb_entry(n, struct page, rb_page_cache);
-
-               if (offset < page->offset)
-                       n = n->rb_left;
-               else if (offset > page->offset)
-                       n = n->rb_right;
-               else
-                       return page;
-       }
-       return NULL;
-}
-
-static inline struct page * __rb_insert_page_cache(struct inode * inode,
-                                                  unsigned long offset,
-                                                  struct rb_node * node)
-{
-       struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
-       struct rb_node * parent = NULL;
-       struct page * page;
-
-       while (*p)
-       {
-               parent = *p;
-               page = rb_entry(parent, struct page, rb_page_cache);
-
-               if (offset < page->offset)
-                       p = &(*p)->rb_left;
-               else if (offset > page->offset)
-                       p = &(*p)->rb_right;
-               else
-                       return page;
-       }
-
-       rb_link_node(node, parent, p);
-
-       return NULL;
-}
-
-static inline struct page * rb_insert_page_cache(struct inode * inode,
-                                                unsigned long offset,
-                                                struct rb_node * node)
-{
-       struct page * ret;
-       if ((ret = __rb_insert_page_cache(inode, offset, node)))
-               goto out;
-       rb_insert_color(node, &inode->i_rb_page_cache);
- out:
-       return ret;
-}
------------------------------------------------------------------------
-*/
-
-#ifndef        _LINUX_RBTREE_H
-#define        _LINUX_RBTREE_H
-
-#include <stdlib.h>
-#include <inttypes.h>
-
-struct rb_node
-{
-       intptr_t rb_parent_color;
-#define        RB_RED          0
-#define        RB_BLACK        1
-       struct rb_node *rb_right;
-       struct rb_node *rb_left;
-} __attribute__((aligned(sizeof(long))));
-    /* The alignment might seem pointless, but allegedly CRIS needs it */
-
-struct rb_root
-{
-       struct rb_node *rb_node;
-};
-
-
-#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
-#define rb_color(r)   ((r)->rb_parent_color & 1)
-#define rb_is_red(r)   (!rb_color(r))
-#define rb_is_black(r) rb_color(r)
-#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
-#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
-
-static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
-{
-       rb->rb_parent_color = (rb->rb_parent_color & 3) | (uintptr_t)p;
-}
-static inline void rb_set_color(struct rb_node *rb, int color)
-{
-       rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
-}
-
-#define RB_ROOT        (struct rb_root) { NULL, }
-#define        rb_entry(ptr, type, member) container_of(ptr, type, member)
-
-#define RB_EMPTY_ROOT(root)    ((root)->rb_node == NULL)
-#define RB_EMPTY_NODE(node)    (rb_parent(node) == node)
-#define RB_CLEAR_NODE(node)    (rb_set_parent(node, node))
-
-extern void rb_insert_color(struct rb_node *, struct rb_root *);
-extern void rb_erase(struct rb_node *, struct rb_root *);
-
-/* Find logical next and previous nodes in a tree */
-extern struct rb_node *rb_first(struct rb_root *);
-
-static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
-                               struct rb_node ** rb_link)
-{
-       node->rb_parent_color = (uintptr_t)parent;
-       node->rb_left = node->rb_right = NULL;
-
-       *rb_link = node;
-}
-
-#endif /* _LINUX_RBTREE_H */
index 72def7e003ee938a1c41f3d4a7e31d14b4ce3ce3..d120c523d779bed1e228e99af7f461ce5ac1b1c1 100644 (file)
--- a/server.c
+++ b/server.c
 #include <netdb.h>
 #include <syslog.h>
 #include <signal.h>
+#include <zlib.h>
 
 #include "fio.h"
 #include "server.h"
 #include "crc/crc16.h"
 #include "lib/ieee754.h"
 
-int fio_net_port = 8765;
+int fio_net_port = FIO_NET_PORT;
 
 int exit_backend = 0;
 
@@ -31,9 +32,22 @@ static char *fio_server_arg;
 static char *bind_sock;
 static struct sockaddr_in saddr_in;
 static struct sockaddr_in6 saddr_in6;
-static int first_cmd_check;
 static int use_ipv6;
 
+struct fio_fork_item {
+       struct flist_head list;
+       int exitval;
+       int signal;
+       int exited;
+       pid_t pid;
+};
+
+/* Created on fork on new connection */
+static FLIST_HEAD(conn_list);
+
+/* Created on job fork from connection */
+static FLIST_HEAD(job_list);
+
 static const char *fio_server_ops[FIO_NET_CMD_NR] = {
        "",
        "QUIT",
@@ -49,7 +63,10 @@ static const char *fio_server_ops[FIO_NET_CMD_NR] = {
        "START",
        "STOP",
        "DISK_UTIL",
-       "RUN",
+       "SERVER_START",
+       "ADD_JOB",
+       "CMD_RUN"
+       "CMD_IOLOG",
 };
 
 const char *fio_server_op(unsigned int op)
@@ -63,19 +80,40 @@ const char *fio_server_op(unsigned int op)
        return buf;
 }
 
-int fio_send_data(int sk, const void *p, unsigned int len)
+static ssize_t iov_total_len(const struct iovec *iov, int count)
 {
-       assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_PDU);
+       ssize_t ret = 0;
 
-       do {
-               int ret = send(sk, p, len, 0);
+       while (count--) {
+               ret += iov->iov_len;
+               iov++;
+       }
+
+       return ret;
+}
+
+static int fio_sendv_data(int sk, struct iovec *iov, int count)
+{
+       ssize_t total_len = iov_total_len(iov, count);
+       ssize_t ret;
 
+       do {
+               ret = writev(sk, iov, count);
                if (ret > 0) {
-                       len -= ret;
-                       if (!len)
+                       total_len -= ret;
+                       if (!total_len)
                                break;
-                       p += ret;
-                       continue;
+
+                       while (ret) {
+                               if (ret >= iov->iov_len) {
+                                       ret -= iov->iov_len;
+                                       iov++;
+                                       continue;
+                               }
+                               iov->iov_base += ret;
+                               iov->iov_len -= ret;
+                               ret = 0;
+                       }
                } else if (!ret)
                        break;
                else if (errno == EAGAIN || errno == EINTR)
@@ -84,12 +122,24 @@ int fio_send_data(int sk, const void *p, unsigned int len)
                        break;
        } while (!exit_backend);
 
-       if (!len)
+       if (!total_len)
                return 0;
 
+       if (errno)
+               return -errno;
+
        return 1;
 }
 
+int fio_send_data(int sk, const void *p, unsigned int len)
+{
+       struct iovec iov = { .iov_base = (void *) p, .iov_len = len };
+
+       assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_FRAGMENT_PDU);
+
+       return fio_sendv_data(sk, &iov, 1);
+}
+
 int fio_recv_data(int sk, void *p, unsigned int len)
 {
        do {
@@ -143,7 +193,7 @@ static int verify_convert_cmd(struct fio_net_cmd *cmd)
                return 1;
        }
 
-       if (cmd->pdu_len > FIO_SERVER_MAX_PDU) {
+       if (cmd->pdu_len > FIO_SERVER_MAX_FRAGMENT_PDU) {
                log_err("fio: command payload too large: %u\n", cmd->pdu_len);
                return 1;
        }
@@ -219,12 +269,21 @@ struct fio_net_cmd *fio_net_recv_cmd(int sk)
                cmdret = NULL;
        } else if (cmdret) {
                /* zero-terminate text input */
-               if (cmdret->pdu_len && (cmdret->opcode == FIO_NET_CMD_TEXT ||
-                   cmdret->opcode == FIO_NET_CMD_JOB)) {
-                       char *buf = (char *) cmdret->payload;
-
-                       buf[cmdret->pdu_len ] = '\0';
+               if (cmdret->pdu_len) {
+                       if (cmdret->opcode == FIO_NET_CMD_TEXT) {
+                               struct cmd_text_pdu *pdu = (struct cmd_text_pdu *) cmdret->payload;
+                               char *buf = (char *) pdu->buf;
+
+                               buf[pdu->buf_len] = '\0';
+                       } else if (cmdret->opcode == FIO_NET_CMD_JOB) {
+                               struct cmd_job_pdu *pdu = (struct cmd_job_pdu *) cmdret->payload;
+                               char *buf = (char *) pdu->buf;
+                               int len = le32_to_cpu(pdu->buf_len);
+
+                               buf[len] = '\0';
+                       }
                }
+
                /* frag flag is internal */
                cmdret->flags &= ~FIO_NET_CMD_F_MORE;
        }
@@ -232,28 +291,66 @@ struct fio_net_cmd *fio_net_recv_cmd(int sk)
        return cmdret;
 }
 
-void fio_net_cmd_crc(struct fio_net_cmd *cmd)
+static void add_reply(uint64_t tag, struct flist_head *list)
+{
+       struct fio_net_cmd_reply *reply = (struct fio_net_cmd_reply *) tag;
+
+       flist_add_tail(&reply->list, list);
+}
+
+static uint64_t alloc_reply(uint64_t tag, uint16_t opcode)
+{
+       struct fio_net_cmd_reply *reply;
+
+       reply = calloc(1, sizeof(*reply));
+       INIT_FLIST_HEAD(&reply->list);
+       gettimeofday(&reply->tv, NULL);
+       reply->saved_tag = tag;
+       reply->opcode = opcode;
+
+       return (uintptr_t) reply;
+}
+
+static void free_reply(uint64_t tag)
+{
+       struct fio_net_cmd_reply *reply = (struct fio_net_cmd_reply *) tag;
+
+       free(reply);
+}
+
+void fio_net_cmd_crc_pdu(struct fio_net_cmd *cmd, const void *pdu)
 {
        uint32_t pdu_len;
 
        cmd->cmd_crc16 = __cpu_to_le16(fio_crc16(cmd, FIO_NET_CMD_CRC_SZ));
 
        pdu_len = le32_to_cpu(cmd->pdu_len);
-       if (pdu_len)
-               cmd->pdu_crc16 = __cpu_to_le16(fio_crc16(cmd->payload, pdu_len));
+       cmd->pdu_crc16 = __cpu_to_le16(fio_crc16(pdu, pdu_len));
+}
+
+void fio_net_cmd_crc(struct fio_net_cmd *cmd)
+{
+       fio_net_cmd_crc_pdu(cmd, cmd->payload);
 }
 
 int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size,
-                    uint64_t tag)
+                    uint64_t *tagptr, struct flist_head *list)
 {
        struct fio_net_cmd *cmd = NULL;
        size_t this_len, cur_len = 0;
+       uint64_t tag;
        int ret;
 
+       if (list) {
+               assert(tagptr);
+               tag = *tagptr = alloc_reply(*tagptr, opcode);
+       } else
+               tag = tagptr ? *tagptr : 0;
+
        do {
                this_len = size;
-               if (this_len > FIO_SERVER_MAX_PDU)
-                       this_len = FIO_SERVER_MAX_PDU;
+               if (this_len > FIO_SERVER_MAX_FRAGMENT_PDU)
+                       this_len = FIO_SERVER_MAX_FRAGMENT_PDU;
 
                if (!cmd || cur_len < sizeof(*cmd) + this_len) {
                        if (cmd)
@@ -275,6 +372,13 @@ int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size,
                buf += this_len;
        } while (!ret && size);
 
+       if (list) {
+               if (ret)
+                       free_reply(tag);
+               else
+                       add_reply(tag, list);
+       }
+
        if (cmd)
                free(cmd);
 
@@ -298,60 +402,173 @@ static int fio_net_send_simple_stack_cmd(int sk, uint16_t opcode, uint64_t tag)
 int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t tag,
                            struct flist_head *list)
 {
-       struct fio_net_int_cmd *cmd;
        int ret;
 
-       if (!list)
-               return fio_net_send_simple_stack_cmd(sk, opcode, tag);
-
-       cmd = malloc(sizeof(*cmd));
-
-       fio_init_net_cmd(&cmd->cmd, opcode, NULL, 0, (uintptr_t) cmd);
-       fio_net_cmd_crc(&cmd->cmd);
+       if (list)
+               tag = alloc_reply(tag, opcode);
 
-       INIT_FLIST_HEAD(&cmd->list);
-       gettimeofday(&cmd->tv, NULL);
-       cmd->saved_tag = tag;
-
-       ret = fio_send_data(sk, &cmd->cmd, sizeof(cmd->cmd));
+       ret = fio_net_send_simple_stack_cmd(sk, opcode, tag);
        if (ret) {
-               free(cmd);
+               if (list)
+                       free_reply(tag);
+
                return ret;
        }
 
-       flist_add_tail(&cmd->list, list);
+       if (list)
+               add_reply(tag, list);
+
        return 0;
 }
 
-static int fio_server_send_quit_cmd(void)
+int fio_net_send_quit(int sk)
 {
        dprint(FD_NET, "server: sending quit\n");
-       return fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_QUIT, 0, NULL);
+
+       return fio_net_send_simple_cmd(sk, FIO_NET_CMD_QUIT, 0, NULL);
 }
 
-static int handle_job_cmd(struct fio_net_cmd *cmd)
+static int fio_net_send_ack(int sk, struct fio_net_cmd *cmd, int error,
+                           int signal)
 {
-       char *buf = (char *) cmd->payload;
-       struct cmd_start_pdu spdu;
        struct cmd_end_pdu epdu;
-       int ret;
+       uint64_t tag = 0;
 
-       if (parse_jobs_ini(buf, 1, 0)) {
-               fio_server_send_quit_cmd();
-               return -1;
+       if (cmd)
+               tag = cmd->tag;
+
+       epdu.error = __cpu_to_le32(error);
+       epdu.signal = __cpu_to_le32(signal);
+       return fio_net_send_cmd(sk, FIO_NET_CMD_STOP, &epdu, sizeof(epdu), &tag, NULL);
+}
+
+int fio_net_send_stop(int sk, int error, int signal)
+{
+       dprint(FD_NET, "server: sending stop (%d, %d)\n", error, signal);
+       return fio_net_send_ack(sk, NULL, error, signal);
+}
+
+static void fio_server_add_fork_item(pid_t pid, struct flist_head *list)
+{
+       struct fio_fork_item *ffi;
+
+       ffi = malloc(sizeof(*ffi));
+       ffi->exitval = 0;
+       ffi->signal = 0;
+       ffi->exited = 0;
+       ffi->pid = pid;
+       flist_add_tail(&ffi->list, list);
+}
+
+static void fio_server_add_conn_pid(pid_t pid)
+{
+       dprint(FD_NET, "server: forked off connection job (pid=%u)\n", pid);
+       fio_server_add_fork_item(pid, &conn_list);
+}
+
+static void fio_server_add_job_pid(pid_t pid)
+{
+       dprint(FD_NET, "server: forked off job job (pid=%u)\n", pid);
+       fio_server_add_fork_item(pid, &job_list);
+}
+
+static void fio_server_check_fork_item(struct fio_fork_item *ffi)
+{
+       int ret, status;
+
+       ret = waitpid(ffi->pid, &status, WNOHANG);
+       if (ret < 0) {
+               if (errno == ECHILD) {
+                       log_err("fio: connection pid %u disappeared\n", ffi->pid);
+                       ffi->exited = 1;
+               } else
+                       log_err("fio: waitpid: %s\n", strerror(errno));
+       } else if (ret == ffi->pid) {
+               if (WIFSIGNALED(status)) {
+                       ffi->signal = WTERMSIG(status);
+                       ffi->exited = 1;
+               }
+               if (WIFEXITED(status)) {
+                       if (WEXITSTATUS(status))
+                               ffi->exitval = WEXITSTATUS(status);
+                       ffi->exited = 1;
+               }
        }
+}
 
-       spdu.jobs = cpu_to_le32(thread_number);
-       fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), 0);
+static void fio_server_fork_item_done(struct fio_fork_item *ffi)
+{
+       dprint(FD_NET, "pid %u exited, sig=%u, exitval=%d\n", ffi->pid, ffi->signal, ffi->exitval);
+
+       /*
+        * Fold STOP and QUIT...
+        */
+       fio_net_send_stop(server_fd, ffi->exitval, ffi->signal);
+       fio_net_send_quit(server_fd);
+       flist_del(&ffi->list);
+       free(ffi);
+}
+
+static void fio_server_check_fork_items(struct flist_head *list)
+{
+       struct flist_head *entry, *tmp;
+       struct fio_fork_item *ffi;
+
+       flist_for_each_safe(entry, tmp, list) {
+               ffi = flist_entry(entry, struct fio_fork_item, list);
+
+               fio_server_check_fork_item(ffi);
+
+               if (ffi->exited)
+                       fio_server_fork_item_done(ffi);
+       }
+}
+
+static void fio_server_check_jobs(void)
+{
+       fio_server_check_fork_items(&job_list);
+}
+
+static void fio_server_check_conns(void)
+{
+       fio_server_check_fork_items(&conn_list);
+}
+
+static int handle_run_cmd(struct fio_net_cmd *cmd)
+{
+       pid_t pid;
+       int ret;
+
+       set_genesis_time();
+
+       pid = fork();
+       if (pid) {
+               fio_server_add_job_pid(pid);
+               return 0;
+       }
 
        ret = fio_backend();
+       free_threads_shm();
+       _exit(ret);
+}
 
-       epdu.error = ret;
-       fio_net_send_cmd(server_fd, FIO_NET_CMD_STOP, &epdu, sizeof(epdu), 0);
+static int handle_job_cmd(struct fio_net_cmd *cmd)
+{
+       struct cmd_job_pdu *pdu = (struct cmd_job_pdu *) cmd->payload;
+       void *buf = pdu->buf;
+       struct cmd_start_pdu spdu;
 
-       fio_server_send_quit_cmd();
-       reset_fio_state();
-       return ret;
+       pdu->buf_len = le32_to_cpu(pdu->buf_len);
+       pdu->client_type = le32_to_cpu(pdu->client_type);
+
+       if (parse_jobs_ini(buf, 1, 0, pdu->client_type)) {
+               fio_net_send_quit(server_fd);
+               return -1;
+       }
+
+       spdu.jobs = cpu_to_le32(thread_number);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), NULL, NULL);
+       return 0;
 }
 
 static int handle_jobline_cmd(struct fio_net_cmd *cmd)
@@ -360,11 +577,13 @@ static int handle_jobline_cmd(struct fio_net_cmd *cmd)
        struct cmd_single_line_pdu *cslp;
        struct cmd_line_pdu *clp;
        unsigned long offset;
+       struct cmd_start_pdu spdu;
        char **argv;
-       int ret, i;
+       int i;
 
        clp = pdu;
        clp->lines = le16_to_cpu(clp->lines);
+       clp->client_type = le16_to_cpu(clp->client_type);
        argv = malloc(clp->lines * sizeof(char *));
        offset = sizeof(*clp);
 
@@ -378,25 +597,23 @@ static int handle_jobline_cmd(struct fio_net_cmd *cmd)
                dprint(FD_NET, "server: %d: %s\n", i, argv[i]);
        }
 
-       if (parse_cmd_line(clp->lines, argv)) {
-               fio_server_send_quit_cmd();
+       if (parse_cmd_line(clp->lines, argv, clp->client_type)) {
+               fio_net_send_quit(server_fd);
                free(argv);
                return -1;
        }
 
        free(argv);
 
-       fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0, NULL);
-
-       ret = fio_backend();
-       fio_server_send_quit_cmd();
-       reset_fio_state();
-       return ret;
+       spdu.jobs = cpu_to_le32(thread_number);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), NULL, NULL);
+       return 0;
 }
 
 static int handle_probe_cmd(struct fio_net_cmd *cmd)
 {
        struct cmd_probe_pdu probe;
+       uint64_t tag = cmd->tag;
 
        dprint(FD_NET, "server: sending probe reply\n");
 
@@ -409,16 +626,18 @@ static int handle_probe_cmd(struct fio_net_cmd *cmd)
 
        probe.os        = FIO_OS;
        probe.arch      = FIO_ARCH;
-
        probe.bpp       = sizeof(void *);
+       probe.cpus      = __cpu_to_le32(cpus_online());
+       probe.flags     = 0;
 
-       return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe), cmd->tag);
+       return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe), &tag, NULL);
 }
 
 static int handle_send_eta_cmd(struct fio_net_cmd *cmd)
 {
        struct jobs_eta *je;
        size_t size;
+       uint64_t tag = cmd->tag;
        int i;
 
        if (!thread_number)
@@ -439,25 +658,56 @@ static int handle_send_eta_cmd(struct fio_net_cmd *cmd)
        je->nr_ramp             = cpu_to_le32(je->nr_ramp);
        je->nr_pending          = cpu_to_le32(je->nr_pending);
        je->files_open          = cpu_to_le32(je->files_open);
-       je->m_rate              = cpu_to_le32(je->m_rate);
-       je->t_rate              = cpu_to_le32(je->t_rate);
-       je->m_iops              = cpu_to_le32(je->m_iops);
-       je->t_iops              = cpu_to_le32(je->t_iops);
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
+               je->m_rate[i]   = cpu_to_le32(je->m_rate[i]);
+               je->t_rate[i]   = cpu_to_le32(je->t_rate[i]);
+               je->m_iops[i]   = cpu_to_le32(je->m_iops[i]);
+               je->t_iops[i]   = cpu_to_le32(je->t_iops[i]);
                je->rate[i]     = cpu_to_le32(je->rate[i]);
                je->iops[i]     = cpu_to_le32(je->iops[i]);
        }
 
        je->elapsed_sec         = cpu_to_le64(je->elapsed_sec);
        je->eta_sec             = cpu_to_le64(je->eta_sec);
+       je->nr_threads          = cpu_to_le32(je->nr_threads);
        je->is_pow2             = cpu_to_le32(je->is_pow2);
 
-       fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, je, size, cmd->tag);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, je, size, &tag, NULL);
        free(je);
        return 0;
 }
 
+static int send_update_job_reply(int fd, uint64_t __tag, int error)
+{
+       uint64_t tag = __tag;
+       uint32_t pdu_error;
+
+       pdu_error = __cpu_to_le32(error);
+       return fio_net_send_cmd(fd, FIO_NET_CMD_UPDATE_JOB, &pdu_error, sizeof(pdu_error), &tag, NULL);
+}
+
+static int handle_update_job_cmd(struct fio_net_cmd *cmd)
+{
+       struct cmd_add_job_pdu *pdu = (struct cmd_add_job_pdu *) cmd->payload;
+       struct thread_data *td;
+       uint32_t tnumber;
+
+       tnumber = le32_to_cpu(pdu->thread_number);
+
+       dprint(FD_NET, "server: updating options for job %u\n", tnumber);
+
+       if (!tnumber || tnumber > thread_number) {
+               send_update_job_reply(server_fd, cmd->tag, ENODEV);
+               return 0;
+       }
+
+       td = &threads[tnumber - 1];
+       convert_thread_options_to_cpu(&td->o, &pdu->top);
+       send_update_job_reply(server_fd, cmd->tag, 0);
+       return 0;
+}
+
 static int handle_command(struct fio_net_cmd *cmd)
 {
        int ret;
@@ -484,19 +734,29 @@ static int handle_command(struct fio_net_cmd *cmd)
        case FIO_NET_CMD_SEND_ETA:
                ret = handle_send_eta_cmd(cmd);
                break;
+       case FIO_NET_CMD_RUN:
+               ret = handle_run_cmd(cmd);
+               break;
+       case FIO_NET_CMD_UPDATE_JOB:
+               ret = handle_update_job_cmd(cmd);
+               break;
        default:
-               log_err("fio: unknown opcode: %s\n",fio_server_op(cmd->opcode));
+               log_err("fio: unknown opcode: %s\n", fio_server_op(cmd->opcode));
                ret = 1;
        }
 
        return ret;
 }
 
-static int handle_connection(int sk, int block)
+static int handle_connection(int sk)
 {
        struct fio_net_cmd *cmd = NULL;
        int ret = 0;
 
+       reset_fio_state();
+       INIT_FLIST_HEAD(&job_list);
+       server_fd = sk;
+
        /* read forever */
        while (!exit_backend) {
                struct pollfd pfd = {
@@ -506,15 +766,19 @@ static int handle_connection(int sk, int block)
 
                ret = 0;
                do {
-                       ret = poll(&pfd, 1, 100);
+                       int timeout = 1000;
+
+                       if (!flist_empty(&job_list))
+                               timeout = 100;
+
+                       ret = poll(&pfd, 1, timeout);
                        if (ret < 0) {
                                if (errno == EINTR)
                                        break;
                                log_err("fio: poll: %s\n", strerror(errno));
                                break;
                        } else if (!ret) {
-                               if (!block)
-                                       return 0;
+                               fio_server_check_jobs();
                                continue;
                        }
 
@@ -526,6 +790,8 @@ static int handle_connection(int sk, int block)
                        }
                } while (!exit_backend);
 
+               fio_server_check_jobs();
+
                if (ret < 0)
                        break;
 
@@ -546,15 +812,8 @@ static int handle_connection(int sk, int block)
        if (cmd)
                free(cmd);
 
-       return ret;
-}
-
-void fio_server_idle_loop(void)
-{
-       if (!first_cmd_check)
-               fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_RUN, 0, NULL);
-       if (server_fd != -1)
-               handle_connection(server_fd, 0);
+       close(sk);
+       _exit(ret);
 }
 
 static int accept_loop(int listen_sk)
@@ -562,61 +821,91 @@ static int accept_loop(int listen_sk)
        struct sockaddr_in addr;
        fio_socklen_t len = sizeof(addr);
        struct pollfd pfd;
-       int ret, sk, flags, exitval = 0;
+       int ret = 0, sk, flags, exitval = 0;
 
        dprint(FD_NET, "server enter accept loop\n");
 
        flags = fcntl(listen_sk, F_GETFL);
        flags |= O_NONBLOCK;
        fcntl(listen_sk, F_SETFL, flags);
-again:
-       pfd.fd = listen_sk;
-       pfd.events = POLLIN;
-       do {
-               ret = poll(&pfd, 1, 100);
-               if (ret < 0) {
-                       if (errno == EINTR)
-                               break;
-                       log_err("fio: poll: %s\n", strerror(errno));
-                       goto out;
-               } else if (!ret)
-                       continue;
 
-               if (pfd.revents & POLLIN)
-                       break;
-       } while (!exit_backend);
+       while (!exit_backend) {
+               pid_t pid;
 
-       if (exit_backend)
-               goto out;
+               pfd.fd = listen_sk;
+               pfd.events = POLLIN;
+               do {
+                       int timeout = 1000;
 
-       sk = accept(listen_sk, (struct sockaddr *) &addr, &len);
-       if (sk < 0) {
-               log_err("fio: accept: %s\n", strerror(errno));
-               return -1;
-       }
+                       if (!flist_empty(&conn_list))
+                               timeout = 100;
 
-       dprint(FD_NET, "server: connect from %s\n", inet_ntoa(addr.sin_addr));
+                       ret = poll(&pfd, 1, timeout);
+                       if (ret < 0) {
+                               if (errno == EINTR)
+                                       break;
+                               log_err("fio: poll: %s\n", strerror(errno));
+                               break;
+                       } else if (!ret) {
+                               fio_server_check_conns();
+                               continue;
+                       }
 
-       server_fd = sk;
+                       if (pfd.revents & POLLIN)
+                               break;
+               } while (!exit_backend);
 
-       exitval = handle_connection(sk, 1);
+               fio_server_check_conns();
 
-       server_fd = -1;
-       close(sk);
+               if (exit_backend || ret < 0)
+                       break;
+
+               sk = accept(listen_sk, (struct sockaddr *) &addr, &len);
+               if (sk < 0) {
+                       log_err("fio: accept: %s\n", strerror(errno));
+                       return -1;
+               }
 
-       if (!exit_backend)
-               goto again;
+               dprint(FD_NET, "server: connect from %s\n", inet_ntoa(addr.sin_addr));
+
+               pid = fork();
+               if (pid) {
+                       close(sk);
+                       fio_server_add_conn_pid(pid);
+                       continue;
+               }
+
+               /* exits */
+               handle_connection(sk);
+       }
 
-out:
        return exitval;
 }
 
-int fio_server_text_output(const char *buf, size_t len)
+int fio_server_text_output(int level, const char *buf, size_t len)
 {
-       if (server_fd != -1)
-               return fio_net_send_cmd(server_fd, FIO_NET_CMD_TEXT, buf, len, 0);
+       struct cmd_text_pdu *pdu;
+       unsigned int tlen;
+       struct timeval tv;
+
+       if (server_fd == -1)
+               return log_local_buf(buf, len);
+
+       tlen = sizeof(*pdu) + len;
+       pdu = malloc(tlen);
+
+       pdu->level      = __cpu_to_le32(level);
+       pdu->buf_len    = __cpu_to_le32(len);
+
+       gettimeofday(&tv, NULL);
+       pdu->log_sec    = __cpu_to_le64(tv.tv_sec);
+       pdu->log_usec   = __cpu_to_le64(tv.tv_usec);
+
+       memcpy(pdu->buf, buf, len);
 
-       return log_local_buf(buf, len);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_TEXT, pdu, tlen, NULL, NULL);
+       free(pdu);
+       return len;
 }
 
 static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
@@ -636,7 +925,7 @@ static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
 {
        int i;
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                dst->max_run[i]         = cpu_to_le64(src->max_run[i]);
                dst->min_run[i]         = cpu_to_le64(src->min_run[i]);
                dst->max_bw[i]          = cpu_to_le64(src->max_bw[i]);
@@ -666,12 +955,13 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
        strcpy(p.ts.verror, ts->verror);
        strcpy(p.ts.description, ts->description);
 
-       p.ts.error      = cpu_to_le32(ts->error);
-       p.ts.groupid    = cpu_to_le32(ts->groupid);
-       p.ts.pid        = cpu_to_le32(ts->pid);
-       p.ts.members    = cpu_to_le32(ts->members);
+       p.ts.error              = cpu_to_le32(ts->error);
+       p.ts.thread_number      = cpu_to_le32(ts->thread_number);
+       p.ts.groupid            = cpu_to_le32(ts->groupid);
+       p.ts.pid                = cpu_to_le32(ts->pid);
+       p.ts.members            = cpu_to_le32(ts->members);
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                convert_io_stat(&p.ts.clat_stat[i], &ts->clat_stat[i]);
                convert_io_stat(&p.ts.slat_stat[i], &ts->slat_stat[i]);
                convert_io_stat(&p.ts.lat_stat[i], &ts->lat_stat[i]);
@@ -703,7 +993,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
                p.ts.io_u_lat_m[i]      = cpu_to_le32(ts->io_u_lat_m[i]);
        }
 
-       for (i = 0; i < 2; i++)
+       for (i = 0; i < DDIR_RWDIR_CNT; i++)
                for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
                        p.ts.io_u_plat[i][j] = cpu_to_le32(ts->io_u_plat[i][j]);
 
@@ -715,7 +1005,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
        p.ts.total_submit       = cpu_to_le64(ts->total_submit);
        p.ts.total_complete     = cpu_to_le64(ts->total_complete);
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                p.ts.io_bytes[i]        = cpu_to_le64(ts->io_bytes[i]);
                p.ts.runtime[i]         = cpu_to_le64(ts->runtime[i]);
        }
@@ -728,7 +1018,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
 
        convert_gs(&p.rs, rs);
 
-       fio_net_send_cmd(server_fd, FIO_NET_CMD_TS, &p, sizeof(p), 0);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_TS, &p, sizeof(p), NULL, NULL);
 }
 
 void fio_server_send_gs(struct group_run_stats *rs)
@@ -738,7 +1028,7 @@ void fio_server_send_gs(struct group_run_stats *rs)
        dprint(FD_NET, "server sending group run stats\n");
 
        convert_gs(&gs, rs);
-       fio_net_send_cmd(server_fd, FIO_NET_CMD_GS, &gs, sizeof(gs), 0);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_GS, &gs, sizeof(gs), NULL, NULL);
 }
 
 static void convert_agg(struct disk_util_agg *dst, struct disk_util_agg *src)
@@ -792,23 +1082,125 @@ void fio_server_send_du(void)
                convert_dus(&pdu.dus, &du->dus);
                convert_agg(&pdu.agg, &du->agg);
 
-               fio_net_send_cmd(server_fd, FIO_NET_CMD_DU, &pdu, sizeof(pdu), 0);
+               fio_net_send_cmd(server_fd, FIO_NET_CMD_DU, &pdu, sizeof(pdu), NULL, NULL);
+       }
+}
+
+/*
+ * Send a command with a separate PDU, not inlined in the command
+ */
+static int fio_send_cmd_ext_pdu(int sk, uint16_t opcode, const void *buf,
+                               off_t size, uint64_t tag, uint32_t flags)
+{
+       struct fio_net_cmd cmd;
+       struct iovec iov[2];
+
+       iov[0].iov_base = &cmd;
+       iov[0].iov_len = sizeof(cmd);
+       iov[1].iov_base = (void *) buf;
+       iov[1].iov_len = size;
+
+       __fio_init_net_cmd(&cmd, opcode, size, tag);
+       cmd.flags = __cpu_to_le32(flags);
+       fio_net_cmd_crc_pdu(&cmd, buf);
+
+       return fio_sendv_data(sk, iov, 2);
+}
+
+int fio_send_iolog(struct thread_data *td, struct io_log *log, const char *name)
+{
+       struct cmd_iolog_pdu pdu;
+       z_stream stream;
+       void *out_pdu;
+       int i, ret = 0;
+
+       pdu.thread_number = cpu_to_le32(td->thread_number);
+       pdu.nr_samples = __cpu_to_le32(log->nr_samples);
+       pdu.log_type = cpu_to_le32(log->log_type);
+       strcpy((char *) pdu.name, name);
+
+       for (i = 0; i < log->nr_samples; i++) {
+               struct io_sample *s = &log->log[i];
+
+               s->time = cpu_to_le64(s->time);
+               s->val  = cpu_to_le64(s->val);
+               s->ddir = cpu_to_le32(s->ddir);
+               s->bs   = cpu_to_le32(s->bs);
        }
+
+       /*
+        * Dirty - since the log is potentially huge, compress it into
+        * FIO_SERVER_MAX_FRAGMENT_PDU chunks and let the receiving
+        * side defragment it.
+        */
+       out_pdu = malloc(FIO_SERVER_MAX_FRAGMENT_PDU);
+
+       stream.zalloc = Z_NULL;
+       stream.zfree = Z_NULL;
+       stream.opaque = Z_NULL;
+
+       if (deflateInit(&stream, Z_DEFAULT_COMPRESSION) != Z_OK) {
+               ret = 1;
+               goto err;
+       }
+
+       /*
+        * Send header first, it's not compressed.
+        */
+       ret = fio_send_cmd_ext_pdu(server_fd, FIO_NET_CMD_IOLOG, &pdu,
+                                       sizeof(pdu), 0, FIO_NET_CMD_F_MORE);
+       if (ret)
+               goto err_zlib;
+
+       stream.next_in = (void *) log->log;
+       stream.avail_in = log->nr_samples * sizeof(struct io_sample);
+
+       do {
+               unsigned int this_len, flags = 0;
+               int ret;
+
+               stream.avail_out = FIO_SERVER_MAX_FRAGMENT_PDU;
+               stream.next_out = out_pdu;
+               ret = deflate(&stream, Z_FINISH);
+               /* may be Z_OK, or Z_STREAM_END */
+               if (ret < 0)
+                       goto err_zlib;
+
+               this_len = FIO_SERVER_MAX_FRAGMENT_PDU - stream.avail_out;
+
+               if (stream.avail_in)
+                       flags = FIO_NET_CMD_F_MORE;
+
+               ret = fio_send_cmd_ext_pdu(server_fd, FIO_NET_CMD_IOLOG,
+                                          out_pdu, this_len, 0, flags);
+               if (ret)
+                       goto err_zlib;
+       } while (stream.avail_in);
+
+err_zlib:
+       deflateEnd(&stream);
+err:
+       free(out_pdu);
+       return ret;
 }
 
-int fio_server_log(const char *format, ...)
+void fio_server_send_add_job(struct thread_data *td)
 {
-       char buffer[1024];
-       va_list args;
-       size_t len;
+       struct cmd_add_job_pdu pdu;
+
+       memset(&pdu, 0, sizeof(pdu));
+       pdu.thread_number = cpu_to_le32(td->thread_number);
+       pdu.groupid = cpu_to_le32(td->groupid);
+       convert_thread_options_to_net(&pdu.top, &td->o);
 
-       dprint(FD_NET, "server log\n");
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_ADD_JOB, &pdu, sizeof(pdu), NULL, NULL);
+}
 
-       va_start(args, format);
-       len = vsnprintf(buffer, sizeof(buffer), format, args);
-       va_end(args);
+void fio_server_send_start(struct thread_data *td)
+{
+       assert(server_fd != -1);
 
-       return fio_server_text_output(buffer, len);
+       fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_SERVER_START, 0, NULL);
 }
 
 static int fio_init_server_ip(void)
@@ -940,6 +1332,46 @@ static int fio_init_server_connection(void)
        return sk;
 }
 
+int fio_server_parse_host(const char *host, int *ipv6, struct in_addr *inp,
+                         struct in6_addr *inp6)
+
+{
+       int ret = 0;
+
+       if (*ipv6)
+               ret = inet_pton(AF_INET6, host, inp6);
+       else
+               ret = inet_pton(AF_INET, host, inp);
+
+       if (ret != 1) {
+               struct hostent *hent;
+
+               hent = gethostbyname(host);
+               if (!hent) {
+                       log_err("fio: failed to resolve <%s>\n", host);
+                       return 0;
+               }
+
+               if (*ipv6) {
+                       if (hent->h_addrtype != AF_INET6) {
+                               log_info("fio: falling back to IPv4\n");
+                               *ipv6 = 0;
+                       } else
+                               memcpy(inp6, hent->h_addr_list[0], 16);
+               }
+               if (!*ipv6) {
+                       if (hent->h_addrtype != AF_INET) {
+                               log_err("fio: lookup type mismatch\n");
+                               return 0;
+                       }
+                       memcpy(inp, hent->h_addr_list[0], 4);
+               }
+               ret = 1;
+       }
+
+       return !(ret == 1);
+}
+
 /*
  * Parse a host/ip/port string. Reads from 'str'.
  *
@@ -958,7 +1390,7 @@ int fio_server_parse_string(const char *str, char **ptr, int *is_sock,
 {
        const char *host = str;
        char *portp;
-       int ret, lport = 0;
+       int lport = 0;
 
        *ptr = NULL;
        *is_sock = 0;
@@ -1019,38 +1451,10 @@ int fio_server_parse_string(const char *str, char **ptr, int *is_sock,
 
        *ptr = strdup(host);
 
-       if (*ipv6)
-               ret = inet_pton(AF_INET6, host, inp6);
-       else
-               ret = inet_pton(AF_INET, host, inp);
-
-       if (ret != 1) {
-               struct hostent *hent;
-
-               hent = gethostbyname(host);
-               if (!hent) {
-                       log_err("fio: failed to resolve <%s>\n", host);
-                       free(*ptr);
-                       *ptr = NULL;
-                       return 1;
-               }
-
-               if (*ipv6) {
-                       if (hent->h_addrtype != AF_INET6) {
-                               log_info("fio: falling back to IPv4\n");
-                               *ipv6 = 0;
-                       } else
-                               memcpy(inp6, hent->h_addr_list[0], 16);
-               }
-               if (!*ipv6) {
-                       if (hent->h_addrtype != AF_INET) {
-                               log_err("fio: lookup type mismatch\n");
-                               free(*ptr);
-                               *ptr = NULL;
-                               return 1;
-                       }
-                       memcpy(inp, hent->h_addr_list[0], 4);
-               }
+       if (fio_server_parse_host(*ptr, ipv6, inp, inp6)) {
+               free(*ptr);
+               *ptr = NULL;
+               return 1;
        }
 
        if (*port == 0)
@@ -1186,7 +1590,7 @@ int fio_start_server(char *pidfile)
 
 #if defined(WIN32)
        WSADATA wsd;
-       WSAStartup(MAKEWORD(2,2), &wsd);
+       WSAStartup(MAKEWORD(2, 2), &wsd);
 #endif
 
        if (!pidfile)
index 9bf8907f1b7d754fb7f35386be70a95ea787015c..5273fd17cff55b45b7a20ccf45b042a3070f936a 100644 (file)
--- a/server.h
+++ b/server.h
@@ -10,6 +10,8 @@
 #include "os/os.h"
 #include "diskutil.h"
 
+#define FIO_NET_PORT 8765
+
 /*
  * On-wire encoding is little endian
  */
@@ -28,41 +30,47 @@ struct fio_net_cmd {
        uint8_t payload[0];     /* payload */
 };
 
-struct fio_net_int_cmd {
-       struct fio_net_cmd cmd;
+struct fio_net_cmd_reply {
        struct flist_head list;
        struct timeval tv;
        uint64_t saved_tag;
+       uint16_t opcode;
 };
 
 enum {
-       FIO_SERVER_VER          = 8,
-
-       FIO_SERVER_MAX_PDU      = 1024,
-
-       FIO_NET_CMD_QUIT        = 1,
-       FIO_NET_CMD_EXIT        = 2,
-       FIO_NET_CMD_JOB         = 3,
-       FIO_NET_CMD_JOBLINE     = 4,
-       FIO_NET_CMD_TEXT        = 5,
-       FIO_NET_CMD_TS          = 6,
-       FIO_NET_CMD_GS          = 7,
-       FIO_NET_CMD_SEND_ETA    = 8,
-       FIO_NET_CMD_ETA         = 9,
-       FIO_NET_CMD_PROBE       = 10,
-       FIO_NET_CMD_START       = 11,
-       FIO_NET_CMD_STOP        = 12,
-       FIO_NET_CMD_DU          = 13,
-       FIO_NET_CMD_RUN         = 14,
-       FIO_NET_CMD_NR          = 15,
-
-       FIO_NET_CMD_F_MORE      = 1UL << 0,
+       FIO_SERVER_VER                  = 18,
+
+       FIO_SERVER_MAX_FRAGMENT_PDU     = 1024,
+
+       FIO_NET_CMD_QUIT                = 1,
+       FIO_NET_CMD_EXIT                = 2,
+       FIO_NET_CMD_JOB                 = 3,
+       FIO_NET_CMD_JOBLINE             = 4,
+       FIO_NET_CMD_TEXT                = 5,
+       FIO_NET_CMD_TS                  = 6,
+       FIO_NET_CMD_GS                  = 7,
+       FIO_NET_CMD_SEND_ETA            = 8,
+       FIO_NET_CMD_ETA                 = 9,
+       FIO_NET_CMD_PROBE               = 10,
+       FIO_NET_CMD_START               = 11,
+       FIO_NET_CMD_STOP                = 12,
+       FIO_NET_CMD_DU                  = 13,
+       FIO_NET_CMD_SERVER_START        = 14,
+       FIO_NET_CMD_ADD_JOB             = 15,
+       FIO_NET_CMD_RUN                 = 16,
+       FIO_NET_CMD_IOLOG               = 17,
+       FIO_NET_CMD_UPDATE_JOB          = 18,
+       FIO_NET_CMD_NR                  = 19,
+
+       FIO_NET_CMD_F_MORE              = 1UL << 0,
 
        /* crc does not include the crc fields */
-       FIO_NET_CMD_CRC_SZ      = sizeof(struct fio_net_cmd) -
-                                       2 * sizeof(uint16_t),
+       FIO_NET_CMD_CRC_SZ              = sizeof(struct fio_net_cmd) -
+                                               2 * sizeof(uint16_t),
+
+       FIO_NET_NAME_MAX                = 256,
 
-       FIO_NET_CLIENT_TIMEOUT  = 5000,
+       FIO_NET_CLIENT_TIMEOUT          = 5000,
 };
 
 struct cmd_ts_pdu {
@@ -82,6 +90,8 @@ struct cmd_probe_pdu {
        uint8_t os;
        uint8_t arch;
        uint8_t bpp;
+       uint32_t cpus;
+       uint64_t flags;
 };
 
 struct cmd_single_line_pdu {
@@ -91,24 +101,54 @@ struct cmd_single_line_pdu {
 
 struct cmd_line_pdu {
        uint16_t lines;
+       uint16_t client_type;
        struct cmd_single_line_pdu options[0];
 };
 
+struct cmd_job_pdu {
+       uint32_t buf_len;
+       uint32_t client_type;
+       uint8_t buf[0];
+};
+
 struct cmd_start_pdu {
        uint32_t jobs;
 };
 
 struct cmd_end_pdu {
        uint32_t error;
+       uint32_t signal;
+};
+
+struct cmd_add_job_pdu {
+       uint32_t thread_number;
+       uint32_t groupid;
+       struct thread_options_pack top;
+};
+
+struct cmd_text_pdu {
+       uint32_t level;
+       uint32_t buf_len;
+       uint64_t log_sec;
+       uint64_t log_usec;
+       uint8_t buf[0];
+};
+
+struct cmd_iolog_pdu {
+       uint32_t thread_number;
+       uint32_t nr_samples;
+       uint32_t log_type;
+       uint8_t name[FIO_NET_NAME_MAX];
+       struct io_sample samples[0];
 };
 
 extern int fio_start_server(char *);
-extern int fio_server_text_output(const char *, size_t);
-extern int fio_server_log(const char *format, ...);
-extern int fio_net_send_cmd(int, uint16_t, const void *, off_t, uint64_t);
+extern int fio_server_text_output(int, const char *, size_t);
+extern int fio_net_send_cmd(int, uint16_t, const void *, off_t, uint64_t *, struct flist_head *);
 extern int fio_net_send_simple_cmd(int, uint16_t, uint64_t, struct flist_head *);
 extern void fio_server_set_arg(const char *);
 extern int fio_server_parse_string(const char *, char **, int *, int *, struct in_addr *, struct in6_addr *, int *);
+extern int fio_server_parse_host(const char *, int *, struct in_addr *, struct in6_addr *);
 extern const char *fio_server_op(unsigned int);
 extern void fio_server_got_signal(int);
 
@@ -121,33 +161,44 @@ extern void fio_server_idle_loop(void);
 
 extern int fio_clients_connect(void);
 extern int fio_clients_send_ini(const char *);
-extern int fio_handle_clients(void);
-extern int fio_client_add(const char *, void **);
 extern void fio_client_add_cmd_option(void *, const char *);
 extern void fio_client_add_ini_file(void *, const char *);
 
 extern int fio_recv_data(int sk, void *p, unsigned int len);
 extern int fio_send_data(int sk, const void *p, unsigned int len);
 extern void fio_net_cmd_crc(struct fio_net_cmd *);
+extern void fio_net_cmd_crc_pdu(struct fio_net_cmd *, const void *);
 extern struct fio_net_cmd *fio_net_recv_cmd(int sk);
 
+extern int fio_send_iolog(struct thread_data *, struct io_log *, const char *);
+extern void fio_server_send_add_job(struct thread_data *);
+extern void fio_server_send_start(struct thread_data *);
+extern int fio_net_send_stop(int sk, int error, int signal);
+extern int fio_net_send_quit(int sk);
+
 extern int exit_backend;
 extern int fio_net_port;
 
-static inline void fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode,
-                                   const void *pdu, uint32_t pdu_len,
-                                   uint64_t tag)
+static inline void __fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode,
+                                     uint32_t pdu_len, uint64_t tag)
 {
        memset(cmd, 0, sizeof(*cmd));
 
        cmd->version    = __cpu_to_le16(FIO_SERVER_VER);
        cmd->opcode     = cpu_to_le16(opcode);
        cmd->tag        = cpu_to_le64(tag);
+       cmd->pdu_len    = cpu_to_le32(pdu_len);
+}
+
+
+static inline void fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode,
+                                   const void *pdu, uint32_t pdu_len,
+                                   uint64_t tag)
+{
+       __fio_init_net_cmd(cmd, opcode, pdu_len, tag);
 
-       if (pdu) {
-               cmd->pdu_len    = cpu_to_le32(pdu_len);
+       if (pdu)
                memcpy(&cmd->payload, pdu, pdu_len);
-       }
 }
 
 #endif
diff --git a/stat.c b/stat.c
index d041ef3c0bb56c770334d551061824113c8b5b2d..4a881d4fedf75b22b6283cffa2fdd0236cf82dda 100644 (file)
--- a/stat.c
+++ b/stat.c
@@ -64,12 +64,12 @@ static unsigned int plat_val_to_idx(unsigned int val)
 
        /*
         * Discard the error bits and apply the mask to find the
-         * index for the buckets in the group
+        * index for the buckets in the group
         */
        offset = (FIO_IO_U_PLAT_VAL - 1) & (val >> error_bits);
 
        /* Make sure the index does not exceed (array size - 1) */
-       idx = (base + offset) < (FIO_IO_U_PLAT_NR - 1)?
+       idx = (base + offset) < (FIO_IO_U_PLAT_NR - 1) ?
                (base + offset) : (FIO_IO_U_PLAT_NR - 1);
 
        return idx;
@@ -87,11 +87,11 @@ static unsigned int plat_idx_to_val(unsigned int idx)
 
        /* MSB <= (FIO_IO_U_PLAT_BITS-1), cannot be rounded off. Use
         * all bits of the sample as index */
-       if (idx < (FIO_IO_U_PLAT_VAL << 1) )
+       if (idx < (FIO_IO_U_PLAT_VAL << 1))
                return idx;
 
        /* Find the group and compute the minimum value of that group */
-       error_bits = (idx >> FIO_IO_U_PLAT_BITS) -1;
+       error_bits = (idx >> FIO_IO_U_PLAT_BITS) - 1;
        base = 1 << (error_bits + FIO_IO_U_PLAT_BITS);
 
        /* Find its bucket number of the group */
@@ -115,11 +115,9 @@ static int double_cmp(const void *a, const void *b)
        return cmp;
 }
 
-static unsigned int calc_clat_percentiles(unsigned int *io_u_plat,
-                                         unsigned long nr, fio_fp64_t *plist,
-                                         unsigned int **output,
-                                         unsigned int *maxv,
-                                         unsigned int *minv)
+unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
+                                  fio_fp64_t *plist, unsigned int **output,
+                                  unsigned int *maxv, unsigned int *minv)
 {
        unsigned long sum = 0;
        unsigned int len, i, j = 0;
@@ -143,7 +141,7 @@ static unsigned int calc_clat_percentiles(unsigned int *io_u_plat,
         * isn't a worry. Also note that this does not work for NaN values.
         */
        if (len > 1)
-               qsort((void*)plist, len, sizeof(plist[0]), double_cmp);
+               qsort((void *)plist, len, sizeof(plist[0]), double_cmp);
 
        /*
         * Calculate bucket values, note down max and min values
@@ -235,8 +233,8 @@ out:
                free(ovals);
 }
 
-static int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max,
-                   double *mean, double *dev)
+int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max,
+            double *mean, double *dev)
 {
        double n = is->samples;
 
@@ -288,12 +286,7 @@ void show_group_stats(struct group_run_stats *rs)
        }
 }
 
-#define ts_total_io_u(ts)      \
-       ((ts)->total_io_u[DDIR_READ] + (ts)->total_io_u[DDIR_WRITE] +\
-               (ts)->total_io_u[DDIR_TRIM])
-
-static void stat_calc_dist(unsigned int *map, unsigned long total,
-                          double *io_u_dist)
+void stat_calc_dist(unsigned int *map, unsigned long total, double *io_u_dist)
 {
        int i;
 
@@ -314,7 +307,7 @@ static void stat_calc_dist(unsigned int *map, unsigned long total,
 static void stat_calc_lat(struct thread_stat *ts, double *dst,
                          unsigned int *src, int nr)
 {
-       unsigned long total = ts_total_io_u(ts);
+       unsigned long total = ddir_rw_sum(ts->total_io_u);
        int i;
 
        /*
@@ -331,28 +324,33 @@ static void stat_calc_lat(struct thread_stat *ts, double *dst,
        }
 }
 
-static void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat)
+void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat)
 {
        stat_calc_lat(ts, io_u_lat, ts->io_u_lat_u, FIO_IO_U_LAT_U_NR);
 }
 
-static void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat)
+void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat)
 {
        stat_calc_lat(ts, io_u_lat, ts->io_u_lat_m, FIO_IO_U_LAT_M_NR);
 }
 
-static int usec_to_msec(unsigned long *min, unsigned long *max, double *mean,
-                       double *dev)
+static void display_lat(const char *name, unsigned long min, unsigned long max,
+                       double mean, double dev)
 {
-       if (*min > 1000 && *max > 1000 && *mean > 1000.0 && *dev > 1000.0) {
-               *min /= 1000;
-               *max /= 1000;
-               *mean /= 1000.0;
-               *dev /= 1000.0;
-               return 0;
-       }
+       const char *base = "(usec)";
+       char *minp, *maxp;
 
-       return 1;
+       if (!usec_to_msec(&min, &max, &mean, &dev))
+               base = "(msec)";
+
+       minp = num2str(min, 6, 1, 0);
+       maxp = num2str(max, 6, 1, 0);
+
+       log_info("    %s %s: min=%s, max=%s, avg=%5.02f,"
+                " stdev=%5.02f\n", name, base, minp, maxp, mean, dev);
+
+       free(minp);
+       free(maxp);
 }
 
 static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
@@ -388,54 +386,13 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
        free(bw_p);
        free(iops_p);
 
-       if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev)) {
-               const char *base = "(usec)";
-               char *minp, *maxp;
-
-               if (!usec_to_msec(&min, &max, &mean, &dev))
-                       base = "(msec)";
-
-               minp = num2str(min, 6, 1, 0);
-               maxp = num2str(max, 6, 1, 0);
-
-               log_info("    slat %s: min=%s, max=%s, avg=%5.02f,"
-                        " stdev=%5.02f\n", base, minp, maxp, mean, dev);
-
-               free(minp);
-               free(maxp);
-       }
-       if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev)) {
-               const char *base = "(usec)";
-               char *minp, *maxp;
-
-               if (!usec_to_msec(&min, &max, &mean, &dev))
-                       base = "(msec)";
-
-               minp = num2str(min, 6, 1, 0);
-               maxp = num2str(max, 6, 1, 0);
-
-               log_info("    clat %s: min=%s, max=%s, avg=%5.02f,"
-                        " stdev=%5.02f\n", base, minp, maxp, mean, dev);
-
-               free(minp);
-               free(maxp);
-       }
-       if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) {
-               const char *base = "(usec)";
-               char *minp, *maxp;
-
-               if (!usec_to_msec(&min, &max, &mean, &dev))
-                       base = "(msec)";
-
-               minp = num2str(min, 6, 1, 0);
-               maxp = num2str(max, 6, 1, 0);
-
-               log_info("     lat %s: min=%s, max=%s, avg=%5.02f,"
-                        " stdev=%5.02f\n", base, minp, maxp, mean, dev);
+       if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
+               display_lat("slat", min, max, mean, dev);
+       if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
+               display_lat("clat", min, max, mean, dev);
+       if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
+               display_lat(" lat", min, max, mean, dev);
 
-               free(minp);
-               free(maxp);
-       }
        if (ts->clat_percentiles) {
                show_clat_percentiles(ts->io_u_plat[ddir],
                                        ts->clat_stat[ddir].samples,
@@ -512,8 +469,14 @@ static void show_lat_m(double *io_u_lat_m)
        show_lat(io_u_lat_m, FIO_IO_U_LAT_M_NR, ranges, "msec");
 }
 
-static void show_latencies(double *io_u_lat_u, double *io_u_lat_m)
+static void show_latencies(struct thread_stat *ts)
 {
+       double io_u_lat_u[FIO_IO_U_LAT_U_NR];
+       double io_u_lat_m[FIO_IO_U_LAT_M_NR];
+
+       stat_calc_lat_u(ts, io_u_lat_u);
+       stat_calc_lat_m(ts, io_u_lat_m);
+
        show_lat_u(io_u_lat_u);
        show_lat_m(io_u_lat_m);
 }
@@ -523,8 +486,6 @@ void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs)
        double usr_cpu, sys_cpu;
        unsigned long runtime;
        double io_u_dist[FIO_IO_U_MAP_NR];
-       double io_u_lat_u[FIO_IO_U_LAT_U_NR];
-       double io_u_lat_m[FIO_IO_U_LAT_M_NR];
        time_t time_p;
        char time_buf[64];
 
@@ -557,9 +518,7 @@ void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs)
        if (ts->io_bytes[DDIR_TRIM])
                show_ddir_status(rs, ts, DDIR_TRIM);
 
-       stat_calc_lat_u(ts, io_u_lat_u);
-       stat_calc_lat_m(ts, io_u_lat_m);
-       show_latencies(io_u_lat_u, io_u_lat_m);
+       show_latencies(ts);
 
        runtime = ts->total_run_time;
        if (runtime) {
@@ -575,7 +534,7 @@ void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs)
        log_info("  cpu          : usr=%3.2f%%, sys=%3.2f%%, ctx=%lu, majf=%lu,"
                 " minf=%lu\n", usr_cpu, sys_cpu, ts->ctx, ts->majf, ts->minf);
 
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+       stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        log_info("  IO depths    : 1=%3.1f%%, 2=%3.1f%%, 4=%3.1f%%, 8=%3.1f%%,"
                 " 16=%3.1f%%, 32=%3.1f%%, >=64=%3.1f%%\n", io_u_dist[0],
                                        io_u_dist[1], io_u_dist[2],
@@ -783,7 +742,7 @@ static void add_ddir_status_json(struct thread_stat *ts,
 }
 
 static void show_thread_status_terse_v2(struct thread_stat *ts,
-                                       struct group_run_stats *rs)
+                                       struct group_run_stats *rs)
 {
        double io_u_dist[FIO_IO_U_MAP_NR];
        double io_u_lat_u[FIO_IO_U_LAT_U_NR];
@@ -815,7 +774,7 @@ static void show_thread_status_terse_v2(struct thread_stat *ts,
                                                                ts->minf);
 
        /* Calc % distribution of IO depths, usecond, msecond latency */
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+       stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        stat_calc_lat_u(ts, io_u_lat_u);
        stat_calc_lat_m(ts, io_u_lat_m);
 
@@ -877,7 +836,7 @@ static void show_thread_status_terse_v3_v4(struct thread_stat *ts,
                                                                ts->minf);
 
        /* Calc % distribution of IO depths, usecond, msecond latency */
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+       stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        stat_calc_lat_u(ts, io_u_lat_u);
        stat_calc_lat_m(ts, io_u_lat_m);
 
@@ -944,7 +903,7 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts,
 
 
        /* Calc % distribution of IO depths, usecond, msecond latency */
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+       stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        stat_calc_lat_u(ts, io_u_lat_u);
        stat_calc_lat_m(ts, io_u_lat_m);
 
@@ -1202,6 +1161,11 @@ void show_run_stats(void)
                        else
                                memset(ts->description, 0, FIO_JOBNAME_SIZE);
 
+                       /*
+                        * If multiple entries in this group, this is
+                        * the first member.
+                        */
+                       ts->thread_number = td->thread_number;
                        ts->groupid = td->groupid;
 
                        /*
diff --git a/stat.h b/stat.h
index b9e0448153ed50979f38efec7ec23d47ee3d38f6..8a1536e889b633593f2dabcfe439b9046e8fb42a 100644 (file)
--- a/stat.h
+++ b/stat.h
@@ -1,6 +1,8 @@
 #ifndef FIO_STAT_H
 #define FIO_STAT_H
 
+#include "iolog.h"
+
 struct group_run_stats {
        uint64_t max_run[DDIR_RWDIR_CNT], min_run[DDIR_RWDIR_CNT];
        uint64_t max_bw[DDIR_RWDIR_CNT], min_bw[DDIR_RWDIR_CNT];
@@ -116,6 +118,7 @@ struct thread_stat {
        char name[FIO_JOBNAME_SIZE];
        char verror[FIO_VERROR_SIZE];
        uint32_t error;
+       uint32_t thread_number;
        uint32_t groupid;
        uint32_t pid;
        char description[FIO_JOBNAME_SIZE];
@@ -174,8 +177,8 @@ struct jobs_eta {
        uint32_t nr_ramp;
        uint32_t nr_pending;
        uint32_t files_open;
-       uint32_t m_rate, t_rate;
-       uint32_t m_iops, t_iops;
+       uint32_t m_rate[DDIR_RWDIR_CNT], t_rate[DDIR_RWDIR_CNT];
+       uint32_t m_iops[DDIR_RWDIR_CNT], t_iops[DDIR_RWDIR_CNT];
        uint32_t rate[DDIR_RWDIR_CNT];
        uint32_t iops[DDIR_RWDIR_CNT];
        uint64_t elapsed_sec;
@@ -199,5 +202,25 @@ extern void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, i
 extern void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src);
 extern void init_thread_stat(struct thread_stat *ts);
 extern void init_group_run_stat(struct group_run_stats *gs);
+extern void eta_to_str(char *str, unsigned long eta_sec);
+extern int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max, double *mean, double *dev);
+extern unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr, fio_fp64_t *plist, unsigned int **output, unsigned int *maxv, unsigned int *minv);
+extern void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat);
+extern void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat);
+extern void stat_calc_dist(unsigned int *map, unsigned long total, double *io_u_dist);
+
+static inline int usec_to_msec(unsigned long *min, unsigned long *max,
+                              double *mean, double *dev)
+{
+       if (*min > 1000 && *max > 1000 && *mean > 1000.0 && *dev > 1000.0) {
+               *min /= 1000;
+               *max /= 1000;
+               *mean /= 1000.0;
+               *dev /= 1000.0;
+               return 0;
+       }
+
+       return 1;
+}
 
 #endif
index afc25f3cf0122336bac03330e45a14b5b24c72b8..3898ab74c1afe5b65834c3500bda72d730d20df3 100644 (file)
@@ -1,5 +1,5 @@
 #include <stdio.h>
-#include "../ieee754.h"
+#include "../lib/ieee754.h"
 
 static double values[] = { -17.23, 17.23, 123.4567, 98765.4321, 0.0 };
 
diff --git a/t/jobs/t0001-52c58027 b/t/jobs/t0001-52c58027
deleted file mode 100644 (file)
index 49b7d11..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#Commit 52c580272d87d2b9b8a65d317bf7c2d432a30fec
-[foo]
-size=20000
-bsrange=1k-4k
-rw=randread
-ioengine=null
diff --git a/t/jobs/t0001-52c58027.fio b/t/jobs/t0001-52c58027.fio
new file mode 100644 (file)
index 0000000..49b7d11
--- /dev/null
@@ -0,0 +1,6 @@
+#Commit 52c580272d87d2b9b8a65d317bf7c2d432a30fec
+[foo]
+size=20000
+bsrange=1k-4k
+rw=randread
+ioengine=null
diff --git a/thread_options.h b/thread_options.h
new file mode 100644 (file)
index 0000000..9b90796
--- /dev/null
@@ -0,0 +1,429 @@
+#ifndef FIO_THREAD_OPTIONS_H
+#define FIO_THREAD_OPTIONS_H
+
+#include "arch/arch.h"
+#include "os/os.h"
+#include "stat.h"
+#include "gettime.h"
+
+/*
+ * What type of allocation to use for io buffers
+ */
+enum fio_memtype {
+       MEM_MALLOC = 0, /* ordinary malloc */
+       MEM_SHM,        /* use shared memory segments */
+       MEM_SHMHUGE,    /* use shared memory segments with huge pages */
+       MEM_MMAP,       /* use anonynomous mmap */
+       MEM_MMAPHUGE,   /* memory mapped huge file */
+};
+
+/*
+ * What type of errors to continue on when continue_on_error is used
+ */
+enum error_type_bit {
+       ERROR_TYPE_READ_BIT = 0,
+       ERROR_TYPE_WRITE_BIT = 1,
+       ERROR_TYPE_VERIFY_BIT = 2,
+       ERROR_TYPE_CNT = 3,
+};
+
+#define ERROR_STR_MAX  128
+
+enum error_type {
+        ERROR_TYPE_NONE = 0,
+        ERROR_TYPE_READ = 1 << ERROR_TYPE_READ_BIT,
+        ERROR_TYPE_WRITE = 1 << ERROR_TYPE_WRITE_BIT,
+        ERROR_TYPE_VERIFY = 1 << ERROR_TYPE_VERIFY_BIT,
+        ERROR_TYPE_ANY = 0xffff,
+};
+
+#define BSSPLIT_MAX    64
+
+struct bssplit {
+       uint32_t bs;
+       uint32_t perc;
+};
+
+struct thread_options {
+       int pad;
+       char *description;
+       char *name;
+       char *directory;
+       char *filename;
+       char *opendir;
+       char *ioengine;
+       char *mmapfile;
+       enum td_ddir td_ddir;
+       unsigned int rw_seq;
+       unsigned int kb_base;
+       unsigned int ddir_seq_nr;
+       long ddir_seq_add;
+       unsigned int iodepth;
+       unsigned int iodepth_low;
+       unsigned int iodepth_batch;
+       unsigned int iodepth_batch_complete;
+
+       unsigned long long size;
+       unsigned int size_percent;
+       unsigned int fill_device;
+       unsigned long long file_size_low;
+       unsigned long long file_size_high;
+       unsigned long long start_offset;
+
+       unsigned int bs[DDIR_RWDIR_CNT];
+       unsigned int ba[DDIR_RWDIR_CNT];
+       unsigned int min_bs[DDIR_RWDIR_CNT];
+       unsigned int max_bs[DDIR_RWDIR_CNT];
+       struct bssplit *bssplit[DDIR_RWDIR_CNT];
+       unsigned int bssplit_nr[DDIR_RWDIR_CNT];
+
+       int *ignore_error[ERROR_TYPE_CNT];
+       unsigned int ignore_error_nr[ERROR_TYPE_CNT];
+       unsigned int error_dump;
+
+       unsigned int nr_files;
+       unsigned int open_files;
+       enum file_lock_mode file_lock_mode;
+       unsigned int lockfile_batch;
+
+       unsigned int odirect;
+       unsigned int invalidate_cache;
+       unsigned int create_serialize;
+       unsigned int create_fsync;
+       unsigned int create_on_open;
+       unsigned int create_only;
+       unsigned int end_fsync;
+       unsigned int pre_read;
+       unsigned int sync_io;
+       unsigned int verify;
+       unsigned int do_verify;
+       unsigned int verifysort;
+       unsigned int verify_interval;
+       unsigned int verify_offset;
+       char verify_pattern[MAX_PATTERN_SIZE];
+       unsigned int verify_pattern_bytes;
+       unsigned int verify_fatal;
+       unsigned int verify_dump;
+       unsigned int verify_async;
+       unsigned long long verify_backlog;
+       unsigned int verify_batch;
+       unsigned int use_thread;
+       unsigned int unlink;
+       unsigned int do_disk_util;
+       unsigned int override_sync;
+       unsigned int rand_repeatable;
+       unsigned int use_os_rand;
+       unsigned int log_avg_msec;
+       unsigned int norandommap;
+       unsigned int softrandommap;
+       unsigned int bs_unaligned;
+       unsigned int fsync_on_close;
+
+       unsigned int hugepage_size;
+       unsigned int rw_min_bs;
+       unsigned int thinktime;
+       unsigned int thinktime_spin;
+       unsigned int thinktime_blocks;
+       unsigned int fsync_blocks;
+       unsigned int fdatasync_blocks;
+       unsigned int barrier_blocks;
+       unsigned long long start_delay;
+       unsigned long long timeout;
+       unsigned long long ramp_time;
+       unsigned int overwrite;
+       unsigned int bw_avg_time;
+       unsigned int iops_avg_time;
+       unsigned int loops;
+       unsigned long long zone_range;
+       unsigned long long zone_size;
+       unsigned long long zone_skip;
+       unsigned long long lockmem;
+       enum fio_memtype mem_type;
+       unsigned int mem_align;
+
+       unsigned int stonewall;
+       unsigned int new_group;
+       unsigned int numjobs;
+       os_cpu_mask_t cpumask;
+       unsigned int cpumask_set;
+       os_cpu_mask_t verify_cpumask;
+       unsigned int verify_cpumask_set;
+       unsigned int iolog;
+       unsigned int rwmixcycle;
+       unsigned int rwmix[2];
+       unsigned int nice;
+       unsigned int ioprio;
+       unsigned int ioprio_class;
+       unsigned int file_service_type;
+       unsigned int group_reporting;
+       unsigned int fadvise_hint;
+       enum fio_fallocate_mode fallocate_mode;
+       unsigned int zero_buffers;
+       unsigned int refill_buffers;
+       unsigned int scramble_buffers;
+       unsigned int compress_percentage;
+       unsigned int compress_chunk;
+       unsigned int time_based;
+       unsigned int disable_lat;
+       unsigned int disable_clat;
+       unsigned int disable_slat;
+       unsigned int disable_bw;
+       unsigned int gtod_reduce;
+       unsigned int gtod_cpu;
+       unsigned int gtod_offload;
+       enum fio_cs clocksource;
+       unsigned int no_stall;
+       unsigned int trim_percentage;
+       unsigned int trim_batch;
+       unsigned int trim_zero;
+       unsigned long long trim_backlog;
+       unsigned int clat_percentiles;
+       unsigned int overwrite_plist;
+       fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
+
+       char *read_iolog_file;
+       char *write_iolog_file;
+       char *bw_log_file;
+       char *lat_log_file;
+       char *iops_log_file;
+       char *replay_redirect;
+
+       /*
+        * Pre-run and post-run shell
+        */
+       char *exec_prerun;
+       char *exec_postrun;
+
+       unsigned int rate[DDIR_RWDIR_CNT];
+       unsigned int ratemin[DDIR_RWDIR_CNT];
+       unsigned int ratecycle;
+       unsigned int rate_iops[DDIR_RWDIR_CNT];
+       unsigned int rate_iops_min[DDIR_RWDIR_CNT];
+
+       char *ioscheduler;
+
+       /*
+        * I/O Error handling
+        */
+       enum error_type continue_on_error;
+
+       /*
+        * Benchmark profile type
+        */
+       char *profile;
+
+       /*
+        * blkio cgroup support
+        */
+       char *cgroup;
+       unsigned int cgroup_weight;
+       unsigned int cgroup_nodelete;
+
+       unsigned int uid;
+       unsigned int gid;
+
+       int flow_id;
+       int flow;
+       int flow_watermark;
+       unsigned int flow_sleep;
+
+       unsigned long long offset_increment;
+
+       unsigned int sync_file_range;
+};
+
+#define FIO_TOP_STR_MAX                256
+
+struct thread_options_pack {
+       uint8_t description[FIO_TOP_STR_MAX];
+       uint8_t name[FIO_TOP_STR_MAX];
+       uint8_t directory[FIO_TOP_STR_MAX];
+       uint8_t filename[FIO_TOP_STR_MAX];
+       uint8_t opendir[FIO_TOP_STR_MAX];
+       uint8_t ioengine[FIO_TOP_STR_MAX];
+       uint8_t mmapfile[FIO_TOP_STR_MAX];
+       uint32_t td_ddir;
+       uint32_t rw_seq;
+       uint32_t kb_base;
+       uint32_t ddir_seq_nr;
+       uint64_t ddir_seq_add;
+       uint32_t iodepth;
+       uint32_t iodepth_low;
+       uint32_t iodepth_batch;
+       uint32_t iodepth_batch_complete;
+
+       uint64_t size;
+       uint32_t size_percent;
+       uint32_t fill_device;
+       uint64_t file_size_low;
+       uint64_t file_size_high;
+       uint64_t start_offset;
+
+       uint32_t bs[DDIR_RWDIR_CNT];
+       uint32_t ba[DDIR_RWDIR_CNT];
+       uint32_t min_bs[DDIR_RWDIR_CNT];
+       uint32_t max_bs[DDIR_RWDIR_CNT];
+       struct bssplit bssplit[DDIR_RWDIR_CNT][BSSPLIT_MAX];
+       uint32_t bssplit_nr[DDIR_RWDIR_CNT];
+
+       uint32_t ignore_error[ERROR_TYPE_CNT][ERROR_STR_MAX];
+       uint32_t ignore_error_nr[ERROR_TYPE_CNT];
+       uint32_t error_dump;
+
+       uint32_t nr_files;
+       uint32_t open_files;
+       uint32_t file_lock_mode;
+       uint32_t lockfile_batch;
+
+       uint32_t odirect;
+       uint32_t invalidate_cache;
+       uint32_t create_serialize;
+       uint32_t create_fsync;
+       uint32_t create_on_open;
+       uint32_t create_only;
+       uint32_t end_fsync;
+       uint32_t pre_read;
+       uint32_t sync_io;
+       uint32_t verify;
+       uint32_t do_verify;
+       uint32_t verifysort;
+       uint32_t verify_interval;
+       uint32_t verify_offset;
+       uint8_t verify_pattern[MAX_PATTERN_SIZE];
+       uint32_t verify_pattern_bytes;
+       uint32_t verify_fatal;
+       uint32_t verify_dump;
+       uint32_t verify_async;
+       uint64_t verify_backlog;
+       uint32_t verify_batch;
+       uint32_t use_thread;
+       uint32_t unlink;
+       uint32_t do_disk_util;
+       uint32_t override_sync;
+       uint32_t rand_repeatable;
+       uint32_t use_os_rand;
+       uint32_t log_avg_msec;
+       uint32_t norandommap;
+       uint32_t softrandommap;
+       uint32_t bs_unaligned;
+       uint32_t fsync_on_close;
+
+       uint32_t hugepage_size;
+       uint32_t rw_min_bs;
+       uint32_t thinktime;
+       uint32_t thinktime_spin;
+       uint32_t thinktime_blocks;
+       uint32_t fsync_blocks;
+       uint32_t fdatasync_blocks;
+       uint32_t barrier_blocks;
+       uint64_t start_delay;
+       uint64_t timeout;
+       uint64_t ramp_time;
+       uint32_t overwrite;
+       uint32_t bw_avg_time;
+       uint32_t iops_avg_time;
+       uint32_t loops;
+       uint64_t zone_range;
+       uint64_t zone_size;
+       uint64_t zone_skip;
+       uint64_t lockmem;
+       uint32_t mem_type;
+       uint32_t mem_align;
+
+       uint32_t stonewall;
+       uint32_t new_group;
+       uint32_t numjobs;
+       uint8_t cpumask[FIO_TOP_STR_MAX];
+       uint32_t cpumask_set;
+       uint8_t verify_cpumask[FIO_TOP_STR_MAX];
+       uint32_t verify_cpumask_set;
+       uint32_t iolog;
+       uint32_t rwmixcycle;
+       uint32_t rwmix[2];
+       uint32_t nice;
+       uint32_t ioprio;
+       uint32_t ioprio_class;
+       uint32_t file_service_type;
+       uint32_t group_reporting;
+       uint32_t fadvise_hint;
+       uint32_t fallocate_mode;
+       uint32_t zero_buffers;
+       uint32_t refill_buffers;
+       uint32_t scramble_buffers;
+       unsigned int compress_percentage;
+       unsigned int compress_chunk;
+       uint32_t time_based;
+       uint32_t disable_lat;
+       uint32_t disable_clat;
+       uint32_t disable_slat;
+       uint32_t disable_bw;
+       uint32_t gtod_reduce;
+       uint32_t gtod_cpu;
+       uint32_t gtod_offload;
+       uint32_t clocksource;
+       uint32_t no_stall;
+       uint32_t trim_percentage;
+       uint32_t trim_batch;
+       uint32_t trim_zero;
+       uint64_t trim_backlog;
+       uint32_t clat_percentiles;
+       uint32_t overwrite_plist;
+       fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
+
+       uint8_t read_iolog_file[FIO_TOP_STR_MAX];
+       uint8_t write_iolog_file[FIO_TOP_STR_MAX];
+       uint8_t bw_log_file[FIO_TOP_STR_MAX];
+       uint8_t lat_log_file[FIO_TOP_STR_MAX];
+       uint8_t iops_log_file[FIO_TOP_STR_MAX];
+       uint8_t replay_redirect[FIO_TOP_STR_MAX];
+
+       /*
+        * Pre-run and post-run shell
+        */
+       uint8_t exec_prerun[FIO_TOP_STR_MAX];
+       uint8_t exec_postrun[FIO_TOP_STR_MAX];
+
+       uint32_t rate[DDIR_RWDIR_CNT];
+       uint32_t ratemin[DDIR_RWDIR_CNT];
+       uint32_t ratecycle;
+       uint32_t rate_iops[DDIR_RWDIR_CNT];
+       uint32_t rate_iops_min[DDIR_RWDIR_CNT];
+
+       uint8_t ioscheduler[FIO_TOP_STR_MAX];
+
+       /*
+        * I/O Error handling
+        */
+       uint32_t continue_on_error;
+
+       /*
+        * Benchmark profile type
+        */
+       uint8_t profile[FIO_TOP_STR_MAX];
+
+       /*
+        * blkio cgroup support
+        */
+       uint8_t cgroup[FIO_TOP_STR_MAX];
+       uint32_t cgroup_weight;
+       uint32_t cgroup_nodelete;
+
+       uint32_t uid;
+       uint32_t gid;
+
+       int32_t flow_id;
+       int32_t flow;
+       int32_t flow_watermark;
+       uint32_t flow_sleep;
+
+       uint64_t offset_increment;
+
+       uint32_t sync_file_range;
+} __attribute__((packed));
+
+extern void convert_thread_options_to_cpu(struct thread_options *o, struct thread_options_pack *top);
+extern void convert_thread_options_to_net(struct thread_options_pack *top, struct thread_options *);
+extern int fio_test_cconv(struct thread_options *);
+extern void options_default_fill(struct thread_options *o);
+
+#endif
diff --git a/tickmarks.c b/tickmarks.c
new file mode 100644 (file)
index 0000000..808de67
--- /dev/null
@@ -0,0 +1,147 @@
+#include <stdio.h>
+#include <math.h>
+#include <malloc.h>
+#include <string.h>
+
+/*
+ * adapted from Paul Heckbert's algorithm on p 657-659 of
+ * Andrew S. Glassner's book, "Graphics Gems"
+ * ISBN 0-12-286166-3
+ *
+ */
+
+#include "tickmarks.h"
+
+#define MAX(a, b) (((a) < (b)) ? (b) : (a))
+
+static double nicenum(double x, int round)
+{
+       int exp;        /* exponent of x */
+       double f;       /* fractional part of x */
+
+       exp = floor(log10(x));
+       f = x / pow(10.0, exp);
+       if (round) {
+               if (f < 1.5)
+                       return 1.0 * pow(10.0, exp);
+               if (f < 3.0)
+                       return 2.0 * pow(10.0, exp);
+               if (f < 7.0)
+                       return 5.0 * pow(10.0, exp);
+               return 10.0 * pow(10.0, exp);
+       }
+       if (f <= 1.0)
+               return 1.0 * pow(10.0, exp);
+       if (f <= 2.0)
+               return 2.0 * pow(10.0, exp);
+       if (f <= 5.0)
+               return 5.0 * pow(10.0, exp);
+       return 10.0 * pow(10.0, exp);
+}
+
+static void shorten(struct tickmark *tm, int nticks, int *power_of_ten,
+                       int use_KMG_symbols, int base_offset)
+{
+       const char shorten_chr[] = { 0, 'K', 'M', 'G', 'P', 'E', 0 };
+       int i, l, minshorten, shorten_idx = 0;
+       char *str;
+
+       minshorten = 100;
+       for (i = 0; i < nticks; i++) {
+               str = tm[i].string;
+               l = strlen(str);
+
+               if (strcmp(str, "0") == 0)
+                       continue;
+               if (l > 9 && strcmp(&str[l - 9], "000000000") == 0) {
+                       *power_of_ten = 9;
+                       shorten_idx = 3;
+               } else if (6 < minshorten && l > 6 &&
+                               strcmp(&str[l - 6], "000000") == 0) {
+                       *power_of_ten = 6;
+                       shorten_idx = 2;
+               } else if (l > 3 && strcmp(&str[l - 3], "000") == 0) {
+                       *power_of_ten = 3;
+                       shorten_idx = 1;
+               } else {
+                       *power_of_ten = 0;
+               }
+
+               if (*power_of_ten < minshorten)
+                       minshorten = *power_of_ten;
+       }
+
+       if (minshorten == 0)
+               return;
+       if (!use_KMG_symbols)
+               shorten_idx = 0;
+       else if (base_offset)
+               shorten_idx += base_offset;
+
+       for (i = 0; i < nticks; i++) {
+               str = tm[i].string;
+               l = strlen(str);
+               str[l - minshorten] = shorten_chr[shorten_idx];
+               if (shorten_idx)
+                       str[l - minshorten + 1] = '\0';
+       }
+}
+
+int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm,
+               int *power_of_ten, int use_KMG_symbols, int base_offset)
+{
+       char str[100];
+       int nfrac;
+       double d;       /* tick mark spacing */
+       double graphmin, graphmax;      /* graph range min and max */
+       double range, x;
+       int count, i;
+
+       /* we expect min != max */
+       range = nicenum(max - min, 0);
+       d = nicenum(range / (nticks - 1), 1);
+       graphmin = floor(min / d) * d;
+       graphmax = ceil(max / d) * d;
+       nfrac = MAX(-floor(log10(d)), 0);
+       snprintf(str, sizeof(str)-1, "%%.%df", nfrac);
+
+       count = ((graphmax + 0.5 * d) - graphmin) / d + 1;
+       *tm = malloc(sizeof(**tm) * count);
+
+       i = 0;
+       for (x = graphmin; x < graphmax + 0.5 * d; x += d) {
+               (*tm)[i].value = x;
+               sprintf((*tm)[i].string, str, x);
+               i++;
+       }
+       shorten(*tm, i, power_of_ten, use_KMG_symbols, base_offset);
+       return i;
+}
+
+#if 0
+
+static void test_range(double x, double y)
+{
+       int nticks, i;
+
+       struct tickmark *tm = NULL;
+       printf("Testing range %g - %g\n", x, y);
+       nticks = calc_tickmarks(x, y, 10, &tm);
+
+       for (i = 0; i < nticks; i++)
+               printf("   (%s) %g\n", tm[i].string, tm[i].value);
+
+       printf("\n\n");
+       free(tm);
+}
+
+int main(int argc, char *argv[])
+{
+       test_range(0.0005, 0.008);
+       test_range(0.5, 0.8);
+       test_range(5.5, 8.8);
+       test_range(50.5, 80.8);
+       test_range(-20, 20.8);
+       test_range(-30, 700.8);
+}
+#endif
diff --git a/tickmarks.h b/tickmarks.h
new file mode 100644 (file)
index 0000000..1e310db
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef TICKMARKS_H
+#define TICKMARKS_H
+
+struct tickmark {
+       double value;
+       char string[20];
+};
+
+int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm,
+                       int *power_of_ten, int use_KMG_symbols, int base_off);
+
+#endif
index f246dc8f9ba0cfe3f4a9e2b43316f398b75cb4b1..01b56fde0b3c48529d70750be5c32616a393b82c 100644 (file)
--- a/verify.c
+++ b/verify.c
@@ -1004,6 +1004,14 @@ int get_next_verify(struct thread_data *td, struct io_u *io_u)
        return 1;
 }
 
+void fio_verify_init(struct thread_data *td)
+{
+       if (td->o.verify == VERIFY_CRC32C_INTEL ||
+           td->o.verify == VERIFY_CRC32C) {
+               crc32c_intel_probe();
+       }
+}
+
 static void *verify_async_thread(void *data)
 {
        struct thread_data *td = data;
index 7c238c8d2de6c07f19497061e12a4c60ba6b06c3..6a81e9b012bc6fa0632a90ea0dc2e9acfe26755f 100644 (file)
--- a/verify.h
+++ b/verify.h
@@ -75,6 +75,7 @@ extern int __must_check get_next_verify(struct thread_data *td, struct io_u *);
 extern int __must_check verify_io_u(struct thread_data *, struct io_u *);
 extern int verify_io_u_async(struct thread_data *, struct io_u *);
 extern void fill_pattern(struct thread_data *td, void *p, unsigned int len, struct io_u *io_u, unsigned long seed, int use_seed);
+extern void fio_verify_init(struct thread_data *td);
 
 /*
  * Async verify offload