Merge branch 'master' into gfio
authorJens Axboe <axboe@kernel.dk>
Tue, 13 Mar 2012 13:01:11 +0000 (14:01 +0100)
committerJens Axboe <axboe@kernel.dk>
Tue, 13 Mar 2012 13:01:11 +0000 (14:01 +0100)
Conflicts:
client.c
fio.h

Signed-off-by: Jens Axboe <axboe@kernel.dk>
55 files changed:
GFIO-TODO [new file with mode: 0644]
Makefile
cconv.c [new file with mode: 0644]
client.c
client.h [new file with mode: 0644]
debug.h
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]
fio.c
fio.h
gfio.c [new file with mode: 0644]
graph.c [new file with mode: 0644]
graph.h [new file with mode: 0644]
init.c
io_ddir.h
ioengine.h
iolog.h
libfio.c
log.c
log.h
options.c
parse.c
parse.h
server.c
server.h
stat.c
stat.h
thread_options.h [new file with mode: 0644]
tickmarks.c [new file with mode: 0644]
tickmarks.h [new file with mode: 0644]

diff --git a/GFIO-TODO b/GFIO-TODO
new file mode 100644 (file)
index 0000000..f98bf55
--- /dev/null
+++ b/GFIO-TODO
@@ -0,0 +1,54 @@
+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.
+
+- Add 'Recent Files' to File menu.
+
+- 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).
index 673107f0d1dffb9a9c58719ba52fab2e865d60e2..762bd7d4dd122302b8a815e5ea1527a4e68aa1d4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -9,12 +9,16 @@ 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/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
+               memalign.c server.c client.c iolog.c backend.c libfio.c flow.c \
+               cconv.c
 
 ifeq ($(UNAME), Linux)
   SOURCE += diskutil.c fifo.c blktrace.c helpers.c cgroup.c trim.c \
@@ -64,6 +68,8 @@ ifneq (,$(findstring CYGWIN,$(UNAME)))
 endif
 
 OBJS = $(SOURCE:.c=.o)
+FIO_OBJS = $(OBJS) fio.o
+GFIO_OBJS = $(OBJS) gfio.o graph.o tickmarks.o
 
 T_SMALLOC_OBJS = t/stest.o
 T_SMALLOC_OBJS += mutex.o smalloc.o t/log.o
@@ -98,14 +104,23 @@ all: .depend $(PROGS) $(SCRIPTS)
 .c.o: .depend
        $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(CPPFLAGS) $<
 
+gfio.o: gfio.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
+
 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
@@ -113,7 +128,7 @@ fio: $(OBJS)
 $(PROGS): .depend
 
 clean:
-       -rm -f .depend $(OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) core.* core
+       -rm -f .depend $(GFIO_OBJS) $(FIO_OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) core.* core gfio
 
 cscope:
        @cscope -b -R
@@ -128,3 +143,5 @@ install: $(PROGS) $(SCRIPTS)
 ifneq ($(wildcard .depend),)
 include .depend
 endif
+
+
diff --git a/cconv.c b/cconv.c
new file mode 100644 (file)
index 0000000..eba1de8
--- /dev/null
+++ b/cconv.c
@@ -0,0 +1,400 @@
+#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->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 < 2; 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->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->write_lat_log = le32_to_cpu(top->write_lat_log);
+       o->write_bw_log = le32_to_cpu(top->write_bw_log);
+       o->write_iops_log = le32_to_cpu(top->write_iops_log);
+       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->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->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->cpuload = le32_to_cpu(top->cpuload);
+       o->cpucycle = le32_to_cpu(top->cpucycle);
+       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->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->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->write_lat_log = cpu_to_le32(o->write_lat_log);
+       top->write_bw_log = cpu_to_le32(o->write_bw_log);
+       top->write_iops_log = cpu_to_le32(o->write_iops_log);
+       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->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->cpuload = cpu_to_le32(o->cpuload);
+       top->cpucycle = cpu_to_le32(o->cpucycle);
+       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 < 2; 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->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);
+
+       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 dd75882c54f3714db8d8ca37e7119b91be742a66..e64b3f08e8a8b88be4b95ca665dedd4d66e776b8 100644 (file)
--- a/client.c
+++ b/client.c
 #include <signal.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;
+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_op        = 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,
 };
 
 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
@@ -86,9 +59,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);
@@ -129,37 +99,46 @@ 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;
 
+       free(client->hostname);
+       if (client->argv)
+               free(client->argv);
+       if (client->name)
+               free(client->name);
+
+       free(client);
+}
+
+static void remove_client(struct fio_client *client)
+{
+       assert(client->refs);
+
        dprint(FD_NET, "client: removed <%s>\n", client->hostname);
-       flist_del(&client->list);
+
+       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);
-               dec_jobs_eta(client->eta_in_flight);
+               fio_client_dec_jobs_eta(client->eta_in_flight, client->ops->eta);
        }
 
-       free(client->hostname);
-       if (client->argv)
-               free(client->argv);
-       if (client->name)
-               free(client->name);
-
-       free(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,
@@ -193,7 +172,53 @@ void fio_client_add_cmd_option(void *cookie, const char *opt)
        }
 }
 
-int fio_client_add(const char *hostname, void **cookie)
+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;
+
+       __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;
+}
+
+int fio_client_add(struct client_ops *ops, const char *hostname, void **cookie)
 {
        struct fio_client *existing = *cookie;
        struct fio_client *client;
@@ -228,6 +253,7 @@ int fio_client_add(const char *hostname, void **cookie)
                return -1;
 
        client->fd = -1;
+       client->ops = ops;
        client->refs = 1;
 
        __fio_client_add_cmd_option(client, "fio");
@@ -239,6 +265,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;
@@ -261,16 +294,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;
@@ -288,21 +325,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;
 
@@ -316,14 +357,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;
 }
 
+void fio_client_terminate(struct fio_client *client)
+{
+       fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_QUIT, 0, NULL);
+}
+
 void fio_clients_terminate(void)
 {
        struct flist_head *entry;
@@ -333,8 +381,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);
        }
 }
 
@@ -359,13 +406,6 @@ static void client_signal_handler(void)
        sigaction(SIGTERM, &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;
@@ -437,8 +477,6 @@ int fio_clients_connect(void)
                        continue;
                }
 
-               probe_client(client);
-
                if (client->argc > 1)
                        send_client_cmd_line(client);
        }
@@ -446,11 +484,38 @@ 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 stat sb;
        char *p, *buf;
@@ -461,14 +526,18 @@ 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);
@@ -503,6 +572,17 @@ static int fio_client_send_ini(struct fio_client *client, const char *filename)
        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;
@@ -513,8 +593,6 @@ int fio_clients_send_ini(const char *filename)
 
                if (fio_client_send_ini(client, filename))
                        remove_client(client);
-
-               client->sent_job = 1;
        }
 
        return !nr_clients;
@@ -615,13 +693,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)
@@ -639,14 +714,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;
@@ -684,9 +774,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");
@@ -703,21 +790,22 @@ 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++) {
+               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);
 }
 
-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;
 
@@ -725,12 +813,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++) {
+               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];
        }
@@ -739,12 +827,15 @@ 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);
        }
 }
@@ -785,9 +876,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)
@@ -819,22 +912,35 @@ 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)
+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);
@@ -843,61 +949,91 @@ 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);
                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_op(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 = pdu->error;
+               ops->stop(client, cmd);
                free(cmd);
                break;
-       case FIO_NET_CMD_STOP:
-               handle_stop(client, cmd);
+               }
+       case FIO_NET_CMD_ADD_JOB:
+               if (ops->add_job)
+                       ops->add_job(client, cmd);
                free(cmd);
                break;
        default:
@@ -909,7 +1045,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;
@@ -940,7 +1076,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);
 }
@@ -968,7 +1104,7 @@ static int client_check_cmd_timeout(struct fio_client *client,
        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;
@@ -986,7 +1122,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;
        }
@@ -994,7 +1134,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;
@@ -1015,7 +1155,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;
@@ -1035,11 +1175,11 @@ 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;
                        }
 
@@ -1062,14 +1202,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..ea7ea6f
--- /dev/null
+++ b/client.h
@@ -0,0 +1,129 @@
+#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 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;
+
+       struct client_ops *ops;
+       void *client_data;
+};
+
+typedef void (*client_text_op_func)(struct fio_client *client, struct fio_net_cmd *cmd);
+typedef void (*client_disk_util_op_func)(struct fio_client *client, struct fio_net_cmd *cmd);
+typedef void (*client_thread_status_op)(struct fio_client *client, struct fio_net_cmd *cmd);
+typedef void (*client_group_stats_op)(struct fio_client *client, struct fio_net_cmd *cmd);
+typedef void (*client_eta_op)(struct jobs_eta *je);
+typedef void (*client_jobs_eta_op)(struct fio_client *client, struct jobs_eta *je);
+typedef void (*client_probe_op)(struct fio_client *client, struct fio_net_cmd *cmd);
+typedef void (*client_thread_status_display_op)(char *status_message, double perc);
+typedef void (*client_quit_op)(struct fio_client *);
+typedef void (*client_add_job_op)(struct fio_client *, struct fio_net_cmd *);
+typedef void (*client_timed_out)(struct fio_client *);
+typedef void (*client_stop_op)(struct fio_client *, struct fio_net_cmd *);
+typedef void (*client_start_op)(struct fio_client *, struct fio_net_cmd *);
+typedef void (*client_job_start_op)(struct fio_client *, struct fio_net_cmd *);
+
+struct client_ops {
+       client_text_op_func text_op;
+       client_disk_util_op_func disk_util;
+       client_thread_status_op thread_status;
+       client_group_stats_op group_stats;
+       client_jobs_eta_op jobs_eta;
+       client_eta_op eta;
+       client_probe_op probe;
+       client_quit_op quit;
+       client_add_job_op add_job;
+       client_timed_out timed_out;
+       client_stop_op stop;
+       client_start_op start;
+       client_job_start_op job_start;
+       unsigned int eta_msec;
+       int stay_connected;
+};
+
+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_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 *);
+
+#define FIO_CLIENT_DEF_ETA_MSEC                900
+
+#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;
 };
diff --git a/eta.c b/eta.c
index 6118d1af21b5afccee8c9f106bc4cbd93b557c61..b07ae8062dbbfbd7a02a5ffd96dec0ec561021cb 100644 (file)
--- a/eta.c
+++ b/eta.c
@@ -83,7 +83,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;
@@ -273,11 +273,14 @@ int calc_thread_status(struct jobs_eta *je, int force)
                    || td->runstate == TD_FSYNCING
                    || td->runstate == TD_PRE_READING) {
                        je->nr_running++;
-                       je->t_rate += td->o.rate[0] + td->o.rate[1];
-                       je->m_rate += td->o.ratemin[0] + td->o.ratemin[1];
-                       je->t_iops += td->o.rate_iops[0] + td->o.rate_iops[1];
-                       je->m_iops += td->o.rate_iops_min[0] +
-                                       td->o.rate_iops_min[1];
+                       je->t_rate[0] += td->o.rate[0];
+                       je->t_rate[1] += td->o.rate[1];
+                       je->m_rate[0] += td->o.ratemin[0];
+                       je->m_rate[1] += td->o.ratemin[1];
+                       je->t_iops[0] += td->o.rate_iops[0];
+                       je->t_iops[1] += td->o.rate_iops[1];
+                       je->m_iops[0] += td->o.rate_iops_min[0];
+                       je->m_iops[1] += td->o.rate_iops_min[1];
                        je->files_open += td->nr_open_files;
                } else if (td->runstate == TD_RAMP) {
                        je->nr_running++;
@@ -366,16 +369,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, i2p);
-               tr = num2str(je->t_rate, 4, 0, i2p);
+               mr = num2str(je->m_rate[0] + je->m_rate[1], 4, 0, i2p);
+               tr = num2str(je->t_rate[0] + je->t_rate[1], 4, 0, i2p);
                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->t_iops[1]);
+       }
        if (je->eta_sec != INT_MAX && je->nr_running) {
                char perc_str[32];
                char *iops_str[2];
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
diff --git a/fio.c b/fio.c
index be60c5ff64bce2a3b24760bf668ce477ba2a84c5..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"
 
-unsigned long page_mask;
-unsigned long 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 4afdd2d469a1de480b7bea19262bfb7f93565b71..cafc93ae54115db57df57f929f1ec094287bf222 100644 (file)
--- a/fio.h
+++ b/fio.h
@@ -17,6 +17,7 @@
 struct thread_data;
 
 #include "compiler/compiler.h"
+#include "thread_options.h"
 #include "flist.h"
 #include "fifo.h"
 #include "rbtree.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,208 +58,6 @@ enum {
        RW_SEQ_IDENT,
 };
 
-/*
- * What type of errors to continue on when continue_on_error is used
- */
-enum error_type {
-        ERROR_TYPE_NONE = 0,
-        ERROR_TYPE_READ = 1 << 0,
-        ERROR_TYPE_WRITE = 1 << 1,
-        ERROR_TYPE_VERIFY = 1 << 2,
-        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[2];
-       unsigned int ba[2];
-       unsigned int min_bs[2];
-       unsigned int max_bs[2];
-       struct bssplit *bssplit[2];
-       unsigned int bssplit_nr[2];
-
-       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 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[2];
-       unsigned int ratemin[2];
-       unsigned int ratecycle;
-       unsigned int rate_iops[2];
-       unsigned int rate_iops_min[2];
-
-       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 int sync_file_range;
-};
-
 /*
  * This describes a single thread/process executing a fio job.
  */
@@ -588,6 +377,7 @@ 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 **);
@@ -609,6 +399,10 @@ extern void add_job_opts(const char **);
 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)
diff --git a/gfio.c b/gfio.c
new file mode 100644 (file)
index 0000000..f5df1bb
--- /dev/null
+++ b/gfio.c
@@ -0,0 +1,3020 @@
+/*
+ * 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 "graph.h"
+
+#define GFIO_MIME      "text/fio"
+
+static int gfio_server_running;
+static const char *gfio_graph_font;
+static unsigned int gfio_graph_limit = 100;
+
+static void view_log(GtkWidget *w, gpointer data);
+
+#define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
+
+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;
+       const int start_insensitive;
+} buttonspeclist[] = {
+#define CONNECT_BUTTON 0
+#define SEND_BUTTON 1
+#define START_JOB_BUTTON 2
+       { "Connect", connect_clicked, "Connect to host", 0 },
+       { "Send", send_clicked, "Send job description to host", 1 },
+       { "Start Job", start_job_clicked,
+               "Start the current job on the server", 1 },
+};
+
+struct probe_widget {
+       GtkWidget *hostname;
+       GtkWidget *os;
+       GtkWidget *arch;
+       GtkWidget *fio_ver;
+};
+
+struct multitext_widget {
+       GtkWidget *entry;
+       char **text;
+       unsigned int cur_text;
+       unsigned int max_text;
+};
+
+struct eta_widget {
+       GtkWidget *names;
+       struct multitext_widget iotype;
+       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;
+};
+
+struct gfio_graphs {
+#define DRAWING_AREA_XDIM 1000
+#define DRAWING_AREA_YDIM 400
+       GtkWidget *drawing_area;
+       struct graph *iops_graph;
+       struct graph *bandwidth_graph;
+};
+
+/*
+ * 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;
+
+       struct flist_head list;
+} 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,
+};
+
+/*
+ * Notebook entry
+ */
+struct gui_entry {
+       struct flist_head list;
+       struct gui *ui;
+
+       GtkWidget *vbox;
+       GtkWidget *job_notebook;
+       GtkWidget *thread_status_pb;
+       GtkWidget *buttonbox;
+       GtkWidget *button[ARRAYSIZE(buttonspeclist)];
+       GtkWidget *notebook;
+       GtkWidget *error_info_bar;
+       GtkWidget *error_label;
+       GtkWidget *results_notebook;
+       GtkWidget *results_window;
+       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 gfio_client *client;
+       int nr_job_files;
+       char **job_files;
+};
+
+struct gfio_client {
+       struct gui_entry *ge;
+       struct fio_client *client;
+       GtkWidget *results_widget;
+       GtkWidget *disk_util_frame;
+       GtkWidget *err_entry;
+       unsigned int job_added;
+       struct thread_options o;
+};
+
+static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc);
+static void gfio_update_thread_status_all(char *status_message, double perc);
+void report_error(GError *error);
+
+static void iops_graph_y_axis_unit_change(struct graph *g, int power_of_ten)
+{
+       switch (power_of_ten) {
+               case 9: graph_y_title(g, "Billions of IOs / sec");
+                       break;
+               case 6: graph_y_title(g, "Millions of IOs / sec");
+                       break;
+               case 3: graph_y_title(g, "Thousands of IOs / sec");
+                       break;
+               case 0:
+               default: graph_y_title(g, "IOs / sec");
+                       break;
+       }
+}
+
+static struct graph *setup_iops_graph(void)
+{
+       struct graph *g;
+
+       g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
+       graph_title(g, "IOPS");
+       graph_x_title(g, "Time (secs)");
+       graph_y_title(g, "IOs / sec");
+       graph_add_label(g, "Read IOPS");
+       graph_add_label(g, "Write IOPS");
+       graph_set_color(g, "Read IOPS", 0.13, 0.54, 0.13);
+       graph_set_color(g, "Write IOPS", 1.0, 0.0, 0.0);
+       line_graph_set_data_count_limit(g, gfio_graph_limit);
+       graph_y_axis_unit_change_notify(g, iops_graph_y_axis_unit_change);
+       graph_add_extra_space(g, 0.005, 0.005, 0.03, 0.03);
+       return g;
+}
+
+static void bandwidth_graph_y_axis_unit_change(struct graph *g, int power_of_ten)
+{
+       switch (power_of_ten) {
+               case 9: graph_y_title(g, "Petabytes / sec");
+                       break;
+               case 6: graph_y_title(g, "Gigabytes / sec");
+                       break;
+               case 3: graph_y_title(g, "Megabytes / sec");
+                       break;
+               case 0:
+               default: graph_y_title(g, "Kilobytes / sec");
+                       break;
+       }
+}
+
+static struct graph *setup_bandwidth_graph(void)
+{
+       struct graph *g;
+
+       g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
+       graph_title(g, "Bandwidth");
+       graph_x_title(g, "Time (secs)");
+       graph_y_title(g, "Kbytes / sec");
+       graph_add_label(g, "Read Bandwidth");
+       graph_add_label(g, "Write Bandwidth");
+       graph_set_color(g, "Read Bandwidth", 0.13, 0.54, 0.13);
+       graph_set_color(g, "Write Bandwidth", 1.0, 0.0, 0.0);
+       line_graph_set_data_count_limit(g, 100);
+       graph_y_axis_unit_change_notify(g, bandwidth_graph_y_axis_unit_change);
+       graph_add_extra_space(g, 0.005, 0.005, 0.03, 0.03);
+
+       return g;
+}
+
+static void setup_graphs(struct gfio_graphs *g)
+{
+       g->iops_graph = setup_iops_graph();
+       g->bandwidth_graph = setup_bandwidth_graph();
+}
+
+static 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++;
+}
+
+static 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]);
+}
+
+static 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]);
+}
+
+static 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;
+}
+
+static 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.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 GtkWidget *new_combo_entry_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *entry, *frame;
+
+       frame = gtk_frame_new(label);
+       entry = gtk_combo_box_new_text();
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), entry);
+
+       return entry;
+}
+
+static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *entry, *frame;
+
+       frame = gtk_frame_new(label);
+       entry = gtk_entry_new();
+       gtk_entry_set_editable(GTK_ENTRY(entry), 0);
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), entry);
+
+       return entry;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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);
+}
+
+static 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);
+}
+
+static void show_info_dialog(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);
+}
+
+/*
+ * 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;
+       GtkWidget *w;
+
+       switch (ge->state) {
+       default: {
+               char tmp[80];
+
+               sprintf(tmp, "Bad client state: %u\n", ge->state);
+               show_info_dialog(ui, "Error", tmp);
+               /* fall through to new state */
+               }
+
+       case GE_STATE_NEW:
+               connect_state = 1;
+               edit_state = 0;
+               connect_str = "Connect";
+               send_state = 0;
+               start_state = 0;
+               break;
+       case GE_STATE_CONNECTED:
+               connect_state = 1;
+               edit_state = 0;
+               connect_str = "Disconnect";
+               send_state = 1;
+               start_state = 0;
+               break;
+       case GE_STATE_JOB_SENT:
+               connect_state = 1;
+               edit_state = 0;
+               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[CONNECT_BUTTON], connect_state);
+       gtk_widget_set_sensitive(ge->button[SEND_BUTTON], send_state);
+       gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], start_state);
+       gtk_button_set_label(GTK_BUTTON(ge->button[CONNECT_BUTTON]), connect_str);
+
+       /*
+        * So the below doesn't work at all, how to set those menu items
+        * invisibible...
+        */
+       w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Connect");
+       gtk_widget_set_sensitive(w, connect_state);
+       gtk_menu_item_set_label(GTK_MENU_ITEM(w), connect_str);
+
+       w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Edit job");
+       gtk_widget_set_sensitive(w, edit_state);
+
+       w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Send job");
+       gtk_widget_set_sensitive(w, send_state);
+
+       w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Start job");
+       gtk_widget_set_sensitive(w, start_state);
+}
+
+static void gfio_set_state(struct gui_entry *ge, unsigned int state)
+{
+       ge->state = state;
+       update_button_states(ge->ui, ge);
+}
+
+#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)
+{
+       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_add_attribute(col, renderer, "text", index);
+       gtk_object_set(GTK_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;
+}
+
+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_INT, 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 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 void gfio_show_clat_percentiles(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;
+       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";
+       }
+
+       tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
+
+       sprintf(tmp, "Completion percentiles (%s)", 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);
+
+       gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
+out:
+       if (ovals)
+               free(ovals);
+}
+
+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);
+
+}
+
+#define GFIO_CLAT      1
+#define GFIO_SLAT      2
+#define GFIO_LAT       4
+
+static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
+                                 struct thread_stat *ts, int ddir)
+{
+       const char *ddir_label[2] = { "Read", "Write" };
+       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, FALSE, FALSE, 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(main_vbox, ts, ddir);
+
+
+       free(io_p);
+       free(bw_p);
+       free(iops_p);
+}
+
+static GtkWidget *gfio_output_lat_buckets(double *lat, unsigned int num,
+                                         const char **labels)
+{
+       GtkWidget *tree_view;
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       GtkTreeIter iter;
+       GType *types;
+       int i, skipped;
+
+       /*
+        * Check if all are empty, in which case don't bother
+        */
+       for (i = 0, skipped = 0; i < num; i++)
+               if (lat[i] <= 0.0)
+                       skipped++;
+
+       if (skipped == num)
+               return NULL;
+
+       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 void gfio_show_latency_buckets(GtkWidget *vbox, struct thread_stat *ts)
+{
+       GtkWidget *box, *frame, *tree_view;
+       double io_u_lat_u[FIO_IO_U_LAT_U_NR];
+       double io_u_lat_m[FIO_IO_U_LAT_M_NR];
+       const char *uranges[] = { "2", "4", "10", "20", "50", "100",
+                                 "250", "500", "750", "1000", };
+       const char *mranges[] = { "2", "4", "10", "20", "50", "100",
+                                 "250", "500", "750", "1000", "2000",
+                                 ">= 2000", };
+
+       stat_calc_lat_u(ts, io_u_lat_u);
+       stat_calc_lat_m(ts, io_u_lat_m);
+
+       tree_view = gfio_output_lat_buckets(io_u_lat_u, FIO_IO_U_LAT_U_NR, uranges);
+       if (tree_view) {
+               frame = gtk_frame_new("Latency buckets (usec)");
+               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+               box = gtk_hbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), box);
+               gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
+       }
+
+       tree_view = gfio_output_lat_buckets(io_u_lat_m, FIO_IO_U_LAT_M_NR, mranges);
+       if (tree_view) {
+               frame = gtk_frame_new("Latency buckets (msec)");
+               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+               box = gtk_hbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), box);
+               gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 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 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_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, ts_total_io_u(ts), 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_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
+{
+       GtkWidget *frame, *box, *tree_view;
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       GType types[FIO_IO_U_MAP_NR + 1];
+       int i;
+#define NR_LABELS      10
+       const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
+
+       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, FALSE, 3);
+}
+
+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 GtkWidget *get_results_window(struct gui_entry *ge)
+{
+       GtkWidget *win, *notebook;
+
+       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);
+
+       notebook = gtk_notebook_new();
+       gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
+       gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
+       gtk_container_add(GTK_CONTAINER(win), notebook);
+
+       ge->results_window = win;
+       ge->results_notebook = notebook;
+       return ge->results_notebook;
+}
+
+static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
+                           struct group_run_stats *rs)
+{
+       GtkWidget *res_win, *box, *vbox, *entry, *scroll;
+       struct gfio_client *gc = client->client_data;
+
+       gdk_threads_enter();
+
+       res_win = get_results_window(gc->ge);
+
+       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(res_win), scroll, gtk_label_new(ts->name));
+
+       gc->results_widget = vbox;
+
+       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);
+
+       if (ts->io_bytes[DDIR_READ])
+               gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
+       if (ts->io_bytes[DDIR_WRITE])
+               gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
+
+       gfio_show_latency_buckets(vbox, ts);
+       gfio_show_cpu_usage(vbox, ts);
+       gfio_show_io_depths(vbox, ts);
+
+       gtk_widget_show_all(gc->ge->results_window);
+       gdk_threads_leave();
+}
+
+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 gui *ui = &main_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, 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, p->level, -1);
+       gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
+
+       if (p->level == FIO_LOG_ERR)
+               view_log(NULL, (gpointer) ui);
+
+       gdk_threads_leave();
+}
+
+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;
+       GtkWidget *box, *frame, *entry, *vbox;
+       double util;
+       char tmp[16];
+
+       gdk_threads_enter();
+
+       if (!gc->results_widget)
+               goto out;
+
+       if (!gc->disk_util_frame) {
+               gc->disk_util_frame = gtk_frame_new("Disk utilization");
+               gtk_box_pack_start(GTK_BOX(gc->results_widget), gc->disk_util_frame, FALSE, FALSE, 5);
+       }
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(gc->disk_util_frame), 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(gc->results_widget);
+out:
+       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.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 gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
+                                  gpointer data)
+{
+       struct gfio_graphs *g = data;
+
+       graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
+       graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
+       graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.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(w->window);
+
+       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;
+}
+
+/*
+ * 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[2];
+               char *rate_str[2];
+
+               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);
+
+               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+               iops_str[1] = num2str(je->iops[1], 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]);
+
+               graph_add_xy_data(ge->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
+               graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
+
+               free(rate_str[0]);
+               free(rate_str[1]);
+               free(iops_str[0]);
+               free(iops_str[1]);
+       }
+
+       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 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[2];
+               char *rate_str[2];
+
+               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);
+
+               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+               iops_str[1] = num2str(je->iops[1], 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]);
+
+               graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
+               graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
+
+               free(rate_str[0]);
+               free(rate_str[1]);
+               free(iops_str[0]);
+               free(iops_str[1]);
+       }
+
+       if (eta_str[0]) {
+               char *dst = output + strlen(output);
+
+               sprintf(dst, " - %s", eta_str);
+       }
+               
+       gfio_update_thread_status_all(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;
+       char buf[64];
+
+       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);
+
+       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);
+       sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
+       gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
+
+       gfio_set_state(ge, GE_STATE_CONNECTED);
+
+       gdk_threads_leave();
+}
+
+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(main_ui.window);
+}
+
+static void gfio_update_thread_status_all(char *status_message, double perc)
+{
+       struct gui *ui = &main_ui;
+       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);
+}
+
+static void gfio_quit_op(struct fio_client *client)
+{
+       struct gfio_client *gc = client->client_data;
+
+       gdk_threads_enter();
+       gfio_set_state(gc->ge, GE_STATE_NEW);
+       gdk_threads_leave();
+}
+
+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 thread_options *o = &gc->o;
+       struct gui_entry *ge = gc->ge;
+       char tmp[8];
+
+       convert_thread_options_to_cpu(o, &p->top);
+
+       gdk_threads_enter();
+
+       gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
+
+       gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
+       gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
+
+       multitext_add_entry(&ge->eta.iotype, ddir_str(o->td_ddir));
+       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.ioengine, 0);
+       multitext_set_entry(&ge->eta.iodepth, 0);
+
+       gc->job_added++;
+
+       gfio_set_state(ge, GE_STATE_JOB_SENT);
+
+       gdk_threads_leave();
+}
+
+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);
+       show_info_dialog(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();
+}
+
+struct client_ops gfio_client_ops = {
+       .text_op                = 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,
+       .timed_out              = gfio_client_timed_out,
+       .stop                   = gfio_client_stop,
+       .start                  = gfio_client_start,
+       .job_start              = gfio_client_job_start,
+       .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
+       .stay_connected         = 1,
+};
+
+static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
+                __attribute__((unused)) gpointer data)
+{
+        gtk_main_quit();
+}
+
+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_files(struct gui_entry *ge)
+{
+       struct gfio_client *gc = ge->client;
+       int i, ret = 0;
+
+       for (i = 0; i < ge->nr_job_files; i++) {
+               ret = fio_client_send_ini(gc->client, ge->job_files[i]);
+               if (ret < 0) {
+                       GError *error;
+
+                       error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
+                       report_error(error);
+                       g_error_free(error);
+                       break;
+               } else if (ret)
+                       break;
+
+               free(ge->job_files[i]);
+               ge->job_files[i] = NULL;
+       }
+       while (i < ge->nr_job_files) {
+               free(ge->job_files[i]);
+               ge->job_files[i] = NULL;
+               i++;
+       }
+
+       ge->nr_job_files = 0;
+       return ret;
+}
+
+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(void)
+{
+       struct gui *ui = &main_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);
+
+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->nr_job_files)
+                       file_open(widget, ge->ui);
+               if (!ge->nr_job_files)
+                       return;
+
+               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);
+               } else {
+                       GError *error;
+
+                       error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
+                       report_error(error);
+                       g_error_free(error);
+               }
+       } 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_files(ge)) {
+               GError *error;
+
+               error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send one or more job files for client %s", ge->client->client->hostname);
+               report_error(error);
+               g_error_free(error);
+
+               gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
+       }
+}
+
+static GtkWidget *add_button(GtkWidget *buttonbox,
+                            struct button_spec *buttonspec, gpointer data)
+{
+       GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
+
+       g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
+       gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
+       gtk_widget_set_tooltip_text(button, buttonspec->tooltiptext);
+       gtk_widget_set_sensitive(button, !buttonspec->start_insensitive);
+
+       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);
+}
+
+static void on_info_bar_response(GtkWidget *widget, gint response,
+                                 gpointer data)
+{
+       struct gui *ui = &main_ui;
+
+       if (response == GTK_RESPONSE_OK) {
+               gtk_widget_destroy(widget);
+               ui->error_info_bar = NULL;
+       }
+}
+
+void report_error(GError *error)
+{
+       struct gui *ui = &main_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), NULL);
+               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(GTK_LABEL(ui->error_label), buffer);
+       }
+}
+
+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_get_active_text(GTK_COMBO_BOX(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(char **host, int *port, int *type,
+                                 int *server_start)
+{
+       GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
+       struct connection_widgets cw;
+       char *typeentry;
+
+       dialog = gtk_dialog_new_with_buttons("Connection details",
+                       GTK_WINDOW(main_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");
+       /* gtk_dialog_get_content_area() is 2.14 and newer */
+       vbox = GTK_DIALOG(dialog)->vbox;
+       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_new_text();
+       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
+       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
+       gtk_combo_box_append_text(GTK_COMBO_BOX(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(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
+       g_signal_connect(GTK_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;
+       }
+
+       *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
+       *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
+
+       typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
+       if (!typeentry || !strncmp(typeentry, "IPv4", 4))
+               *type = Fio_client_ipv4;
+       else if (!strncmp(typeentry, "IPv6", 4))
+               *type = Fio_client_ipv6;
+       else
+               *type = Fio_client_socket;
+       g_free(typeentry);
+
+       *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
+
+       gtk_widget_destroy(dialog);
+       return 0;
+}
+
+static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
+{
+       struct gfio_client *gc;
+
+       gc = malloc(sizeof(*gc));
+       memset(gc, 0, sizeof(*gc));
+       gc->ge = ge;
+       gc->client = fio_get_client(client);
+
+       ge->client = gc;
+
+       client->client_data = gc;
+}
+
+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;
+       INIT_FLIST_HEAD(&ge->list);
+       flist_add_tail(&ge->list, &ui->list);
+       ge->ui = ui;
+       return ge;
+}
+
+/*
+ * FIXME: need more handling here
+ */
+static void ge_destroy(GtkWidget *w, gpointer data)
+{
+       struct gui_entry *ge = data;
+       struct gfio_client *gc = ge->client;
+
+       if (gc && gc->client) {
+               if (ge->state >= GE_STATE_CONNECTED)
+                       fio_client_terminate(gc->client);
+
+               fio_put_client(gc->client);
+       }
+
+       flist_del(&ge->list);
+       free(ge);
+}
+
+static struct gui_entry *get_new_ge_with_tab(const char *name)
+{
+       struct gui_entry *ge;
+
+       ge = alloc_new_gui_entry(&main_ui);
+
+       ge->vbox = new_client_page(ge);
+       g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_destroy), ge);
+
+       ge->page_label = gtk_label_new(name);
+       ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
+
+       gtk_widget_show_all(main_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("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(gint cur_page, int *created)
+{
+       struct flist_head *entry;
+       struct gui_entry *ge;
+
+       if (!cur_page) {
+               if (created)
+                       *created = 1;
+               return get_new_ge_with_tab("Untitled");
+       }
+
+       if (created)
+               *created = 0;
+
+       flist_for_each(entry, &main_ui.list) {
+               ge = flist_entry(entry, struct gui_entry, list);
+               if (ge->page_num == cur_page)
+                       return ge;
+       }
+
+       return NULL;
+}
+
+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(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 (!flist_empty(&ui->list)) {
+               show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
+               return;
+       }
+
+               gtk_main_quit();
+}
+
+static void file_add_recent(struct gui *ui, const gchar *uri)
+{
+       gtk_recent_manager_add_item(ui->recentmanager, uri);
+}
+
+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, char *host,
+                       int type, int port)
+{
+       struct fio_client *client;
+       gchar *filename;
+
+       filename = get_filename_from_uri(uri);
+
+       ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
+       ge->job_files[ge->nr_job_files] = strdup(filename);
+       ge->nr_job_files++;
+
+       client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
+       if (!client) {
+               GError *error;
+
+               error = g_error_new(g_quark_from_string("fio"), 1,
+                               "Failed to add client %s", host);
+               report_error(error);
+               g_error_free(error);
+               return 1;
+       }
+
+       gfio_client_added(ge, client);
+       file_add_recent(ge->ui, uri);
+       return 0;
+}
+
+static void file_open(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = data;
+       GtkWidget *dialog;
+       GSList *filenames, *fn_glist;
+       GtkFileFilter *filter;
+       int port, type, server_start, ge_is_new = 0;
+       struct gui_entry *ge;
+       gint cur_page;
+       char *host;
+
+       /*
+        * 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(cur_page, &ge_is_new);
+       if (ge->client) {
+               ge = get_new_ge_with_tab("Untitled");
+               ge_is_new = 1;
+       }
+
+       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+
+       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), TRUE);
+
+       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) {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+
+               gtk_widget_destroy(dialog);
+               return;
+       }
+
+       fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+
+       gtk_widget_destroy(dialog);
+
+       if (get_connection_details(&host, &port, &type, &server_start)) {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+       
+               goto done;
+       }
+
+       filenames = fn_glist;
+       while (filenames != NULL) {
+               do_file_open(ge, filenames->data, host, type, port);
+               g_free(filenames->data);
+               filenames = g_slist_next(filenames);
+       }
+       free(host);
+
+       if (server_start)
+               gfio_start_server();
+done:
+       g_slist_free(fn_glist);
+
+}
+
+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;
+
+       gtk_widget_ref(ui->log_tree);
+       gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
+       gtk_widget_destroy(w);
+       ui->log_view = NULL;
+}
+
+static void view_log(GtkWidget *w, gpointer data)
+{
+       GtkWidget *win, *scroll, *vbox, *box;
+       struct gui *ui = (struct gui *) data;
+
+       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_defaults(GTK_BOX(box), ui->log_tree);
+       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_defaults(GTK_BOX(vbox), scroll);
+
+       gtk_container_add(GTK_CONTAINER(win), vbox);
+       gtk_widget_show_all(win);
+}
+
+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)
+{
+}
+
+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 __update_graph_limits(struct gfio_graphs *g)
+{
+       line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
+       line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
+}
+
+static void update_graph_limits(void)
+{
+       struct flist_head *entry;
+       struct gui_entry *ge;
+
+       __update_graph_limits(&main_ui.graphs);
+
+       flist_for_each(entry, &main_ui.list) {
+               ge = flist_entry(entry, struct gui_entry, list);
+               __update_graph_limits(&ge->graphs);
+       }
+}
+
+static void preferences(GtkWidget *w, gpointer data)
+{
+       GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
+       GtkWidget *hbox, *spin, *entry, *spin_int;
+       int i;
+
+       dialog = gtk_dialog_new_with_buttons("Preferences",
+               GTK_WINDOW(main_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");
+       gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->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();
+       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");
+       gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->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) },
+       { "ConnectJob", NULL, "Connect", "<Control>E", 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=\"Close\" action=\"CloseFile\" /> \
+                               <separator name=\"Separator1\"/> \
+                               <menuitem name=\"Open\" action=\"OpenFile\" /> \
+                               <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=\"Log\" action=\"ViewLog\" /> \
+                       </menu>\
+                       <menu name=\"Help\" action=\"HelpMenuAction\"> \
+                               <menuitem name=\"About\" action=\"About\" /> \
+                       </menu> \
+               </menubar> \
+       </ui> \
+";
+
+static void set_job_menu_visible(struct gui *ui, int visible)
+{
+       GtkWidget *job;
+
+       job = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu");
+       gtk_widget_set_sensitive(job, visible);
+}
+
+static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
+                                  struct gui *ui)
+{
+       GtkActionGroup *action_group = gtk_action_group_new("Menu");
+       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.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.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;
+       GdkColor white;
+
+       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.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(probe_box, "Read BW");
+       ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
+       ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
+       ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
+
+       /*
+        * 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
+        */
+       gdk_color_parse("white", &white);
+       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, &white);
+       g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_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, ARRAYSIZE(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;
+       GdkColor white;
+
+       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(probe_box, "Read BW");
+       ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
+       ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
+       ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
+
+       /*
+        * 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
+        */
+       gdk_color_parse("white", &white);
+       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, &white);
+       g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_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);
+               return TRUE;
+       }
+
+       set_job_menu_visible(ui, 1);
+       ge = get_ge_from_page(page, NULL);
+       if (ge)
+               update_button_states(ui, ge);
+
+       return TRUE;
+}
+
+static void recent_open(GtkAction *action, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       int port, type, server_start;
+       struct gui_entry *ge;
+       GtkRecentInfo *info;
+       const gchar *uri;
+       gint cur_page;
+       char *host;
+       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(cur_page, &ge_is_new);
+       if (ge->client) {
+               ge = get_new_ge_with_tab("Untitled");
+               ge_is_new = 1;
+       }
+
+       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+
+       info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
+       uri = gtk_recent_info_get_uri(info);
+
+       if (get_connection_details(&host, &port, &type, &server_start)) {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+                       
+               return;
+       }
+
+       ret = do_file_open(ge, uri, host, type, port);
+
+       free(host);
+
+       if (!ret) {
+               if (server_start)
+                       gfio_start_server();
+       } else {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+       }
+}
+
+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 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();
+       
+       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), NULL);
+       g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
+
+       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_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;
+
+       memset(&main_ui, 0, sizeof(main_ui));
+       INIT_FLIST_HEAD(&main_ui.list);
+
+       init_ui(&argc, &argv, &main_ui);
+
+       gdk_threads_enter();
+       gtk_main();
+       gdk_threads_leave();
+       return 0;
+}
diff --git a/graph.c b/graph.c
new file mode 100644 (file)
index 0000000..aba095b
--- /dev/null
+++ b/graph.c
@@ -0,0 +1,823 @@
+/*
+ * 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"
+
+struct xyvalue {
+       double x, y;
+       int gx, gy;
+};
+
+struct graph_value {
+       struct graph_value *next;
+       char *tooltip;
+       void *value;
+};
+
+struct graph_label {
+       char *label;
+       struct graph_value *tail;
+       struct graph_value *values;
+       struct graph_label *next;
+       double r, g, b;
+       int value_count;
+       unsigned int tooltip_count;
+       struct graph *parent;
+};
+
+struct graph {
+       char *title;
+       char *xtitle;
+       char *ytitle;
+       unsigned int xdim, ydim;
+       double xoffset, yoffset;
+       struct graph_label *labels;
+       struct graph_label *tail;
+       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;
+       double left_extra;      
+       double right_extra;     
+       double top_extra;       
+       double bottom_extra;    
+};
+
+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));
+       graph_set_size(g, xdim, ydim);
+       g->per_label_limit = -1;
+       g->font = font;
+       if (!g->font)
+               g->font = "Sans";
+       return g;
+}
+
+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_label *labels)
+{
+       int count = 0;
+       struct graph_label *i;
+
+       for (i = labels; i; i = i->next)
+               count++;
+       return count;
+}
+
+static int count_values(struct graph_value *values)
+{
+       int count = 0;
+       struct graph_value *i;
+
+       for (i = values; i; i = i->next)
+               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_value *values, double_comparator cmp)
+{
+       struct graph_value *i;
+       int first = 1;
+       double answer, tmp;
+
+       assert(values != NULL);
+       answer = 0.0; /* shut the compiler up, might need to think harder though. */
+       for (i = values; i; i = i->next) {
+               tmp = *(double *) i->value; 
+               if (first) {
+                       answer = tmp;
+                       first = 0;
+               } else {
+                       answer = cmp(answer, tmp);
+               }
+       }
+       return answer;
+}
+
+static double find_double_data(struct graph_label *labels, double_comparator cmp)
+{
+       struct graph_label *i;
+       int first = 1;
+       double answer, tmp;
+
+       assert(labels != NULL);
+       answer = 0.0; /* shut the compiler up, might need to think harder though. */
+       for (i = labels; i; i = i->next) {
+               tmp = find_double_values(i->values, cmp);
+               if (first) {
+                       answer = tmp;
+                       first = 0;
+               } else {
+                       answer = cmp(tmp, answer);
+               }
+       }
+       return answer;
+}
+
+static double find_min_data(struct graph_label *labels)
+{
+       return find_double_data(labels, mindouble);
+}
+
+static double find_max_data(struct graph_label *labels)
+{
+       return find_double_data(labels, 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 graph_value *i;
+       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);
+       for (i = lb->values; i; i = i->next) {
+
+               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 draw_aligned_text(struct graph *g, cairo_t *cr, 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 = 1.0;
+                       break;
+       }
+       cairo_select_font_face (cr, g->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);
+}
+
+static inline void draw_centered_text(struct graph *g, cairo_t *cr, double x, double y,
+                              double fontsize, const char *text)
+{
+       draw_aligned_text(g, cr, x, y, fontsize, text, CENTERED);
+}
+
+static inline void draw_right_justified_text(struct graph *g, cairo_t *cr,
+                               double x, double y,
+                               double fontsize, const char *text)
+{
+       draw_aligned_text(g, cr, x, y, fontsize, text, RIGHT_JUSTIFIED);
+}
+
+static inline void draw_left_justified_text(struct graph *g, cairo_t *cr,
+                               double x, double y,
+                               double fontsize, const char *text)
+{
+       draw_aligned_text(g, cr, x, y, fontsize, text, LEFT_JUSTIFIED);
+}
+
+static void draw_vertical_centered_text(struct graph *g, cairo_t *cr, double x,
+                                       double y, double fontsize,
+                                       const char *text)
+{
+       double sx, sy;
+       cairo_text_extents_t extents;
+
+       cairo_select_font_face(cr, g->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);
+}
+
+static void graph_draw_common(struct graph *g, cairo_t *cr,
+       double *x1, double *y1, double *x2, double *y2)
+{
+        cairo_set_source_rgb(cr, 0, 0, 0);
+        cairo_set_line_width (cr, 0.8);
+
+       *x1 = 0.15 * g->xdim;   
+       *x2 = 0.95 * g->xdim;
+       *y1 = 0.10 * g->ydim;   
+       *y2 = 0.90 * g->ydim;
+
+       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(g, cr, g->xdim / 2, g->ydim / 20, 20.0, g->title);
+       draw_centered_text(g, cr, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle);
+       draw_vertical_centered_text(g, cr, 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)
+{
+       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);
+       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;
+               if (tx < x1 || tx > x2)
+                       continue;
+
+               /* Draw tick mark */
+               cairo_set_line_width(cr, 0.8);
+               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, 2.0);
+               cairo_set_line_width(cr, 0.5);
+               cairo_move_to(cr, tx, y1);
+               cairo_line_to(cr, tx, y2);
+               cairo_stroke(cr);
+               cairo_restore(cr);
+
+               /* draw tickmark label */
+               draw_centered_text(g, cr, tx, y2 * 1.04, 12.0, tm[i].string);
+               cairo_stroke(cr);
+               
+       }
+}
+
+static void graph_draw_y_ticks(struct graph *g, cairo_t *cr,
+       double x1, double y1, double x2, double y2,
+       double miny, double maxy, int nticks)
+{
+       struct tickmark *tm;
+       double ty;
+       int i, power_of_ten;
+       static double dash[] = { 2.0, 2.0 };
+
+       nticks = calc_tickmarks(miny, maxy, nticks, &tm, &power_of_ten,
+               g->y_axis_unit_change_callback == NULL);
+       if (g->y_axis_unit_change_callback)
+               g->y_axis_unit_change_callback(g, power_of_ten);
+
+       for (i = 0; i < nticks; i++) {
+               ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1);
+               if (ty < y1 || ty > y2)
+                       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, 2.0);
+               cairo_set_line_width(cr, 0.5);
+               cairo_move_to(cr, x1, ty);
+               cairo_line_to(cr, x2, ty);
+               cairo_stroke(cr);
+               cairo_restore(cr);
+
+               /* draw tickmark label */
+               draw_right_justified_text(g, cr, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string);
+               cairo_stroke(cr);
+       }
+}
+
+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;
+
+       cairo_save(cr);
+       cairo_translate(cr, bg->xoffset, bg->yoffset);
+       graph_draw_common(bg, cr, &x1, &y1, &x2, &y2);
+
+       nlabels = count_labels(bg->labels);
+       space_per_label = (x2 - x1) / (double) nlabels; 
+
+       mindata = find_min_data(bg->labels);
+       maxdata = find_max_data(bg->labels);
+
+       if (fabs(maxdata - mindata) < 1e-20) {
+               draw_centered_text(bg, cr,
+                       x1 + (x2 - x1) / 2.0,
+                       y1 + (y2 - y1) / 2.0, 20.0, "No good data");
+               return;
+       }
+
+       graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10);
+
+       i = 0;
+       for (lb = bg->labels; lb; lb = lb->next) {
+               int nvalues;
+               nvalues = count_values(lb->values);
+               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(bg, cr, 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;
+       int first = 1;
+
+       for (i = g->labels; i; i = i->next)
+               for (j = i->values; j; j = j->next) {
+                       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;
+
+       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);
+       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);
+       graph_draw_y_ticks(g, cr, x1, y1, x2, y2, gminy, gmaxy, 10);
+
+       if (!good_data)
+               goto skip_data;
+
+       cairo_set_line_width(cr, 1.5);
+       for (i = g->labels; i; i = i->next) {
+               first = 1;
+               if (i->r < 0) /* invisible data */
+                       continue;
+               cairo_set_source_rgb(cr, i->r, i->g, i->b);
+               for (j = i->values; j; j = j->next) {
+                       struct xyvalue *xy = j->value;
+
+                       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);
+                       }
+                       xy->gx = tx;
+                       xy->gy = ty;
+               }
+               cairo_stroke(cr);
+       }
+
+skip_data:
+       cairo_restore(cr);
+
+}
+
+static void gfree(void *f)
+{
+       if (f)
+               free(f);
+}
+
+static void setstring(char **str, const char *value)
+{
+       gfree(*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 graph_label *i;
+       
+       for (i = bg->labels; i; i = i->next)
+               if (strcmp(label, i->label) == 0)
+                       return i;
+       return NULL;
+}
+
+void graph_add_label(struct graph *bg, const char *label)
+{
+       struct graph_label *i;
+       
+       i = graph_find_label(bg, label);
+       if (i)
+               return; /* already present. */
+       i = calloc(1, sizeof(*i));
+       i->parent = bg;
+       setstring(&i->label, label);
+       i->next = NULL;
+       if (!bg->tail)
+               bg->labels = i;
+       else
+               bg->tail->next = i;
+       bg->tail = i;
+}
+
+static void graph_label_add_value(struct graph_label *i, void *value,
+                                 const char *tooltip)
+{
+       struct graph_value *x;
+
+       x = malloc(sizeof(*x));
+       x->value = value;
+       if (tooltip)
+               x->tooltip = strdup(tooltip);
+       else
+               x->tooltip = NULL;
+       x->next = NULL;
+       if (!i->tail) {
+               i->values = x;
+       } else {
+               i->tail->next = x;
+       }
+       i->tail = x;
+       i->value_count++;
+       if (x->tooltip)
+               i->tooltip_count++;
+
+       if (i->parent->per_label_limit != -1 &&
+               i->value_count > i->parent->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 - i->parent->per_label_limit >= 2)
+                       to_drop = 2;
+
+               while (to_drop--) {
+                       x = i->values;
+                       i->values = i->values->next;
+                       if (x->tooltip) {
+                               free(x->tooltip);
+                               i->tooltip_count--;
+                       }
+                       free(x->value);
+                       free(x);
+                       i->value_count--;
+               }
+       }
+}
+
+int graph_add_data(struct graph *bg, const char *label, const double value)
+{
+       struct graph_label *i;
+       double *d;
+
+       d = malloc(sizeof(*d));
+       *d = value;
+
+       i = graph_find_label(bg, label);
+       if (!i)
+               return -1;
+       graph_label_add_value(i, d, NULL);
+       return 0;
+}
+
+int graph_add_xy_data(struct graph *bg, const char *label,
+                     const double x, const double y, const char *tooltip)
+{
+       struct graph_label *i;
+       struct xyvalue *xy;
+
+       xy = malloc(sizeof(*xy));
+       xy->x = x;
+       xy->y = y;
+
+       i = graph_find_label(bg, label);
+       if (!i)
+               return -1;
+
+       graph_label_add_value(i, xy, tooltip);
+       return 0;
+}
+
+static void graph_free_values(struct graph_value *values)
+{
+       struct graph_value *i, *next;
+
+       for (i = values; i; i = next) {
+               next = i->next;
+               gfree(i->value);
+               gfree(i);
+       }       
+}
+
+static void graph_free_labels(struct graph_label *labels)
+{
+       struct graph_label *i, *next;
+
+       for (i = labels; i; i = next) {
+               next = i->next;
+               graph_free_values(i->values);
+               gfree(i);
+       }       
+}
+
+void graph_set_color(struct graph *gr, const char *label,
+       double red, double green, double blue)
+{
+       struct graph_label *i;
+       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;
+       }
+
+       for (i = gr->labels; i; i = i->next)
+               if (strcmp(i->label, label) == 0) {
+                       i->r = r;       
+                       i->g = g;       
+                       i->b = b;       
+                       break;
+               }
+}
+
+void graph_free(struct graph *bg)
+{
+       gfree(bg->title);
+       gfree(bg->xtitle);
+       gfree(bg->ytitle);
+       graph_free_labels(bg->labels);
+}
+
+/* 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;       
+}
+
+int graph_has_tooltips(struct graph *g)
+{
+       struct graph_label *i;
+
+       for (i = g->labels; i; i = i->next)
+               if (i->tooltip_count)
+                       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);
+}
+
+static int xy_match(struct xyvalue *xy, int x, int y)
+{
+       int xdiff = abs(xy->gx - x);
+       int ydiff = abs(xy->gy - y);
+
+       return xdiff <= 20 && ydiff <= 10;
+}
+
+const char *graph_find_tooltip(struct graph *g, int x, int y)
+{
+       struct graph_label *i;
+       struct graph_value *j;
+
+       for (i = g->labels; i; i = i->next) {
+               for (j = i->values; j; j = j->next) {
+                       struct xyvalue *xy = j->value;
+
+                       if (xy_match(xy, x - g->xoffset, y))
+                               return j->tooltip;
+               }
+       }
+
+       return NULL;
+}
diff --git a/graph.h b/graph.h
new file mode 100644 (file)
index 0000000..cb1e2d5
--- /dev/null
+++ b/graph.h
@@ -0,0 +1,89 @@
+#ifndef GRAPH_H
+#define GRAPH_H
+
+struct graph;
+
+
+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_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 */
+void 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, const char *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, const char *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, const char *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);
+
+#endif
+
diff --git a/init.c b/init.c
index 6c74ea6b75ddc403d0abc4b59d84de0b8c22a960..3041ac45fed2ac66a77af0f6a811747eaa74cf34 100644 (file)
--- a/init.c
+++ b/init.c
@@ -744,10 +744,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)
 {
-       const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
-                                  "randread", "randwrite", "randrw" };
        unsigned int i;
        char fname[PATH_MAX];
        int numjobs, file_alloced;
@@ -859,6 +858,9 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
 
        if (!terse_output) {
                if (!job_add_num) {
+                       if (is_backend && !recursed)
+                               fio_server_send_add_job(&td->o, td->io_ops->name);
+
                        if (!strcmp(td->io_ops->name, "cpuio")) {
                                log_info("%s: ioengine=cpu, cpuload=%u,"
                                         " cpucycle=%u\n", td->o.name,
@@ -875,7 +877,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
                                log_info("%s: (g=%d): rw=%s, bs=%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,
                                                td->io_ops->name,
                                                td->o.iodepth);
@@ -913,7 +915,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))
                        goto err;
        }
 
@@ -938,7 +940,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);
                        td = NULL;
                        sprintf(jobname, "%s", o[i] + 5);
                }
@@ -957,7 +959,7 @@ void add_job_opts(const char **o)
        }
 
        if (td)
-               add_job(td, jobname, 0);
+               add_job(td, jobname, 0, 0);
 }
 
 static int skip_this_section(const char *name)
@@ -1139,7 +1141,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);
                } else {
                        log_err("fio: job %s dropped\n", name);
                        put_job(td);
@@ -1224,20 +1226,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, },
 };
 
@@ -1463,7 +1507,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);
                                if (ret)
                                        return 0;
                                td = NULL;
@@ -1535,7 +1579,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;
@@ -1567,7 +1611,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);
        }
 
        while (!ret && optind < argc) {
@@ -1580,10 +1624,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;
 
@@ -1595,6 +1637,20 @@ int parse_options(int argc, char *argv[])
        if (fill_def_thread())
                return 1;
 
+       return 0;
+}
+
+extern int fio_check_options(struct thread_options *);
+
+int parse_options(int argc, char *argv[])
+{
+       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);
 
        if (job_files > 0) {
index b234256243db00b1ea608f07173709618c47d5b9..908101aae7add6dcc1b587975ea7838572fbcc20 100644 (file)
--- a/io_ddir.h
+++ b/io_ddir.h
@@ -39,4 +39,12 @@ static inline int ddir_rw(enum fio_ddir ddir)
        return ddir == DDIR_READ || ddir == DDIR_WRITE;
 }
 
+static inline const char *ddir_str(enum fio_ddir ddir)
+{
+       const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
+                                  "randread", "randwrite", "randrw" };
+
+       return ddir_str[ddir];
+}
+
 #endif
index 61cb396f5e0e792f87b862976a426695338e524b..9392bbd6ed4f329dbec26932718eb249102250dc 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef FIO_IOENGINE_H
 #define FIO_IOENGINE_H
 
+#include "debug.h"
+
 #define FIO_IOOPS_VERSION      13
 
 enum {
diff --git a/iolog.h b/iolog.h
index 95617fccb6891a92970c60cb0873e06b99d9782f..e790e7d6f1042c15cec42873eeb9feeeb13dcb14 100644 (file)
--- a/iolog.h
+++ b/iolog.h
@@ -1,7 +1,9 @@
 #ifndef FIO_IOLOG_H
 #define FIO_IOLOG_H
 
+#include "rbtree.h"
 #include "lib/ieee754.h"
+#include "ioengine.h"
 
 /*
  * Use for maintaining statistics
index 668df455b95d40daf8a14347113b7aaea7741717..95938c88da4e12e4b3eb8adc9b33e74438be340e 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;
 
+unsigned long page_mask;
+unsigned long page_size;
+
 static const char *fio_os_strings[os_nr] = {
        "Invalid",
        "Linux",
@@ -187,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..362ab23c9813b9366c78ff55b4de8ffdd013a631 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;
diff --git a/log.h b/log.h
index fdf3d7b19aed3d3981b166c32339fbd7bb596f79..2b2cae0c74ee1aa975d5d91cf966c657ce0cc2d7 100644 (file)
--- a/log.h
+++ b/log.h
@@ -13,4 +13,10 @@ 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,
+};
+
 #endif
index 463b66dcd99ea85ddbeb265f5cb17959ab39a53b..714741a038ae94e248357a3295cb78db155ec7d9 100644 (file)
--- a/options.c
+++ b/options.c
@@ -160,7 +160,6 @@ static int bssplit_ddir(struct thread_data *td, int ddir, char *str)
        qsort(bssplit, td->o.bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp);
        td->o.bssplit[ddir] = bssplit;
        return 0;
-
 }
 
 static int str_bssplit_cb(void *data, const char *input)
@@ -852,12 +851,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(description),
                .help   = "Text job description",
+               .category = FIO_OPT_G_DESC,
        },
        {
                .name   = "name",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(name),
                .help   = "Name of this job",
+               .category = FIO_OPT_G_DESC,
        },
        {
                .name   = "directory",
@@ -865,6 +866,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(directory),
                .cb     = str_directory_cb,
                .help   = "Directory to store files in",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "filename",
@@ -873,6 +875,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_filename_cb,
                .prio   = -1, /* must come after "directory" */
                .help   = "File(s) to use for the workload",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "kb_base",
@@ -882,6 +885,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .prio   = 1,
                .def    = "1024",
                .help   = "How many bytes per KB for reporting (1000 or 1024)",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "lockfile",
@@ -891,6 +895,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Lock file when doing IO to it",
                .parent = "filename",
                .def    = "none",
+               .category = FIO_OPT_G_FILE,
                .posval = {
                          { .ival = "none",
                            .oval = FILE_LOCK_NONE,
@@ -913,6 +918,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(opendir),
                .cb     = str_opendir_cb,
                .help   = "Recursively add files from this directory and down",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "rw",
@@ -923,6 +929,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "IO direction",
                .def    = "read",
                .verify = rw_verify,
+               .category = FIO_OPT_G_IO_DDIR,
                .posval = {
                          { .ival = "read",
                            .oval = TD_DDIR_READ,
@@ -956,6 +963,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(rw_seq),
                .help   = "IO offset generator modifier",
                .def    = "sequential",
+               .category = FIO_OPT_G_IO_DDIR,
                .posval = {
                          { .ival = "sequential",
                            .oval = RW_SEQ_SEQ,
@@ -974,6 +982,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(ioengine),
                .help   = "IO engine to use",
                .def    = FIO_PREFERRED_ENGINE,
+               .category = FIO_OPT_G_IO,
                .posval = {
                          { .ival = "sync",
                            .help = "Use read/write",
@@ -1061,6 +1070,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Number of IO buffers to keep in flight",
                .minval = 1,
                .def    = "1",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "iodepth_batch",
@@ -1071,6 +1081,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .parent = "iodepth",
                .minval = 1,
                .def    = "1",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "iodepth_batch_complete",
@@ -1080,6 +1091,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .parent = "iodepth",
                .minval = 0,
                .def    = "1",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "iodepth_low",
@@ -1087,12 +1099,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(iodepth_low),
                .help   = "Low water mark for queuing depth",
                .parent = "iodepth",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "size",
                .type   = FIO_OPT_STR_VAL,
                .cb     = str_size_cb,
                .help   = "Total size of device or files",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "fill_device",
@@ -1101,6 +1115,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(fill_device),
                .help   = "Write until an ENOSPC error occurs",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "filesize",
@@ -1109,6 +1124,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off2   = td_var_offset(file_size_high),
                .minval = 1,
                .help   = "Size of individual files",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_FILE,
        },
        {
                .name   = "offset",
@@ -1117,6 +1133,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(start_offset),
                .help   = "Start IO from this offset",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "bs",
@@ -1128,6 +1145,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Block size unit",
                .def    = "4k",
                .parent = "rw",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "ba",
@@ -1138,6 +1156,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .minval = 1,
                .help   = "IO block offset alignment",
                .parent = "rw",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "bsrange",
@@ -1150,6 +1169,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .minval = 1,
                .help   = "Set block size range (in more detail than bs)",
                .parent = "rw",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "bssplit",
@@ -1157,6 +1177,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_bssplit_cb,
                .help   = "Set a specific mix of block sizes",
                .parent = "rw",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "bs_unaligned",
@@ -1165,6 +1186,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(bs_unaligned),
                .help   = "Don't sector align IO buffer sizes",
                .parent = "rw",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "randrepeat",
@@ -1173,6 +1195,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Use repeatable random IO pattern",
                .def    = "1",
                .parent = "rw",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_RAND,
        },
        {
                .name   = "use_os_rand",
@@ -1181,6 +1204,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Set to use OS random generator",
                .def    = "0",
                .parent = "rw",
+               .category = FIO_OPT_G_RAND,
        },
        {
                .name   = "norandommap",
@@ -1188,6 +1212,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(norandommap),
                .help   = "Accept potential duplicate random blocks",
                .parent = "rw",
+               .category = FIO_OPT_G_RAND,
        },
        {
                .name   = "softrandommap",
@@ -1196,6 +1221,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Set norandommap if randommap allocation fails",
                .parent = "norandommap",
                .def    = "0",
+               .category = FIO_OPT_G_RAND,
        },
        {
                .name   = "nrfiles",
@@ -1204,12 +1230,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(nr_files),
                .help   = "Split job workload between this number of files",
                .def    = "1",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "openfiles",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(open_files),
                .help   = "Number of files to keep open at the same time",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "file_service_type",
@@ -1218,6 +1246,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(file_service_type),
                .help   = "How to select which file to service next",
                .def    = "roundrobin",
+               .category = FIO_OPT_G_FILE,
                .posval = {
                          { .ival = "random",
                            .oval = FIO_FSERVICE_RANDOM,
@@ -1241,6 +1270,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(fallocate_mode),
                .help   = "Whether pre-allocation is performed when laying out files",
                .def    = "posix",
+               .category = FIO_OPT_G_FILE,
                .posval = {
                          { .ival = "none",
                            .oval = FIO_FALLOCATE_NONE,
@@ -1274,6 +1304,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(fadvise_hint),
                .help   = "Use fadvise() to advise the kernel on IO pattern",
                .def    = "1",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "fsync",
@@ -1281,6 +1312,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(fsync_blocks),
                .help   = "Issue fsync for writes every given number of blocks",
                .def    = "0",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "fdatasync",
@@ -1288,6 +1320,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(fdatasync_blocks),
                .help   = "Issue fdatasync for writes every given number of blocks",
                .def    = "0",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "write_barrier",
@@ -1295,6 +1328,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(barrier_blocks),
                .help   = "Make every Nth write a barrier write",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
 #ifdef FIO_HAVE_SYNC_FILE_RANGE
        {
@@ -1321,6 +1355,7 @@ 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_G_FILE,
        },
 #endif
        {
@@ -1329,6 +1364,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(odirect),
                .help   = "Use O_DIRECT IO (negates buffered)",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "buffered",
@@ -1337,6 +1373,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .neg    = 1,
                .help   = "Use buffered IO (negates direct)",
                .def    = "1",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "overwrite",
@@ -1344,6 +1381,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(overwrite),
                .help   = "When writing, set whether to overwrite current data",
                .def    = "0",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_FILE,
        },
        {
                .name   = "loops",
@@ -1351,6 +1389,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(loops),
                .help   = "Number of times to run the job",
                .def    = "1",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "numjobs",
@@ -1358,6 +1397,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(numjobs),
                .help   = "Duplicate this job this many times",
                .def    = "1",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "startdelay",
@@ -1365,6 +1405,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(start_delay),
                .help   = "Only start job when this period has passed",
                .def    = "0",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "runtime",
@@ -1373,18 +1414,21 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(timeout),
                .help   = "Stop workload when this amount of time has passed",
                .def    = "0",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "time_based",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(time_based),
                .help   = "Keep running until runtime/timeout is met",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "ramp_time",
                .type   = FIO_OPT_STR_VAL_TIME,
                .off1   = td_var_offset(ramp_time),
                .help   = "Ramp up time before measuring performance",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "clocksource",
@@ -1392,6 +1436,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = fio_clock_source_cb,
                .off1   = td_var_offset(clocksource),
                .help   = "What type of timing source to use",
+               .category = FIO_OPT_G_OS,
                .posval = {
                          { .ival = "gettimeofday",
                            .oval = CS_GTOD,
@@ -1417,6 +1462,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(mem_type),
                .help   = "Backing type for IO buffers",
                .def    = "malloc",
+               .category = FIO_OPT_G_IO_BUF | FIO_OPT_G_MEM,
                .posval = {
                          { .ival = "malloc",
                            .oval = MEM_MALLOC,
@@ -1453,6 +1499,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "IO memory buffer offset alignment",
                .def    = "0",
                .parent = "iomem",
+               .category = FIO_OPT_G_IO_BUF | FIO_OPT_G_MEM,
        },
        {
                .name   = "verify",
@@ -1461,6 +1508,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Verify data written",
                .cb     = str_verify_cb,
                .def    = "0",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
                .posval = {
                          { .ival = "0",
                            .oval = VERIFY_NONE,
@@ -1524,6 +1572,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Run verification stage after write",
                .def    = "1",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verifysort",
@@ -1532,6 +1581,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Sort written verify blocks for read back",
                .def    = "1",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_interval",
@@ -1540,6 +1590,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .minval = 2 * sizeof(struct verify_header),
                .help   = "Store verify buffer header every N bytes",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_offset",
@@ -1548,6 +1599,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .def    = "0",
                .cb     = str_verify_offset_cb,
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_pattern",
@@ -1555,6 +1607,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_verify_pattern_cb,
                .help   = "Fill pattern for IO buffers",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_fatal",
@@ -1563,6 +1616,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .def    = "0",
                .help   = "Exit on a single verify failure, don't continue",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY | FIO_OPT_G_ERR,
        },
        {
                .name   = "verify_dump",
@@ -1571,6 +1625,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .def    = "0",
                .help   = "Dump contents of good and bad blocks on failure",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY | FIO_OPT_G_ERR,
        },
        {
                .name   = "verify_async",
@@ -1579,6 +1634,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .def    = "0",
                .help   = "Number of async verifier threads to use",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_backlog",
@@ -1586,6 +1642,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(verify_backlog),
                .help   = "Verify after this number of blocks are written",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
        {
                .name   = "verify_backlog_batch",
@@ -1593,6 +1650,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(verify_batch),
                .help   = "Verify this number of IO blocks",
                .parent = "verify",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_VERIFY,
        },
 #ifdef FIO_HAVE_CPU_AFFINITY
        {
@@ -1601,6 +1659,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_verify_cpus_allowed_cb,
                .help   = "Set CPUs allowed for async verify threads",
                .parent = "verify_async",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_CPU | FIO_OPT_G_VERIFY,
        },
 #endif
 #ifdef FIO_HAVE_TRIM
@@ -1612,6 +1671,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Number of verify blocks to discard/trim",
                .parent = "verify",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "trim_verify_zero",
@@ -1620,6 +1680,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(trim_zero),
                .parent = "trim_percentage",
                .def    = "1",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "trim_backlog",
@@ -1627,6 +1688,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(trim_backlog),
                .help   = "Trim after this number of blocks are written",
                .parent = "trim_percentage",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "trim_backlog_batch",
@@ -1634,6 +1696,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(trim_batch),
                .help   = "Trim this number of IO blocks",
                .parent = "trim_percentage",
+               .category = FIO_OPT_G_IO,
        },
 #endif
        {
@@ -1641,12 +1704,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(write_iolog_file),
                .help   = "Store IO pattern to file",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_LOG,
        },
        {
                .name   = "read_iolog",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(read_iolog_file),
                .help   = "Playback IO pattern from file",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_LOG,
        },
        {
                .name   = "replay_no_stall",
@@ -1655,6 +1720,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .def    = "0",
                .parent = "read_iolog",
                .help   = "Playback IO pattern file as fast as possible without stalls",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_LOG,
        },
        {
                .name   = "replay_redirect",
@@ -1662,18 +1728,21 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(replay_redirect),
                .parent = "read_iolog",
                .help   = "Replay all I/O onto this device, regardless of trace device",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_LOG,
        },
        {
                .name   = "exec_prerun",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(exec_prerun),
                .help   = "Execute this file prior to running job",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_OS,
        },
        {
                .name   = "exec_postrun",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(exec_postrun),
                .help   = "Execute this file after running job",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_OS,
        },
 #ifdef FIO_HAVE_IOSCHED_SWITCH
        {
@@ -1681,6 +1750,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(ioscheduler),
                .help   = "Use this IO scheduler on the backing device",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_IO,
        },
 #endif
        {
@@ -1689,6 +1759,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(zone_size),
                .help   = "Amount of data to read per zone",
                .def    = "0",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_ZONE,
        },
        {
                .name   = "zonerange",
@@ -1696,6 +1767,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(zone_range),
                .help   = "Give size of an IO zone",
                .def    = "0",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_ZONE,
        },
        {
                .name   = "zoneskip",
@@ -1703,6 +1775,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(zone_skip),
                .help   = "Space between IO zones",
                .def    = "0",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_ZONE,
        },
        {
                .name   = "lockmem",
@@ -1710,6 +1783,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_lockmem_cb,
                .help   = "Lock down this amount of memory",
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MEM,
        },
        {
                .name   = "rwmixread",
@@ -1718,6 +1792,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .maxval = 100,
                .help   = "Percentage of mixed workload that is reads",
                .def    = "50",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "rwmixwrite",
@@ -1726,10 +1801,12 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .maxval = 100,
                .help   = "Percentage of mixed workload that is writes",
                .def    = "50",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "rwmixcycle",
                .type   = FIO_OPT_DEPRECATED,
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "nice",
@@ -1739,6 +1816,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .minval = -19,
                .maxval = 20,
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_CPU,
        },
 #ifdef FIO_HAVE_IOPRIO
        {
@@ -1748,6 +1826,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Set job IO priority value",
                .minval = 0,
                .maxval = 7,
+               .category = FIO_OPT_G_OS | FIO_OPT_G_CPU,
        },
        {
                .name   = "prioclass",
@@ -1756,6 +1835,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Set job IO priority class",
                .minval = 0,
                .maxval = 3,
+               .category = FIO_OPT_G_OS | FIO_OPT_G_CPU,
        },
 #endif
        {
@@ -1764,6 +1844,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(thinktime),
                .help   = "Idle time between IO buffers (usec)",
                .def    = "0",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "thinktime_spin",
@@ -1772,6 +1853,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Start think time by spinning this amount (usec)",
                .def    = "0",
                .parent = "thinktime",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "thinktime_blocks",
@@ -1780,6 +1862,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "IO buffer period between 'thinktime'",
                .def    = "1",
                .parent = "thinktime",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "rate",
@@ -1787,6 +1870,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(rate[0]),
                .off2   = td_var_offset(rate[1]),
                .help   = "Set bandwidth rate",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "ratemin",
@@ -1795,6 +1879,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off2   = td_var_offset(ratemin[1]),
                .help   = "Job must meet this rate or it will be shutdown",
                .parent = "rate",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "rate_iops",
@@ -1802,6 +1887,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(rate_iops[0]),
                .off2   = td_var_offset(rate_iops[1]),
                .help   = "Limit IO used to this number of IO operations/sec",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "rate_iops_min",
@@ -1810,6 +1896,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off2   = td_var_offset(rate_iops_min[1]),
                .help   = "Job must meet this rate or it will be shut down",
                .parent = "rate_iops",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "ratecycle",
@@ -1818,6 +1905,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Window average for rate limits (msec)",
                .def    = "1000",
                .parent = "rate",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "invalidate",
@@ -1825,6 +1913,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(invalidate_cache),
                .help   = "Invalidate buffer/page cache prior to running job",
                .def    = "1",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_CACHE,
        },
        {
                .name   = "sync",
@@ -1833,6 +1922,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Use O_SYNC for buffered writes",
                .def    = "0",
                .parent = "buffered",
+               .category = FIO_OPT_G_IO | FIO_OPT_G_FILE,
        },
        {
                .name   = "bwavgtime",
@@ -1842,6 +1932,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                          " (msec)",
                .def    = "500",
                .parent = "write_bw_log",
+               .category = FIO_OPT_G_LOG | FIO_OPT_G_STAT,
        },
        {
                .name   = "iopsavgtime",
@@ -1850,6 +1941,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Time window over which to calculate IOPS (msec)",
                .def    = "500",
                .parent = "write_iops_log",
+               .category = FIO_OPT_G_LOG | FIO_OPT_G_STAT,
        },
        {
                .name   = "create_serialize",
@@ -1857,6 +1949,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(create_serialize),
                .help   = "Serialize creating of job files",
                .def    = "1",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "create_fsync",
@@ -1864,6 +1957,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(create_fsync),
                .help   = "fsync file after creation",
                .def    = "1",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "create_on_open",
@@ -1871,6 +1965,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(create_on_open),
                .help   = "Create files when they are opened for IO",
                .def    = "0",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "pre_read",
@@ -1878,12 +1973,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(pre_read),
                .help   = "Pre-read files before starting official testing",
                .def    = "0",
+               .category = FIO_OPT_G_FILE | FIO_OPT_G_CACHE,
        },
        {
                .name   = "cpuload",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(cpuload),
                .help   = "Use this percentage of CPU",
+               .category = FIO_OPT_G_CPU,
        },
        {
                .name   = "cpuchunks",
@@ -1892,6 +1989,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Length of the CPU burn cycles (usecs)",
                .def    = "50000",
                .parent = "cpuload",
+               .category = FIO_OPT_G_CPU,
        },
 #ifdef FIO_HAVE_CPU_AFFINITY
        {
@@ -1899,12 +1997,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_INT,
                .cb     = str_cpumask_cb,
                .help   = "CPU affinity mask",
+               .category = FIO_OPT_G_CPU | FIO_OPT_G_OS,
        },
        {
                .name   = "cpus_allowed",
                .type   = FIO_OPT_STR,
                .cb     = str_cpus_allowed_cb,
                .help   = "Set CPUs allowed",
+               .category = FIO_OPT_G_CPU | FIO_OPT_G_OS,
        },
 #endif
        {
@@ -1913,6 +2013,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(end_fsync),
                .help   = "Include fsync at the end of job",
                .def    = "0",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "fsync_on_close",
@@ -1920,6 +2021,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(fsync_on_close),
                .help   = "fsync files on close",
                .def    = "0",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "unlink",
@@ -1927,12 +2029,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(unlink),
                .help   = "Unlink created files after job has completed",
                .def    = "0",
+               .category = FIO_OPT_G_FILE,
        },
        {
                .name   = "exitall",
                .type   = FIO_OPT_STR_SET,
                .cb     = str_exitall_cb,
                .help   = "Terminate all jobs when one exits",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_JOB,
        },
        {
                .name   = "stonewall",
@@ -1940,18 +2044,21 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(stonewall),
                .help   = "Insert a hard barrier between this job and previous",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_JOB,
        },
        {
                .name   = "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_G_MISC | FIO_OPT_G_JOB,
        },
        {
                .name   = "thread",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(use_thread),
                .help   = "Use threads instead of forks",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_OS | FIO_OPT_G_JOB,
        },
        {
                .name   = "write_bw_log",
@@ -1959,6 +2066,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(write_bw_log),
                .cb     = str_write_bw_log_cb,
                .help   = "Write log of bandwidth during run",
+               .category = FIO_OPT_G_LOG,
        },
        {
                .name   = "write_lat_log",
@@ -1966,6 +2074,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(write_lat_log),
                .cb     = str_write_lat_log_cb,
                .help   = "Write log of latency during run",
+               .category = FIO_OPT_G_LOG,
        },
        {
                .name   = "write_iops_log",
@@ -1973,6 +2082,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(write_iops_log),
                .cb     = str_write_iops_log_cb,
                .help   = "Write log of IOPS during run",
+               .category = FIO_OPT_G_LOG,
        },
        {
                .name   = "log_avg_msec",
@@ -1980,6 +2090,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(log_avg_msec),
                .help   = "Average bw/iops/lat logs over this period of time",
                .def    = "0",
+               .category = FIO_OPT_G_LOG,
        },
        {
                .name   = "hugepage-size",
@@ -1987,24 +2098,28 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(hugepage_size),
                .help   = "When using hugepages, specify size of each page",
                .def    = __fio_stringify(FIO_HUGE_PAGE),
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MEM,
        },
        {
                .name   = "group_reporting",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(group_reporting),
                .help   = "Do reporting on a per-group basis",
+               .category = FIO_OPT_G_MISC,
        },
        {
                .name   = "zero_buffers",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(zero_buffers),
                .help   = "Init IO buffers to all zeroes",
+               .category = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "refill_buffers",
                .type   = FIO_OPT_STR_SET,
                .off1   = td_var_offset(refill_buffers),
                .help   = "Refill IO buffers on every IO submit",
+               .category = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "scramble_buffers",
@@ -2012,6 +2127,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(scramble_buffers),
                .help   = "Slightly scramble buffers on every IO submit",
                .def    = "1",
+               .category = FIO_OPT_G_IO_BUF,
        },
        {
                .name   = "buffer_compress_percentage",
@@ -2034,6 +2150,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(clat_percentiles),
                .help   = "Enable the reporting of completion latency percentiles",
                .def    = "1",
+               .category = FIO_OPT_G_STAT,
        },
        {
                .name   = "percentile_list",
@@ -2044,6 +2161,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .maxlen = FIO_IO_U_LIST_MAX_LEN,
                .minfp  = 0.0,
                .maxfp  = 100.0,
+               .category = FIO_OPT_G_STAT,
        },
 
 #ifdef FIO_HAVE_DISK_UTIL
@@ -2053,6 +2171,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(do_disk_util),
                .help   = "Log disk utilization statistics",
                .def    = "1",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_STAT,
        },
 #endif
        {
@@ -2061,6 +2180,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Greatly reduce number of gettimeofday() calls",
                .cb     = str_gtod_reduce_cb,
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MISC | FIO_OPT_G_STAT,
        },
        {
                .name   = "disable_lat",
@@ -2069,6 +2189,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Disable latency numbers",
                .parent = "gtod_reduce",
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MISC | FIO_OPT_G_STAT,
        },
        {
                .name   = "disable_clat",
@@ -2077,6 +2198,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Disable completion latency numbers",
                .parent = "gtod_reduce",
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MISC | FIO_OPT_G_STAT,
        },
        {
                .name   = "disable_slat",
@@ -2085,6 +2207,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Disable submission latency numbers",
                .parent = "gtod_reduce",
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MISC | FIO_OPT_G_STAT,
        },
        {
                .name   = "disable_bw_measurement",
@@ -2093,6 +2216,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Disable bandwidth logging",
                .parent = "gtod_reduce",
                .def    = "0",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MISC | FIO_OPT_G_STAT,
        },
        {
                .name   = "gtod_cpu",
@@ -2100,6 +2224,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .cb     = str_gtod_cpu_cb,
                .help   = "Set up dedicated gettimeofday() thread on this CPU",
                .verify = gtod_cpu_verify,
+               .category = FIO_OPT_G_OS | FIO_OPT_G_MISC | FIO_OPT_G_STAT,
        },
        {
                .name   = "continue_on_error",
@@ -2107,6 +2232,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(continue_on_error),
                .help   = "Continue on non-fatal errors during IO",
                .def    = "none",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_ERR,
                .posval = {
                          { .ival = "none",
                            .oval = ERROR_TYPE_NONE,
@@ -2147,12 +2273,14 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(profile),
                .help   = "Select a specific builtin performance test",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_JOB,
        },
        {
                .name   = "cgroup",
                .type   = FIO_OPT_STR_STORE,
                .off1   = td_var_offset(cgroup),
                .help   = "Add job to cgroup of this name",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_OS,
        },
        {
                .name   = "cgroup_weight",
@@ -2161,6 +2289,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Use given weight for cgroup",
                .minval = 100,
                .maxval = 1000,
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_OS,
        },
        {
                .name   = "cgroup_nodelete",
@@ -2168,18 +2297,21 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(cgroup_nodelete),
                .help   = "Do not delete cgroups after job completion",
                .def    = "0",
+               .category = FIO_OPT_G_MISC | FIO_OPT_G_OS,
        },
        {
                .name   = "uid",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(uid),
                .help   = "Run job with this user ID",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_JOB,
        },
        {
                .name   = "gid",
                .type   = FIO_OPT_INT,
                .off1   = td_var_offset(gid),
                .help   = "Run job with this group ID",
+               .category = FIO_OPT_G_OS | FIO_OPT_G_JOB,
        },
        {
                .name   = "flow_id",
@@ -2187,6 +2319,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .off1   = td_var_offset(flow_id),
                .help   = "The flow index ID to use",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "flow",
@@ -2195,6 +2328,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                .help   = "Weight for flow control of this job",
                .parent = "flow_id",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "flow_watermark",
@@ -2205,6 +2339,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                        " with non-zero flow.",
                .parent = "flow_id",
                .def    = "1024",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name   = "flow_sleep",
@@ -2214,6 +2349,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
                        " back by the flow control mechanism",
                .parent = "flow_id",
                .def    = "0",
+               .category = FIO_OPT_G_IO,
        },
        {
                .name = NULL,
diff --git a/parse.c b/parse.c
index f1d5f8f8a72922a85d511528b58f9796c77c337b..cdd9ac7e3cf0cb9e81a25d2fd43aba205c90ce88 100644 (file)
--- a/parse.c
+++ b/parse.c
@@ -153,32 +153,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;
diff --git a/parse.h b/parse.h
index 4edf75e5871251a59b078c1a954fd4df21177901..68a7338e32d34ed929cb5173c18df43273e2920f 100644 (file)
--- a/parse.h
+++ b/parse.h
@@ -35,6 +35,26 @@ struct value_pair {
 #define OPT_LEN_MAX    4096
 #define PARSE_MAX_VP   16
 
+enum opt_category {
+       FIO_OPT_G_DESC          = 1UL << 0,
+       FIO_OPT_G_FILE          = 1UL << 1,
+       FIO_OPT_G_MISC          = 1UL << 2,
+       FIO_OPT_G_IO            = 1UL << 3,
+       FIO_OPT_G_IO_DDIR       = 1UL << 4,
+       FIO_OPT_G_IO_BUF        = 1UL << 5,
+       FIO_OPT_G_RAND          = 1UL << 6,
+       FIO_OPT_G_OS            = 1UL << 7,
+       FIO_OPT_G_MEM           = 1UL << 8,
+       FIO_OPT_G_VERIFY        = 1UL << 9,
+       FIO_OPT_G_CPU           = 1UL << 10,
+       FIO_OPT_G_LOG           = 1UL << 11,
+       FIO_OPT_G_ZONE          = 1UL << 12,
+       FIO_OPT_G_CACHE         = 1UL << 13,
+       FIO_OPT_G_STAT          = 1UL << 14,
+       FIO_OPT_G_ERR           = 1UL << 15,
+       FIO_OPT_G_JOB           = 1UL << 16,
+};
+
 /*
  * Option define
  */
@@ -61,6 +81,7 @@ struct fio_option {
        const char *parent;             /* parent option */
        int (*verify)(struct fio_option *, void *);
        const char *prof_name;          /* only valid for specific profile */
+       unsigned int category;          /* for type grouping */
 };
 
 typedef int (str_cb_fn)(void *, char *);
index 33f69ef611a7ae2dd7eda4bd7e0e15c3284487a6..25b15a5d9d01d0ffb3d9c7550f6fd72d1f1737d5 100644 (file)
--- a/server.c
+++ b/server.c
@@ -24,7 +24,7 @@
 
 #include "fio_version.h"
 
-int fio_net_port = 8765;
+int fio_net_port = FIO_NET_PORT;
 
 int exit_backend = 0;
 
@@ -51,7 +51,9 @@ static const char *fio_server_ops[FIO_NET_CMD_NR] = {
        "START",
        "STOP",
        "DISK_UTIL",
-       "RUN",
+       "SERVER_START",
+       "ADD_JOB",
+       "CMD_RUN"
 };
 
 const char *fio_server_op(unsigned int op)
@@ -67,7 +69,7 @@ const char *fio_server_op(unsigned int op)
 
 int fio_send_data(int sk, const void *p, unsigned int len)
 {
-       assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_PDU);
+       assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_FRAGMENT_PDU);
 
        do {
                int ret = send(sk, p, len, 0);
@@ -89,6 +91,9 @@ int fio_send_data(int sk, const void *p, unsigned int len)
        if (!len)
                return 0;
 
+       if (errno)
+               return -errno;
+
        return 1;
 }
 
@@ -145,7 +150,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;
        }
@@ -221,12 +226,19 @@ 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;
+               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) {
+                               char *buf = (char *) cmdret->payload;
 
-                       buf[cmdret->pdu_len ] = '\0';
+                               buf[cmdret->pdu_len ] = '\0';
+                       }
                }
+
                /* frag flag is internal */
                cmdret->flags &= ~FIO_NET_CMD_F_MORE;
        }
@@ -254,8 +266,8 @@ int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size,
 
        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)
@@ -331,21 +343,11 @@ static int fio_server_send_quit_cmd(void)
        return fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_QUIT, 0, NULL);
 }
 
-static int handle_job_cmd(struct fio_net_cmd *cmd)
+static int handle_run_cmd(struct fio_net_cmd *cmd)
 {
-       char *buf = (char *) cmd->payload;
-       struct cmd_start_pdu spdu;
        struct cmd_end_pdu epdu;
        int ret;
 
-       if (parse_jobs_ini(buf, 1, 0)) {
-               fio_server_send_quit_cmd();
-               return -1;
-       }
-
-       spdu.jobs = cpu_to_le32(thread_number);
-       fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), 0);
-
        ret = fio_backend();
 
        epdu.error = ret;
@@ -353,17 +355,34 @@ static int handle_job_cmd(struct fio_net_cmd *cmd)
 
        fio_server_send_quit_cmd();
        reset_fio_state();
+       first_cmd_check = 0;
        return ret;
 }
 
+static int handle_job_cmd(struct fio_net_cmd *cmd)
+{
+       char *buf = (char *) cmd->payload;
+       struct cmd_start_pdu spdu;
+
+       if (parse_jobs_ini(buf, 1, 0)) {
+               fio_server_send_quit_cmd();
+               return -1;
+       }
+
+       spdu.jobs = cpu_to_le32(thread_number);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), 0);
+       return 0;
+}
+
 static int handle_jobline_cmd(struct fio_net_cmd *cmd)
 {
        void *pdu = cmd->payload;
        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);
@@ -388,12 +407,9 @@ static int handle_jobline_cmd(struct fio_net_cmd *cmd)
 
        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), 0);
+       return 0;
 }
 
 static int handle_probe_cmd(struct fio_net_cmd *cmd)
@@ -443,18 +459,19 @@ 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++) {
+               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);
 
        fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, je, size, cmd->tag);
        free(je);
@@ -487,6 +504,9 @@ 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;
        default:
                log_err("fio: unknown opcode: %s\n",fio_server_op(cmd->opcode));
                ret = 1;
@@ -554,8 +574,10 @@ static int handle_connection(int sk, int block)
 
 void fio_server_idle_loop(void)
 {
-       if (!first_cmd_check)
-               fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_RUN, 0, NULL);
+       if (!first_cmd_check) {
+               fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_SERVER_START, 0, NULL);
+               first_cmd_check = 1;
+       }
        if (server_fd != -1)
                handle_connection(server_fd, 0);
 }
@@ -614,12 +636,30 @@ 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;
 
-       return log_local_buf(buf, len);
+       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);
+
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_TEXT, pdu, tlen, 0);
+       free(pdu);
+       return len;
 }
 
 static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
@@ -799,19 +839,13 @@ void fio_server_send_du(void)
        }
 }
 
-int fio_server_log(const char *format, ...)
+void fio_server_send_add_job(struct thread_options *o, const char *ioengine)
 {
-       char buffer[1024];
-       va_list args;
-       size_t len;
-
-       dprint(FD_NET, "server log\n");
+       struct cmd_add_job_pdu pdu;
 
-       va_start(args, format);
-       len = vsnprintf(buffer, sizeof(buffer), format, args);
-       va_end(args);
+       convert_thread_options_to_net(&pdu.top, o);
 
-       return fio_server_text_output(buffer, len);
+       fio_net_send_cmd(server_fd, FIO_NET_CMD_ADD_JOB, &pdu, sizeof(pdu), 0);
 }
 
 static int fio_init_server_ip(void)
@@ -943,6 +977,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'.
  *
@@ -961,7 +1035,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;
@@ -1022,38 +1096,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)
index 27da94f23c69e00ed089af87de833b84a612f9f2..7a801bf0a283235ebc72d50392d7fc99f87f4dc1 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
  */
@@ -36,33 +38,35 @@ struct fio_net_int_cmd {
 };
 
 enum {
-       FIO_SERVER_VER          = 6,
-
-       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                  = 10,
+
+       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_NR                  = 17,
+
+       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_CLIENT_TIMEOUT  = 5000,
+       FIO_NET_CLIENT_TIMEOUT          = 5000,
 };
 
 struct cmd_ts_pdu {
@@ -104,13 +108,25 @@ struct cmd_end_pdu {
        uint32_t error;
 };
 
+struct cmd_add_job_pdu {
+       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];
+};
+
 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_server_text_output(int, const char *, size_t);
 extern int fio_net_send_cmd(int, uint16_t, const void *, off_t, uint64_t);
 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,17 +137,14 @@ extern void fio_server_send_gs(struct group_run_stats *);
 extern void fio_server_send_du(void);
 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 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 struct fio_net_cmd *fio_net_recv_cmd(int sk);
 
+struct thread_options;
+extern void fio_server_send_add_job(struct thread_options *, const char *);
+
 extern int exit_backend;
 extern int fio_net_port;
 
diff --git a/stat.c b/stat.c
index fa9af7c7c400ff8825cba27e1c0967efaa147e11..8b032b881d93d8fe1dc692a1c38898be42848bcd 100644 (file)
--- a/stat.c
+++ b/stat.c
@@ -114,11 +114,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;
@@ -234,8 +232,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;
 
@@ -287,11 +285,7 @@ void show_group_stats(struct group_run_stats *rs)
        }
 }
 
-#define ts_total_io_u(ts)      \
-       ((ts)->total_io_u[0] + (ts)->total_io_u[1])
-
-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;
 
@@ -329,28 +323,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,
@@ -386,54 +385,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,
@@ -510,8 +468,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);
 }
@@ -521,8 +485,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];
 
        if (!(ts->io_bytes[0] + ts->io_bytes[1]) &&
            !(ts->total_io_u[0] + ts->total_io_u[1]))
@@ -546,9 +508,7 @@ void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs)
        if (ts->io_bytes[DDIR_WRITE])
                show_ddir_status(rs, ts, DDIR_WRITE);
 
-       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) {
diff --git a/stat.h b/stat.h
index 3115539fdb183ad57240f1b2ea8fe9cf27ba526e..084724669bbb3ebed266933783d5a6478296af62 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[2], min_run[2];
        uint64_t max_bw[2], min_bw[2];
@@ -174,8 +176,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[2], t_rate[2];
+       uint32_t m_iops[2], t_iops[2];
        uint32_t rate[2];
        uint32_t iops[2];
        uint64_t elapsed_sec;
@@ -197,5 +199,27 @@ 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);
+
+#define ts_total_io_u(ts)      ((ts)->total_io_u[0] + (ts)->total_io_u[1])
+
+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
diff --git a/thread_options.h b/thread_options.h
new file mode 100644 (file)
index 0000000..c0fd7e2
--- /dev/null
@@ -0,0 +1,415 @@
+#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 {
+        ERROR_TYPE_NONE = 0,
+        ERROR_TYPE_READ = 1 << 0,
+        ERROR_TYPE_WRITE = 1 << 1,
+        ERROR_TYPE_VERIFY = 1 << 2,
+        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;
+       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[2];
+       unsigned int ba[2];
+       unsigned int min_bs[2];
+       unsigned int max_bs[2];
+       struct bssplit *bssplit[2];
+       unsigned int bssplit_nr[2];
+
+       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 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[2];
+       unsigned int ratemin[2];
+       unsigned int ratecycle;
+       unsigned int rate_iops[2];
+       unsigned int rate_iops_min[2];
+
+       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 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];
+       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[2];
+       uint32_t ba[2];
+       uint32_t min_bs[2];
+       uint32_t max_bs[2];
+       struct bssplit bssplit[2][BSSPLIT_MAX];
+       uint32_t bssplit_nr[2];
+
+       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 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 write_lat_log;
+       uint32_t write_bw_log;
+       uint32_t write_iops_log;
+       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;
+       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 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[2];
+       uint32_t ratemin[2];
+       uint32_t ratecycle;
+       uint32_t rate_iops[2];
+       uint32_t rate_iops_min[2];
+
+       uint8_t ioscheduler[FIO_TOP_STR_MAX];
+
+       /*
+        * CPU "io" cycle burner
+        */
+       uint32_t cpuload;
+       uint32_t cpucycle;
+
+       /*
+        * 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;
+
+       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 *);
+
+#endif
diff --git a/tickmarks.c b/tickmarks.c
new file mode 100644 (file)
index 0000000..2605f72
--- /dev/null
@@ -0,0 +1,142 @@
+#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 i, l, minshorten;
+       char *str;
+       char shorten_char = '?';
+
+       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_char = use_KMG_symbols ? 'G' : '\0';
+               } else if (6 < minshorten && l > 6 &&
+                               strcmp(&str[l - 6], "000000") == 0) {
+                       *power_of_ten = 6;
+                       shorten_char = use_KMG_symbols ? 'M' : '\0';
+               } else if (l > 3 && strcmp(&str[l - 3], "000") == 0) {
+                       *power_of_ten = 3;
+                       shorten_char = use_KMG_symbols ? 'K': '\0';
+               } else {
+                       *power_of_ten = 0;
+               }
+
+               if (*power_of_ten < minshorten)
+                       minshorten = *power_of_ten;
+       }
+
+       if (minshorten == 0)
+               return;
+
+       for (i = 0; i < nticks; i++) {
+               str = tm[i].string;
+               l = strlen(str);
+               str[l - minshorten] = shorten_char;
+               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)
+{
+       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);
+       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..a8c1bf9
--- /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);
+
+#endif