Merge branch 'master' into gfio
authorJens Axboe <axboe@kernel.dk>
Mon, 24 Sep 2012 06:51:24 +0000 (08:51 +0200)
committerJens Axboe <axboe@kernel.dk>
Mon, 24 Sep 2012 06:51:24 +0000 (08:51 +0200)
Conflicts:
Makefile
backend.c
client.c
fio.h
init.c
io_ddir.h
options.c
server.h
stat.c
stat.h

Signed-off-by: Jens Axboe <axboe@kernel.dk>
24 files changed:
1  2 
HOWTO
Makefile
backend.c
client.c
engines/libaio.c
eta.c
filesetup.c
fio.1
fio.h
gclient.c
init.c
io_ddir.h
io_u.c
ioengines.c
iolog.h
libfio.c
options.c
os/os-linux.h
parse.c
parse.h
server.h
stat.c
stat.h
thread_options.h

diff --cc HOWTO
Simple merge
diff --cc Makefile
+++ b/Makefile
@@@ -18,7 -15,7 +18,7 @@@ SOURCE := gettime.c ioengines.c init.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 \
-               cconv.c lib/prio_tree.c
 -              json.c
++              cconv.c lib/prio_tree.c json.c
  
  ifeq ($(UNAME), Linux)
    SOURCE += diskutil.c fifo.c blktrace.c helpers.c cgroup.c trim.c \
diff --cc backend.c
+++ b/backend.c
@@@ -57,10 -57,8 +57,10 @@@ static struct flist_head *cgroup_list
  static char *cgroup_mnt;
  static int exit_value;
  static volatile int fio_abort;
 +static unsigned int nr_process = 0;
 +static unsigned int nr_thread = 0;
  
- struct io_log *agg_io_log[2];
+ struct io_log *agg_io_log[DDIR_RWDIR_CNT];
  
  int groupid = 0;
  unsigned int thread_number = 0;
@@@ -1156,14 -1153,14 +1166,16 @@@ static void *thread_main(void *data
        }
  
        update_rusage_stat(td);
-       td->ts.runtime[0] = (td->ts.runtime[0] + 999) / 1000;
-       td->ts.runtime[1] = (td->ts.runtime[1] + 999) / 1000;
+       td->ts.runtime[DDIR_READ] = (td->ts.runtime[DDIR_READ] + 999) / 1000;
+       td->ts.runtime[DDIR_WRITE] = (td->ts.runtime[DDIR_WRITE] + 999) / 1000;
+       td->ts.runtime[DDIR_TRIM] = (td->ts.runtime[DDIR_TRIM] + 999) / 1000;
        td->ts.total_run_time = mtime_since_now(&td->epoch);
-       td->ts.io_bytes[0] = td->io_bytes[0];
-       td->ts.io_bytes[1] = td->io_bytes[1];
+       td->ts.io_bytes[DDIR_READ] = td->io_bytes[DDIR_READ];
+       td->ts.io_bytes[DDIR_WRITE] = td->io_bytes[DDIR_WRITE];
+       td->ts.io_bytes[DDIR_TRIM] = td->io_bytes[DDIR_TRIM];
  
 +      fio_unpin_memory(td);
 +
        fio_mutex_down(writeout_mutex);
        if (td->bw_log) {
                if (td->o.bw_log_file) {
@@@ -1381,15 -1381,7 +1393,15 @@@ static void run_threads(void
  
        set_sig_handlers();
  
-       if (!terse_output) {
 +      nr_thread = nr_process = 0;
 +      for_each_td(td, i) {
 +              if (td->o.use_thread)
 +                      nr_thread++;
 +              else
 +                      nr_process++;
 +      }
 +
+       if (output_format == FIO_OUTPUT_NORMAL) {
                log_info("Starting ");
                if (nr_thread)
                        log_info("%d thread%s", nr_thread,
@@@ -1668,8 -1669,9 +1680,9 @@@ int fio_backend(void
                return 0;
  
        if (write_bw_log) {
 -              setup_log(&agg_io_log[DDIR_READ], 0);
 -              setup_log(&agg_io_log[DDIR_WRITE], 0);
 -              setup_log(&agg_io_log[DDIR_TRIM], 0);
 +              setup_log(&agg_io_log[DDIR_READ], 0, IO_LOG_TYPE_BW);
 +              setup_log(&agg_io_log[DDIR_WRITE], 0, IO_LOG_TYPE_BW);
++              setup_log(&agg_io_log[DDIR_TRIM], 0, IO_LOG_TYPE_BW);
        }
  
        startup_mutex = fio_mutex_init(FIO_MUTEX_LOCKED);
diff --cc client.c
Simple merge
Simple merge
diff --cc eta.c
--- 1/eta.c
--- 2/eta.c
+++ b/eta.c
@@@ -286,17 -296,24 +296,24 @@@ int calc_thread_status(struct jobs_eta 
                    || td->runstate == TD_PRE_READING) {
                        je->nr_running++;
                        if (td_read(td)) {
 -                              je->t_rate += td->o.rate[DDIR_READ];
 -                              je->t_iops += td->o.rate_iops[DDIR_READ];
 -                              je->m_rate += td->o.ratemin[DDIR_READ];
 -                              je->m_iops += td->o.rate_iops_min[DDIR_READ];
 +                              je->t_rate[0] += td->o.rate[DDIR_READ];
 +                              je->t_iops[0] += td->o.rate_iops[DDIR_READ];
 +                              je->m_rate[0] += td->o.ratemin[DDIR_READ];
 +                              je->m_iops[0] += td->o.rate_iops_min[DDIR_READ];
                        }
                        if (td_write(td)) {
 -                              je->t_rate += td->o.rate[DDIR_WRITE];
 -                              je->t_iops += td->o.rate_iops[DDIR_WRITE];
 -                              je->m_rate += td->o.ratemin[DDIR_WRITE];
 -                              je->m_iops += td->o.rate_iops_min[DDIR_WRITE];
 +                              je->t_rate[1] += td->o.rate[DDIR_WRITE];
 +                              je->t_iops[1] += td->o.rate_iops[DDIR_WRITE];
 +                              je->m_rate[1] += td->o.ratemin[DDIR_WRITE];
 +                              je->m_iops[1] += td->o.rate_iops_min[DDIR_WRITE];
                        }
 -                              je->t_rate += td->o.rate[DDIR_TRIM];
 -                              je->t_iops += td->o.rate_iops[DDIR_TRIM];
 -                              je->m_rate += td->o.ratemin[DDIR_TRIM];
 -                              je->m_iops += td->o.rate_iops_min[DDIR_TRIM];
+                       if (td_trim(td)) {
++                              je->t_rate[2] += td->o.rate[DDIR_TRIM];
++                              je->t_iops[2] += td->o.rate_iops[DDIR_TRIM];
++                              je->m_rate[2] += td->o.ratemin[DDIR_TRIM];
++                              je->m_iops[2] += td->o.rate_iops_min[DDIR_TRIM];
+                       }
                        je->files_open += td->nr_open_files;
                } else if (td->runstate == TD_RAMP) {
                        je->nr_running++;
@@@ -390,17 -409,15 +409,18 @@@ void display_thread_status(struct jobs_
                p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
                free(tr);
                free(mr);
 -      } else if (je->m_iops || je->t_iops)
 -              p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
 +      } else if (je->m_iops[0] || je->m_iops[1] || je->t_iops[0] || je->t_iops[1]) {
 +              p += sprintf(p, ", CR=%d/%d IOPS",
 +                                      je->t_iops[0] + je->t_iops[1],
 +                                      je->m_iops[0] + je->m_iops[1]);
 +      }
        if (je->eta_sec != INT_MAX && je->nr_running) {
                char perc_str[32];
-               char *iops_str[2];
-               char *rate_str[2];
+               char *iops_str[DDIR_RWDIR_CNT];
+               char *rate_str[DDIR_RWDIR_CNT];
                size_t left;
                int l;
+               int ddir;
  
                if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
                        strcpy(perc_str, "-.-% done");
diff --cc filesetup.c
Simple merge
diff --cc fio.1
Simple merge
diff --cc fio.h
--- 1/fio.h
--- 2/fio.h
+++ b/fio.h
@@@ -310,10 -523,12 +310,10 @@@ enum 
  
  extern int exitall_on_terminate;
  extern unsigned int thread_number;
 -extern unsigned int nr_process, nr_thread;
  extern int shm_id;
  extern int groupid;
- extern int terse_output;
+ extern int output_format;
  extern int temp_stall_ts;
 -extern unsigned long long mlock_size;
  extern uintptr_t page_mask, page_size;
  extern int read_only;
  extern int eta_print;
@@@ -551,6 -761,10 +553,12 @@@ static inline void td_io_u_free_notify(
  extern const char *fio_get_arch_string(int);
  extern const char *fio_get_os_string(int);
  
 +#define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0])))
 +
+ enum {
+       FIO_OUTPUT_TERSE        = 0,
+       FIO_OUTPUT_JSON,
+       FIO_OUTPUT_NORMAL,
+ };
  #endif
diff --cc gclient.c
index 78a5c36,0000000..f317dc7
mode 100644,000000..100644
--- /dev/null
+++ b/gclient.c
@@@ -1,1387 -1,0 +1,1387 @@@
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
 +#include <malloc.h>
 +#include <string.h>
 +
 +#include <glib.h>
 +#include <cairo.h>
 +#include <gtk/gtk.h>
 +
 +#include "fio.h"
 +#include "gfio.h"
 +#include "ghelpers.h"
 +#include "goptions.h"
 +#include "gerror.h"
 +#include "graph.h"
 +#include "gclient.h"
 +#include "printing.h"
 +
 +static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
 +                          struct group_run_stats *rs);
 +
 +static gboolean results_window_delete(GtkWidget *w, gpointer data)
 +{
 +      struct gui_entry *ge = (struct gui_entry *) data;
 +
 +      gtk_widget_destroy(w);
 +      ge->results_window = NULL;
 +      ge->results_notebook = NULL;
 +      return TRUE;
 +}
 +
 +static void results_close(GtkWidget *w, gpointer *data)
 +{
 +      struct gui_entry *ge = (struct gui_entry *) data;
 +
 +      gtk_widget_destroy(ge->results_window);
 +}
 +
 +static void results_print(GtkWidget *w, gpointer *data)
 +{
 +      struct gui_entry *ge = (struct gui_entry *) data;
 +
 +      gfio_print_results(ge);
 +}
 +
 +static GtkActionEntry results_menu_items[] = {
 +      { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
 +      { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
 +      { "PrintFile", GTK_STOCK_PRINT, "Print", "<Control>P", NULL, G_CALLBACK(results_print) },
 +      { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(results_close) },
 +};
 +static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]);
 +
 +static const gchar *results_ui_string = " \
 +      <ui> \
 +              <menubar name=\"MainMenu\"> \
 +                      <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
 +                              <menuitem name=\"Print\" action=\"PrintFile\" /> \
 +                              <menuitem name=\"Close\" action=\"CloseFile\" /> \
 +                      </menu> \
 +                      <menu name=\"GraphMenu\" action=\"GraphMenuAction\"> \
 +                      </menu>\
 +              </menubar> \
 +      </ui> \
 +";
 +
 +static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge)
 +{
 +      GtkActionGroup *action_group;
 +      GtkWidget *widget;
 +      GError *error = 0;
 +
 +      ge->results_uimanager = gtk_ui_manager_new();
 +
 +      action_group = gtk_action_group_new("ResultsMenu");
 +      gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge);
 +
 +      gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0);
 +      gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error);
 +
 +      gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager));
 +
 +      widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu");
 +      return widget;
 +}
 +
 +static GtkWidget *get_results_window(struct gui_entry *ge)
 +{
 +      GtkWidget *win, *notebook, *vbox;
 +
 +      if (ge->results_window)
 +              return ge->results_notebook;
 +
 +      win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 +      gtk_window_set_title(GTK_WINDOW(win), "Results");
 +      gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
 +      g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
 +      g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
 +
 +      vbox = gtk_vbox_new(FALSE, 0);
 +      gtk_container_add(GTK_CONTAINER(win), vbox);
 +
 +      ge->results_menu = get_results_menubar(win, ge);
 +      gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0);
 +
 +      notebook = gtk_notebook_new();
 +      gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
 +      gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
 +      gtk_container_add(GTK_CONTAINER(vbox), notebook);
 +
 +      ge->results_window = win;
 +      ge->results_notebook = notebook;
 +      return ge->results_notebook;
 +}
 +
 +static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
 +      struct gfio_client *gc = client->client_data;
 +      struct gui_entry *ge = gc->ge;
 +      struct gui *ui = ge->ui;
 +      GtkTreeIter iter;
 +      struct tm *tm;
 +      time_t sec;
 +      char tmp[64], timebuf[80];
 +
 +      sec = p->log_sec;
 +      tm = localtime(&sec);
 +      strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm);
 +      sprintf(timebuf, "%s.%03ld", tmp, (long) p->log_usec / 1000);
 +
 +      gdk_threads_enter();
 +
 +      gtk_list_store_append(ui->log_model, &iter);
 +      gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
 +      gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
 +      gtk_list_store_set(ui->log_model, &iter, 2, log_get_level(p->level), -1);
 +      gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
 +
 +      if (p->level == FIO_LOG_ERR)
 +              gfio_view_log(ui);
 +
 +      gdk_threads_leave();
 +}
 +
 +static void disk_util_destroy(GtkWidget *w, gpointer data)
 +{
 +      struct gui_entry *ge = (struct gui_entry *) data;
 +
 +      ge->disk_util_vbox = NULL;
 +      gtk_widget_destroy(w);
 +}
 +
 +static GtkWidget *gfio_disk_util_get_vbox(struct gui_entry *ge)
 +{
 +      GtkWidget *vbox, *box, *scroll, *res_notebook;
 +
 +      if (ge->disk_util_vbox)
 +              return ge->disk_util_vbox;
 +
 +      scroll = get_scrolled_window(5);
 +      vbox = gtk_vbox_new(FALSE, 3);
 +      box = gtk_hbox_new(FALSE, 0);
 +      gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
 +
 +      gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
 +      res_notebook = get_results_window(ge);
 +
 +      gtk_notebook_append_page(GTK_NOTEBOOK(res_notebook), scroll, gtk_label_new("Disk utilization"));
 +      ge->disk_util_vbox = box;
 +      g_signal_connect(vbox, "destroy", G_CALLBACK(disk_util_destroy), ge);
 +
 +      return ge->disk_util_vbox;
 +}
 +
 +static int __gfio_disk_util_show(GtkWidget *res_notebook,
 +                               struct gfio_client *gc, struct cmd_du_pdu *p)
 +{
 +      GtkWidget *box, *frame, *entry, *vbox, *util_vbox;
 +      struct gui_entry *ge = gc->ge;
 +      double util;
 +      char tmp[16];
 +
 +      util_vbox = gfio_disk_util_get_vbox(ge);
 +
 +      vbox = gtk_vbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(util_vbox), vbox);
 +
 +      frame = gtk_frame_new((char *) p->dus.name);
 +      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
 +
 +      box = gtk_vbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), box);
 +
 +      frame = gtk_frame_new("Read");
 +      gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
 +      vbox = gtk_hbox_new(TRUE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), vbox);
 +      entry = new_info_entry_in_frame(vbox, "IOs");
 +      entry_set_int_value(entry, p->dus.ios[0]);
 +      entry = new_info_entry_in_frame(vbox, "Merges");
 +      entry_set_int_value(entry, p->dus.merges[0]);
 +      entry = new_info_entry_in_frame(vbox, "Sectors");
 +      entry_set_int_value(entry, p->dus.sectors[0]);
 +      entry = new_info_entry_in_frame(vbox, "Ticks");
 +      entry_set_int_value(entry, p->dus.ticks[0]);
 +
 +      frame = gtk_frame_new("Write");
 +      gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
 +      vbox = gtk_hbox_new(TRUE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), vbox);
 +      entry = new_info_entry_in_frame(vbox, "IOs");
 +      entry_set_int_value(entry, p->dus.ios[1]);
 +      entry = new_info_entry_in_frame(vbox, "Merges");
 +      entry_set_int_value(entry, p->dus.merges[1]);
 +      entry = new_info_entry_in_frame(vbox, "Sectors");
 +      entry_set_int_value(entry, p->dus.sectors[1]);
 +      entry = new_info_entry_in_frame(vbox, "Ticks");
 +      entry_set_int_value(entry, p->dus.ticks[1]);
 +
 +      frame = gtk_frame_new("Shared");
 +      gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
 +      vbox = gtk_hbox_new(TRUE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), vbox);
 +      entry = new_info_entry_in_frame(vbox, "IO ticks");
 +      entry_set_int_value(entry, p->dus.io_ticks);
 +      entry = new_info_entry_in_frame(vbox, "Time in queue");
 +      entry_set_int_value(entry, p->dus.time_in_queue);
 +
 +      util = 0.0;
 +      if (p->dus.msec)
 +              util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec;
 +      if (util > 100.0)
 +              util = 100.0;
 +
 +      sprintf(tmp, "%3.2f%%", util);
 +      entry = new_info_entry_in_frame(vbox, "Disk utilization");
 +      gtk_entry_set_text(GTK_ENTRY(entry), tmp);
 +
 +      gtk_widget_show_all(ge->results_window);
 +      return 0;
 +}
 +
 +static int gfio_disk_util_show(struct gfio_client *gc)
 +{
 +      struct gui_entry *ge = gc->ge;
 +      GtkWidget *res_notebook;
 +      int i;
 +
 +      if (!gc->nr_du)
 +              return 1;
 +
 +      res_notebook = get_results_window(ge);
 +
 +      for (i = 0; i < gc->nr_du; i++) {
 +              struct cmd_du_pdu *p = &gc->du[i];
 +
 +              __gfio_disk_util_show(res_notebook, gc, p);
 +      }
 +
 +      gtk_widget_show_all(ge->results_window);
 +      return 0;
 +}
 +
 +static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
 +      struct gfio_client *gc = client->client_data;
 +      struct gui_entry *ge = gc->ge;
 +      unsigned int nr = gc->nr_du;
 +
 +      gc->du = realloc(gc->du, (nr + 1) * sizeof(struct cmd_du_pdu));
 +      memcpy(&gc->du[nr], p, sizeof(*p));
 +      gc->nr_du++;
 +
 +      gdk_threads_enter();
 +      if (ge->results_window)
 +              __gfio_disk_util_show(ge->results_notebook, gc, p);
 +      else
 +              gfio_disk_util_show(gc);
 +      gdk_threads_leave();
 +}
 +
 +extern int sum_stat_clients;
 +extern struct thread_stat client_ts;
 +extern struct group_run_stats client_gs;
 +
 +static int sum_stat_nr;
 +
 +static void gfio_thread_status_op(struct fio_client *client,
 +                                struct fio_net_cmd *cmd)
 +{
 +      struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
 +
 +      gfio_display_ts(client, &p->ts, &p->rs);
 +
 +      if (sum_stat_clients == 1)
 +              return;
 +
 +      sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
 +      sum_group_stats(&client_gs, &p->rs);
 +
 +      client_ts.members++;
 +      client_ts.thread_number = p->ts.thread_number;
 +      client_ts.groupid = p->ts.groupid;
 +
 +      if (++sum_stat_nr == sum_stat_clients) {
 +              strcpy(client_ts.name, "All clients");
 +              gfio_display_ts(client, &client_ts, &client_gs);
 +      }
 +}
 +
 +static void gfio_group_stats_op(struct fio_client *client,
 +                              struct fio_net_cmd *cmd)
 +{
 +      /* We're ignoring group stats for now */
 +}
 +
 +static void gfio_update_thread_status(struct gui_entry *ge,
 +                                    char *status_message, double perc)
 +{
 +      static char message[100];
 +      const char *m = message;
 +
 +      strncpy(message, status_message, sizeof(message) - 1);
 +      gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
 +      gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
 +      gtk_widget_queue_draw(ge->ui->window);
 +}
 +
 +static void gfio_update_thread_status_all(struct gui *ui, char *status_message,
 +                                        double perc)
 +{
 +      static char message[100];
 +      const char *m = message;
 +
 +      strncpy(message, status_message, sizeof(message) - 1);
 +      gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
 +      gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
 +      gtk_widget_queue_draw(ui->window);
 +}
 +
 +/*
 + * Client specific ETA
 + */
 +static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
 +{
 +      struct gfio_client *gc = client->client_data;
 +      struct gui_entry *ge = gc->ge;
 +      static int eta_good;
 +      char eta_str[128];
 +      char output[256];
 +      char tmp[32];
 +      double perc = 0.0;
 +      int i2p = 0;
 +
 +      gdk_threads_enter();
 +
 +      eta_str[0] = '\0';
 +      output[0] = '\0';
 +
 +      if (je->eta_sec != INT_MAX && je->elapsed_sec) {
 +              perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
 +              eta_to_str(eta_str, je->eta_sec);
 +      }
 +
 +      sprintf(tmp, "%u", je->nr_running);
 +      gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
 +      sprintf(tmp, "%u", je->files_open);
 +      gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
 +
 +#if 0
 +      if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
 +      if (je->m_rate || je->t_rate) {
 +              char *tr, *mr;
 +
 +              mr = num2str(je->m_rate, 4, 0, i2p);
 +              tr = num2str(je->t_rate, 4, 0, i2p);
 +              gtk_entry_set_text(GTK_ENTRY(ge->eta);
 +              p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
 +              free(tr);
 +              free(mr);
 +      } else if (je->m_iops || je->t_iops)
 +              p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
 +
 +      gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
 +      gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
 +      gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
 +      gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
 +#endif
 +
 +      if (je->eta_sec != INT_MAX && je->nr_running) {
 +              char *iops_str[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, ge->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]);
 +              graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]);
 +              graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]);
 +              graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]);
 +
 +              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, ui->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]);
 +              graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]);
 +              graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]);
 +              graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]);
 +
 +              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(ui, output, perc);
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
 +      struct gfio_client *gc = client->client_data;
 +      struct gui_entry *ge = gc->ge;
 +      const char *os, *arch;
 +
 +      os = fio_get_os_string(probe->os);
 +      if (!os)
 +              os = "unknown";
 +
 +      arch = fio_get_arch_string(probe->arch);
 +      if (!arch)
 +              os = "unknown";
 +
 +      if (!client->name)
 +              client->name = strdup((char *) probe->hostname);
 +
 +      gc->client_cpus = le32_to_cpu(probe->cpus);
 +      gc->client_flags = le64_to_cpu(probe->flags);
 +
 +      gdk_threads_enter();
 +
 +      gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
 +      gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
 +      gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
 +      gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), (char *) probe->fio_version);
 +
 +      gfio_set_state(ge, GE_STATE_CONNECTED);
 +
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct gfio_client *gc = client->client_data;
 +
 +      gdk_threads_enter();
 +      gfio_set_state(gc->ge, GE_STATE_NEW);
 +      gdk_threads_leave();
 +}
 +
 +static struct thread_options *gfio_client_add_job(struct gfio_client *gc,
 +                      struct thread_options_pack *top)
 +{
 +      struct gfio_client_options *gco;
 +
 +      gco = calloc(1, sizeof(*gco));
 +      convert_thread_options_to_cpu(&gco->o, top);
 +      INIT_FLIST_HEAD(&gco->list);
 +      flist_add_tail(&gco->list, &gc->o_list);
 +      gc->o_list_nr = 1;
 +      return &gco->o;
 +}
 +
 +static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
 +      struct gfio_client *gc = client->client_data;
 +      struct gui_entry *ge = gc->ge;
 +      struct thread_options *o;
 +      char *c1, *c2, *c3, *c4;
 +      char tmp[80];
 +
 +      p->thread_number = le32_to_cpu(p->thread_number);
 +      p->groupid = le32_to_cpu(p->groupid);
 +      o = gfio_client_add_job(gc, &p->top);
 +
 +      gdk_threads_enter();
 +
 +      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ge->eta.names), (gchar *) o->name);
 +      gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
 +
 +      sprintf(tmp, "%s %s", o->odirect ? "direct" : "buffered", ddir_str(o->td_ddir));
 +      multitext_add_entry(&ge->eta.iotype, tmp);
 +
 +      c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
 +      c2 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
 +      c3 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
 +      c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
 +      sprintf(tmp, "%s-%s/%s-%s", c1, c2, c3, c4);
 +      free(c1);
 +      free(c2);
 +      free(c3);
 +      free(c4);
 +      multitext_add_entry(&ge->eta.bs, tmp);
 +
 +      multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
 +
 +      sprintf(tmp, "%u", o->iodepth);
 +      multitext_add_entry(&ge->eta.iodepth, tmp);
 +
 +      multitext_set_entry(&ge->eta.iotype, 0);
 +      multitext_set_entry(&ge->eta.bs, 0);
 +      multitext_set_entry(&ge->eta.ioengine, 0);
 +      multitext_set_entry(&ge->eta.iodepth, 0);
 +
 +      gfio_set_state(ge, GE_STATE_JOB_SENT);
 +
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_update_job_op(struct fio_client *client,
 +                             struct fio_net_cmd *cmd)
 +{
 +      uint32_t *pdu_error = (uint32_t *) cmd->payload;
 +      struct gfio_client *gc = client->client_data;
 +
 +      gc->update_job_status = le32_to_cpu(*pdu_error);
 +      gc->update_job_done = 1;
 +}
 +
 +static void gfio_client_timed_out(struct fio_client *client)
 +{
 +      struct gfio_client *gc = client->client_data;
 +      char buf[256];
 +
 +      gdk_threads_enter();
 +
 +      gfio_set_state(gc->ge, GE_STATE_NEW);
 +      clear_ge_ui_info(gc->ge);
 +
 +      sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
 +      gfio_report_info(gc->ge->ui, "Network timeout", buf);
 +
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct gfio_client *gc = client->client_data;
 +
 +      gdk_threads_enter();
 +
 +      gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
 +
 +      if (gc->err_entry)
 +              entry_set_int_value(gc->err_entry, client->error);
 +
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct gfio_client *gc = client->client_data;
 +
 +      gdk_threads_enter();
 +      gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
 +{
 +      struct gfio_client *gc = client->client_data;
 +
 +      gdk_threads_enter();
 +      gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu)
 +{
 +      printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples);
 +      free(pdu);
 +}
 +
 +static void gfio_add_total_depths_tree(GtkListStore *model,
 +                                     struct thread_stat *ts, unsigned int len)
 +{
 +      double io_u_dist[FIO_IO_U_MAP_NR];
 +      GtkTreeIter iter;
 +      /* Bits 1-6, and 8 */
 +      const int add_mask = 0x17e;
 +      int i, j;
 +
++      stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
 +
 +      gtk_list_store_append(model, &iter);
 +
 +      gtk_list_store_set(model, &iter, 0, "Total", -1);
 +
 +      for (i = 1, j = 0; i < len; i++) {
 +              char fbuf[32];
 +
 +              if (!(add_mask & (1UL << (i - 1))))
 +                      sprintf(fbuf, "0.0%%");
 +              else {
 +                      sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
 +                      j++;
 +              }
 +
 +              gtk_list_store_set(model, &iter, i, fbuf, -1);
 +      }
 +
 +}
 +
 +static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts,
 +                               struct group_run_stats *rs)
 +{
 +      unsigned int nr = gc->nr_results;
 +
 +      gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results));
 +      memcpy(&gc->results[nr].ts, ts, sizeof(*ts));
 +      memcpy(&gc->results[nr].gs, rs, sizeof(*rs));
 +      gc->nr_results++;
 +}
 +
 +static void gfio_add_sc_depths_tree(GtkListStore *model,
 +                                  struct thread_stat *ts, unsigned int len,
 +                                  int submit)
 +{
 +      double io_u_dist[FIO_IO_U_MAP_NR];
 +      GtkTreeIter iter;
 +      /* Bits 0, and 3-8 */
 +      const int add_mask = 0x1f9;
 +      int i, j;
 +
 +      if (submit)
 +              stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
 +      else
 +              stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
 +
 +      gtk_list_store_append(model, &iter);
 +
 +      gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
 +
 +      for (i = 1, j = 0; i < len; i++) {
 +              char fbuf[32];
 +
 +              if (!(add_mask & (1UL << (i - 1))))
 +                      sprintf(fbuf, "0.0%%");
 +              else {
 +                      sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
 +                      j++;
 +              }
 +
 +              gtk_list_store_set(model, &iter, i, fbuf, -1);
 +      }
 +
 +}
 +
 +static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
 +{
 +      GtkWidget *frame, *box, *tree_view = NULL;
 +      GtkTreeSelection *selection;
 +      GtkListStore *model;
 +      int i;
 +      const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
 +      const int nr_labels = ARRAY_SIZE(labels);
 +      GType types[nr_labels];
 +
 +      frame = gtk_frame_new("IO depths");
 +      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
 +
 +      box = gtk_hbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), box);
 +
 +      for (i = 0; i < nr_labels; i++)
 +              types[i] = G_TYPE_STRING;
 +
 +      model = gtk_list_store_newv(nr_labels, types);
 +
 +      tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
 +      gtk_widget_set_can_focus(tree_view, FALSE);
 +
 +      g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
 +              "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
 +
 +      selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
 +      gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
 +
 +      for (i = 0; i < nr_labels; i++)
 +              tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
 +
 +      gfio_add_total_depths_tree(model, ts, nr_labels);
 +      gfio_add_sc_depths_tree(model, ts, nr_labels, 1);
 +      gfio_add_sc_depths_tree(model, ts, nr_labels, 0);
 +
 +      gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, TRUE, 3);
 +}
 +
 +static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
 +{
 +      GtkWidget *box, *frame, *entry;
 +      double usr_cpu, sys_cpu;
 +      unsigned long runtime;
 +      char tmp[32];
 +
 +      runtime = ts->total_run_time;
 +      if (runtime) {
 +              double runt = (double) runtime;
 +
 +              usr_cpu = (double) ts->usr_time * 100 / runt;
 +              sys_cpu = (double) ts->sys_time * 100 / runt;
 +      } else {
 +              usr_cpu = 0;
 +              sys_cpu = 0;
 +      }
 +
 +      frame = gtk_frame_new("OS resources");
 +      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
 +
 +      box = gtk_hbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), box);
 +
 +      entry = new_info_entry_in_frame(box, "User CPU");
 +      sprintf(tmp, "%3.2f%%", usr_cpu);
 +      gtk_entry_set_text(GTK_ENTRY(entry), tmp);
 +      entry = new_info_entry_in_frame(box, "System CPU");
 +      sprintf(tmp, "%3.2f%%", sys_cpu);
 +      gtk_entry_set_text(GTK_ENTRY(entry), tmp);
 +      entry = new_info_entry_in_frame(box, "Context switches");
 +      entry_set_int_value(entry, ts->ctx);
 +      entry = new_info_entry_in_frame(box, "Major faults");
 +      entry_set_int_value(entry, ts->majf);
 +      entry = new_info_entry_in_frame(box, "Minor faults");
 +      entry_set_int_value(entry, ts->minf);
 +}
 +
 +static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels,
 +                                        int num)
 +{
 +      GtkWidget *tree_view;
 +      GtkTreeSelection *selection;
 +      GtkListStore *model;
 +      GtkTreeIter iter;
 +      GType *types;
 +      int i;
 +
 +      types = malloc(num * sizeof(GType));
 +
 +      for (i = 0; i < num; i++)
 +              types[i] = G_TYPE_STRING;
 +
 +      model = gtk_list_store_newv(num, types);
 +      free(types);
 +      types = NULL;
 +
 +      tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
 +      gtk_widget_set_can_focus(tree_view, FALSE);
 +
 +      g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
 +              "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
 +
 +      selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
 +      gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
 +
 +      for (i = 0; i < num; i++)
 +              tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
 +
 +      gtk_list_store_append(model, &iter);
 +
 +      for (i = 0; i < num; i++) {
 +              char fbuf[32];
 +
 +              if (lat[i] <= 0.0)
 +                      sprintf(fbuf, "0.00");
 +              else
 +                      sprintf(fbuf, "%3.2f%%", lat[i]);
 +
 +              gtk_list_store_set(model, &iter, i, fbuf, -1);
 +      }
 +
 +      return tree_view;
 +}
 +
 +static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
 +                                          const char **labels,
 +                                          unsigned int len,
 +                                          double xdim, double ydim)
 +{
 +      struct graph *g;
 +      int i;
 +
 +      g = graph_new(xdim, ydim, gfio_graph_font);
 +      graph_title(g, title);
 +      graph_x_title(g, "Buckets");
 +      graph_y_title(g, "Percent");
 +
 +      for (i = 0; i < len; i++) {
 +              graph_label_t l;
 +
 +              l = graph_add_label(g, labels[i]);
 +              graph_add_data(g, l, lat[i]);
 +      }
 +
 +      return g;
 +}
 +
 +static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
 +{
 +      struct graph *g = p;
 +      cairo_t *cr;
 +
 +      cr = gdk_cairo_create(gtk_widget_get_window(w));
 +#if 0
 +      if (graph_has_tooltips(g)) {
 +              g_object_set(w, "has-tooltip", TRUE, NULL);
 +              g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g);
 +      }
 +#endif
 +      cairo_set_source_rgb(cr, 0, 0, 0);
 +      bar_graph_draw(g, cr);
 +      cairo_destroy(cr);
 +
 +      return FALSE;
 +}
 +
 +static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event,
 +                                     gpointer data)
 +{
 +      guint width = gtk_widget_get_allocated_width(w);
 +      guint height = gtk_widget_get_allocated_height(w);
 +      struct graph *g = data;
 +
 +      graph_set_size(g, width, height);
 +      graph_set_size(g, width, height);
 +      graph_set_position(g, 0, 0);
 +      return TRUE;
 +}
 +
 +static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox,
 +                                    struct thread_stat *ts)
 +{
 +      double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR];
 +      const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u",
 +                               "250u", "500u", "750u", "1m", "2m",
 +                               "4m", "10m", "20m", "50m", "100m",
 +                               "250m", "500m", "750m", "1s", "2s", ">= 2s" };
 +      int start, end, i;
 +      const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR;
 +      GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area;
 +      struct gui_entry *ge = gc->ge;
 +
 +      stat_calc_lat_u(ts, io_u_lat);
 +      stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]);
 +
 +      /*
 +       * Found out which first bucket has entries, and which last bucket
 +       */
 +      start = end = -1U;
 +      for (i = 0; i < total; i++) {
 +              if (io_u_lat[i] == 0.00)
 +                      continue;
 +
 +              if (start == -1U)
 +                      start = i;
 +              end = i;
 +      }
 +
 +      /*
 +       * No entries...
 +       */
 +      if (start == -1U)
 +              return;
 +
 +      tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1);
 +      ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0);
 +
 +      frame = gtk_frame_new("Latency buckets");
 +      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
 +
 +      completion_vbox = gtk_vbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
 +      hbox = gtk_hbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
 +
 +      drawing_area = gtk_drawing_area_new();
 +      gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
 +      gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
 +      gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
 +      g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph);
 +      g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph);
 +
 +      gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3);
 +}
 +
 +static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
 +                        unsigned long max, double mean, double dev)
 +{
 +      const char *base = "(usec)";
 +      GtkWidget *hbox, *label, *frame;
 +      char *minp, *maxp;
 +      char tmp[64];
 +
 +      if (!usec_to_msec(&min, &max, &mean, &dev))
 +              base = "(msec)";
 +
 +      minp = num2str(min, 6, 1, 0);
 +      maxp = num2str(max, 6, 1, 0);
 +
 +      sprintf(tmp, "%s %s", name, base);
 +      frame = gtk_frame_new(tmp);
 +      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
 +
 +      hbox = gtk_hbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), hbox);
 +
 +      label = new_info_label_in_frame(hbox, "Minimum");
 +      gtk_label_set_text(GTK_LABEL(label), minp);
 +      label = new_info_label_in_frame(hbox, "Maximum");
 +      gtk_label_set_text(GTK_LABEL(label), maxp);
 +      label = new_info_label_in_frame(hbox, "Average");
 +      sprintf(tmp, "%5.02f", mean);
 +      gtk_label_set_text(GTK_LABEL(label), tmp);
 +      label = new_info_label_in_frame(hbox, "Standard deviation");
 +      sprintf(tmp, "%5.02f", dev);
 +      gtk_label_set_text(GTK_LABEL(label), tmp);
 +
 +      free(minp);
 +      free(maxp);
 +}
 +
 +static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
 +                                             fio_fp64_t *plist,
 +                                             unsigned int len,
 +                                             const char *base,
 +                                             unsigned int scale)
 +{
 +      GType types[FIO_IO_U_LIST_MAX_LEN];
 +      GtkWidget *tree_view;
 +      GtkTreeSelection *selection;
 +      GtkListStore *model;
 +      GtkTreeIter iter;
 +      int i;
 +
 +      for (i = 0; i < len; i++)
 +              types[i] = G_TYPE_INT;
 +
 +      model = gtk_list_store_newv(len, types);
 +
 +      tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
 +      gtk_widget_set_can_focus(tree_view, FALSE);
 +
 +      g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
 +              "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
 +
 +      selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
 +      gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
 +
 +      for (i = 0; i < len; i++) {
 +              char fbuf[8];
 +
 +              sprintf(fbuf, "%2.2f%%", plist[i].u.f);
 +              tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
 +      }
 +
 +      gtk_list_store_append(model, &iter);
 +
 +      for (i = 0; i < len; i++) {
 +              if (scale)
 +                      ovals[i] = (ovals[i] + 999) / 1000;
 +              gtk_list_store_set(model, &iter, i, ovals[i], -1);
 +      }
 +
 +      return tree_view;
 +}
 +
 +static struct graph *setup_clat_graph(char *title, unsigned int *ovals,
 +                                    fio_fp64_t *plist,
 +                                    unsigned int len,
 +                                    double xdim, double ydim)
 +{
 +      struct graph *g;
 +      int i;
 +
 +      g = graph_new(xdim, ydim, gfio_graph_font);
 +      graph_title(g, title);
 +      graph_x_title(g, "Percentile");
 +      graph_y_title(g, "Time");
 +
 +      for (i = 0; i < len; i++) {
 +              graph_label_t l;
 +              char fbuf[8];
 +
 +              sprintf(fbuf, "%2.2f%%", plist[i].u.f);
 +              l = graph_add_label(g, fbuf);
 +              graph_add_data(g, l, (double) ovals[i]);
 +      }
 +
 +      return g;
 +}
 +
 +static void gfio_show_clat_percentiles(struct gfio_client *gc,
 +                                     GtkWidget *vbox, struct thread_stat *ts,
 +                                     int ddir)
 +{
 +      unsigned int *io_u_plat = ts->io_u_plat[ddir];
 +      unsigned long nr = ts->clat_stat[ddir].samples;
 +      fio_fp64_t *plist = ts->percentile_list;
 +      unsigned int *ovals, len, minv, maxv, scale_down;
 +      const char *base;
 +      GtkWidget *tree_view, *frame, *hbox, *drawing_area, *completion_vbox;
 +      struct gui_entry *ge = gc->ge;
 +      char tmp[64];
 +
 +      len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
 +      if (!len)
 +              goto out;
 +
 +      /*
 +       * We default to usecs, but if the value range is such that we
 +       * should scale down to msecs, do that.
 +       */
 +      if (minv > 2000 && maxv > 99999) {
 +              scale_down = 1;
 +              base = "msec";
 +      } else {
 +              scale_down = 0;
 +              base = "usec";
 +      }
 +
 +      sprintf(tmp, "Completion percentiles (%s)", base);
 +      tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
 +      ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0);
 +
 +      frame = gtk_frame_new(tmp);
 +      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
 +
 +      completion_vbox = gtk_vbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
 +      hbox = gtk_hbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
 +      drawing_area = gtk_drawing_area_new();
 +      gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
 +      gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
 +      gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
 +      g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->clat_graph);
 +      g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->clat_graph);
 +
 +      gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3);
 +out:
 +      if (ovals)
 +              free(ovals);
 +}
 +
 +#define GFIO_CLAT     1
 +#define GFIO_SLAT     2
 +#define GFIO_LAT      4
 +
 +static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
 +                                struct group_run_stats *rs,
 +                                struct thread_stat *ts, int ddir)
 +{
 +      const char *ddir_label[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, TRUE, TRUE, 5);
 +
 +      main_vbox = gtk_vbox_new(FALSE, 3);
 +      gtk_container_add(GTK_CONTAINER(frame), main_vbox);
 +
 +      box = gtk_hbox_new(FALSE, 3);
 +      gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
 +
 +      label = new_info_label_in_frame(box, "IO");
 +      gtk_label_set_text(GTK_LABEL(label), io_p);
 +      label = new_info_label_in_frame(box, "Bandwidth");
 +      gtk_label_set_text(GTK_LABEL(label), bw_p);
 +      label = new_info_label_in_frame(box, "IOPS");
 +      gtk_label_set_text(GTK_LABEL(label), iops_p);
 +      label = new_info_label_in_frame(box, "Runtime (msec)");
 +      label_set_int_value(label, ts->runtime[ddir]);
 +
 +      if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
 +              double p_of_agg = 100.0;
 +              const char *bw_str = "KB";
 +              char tmp[32];
 +
 +              if (rs->agg[ddir]) {
 +                      p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
 +                      if (p_of_agg > 100.0)
 +                              p_of_agg = 100.0;
 +              }
 +
 +              if (mean[0] > 999999.9) {
 +                      min[0] /= 1000.0;
 +                      max[0] /= 1000.0;
 +                      mean[0] /= 1000.0;
 +                      dev[0] /= 1000.0;
 +                      bw_str = "MB";
 +              }
 +
 +              sprintf(tmp, "Bandwidth (%s)", bw_str);
 +              frame = gtk_frame_new(tmp);
 +              gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
 +
 +              box = gtk_hbox_new(FALSE, 3);
 +              gtk_container_add(GTK_CONTAINER(frame), box);
 +
 +              label = new_info_label_in_frame(box, "Minimum");
 +              label_set_int_value(label, min[0]);
 +              label = new_info_label_in_frame(box, "Maximum");
 +              label_set_int_value(label, max[0]);
 +              label = new_info_label_in_frame(box, "Percentage of jobs");
 +              sprintf(tmp, "%3.2f%%", p_of_agg);
 +              gtk_label_set_text(GTK_LABEL(label), tmp);
 +              label = new_info_label_in_frame(box, "Average");
 +              sprintf(tmp, "%5.02f", mean[0]);
 +              gtk_label_set_text(GTK_LABEL(label), tmp);
 +              label = new_info_label_in_frame(box, "Standard deviation");
 +              sprintf(tmp, "%5.02f", dev[0]);
 +              gtk_label_set_text(GTK_LABEL(label), tmp);
 +      }
 +
 +      if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
 +              flags |= GFIO_SLAT;
 +      if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
 +              flags |= GFIO_CLAT;
 +      if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
 +              flags |= GFIO_LAT;
 +
 +      if (flags) {
 +              frame = gtk_frame_new("Latency");
 +              gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
 +
 +              vbox = gtk_vbox_new(FALSE, 3);
 +              gtk_container_add(GTK_CONTAINER(frame), vbox);
 +
 +              if (flags & GFIO_SLAT)
 +                      gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
 +              if (flags & GFIO_CLAT)
 +                      gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
 +              if (flags & GFIO_LAT)
 +                      gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
 +      }
 +
 +      if (ts->clat_percentiles)
 +              gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
 +
 +      free(io_p);
 +      free(bw_p);
 +      free(iops_p);
 +}
 +
 +static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc,
 +                                     struct thread_stat *ts,
 +                                     struct group_run_stats *rs)
 +{
 +      GtkWidget *box, *vbox, *entry, *scroll;
 +
 +      scroll = gtk_scrolled_window_new(NULL, NULL);
 +      gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
 +      gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 +
 +      vbox = gtk_vbox_new(FALSE, 3);
 +
 +      box = gtk_hbox_new(FALSE, 0);
 +      gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
 +
 +      gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
 +
 +      gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name));
 +
 +      entry = new_info_entry_in_frame(box, "Name");
 +      gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
 +      if (strlen(ts->description)) {
 +              entry = new_info_entry_in_frame(box, "Description");
 +              gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
 +      }
 +      entry = new_info_entry_in_frame(box, "Group ID");
 +      entry_set_int_value(entry, ts->groupid);
 +      entry = new_info_entry_in_frame(box, "Jobs");
 +      entry_set_int_value(entry, ts->members);
 +      gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
 +      entry_set_int_value(entry, ts->error);
 +      entry = new_info_entry_in_frame(box, "PID");
 +      entry_set_int_value(entry, ts->pid);
 +
 +      if (ts->io_bytes[DDIR_READ])
 +              gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_READ);
 +      if (ts->io_bytes[DDIR_WRITE])
 +              gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_WRITE);
 +
 +      gfio_show_latency_buckets(gc, vbox, ts);
 +      gfio_show_cpu_usage(vbox, ts);
 +      gfio_show_io_depths(vbox, ts);
 +}
 +
 +void gfio_display_end_results(struct gfio_client *gc)
 +{
 +      struct gui_entry *ge = gc->ge;
 +      GtkWidget *res_notebook;
 +      int i;
 +
 +      res_notebook = get_results_window(ge);
 +
 +      for (i = 0; i < gc->nr_results; i++) {
 +              struct end_results *e = &gc->results[i];
 +
 +              __gfio_display_end_results(res_notebook, gc, &e->ts, &e->gs);
 +      }
 +
 +      if (gfio_disk_util_show(gc))
 +              gtk_widget_show_all(ge->results_window);
 +}
 +
 +static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
 +                          struct group_run_stats *rs)
 +{
 +      struct gfio_client *gc = client->client_data;
 +      struct gui_entry *ge = gc->ge;
 +
 +      gfio_add_end_results(gc, ts, rs);
 +
 +      gdk_threads_enter();
 +      if (ge->results_window)
 +              __gfio_display_end_results(ge->results_notebook, gc, ts, rs);
 +      else
 +              gfio_display_end_results(gc);
 +      gdk_threads_leave();
 +}
 +
 +static void gfio_client_removed(struct fio_client *client)
 +{
 +      struct gfio_client *gc = client->client_data;
 +
 +      assert(gc->client == client);
 +      fio_put_client(gc->client);
 +      gc->client = NULL;
 +}
 +
 +struct client_ops gfio_client_ops = {
 +      .text                   = gfio_text_op,
 +      .disk_util              = gfio_disk_util_op,
 +      .thread_status          = gfio_thread_status_op,
 +      .group_stats            = gfio_group_stats_op,
 +      .jobs_eta               = gfio_update_client_eta,
 +      .eta                    = gfio_update_all_eta,
 +      .probe                  = gfio_probe_op,
 +      .quit                   = gfio_quit_op,
 +      .add_job                = gfio_add_job_op,
 +      .update_job             = gfio_update_job_op,
 +      .timed_out              = gfio_client_timed_out,
 +      .stop                   = gfio_client_stop,
 +      .start                  = gfio_client_start,
 +      .job_start              = gfio_client_job_start,
 +      .iolog                  = gfio_client_iolog,
 +      .removed                = gfio_client_removed,
 +      .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
 +      .stay_connected         = 1,
 +      .client_type            = FIO_CLIENT_TYPE_GUI,
 +};
diff --cc init.c
--- 1/init.c
--- 2/init.c
+++ b/init.c
@@@ -36,8 -36,9 +36,8 @@@ static struct thread_data def_thread
  struct thread_data *threads = NULL;
  
  int exitall_on_terminate = 0;
- int terse_output = 0;
+ int output_format = FIO_OUTPUT_NORMAL;
  int eta_print;
 -unsigned long long mlock_size = 0;
  FILE *f_out = NULL;
  FILE *f_err = NULL;
  char **job_sections = NULL;
@@@ -853,24 -875,28 +877,26 @@@ static int add_job(struct thread_data *
        if (!td->o.name)
                td->o.name = strdup(jobname);
  
-       if (!terse_output) {
+       if (output_format == FIO_OUTPUT_NORMAL) {
                if (!job_add_num) {
 -                      if (!strcmp(td->io_ops->name, "cpuio")) {
 -                              log_info("%s: ioengine=cpu, cpuload=%u,"
 -                                       " cpucycle=%u\n", td->o.name,
 -                                                      td->o.cpuload,
 -                                                      td->o.cpucycle);
 -                      } else {
 +                      if (is_backend && !recursed)
 +                              fio_server_send_add_job(td);
 +
 +                      if (!(td->io_ops->flags & FIO_NOIO)) {
-                               char *c1, *c2, *c3, *c4;
+                               char *c1, *c2, *c3, *c4, *c5, *c6;
  
 -                              c1 = to_kmg(td->o.min_bs[DDIR_READ]);
 -                              c2 = to_kmg(td->o.max_bs[DDIR_READ]);
 -                              c3 = to_kmg(td->o.min_bs[DDIR_WRITE]);
 -                              c4 = to_kmg(td->o.max_bs[DDIR_WRITE]);
 -                              c5 = to_kmg(td->o.min_bs[DDIR_TRIM]);
 -                              c6 = to_kmg(td->o.max_bs[DDIR_TRIM]);
 +                              c1 = fio_uint_to_kmg(td->o.min_bs[DDIR_READ]);
 +                              c2 = fio_uint_to_kmg(td->o.max_bs[DDIR_READ]);
 +                              c3 = fio_uint_to_kmg(td->o.min_bs[DDIR_WRITE]);
 +                              c4 = fio_uint_to_kmg(td->o.max_bs[DDIR_WRITE]);
++                              c5 = fio_uint_to_kmg(td->o.min_bs[DDIR_TRIM]);
++                              c6 = fio_uint_to_kmg(td->o.max_bs[DDIR_TRIM]);
  
-                               log_info("%s: (g=%d): rw=%s, bs=%s-%s/%s-%s,"
+                               log_info("%s: (g=%d): rw=%s, bs=%s-%s/%s-%s/%s-%s,"
                                         " ioengine=%s, iodepth=%u\n",
                                                td->o.name, td->groupid,
 -                                              ddir_str[td->o.td_ddir],
 +                                              ddir_str(td->o.td_ddir),
-                                               c1, c2, c3, c4,
+                                               c1, c2, c3, c4, c5, c6,
                                                td->io_ops->name,
                                                td->o.iodepth);
  
diff --cc io_ddir.h
+++ b/io_ddir.h
@@@ -36,15 -40,12 +40,21 @@@ static inline int ddir_sync(enum fio_dd
  
  static inline int ddir_rw(enum fio_ddir ddir)
  {
-       return ddir == DDIR_READ || ddir == DDIR_WRITE;
+       return ddir == DDIR_READ || ddir == DDIR_WRITE || ddir == DDIR_TRIM;
  }
  
-                                  "randread", "randwrite", "randrw" };
 +static inline const char *ddir_str(enum fio_ddir ddir)
 +{
 +      const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
++                                 "randread", "randwrite", "randrw",
++                                 "trim", NULL, NULL, NULL, "randtrim" };
 +
 +      return ddir_str[ddir];
 +}
 +
+ #define ddir_trim(ddir) ((ddir) == DDIR_TRIM)
+ #define ddir_rw_sum(arr)      \
+       ((arr)[DDIR_READ] + (arr)[DDIR_WRITE] + (arr)[DDIR_TRIM])
  #endif
diff --cc io_u.c
Simple merge
diff --cc ioengines.c
Simple merge
diff --cc iolog.h
Simple merge
diff --cc libfio.c
Simple merge
diff --cc options.c
+++ b/options.c
@@@ -175,21 -176,38 +175,38 @@@ static int str_bssplit_cb(void *data, c
  
        odir = strchr(str, ',');
        if (odir) {
-               ret = bssplit_ddir(&td->o, DDIR_WRITE, odir + 1);
+               ddir = strchr(odir + 1, ',');
+               if (ddir) {
 -                      ret = bssplit_ddir(td, DDIR_TRIM, ddir + 1);
++                      ret = bssplit_ddir(&td->o, DDIR_TRIM, ddir + 1);
+                       if (!ret)
+                               *ddir = '\0';
+               } else {
+                       char *op;
+                       op = strdup(odir + 1);
 -                      ret = bssplit_ddir(td, DDIR_TRIM, op);
++                      ret = bssplit_ddir(&td->o, DDIR_TRIM, op);
+                       free(op);
+               }
 -              if (!ret) 
 -                      ret = bssplit_ddir(td, DDIR_WRITE, odir + 1);
++              if (!ret)
++                      ret = bssplit_ddir(&td->o, DDIR_WRITE, odir + 1);
                if (!ret) {
                        *odir = '\0';
 -                      ret = bssplit_ddir(td, DDIR_READ, str);
 +                      ret = bssplit_ddir(&td->o, DDIR_READ, str);
                }
        } else {
                char *op;
  
                op = strdup(str);
 -              ret = bssplit_ddir(td, DDIR_WRITE, op);
++              ret = bssplit_ddir(&td->o, DDIR_WRITE, op);
+               free(op);
  
 -                      ret = bssplit_ddir(td, DDIR_TRIM, op);
+               if (!ret) {
+                       op = strdup(str);
 -              ret = bssplit_ddir(td, DDIR_READ, str);
++                      ret = bssplit_ddir(&td->o, DDIR_TRIM, op);
+                       free(op);
+               }
-               if (!ret)
-                       ret = bssplit_ddir(&td->o, DDIR_WRITE, op);
-               free(op);
 +              ret = bssplit_ddir(&td->o, DDIR_READ, str);
        }
  
        free(p);
@@@ -2113,48 -1839,37 +2157,52 @@@ struct fio_option fio_options[FIO_MAX_O
        },
        {
                .name   = "rate",
 +              .lname  = "I/O rate",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(rate[0]),
-               .off2   = td_var_offset(rate[1]),
+               .off1   = td_var_offset(rate[DDIR_READ]),
+               .off2   = td_var_offset(rate[DDIR_WRITE]),
+               .off3   = td_var_offset(rate[DDIR_TRIM]),
                .help   = "Set bandwidth rate",
 +              .category = FIO_OPT_C_IO,
 +              .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "ratemin",
 +              .lname  = "I/O min rate",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(ratemin[0]),
-               .off2   = td_var_offset(ratemin[1]),
+               .off1   = td_var_offset(ratemin[DDIR_READ]),
+               .off2   = td_var_offset(ratemin[DDIR_WRITE]),
+               .off3   = td_var_offset(ratemin[DDIR_TRIM]),
                .help   = "Job must meet this rate or it will be shutdown",
                .parent = "rate",
 +              .hide   = 1,
 +              .category = FIO_OPT_C_IO,
 +              .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "rate_iops",
 +              .lname  = "I/O rate IOPS",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(rate_iops[0]),
-               .off2   = td_var_offset(rate_iops[1]),
+               .off1   = td_var_offset(rate_iops[DDIR_READ]),
+               .off2   = td_var_offset(rate_iops[DDIR_WRITE]),
+               .off3   = td_var_offset(rate_iops[DDIR_TRIM]),
                .help   = "Limit IO used to this number of IO operations/sec",
 +              .hide   = 1,
 +              .category = FIO_OPT_C_IO,
 +              .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "rate_iops_min",
 +              .lname  = "I/O min rate IOPS",
                .type   = FIO_OPT_INT,
-               .off1   = td_var_offset(rate_iops_min[0]),
-               .off2   = td_var_offset(rate_iops_min[1]),
+               .off1   = td_var_offset(rate_iops_min[DDIR_READ]),
+               .off2   = td_var_offset(rate_iops_min[DDIR_WRITE]),
+               .off3   = td_var_offset(rate_iops_min[DDIR_TRIM]),
                .help   = "Job must meet this rate or it will be shut down",
                .parent = "rate_iops",
 +              .hide   = 1,
 +              .category = FIO_OPT_C_IO,
 +              .group  = FIO_OPT_G_RATE,
        },
        {
                .name   = "ratecycle",
diff --cc os/os-linux.h
Simple merge
diff --cc parse.c
Simple merge
diff --cc parse.h
Simple merge
diff --cc server.h
+++ b/server.h
@@@ -157,6 -117,14 +157,12 @@@ struct group_run_stats
  extern void fio_server_send_ts(struct thread_stat *, struct group_run_stats *);
  extern void fio_server_send_gs(struct group_run_stats *);
  extern void fio_server_send_du(void);
 -extern int fio_handle_clients(void);
 -extern int fio_client_add(const char *, void **);
+ extern void fio_server_idle_loop(void);
+ extern int fio_clients_connect(void);
+ extern int fio_clients_send_ini(const char *);
+ extern void fio_client_add_cmd_option(void *, const char *);
+ extern void fio_client_add_ini_file(void *, const char *);
  
  extern int fio_recv_data(int sk, void *p, unsigned int len);
  extern int fio_send_data(int sk, const void *p, unsigned int len);
diff --cc stat.c
--- 1/stat.c
--- 2/stat.c
+++ b/stat.c
@@@ -306,7 -314,7 +307,7 @@@ void stat_calc_dist(unsigned int *map, 
  static void stat_calc_lat(struct thread_stat *ts, double *dst,
                          unsigned int *src, int nr)
  {
--      unsigned long total = ts_total_io_u(ts);
++      unsigned long total = ddir_rw_sum(ts->total_io_u);
        int i;
  
        /*
@@@ -514,8 -554,12 +515,10 @@@ void show_thread_status(struct thread_s
                show_ddir_status(rs, ts, DDIR_READ);
        if (ts->io_bytes[DDIR_WRITE])
                show_ddir_status(rs, ts, DDIR_WRITE);
+       if (ts->io_bytes[DDIR_TRIM])
+               show_ddir_status(rs, ts, DDIR_TRIM);
  
 -      stat_calc_lat_u(ts, io_u_lat_u);
 -      stat_calc_lat_m(ts, io_u_lat_m);
 -      show_latencies(io_u_lat_u, io_u_lat_m);
 +      show_latencies(ts);
  
        runtime = ts->total_run_time;
        if (runtime) {
        log_info("  cpu          : usr=%3.2f%%, sys=%3.2f%%, ctx=%lu, majf=%lu,"
                 " minf=%lu\n", usr_cpu, sys_cpu, ts->ctx, ts->majf, ts->minf);
  
--      stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
++      stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        log_info("  IO depths    : 1=%3.1f%%, 2=%3.1f%%, 4=%3.1f%%, 8=%3.1f%%,"
                 " 16=%3.1f%%, 32=%3.1f%%, >=64=%3.1f%%\n", io_u_dist[0],
                                        io_u_dist[1], io_u_dist[2],
@@@ -635,8 -679,111 +638,111 @@@ static void show_ddir_status_terse(stru
                log_info(";%lu;%lu;%f%%;%f;%f", 0UL, 0UL, 0.0, 0.0, 0.0);
  }
  
+ static void add_ddir_status_json(struct thread_stat *ts,
+               struct group_run_stats *rs, int ddir, struct json_object *parent)
+ {
+       unsigned long min, max;
+       unsigned long long bw, iops;
+       unsigned int *ovals = NULL;
+       double mean, dev;
+       unsigned int len, minv, maxv;
+       int i;
+       const char *ddirname[] = {"read", "write", "trim"};
+       struct json_object *dir_object, *tmp_object, *percentile_object;
+       char buf[120];
+       double p_of_agg = 100.0;
+       assert(ddir_rw(ddir));
+       dir_object = json_create_object();
+       json_object_add_value_object(parent, ddirname[ddir], dir_object);
+       iops = bw = 0;
+       if (ts->runtime[ddir]) {
+               uint64_t runt = ts->runtime[ddir];
+               bw = ((1000 * ts->io_bytes[ddir]) / runt) / 1024;
+               iops = (1000 * (uint64_t) ts->total_io_u[ddir]) / runt;
+       }
+       json_object_add_value_int(dir_object, "io_bytes", ts->io_bytes[ddir] >> 10);
+       json_object_add_value_int(dir_object, "bw", bw);
+       json_object_add_value_int(dir_object, "iops", iops);
+       json_object_add_value_int(dir_object, "runtime", ts->runtime[ddir]);
+       if (!calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev)) {
+               min = max = 0;
+               mean = dev = 0.0;
+       }
+       tmp_object = json_create_object();
+       json_object_add_value_object(dir_object, "slat", tmp_object);
+       json_object_add_value_int(tmp_object, "min", min);
+       json_object_add_value_int(tmp_object, "max", max);
+       json_object_add_value_float(tmp_object, "mean", mean);
+       json_object_add_value_float(tmp_object, "stddev", dev);
+       if (!calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev)) {
+               min = max = 0;
+               mean = dev = 0.0;
+       }
+       tmp_object = json_create_object();
+       json_object_add_value_object(dir_object, "clat", tmp_object);
+       json_object_add_value_int(tmp_object, "min", min);
+       json_object_add_value_int(tmp_object, "max", max);
+       json_object_add_value_float(tmp_object, "mean", mean);
+       json_object_add_value_float(tmp_object, "stddev", dev);
+       if (ts->clat_percentiles) {
+               len = calc_clat_percentiles(ts->io_u_plat[ddir],
+                                       ts->clat_stat[ddir].samples,
+                                       ts->percentile_list, &ovals, &maxv,
+                                       &minv);
+       } else
+               len = 0;
+       percentile_object = json_create_object();
+       json_object_add_value_object(tmp_object, "percentile", percentile_object);
+       for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
+               if (i >= len) {
+                       json_object_add_value_int(percentile_object, "0.00", 0);
+                       continue;
+               }
+               snprintf(buf, sizeof(buf) - 1, "%2.2f", ts->percentile_list[i].u.f);
+               json_object_add_value_int(percentile_object, (const char *)buf, ovals[i]);
+       }
+       if (!calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) {
+               min = max = 0;
+               mean = dev = 0.0;
+       }
+       tmp_object = json_create_object();
+       json_object_add_value_object(dir_object, "lat", tmp_object);
+       json_object_add_value_int(tmp_object, "min", min);
+       json_object_add_value_int(tmp_object, "max", max);
+       json_object_add_value_float(tmp_object, "mean", mean);
+       json_object_add_value_float(tmp_object, "stddev", dev);
+       if (ovals)
+               free(ovals);
+       if (!calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
+               if (rs->agg[ddir]) {
+                       p_of_agg = mean * 100 / (double) rs->agg[ddir];
+                       if (p_of_agg > 100.0)
+                               p_of_agg = 100.0;
+               }
+       } else {
+               min = max = 0;
+               p_of_agg = mean = dev = 0.0;
+       }
+       json_object_add_value_int(dir_object, "bw_min", min);
+       json_object_add_value_int(dir_object, "bw_max", max);
+       json_object_add_value_float(dir_object, "bw_agg", mean);
+       json_object_add_value_float(dir_object, "bw_mean", mean);
+       json_object_add_value_float(dir_object, "bw_dev", dev);
+ }
  static void show_thread_status_terse_v2(struct thread_stat *ts,
 -                                      struct group_run_stats *rs)
 +                                      struct group_run_stats *rs)
  {
        double io_u_dist[FIO_IO_U_MAP_NR];
        double io_u_lat_u[FIO_IO_U_LAT_U_NR];
                                                                ts->minf);
  
        /* Calc % distribution of IO depths, usecond, msecond latency */
--      stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
++      stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        stat_calc_lat_u(ts, io_u_lat_u);
        stat_calc_lat_m(ts, io_u_lat_m);
  
@@@ -727,7 -877,7 +836,7 @@@ static void show_thread_status_terse_v3
                                                                ts->minf);
  
        /* Calc % distribution of IO depths, usecond, msecond latency */
--      stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
++      stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
        stat_calc_lat_u(ts, io_u_lat_u);
        stat_calc_lat_m(ts, io_u_lat_m);
  
        log_info("\n");
  }
  
 -      stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+ static struct json_object *show_thread_status_json(struct thread_stat *ts,
+                                   struct group_run_stats *rs)
+ {
+       struct json_object *root, *tmp;
+       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];
+       double usr_cpu, sys_cpu;
+       int i;
+       root = json_create_object();
+       json_object_add_value_string(root, "jobname", ts->name);
+       json_object_add_value_int(root, "groupid", ts->groupid);
+       json_object_add_value_int(root, "error", ts->error);
+       add_ddir_status_json(ts, rs, DDIR_READ, root);
+       add_ddir_status_json(ts, rs, DDIR_WRITE, root);
+       add_ddir_status_json(ts, rs, DDIR_TRIM, root);
+       /* CPU Usage */
+       if (ts->total_run_time) {
+               double runt = (double) ts->total_run_time;
+               usr_cpu = (double) ts->usr_time * 100 / runt;
+               sys_cpu = (double) ts->sys_time * 100 / runt;
+       } else {
+               usr_cpu = 0;
+               sys_cpu = 0;
+       }
+       json_object_add_value_float(root, "usr_cpu", usr_cpu);
+       json_object_add_value_float(root, "sys_cpu", sys_cpu);
+       json_object_add_value_int(root, "ctx", ts->ctx);
+       json_object_add_value_int(root, "majf", ts->majf);
+       json_object_add_value_int(root, "minf", ts->minf);
+       /* Calc % distribution of IO depths, usecond, msecond latency */
++      stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
+       stat_calc_lat_u(ts, io_u_lat_u);
+       stat_calc_lat_m(ts, io_u_lat_m);
+       tmp = json_create_object();
+       json_object_add_value_object(root, "iodepth_level", tmp);
+       /* Only show fixed 7 I/O depth levels*/
+       for (i = 0; i < 7; i++) {
+               char name[20];
+               if (i < 6)
+                       snprintf(name, 19, "%d", 1 << i);
+               else
+                       snprintf(name, 19, ">=%d", 1 << i);
+               json_object_add_value_float(tmp, (const char *)name, io_u_dist[i]);
+       }
+       tmp = json_create_object();
+       json_object_add_value_object(root, "latency_us", tmp);
+       /* Microsecond latency */
+       for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) {
+               const char *ranges[] = { "2", "4", "10", "20", "50", "100",
+                                "250", "500", "750", "1000", };
+               json_object_add_value_float(tmp, ranges[i], io_u_lat_u[i]);
+       }
+       /* Millisecond latency */
+       tmp = json_create_object();
+       json_object_add_value_object(root, "latency_ms", tmp);
+       for (i = 0; i < FIO_IO_U_LAT_M_NR; i++) {
+               const char *ranges[] = { "2", "4", "10", "20", "50", "100",
+                                "250", "500", "750", "1000", "2000",
+                                ">=2000", };
+               json_object_add_value_float(tmp, ranges[i], io_u_lat_m[i]);
+       }
+       /* Additional output if continue_on_error set - default off*/
+       if (ts->continue_on_error) {
+               json_object_add_value_int(root, "total_err", ts->total_err_count);
+               json_object_add_value_int(root, "total_err", ts->first_error);
+       }
+       /* Additional output if description is set */
+       if (strlen(ts->description))
+               json_object_add_value_string(root, "desc", ts->description);
+       return root;
+ }
  static void show_thread_status_terse(struct thread_stat *ts,
                                     struct group_run_stats *rs)
  {
diff --cc stat.h
--- 1/stat.h
--- 2/stat.h
+++ b/stat.h
@@@ -1,13 -1,11 +1,13 @@@
  #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];
-       uint64_t io_kb[2];
-       uint64_t agg[2];
+       uint64_t max_run[DDIR_RWDIR_CNT], min_run[DDIR_RWDIR_CNT];
+       uint64_t max_bw[DDIR_RWDIR_CNT], min_bw[DDIR_RWDIR_CNT];
+       uint64_t io_kb[DDIR_RWDIR_CNT];
+       uint64_t agg[DDIR_RWDIR_CNT];
        uint32_t kb_base;
        uint32_t groupid;
  };
@@@ -177,10 -174,10 +177,10 @@@ struct jobs_eta 
        uint32_t nr_ramp;
        uint32_t nr_pending;
        uint32_t files_open;
-       uint32_t m_rate[2], t_rate[2];
-       uint32_t m_iops[2], t_iops[2];
-       uint32_t rate[2];
-       uint32_t iops[2];
 -      uint32_t m_rate, t_rate;
 -      uint32_t m_iops, t_iops;
++      uint32_t m_rate[DDIR_RWDIR_CNT], t_rate[DDIR_RWDIR_CNT];
++      uint32_t m_iops[DDIR_RWDIR_CNT], t_iops[DDIR_RWDIR_CNT];
+       uint32_t rate[DDIR_RWDIR_CNT];
+       uint32_t iops[DDIR_RWDIR_CNT];
        uint64_t elapsed_sec;
        uint64_t eta_sec;
        uint32_t is_pow2;
@@@ -202,27 -199,5 +202,25 @@@ extern void sum_thread_stats(struct thr
  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);
- #define ts_total_io_u(ts)     ((ts)->total_io_u[0] + (ts)->total_io_u[1])
 +extern void eta_to_str(char *str, unsigned long eta_sec);
 +extern int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max, double *mean, double *dev);
 +extern unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr, fio_fp64_t *plist, unsigned int **output, unsigned int *maxv, unsigned int *minv);
 +extern void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat);
 +extern void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat);
 +extern void stat_calc_dist(unsigned int *map, unsigned long total, double *io_u_dist);
 +
 +static inline int usec_to_msec(unsigned long *min, unsigned long *max,
 +                             double *mean, double *dev)
 +{
 +      if (*min > 1000 && *max > 1000 && *mean > 1000.0 && *dev > 1000.0) {
 +              *min /= 1000;
 +              *max /= 1000;
 +              *mean /= 1000.0;
 +              *dev /= 1000.0;
 +              return 0;
 +      }
 +
 +      return 1;
 +}
  
  #endif
index a78684c,0000000..323dacd
mode 100644,000000..100644
--- /dev/null
@@@ -1,412 -1,0 +1,412 @@@
-       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];
 +#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;
 +      char *mmapfile;
 +      enum td_ddir td_ddir;
 +      unsigned int rw_seq;
 +      unsigned int kb_base;
 +      unsigned int ddir_seq_nr;
 +      long ddir_seq_add;
 +      unsigned int iodepth;
 +      unsigned int iodepth_low;
 +      unsigned int iodepth_batch;
 +      unsigned int iodepth_batch_complete;
 +
 +      unsigned long long size;
 +      unsigned int size_percent;
 +      unsigned int fill_device;
 +      unsigned long long file_size_low;
 +      unsigned long long file_size_high;
 +      unsigned long long start_offset;
 +
-       unsigned int rate[2];
-       unsigned int ratemin[2];
++      unsigned int bs[DDIR_RWDIR_CNT];
++      unsigned int ba[DDIR_RWDIR_CNT];
++      unsigned int min_bs[DDIR_RWDIR_CNT];
++      unsigned int max_bs[DDIR_RWDIR_CNT];
++      struct bssplit *bssplit[DDIR_RWDIR_CNT];
++      unsigned int bssplit_nr[DDIR_RWDIR_CNT];
 +
 +      unsigned int nr_files;
 +      unsigned int open_files;
 +      enum file_lock_mode file_lock_mode;
 +      unsigned int lockfile_batch;
 +
 +      unsigned int odirect;
 +      unsigned int invalidate_cache;
 +      unsigned int create_serialize;
 +      unsigned int create_fsync;
 +      unsigned int create_on_open;
 +      unsigned int create_only;
 +      unsigned int end_fsync;
 +      unsigned int pre_read;
 +      unsigned int sync_io;
 +      unsigned int verify;
 +      unsigned int do_verify;
 +      unsigned int verifysort;
 +      unsigned int verify_interval;
 +      unsigned int verify_offset;
 +      char verify_pattern[MAX_PATTERN_SIZE];
 +      unsigned int verify_pattern_bytes;
 +      unsigned int verify_fatal;
 +      unsigned int verify_dump;
 +      unsigned int verify_async;
 +      unsigned long long verify_backlog;
 +      unsigned int verify_batch;
 +      unsigned int use_thread;
 +      unsigned int unlink;
 +      unsigned int do_disk_util;
 +      unsigned int override_sync;
 +      unsigned int rand_repeatable;
 +      unsigned int use_os_rand;
 +      unsigned int log_avg_msec;
 +      unsigned int norandommap;
 +      unsigned int softrandommap;
 +      unsigned int bs_unaligned;
 +      unsigned int fsync_on_close;
 +
 +      unsigned int hugepage_size;
 +      unsigned int rw_min_bs;
 +      unsigned int thinktime;
 +      unsigned int thinktime_spin;
 +      unsigned int thinktime_blocks;
 +      unsigned int fsync_blocks;
 +      unsigned int fdatasync_blocks;
 +      unsigned int barrier_blocks;
 +      unsigned long long start_delay;
 +      unsigned long long timeout;
 +      unsigned long long ramp_time;
 +      unsigned int overwrite;
 +      unsigned int bw_avg_time;
 +      unsigned int iops_avg_time;
 +      unsigned int loops;
 +      unsigned long long zone_range;
 +      unsigned long long zone_size;
 +      unsigned long long zone_skip;
 +      unsigned long long lockmem;
 +      enum fio_memtype mem_type;
 +      unsigned int mem_align;
 +
 +      unsigned int stonewall;
 +      unsigned int new_group;
 +      unsigned int numjobs;
 +      os_cpu_mask_t cpumask;
 +      unsigned int cpumask_set;
 +      os_cpu_mask_t verify_cpumask;
 +      unsigned int verify_cpumask_set;
 +      unsigned int iolog;
 +      unsigned int rwmixcycle;
 +      unsigned int rwmix[2];
 +      unsigned int nice;
 +      unsigned int ioprio;
 +      unsigned int ioprio_class;
 +      unsigned int file_service_type;
 +      unsigned int group_reporting;
 +      unsigned int fadvise_hint;
 +      enum fio_fallocate_mode fallocate_mode;
 +      unsigned int zero_buffers;
 +      unsigned int refill_buffers;
 +      unsigned int scramble_buffers;
 +      unsigned int compress_percentage;
 +      unsigned int compress_chunk;
 +      unsigned int time_based;
 +      unsigned int disable_lat;
 +      unsigned int disable_clat;
 +      unsigned int disable_slat;
 +      unsigned int disable_bw;
 +      unsigned int gtod_reduce;
 +      unsigned int gtod_cpu;
 +      unsigned int gtod_offload;
 +      enum fio_cs clocksource;
 +      unsigned int no_stall;
 +      unsigned int trim_percentage;
 +      unsigned int trim_batch;
 +      unsigned int trim_zero;
 +      unsigned long long trim_backlog;
 +      unsigned int clat_percentiles;
 +      unsigned int overwrite_plist;
 +      fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 +
 +      char *read_iolog_file;
 +      char *write_iolog_file;
 +      char *bw_log_file;
 +      char *lat_log_file;
 +      char *iops_log_file;
 +      char *replay_redirect;
 +
 +      /*
 +       * Pre-run and post-run shell
 +       */
 +      char *exec_prerun;
 +      char *exec_postrun;
 +
-       unsigned int rate_iops[2];
-       unsigned int rate_iops_min[2];
++      unsigned int rate[DDIR_RWDIR_CNT];
++      unsigned int ratemin[DDIR_RWDIR_CNT];
 +      unsigned int ratecycle;
++      unsigned int rate_iops[DDIR_RWDIR_CNT];
++      unsigned int rate_iops_min[DDIR_RWDIR_CNT];
 +
 +      char *ioscheduler;
 +
 +      /*
 +       * I/O Error handling
 +       */
 +      enum error_type continue_on_error;
 +
 +      /*
 +       * Benchmark profile type
 +       */
 +      char *profile;
 +
 +      /*
 +       * blkio cgroup support
 +       */
 +      char *cgroup;
 +      unsigned int cgroup_weight;
 +      unsigned int cgroup_nodelete;
 +
 +      unsigned int uid;
 +      unsigned int gid;
 +
 +      int flow_id;
 +      int flow;
 +      int flow_watermark;
 +      unsigned int flow_sleep;
 +
 +      unsigned long long offset_increment;
 +
 +      unsigned int sync_file_range;
 +};
 +
 +#define FIO_TOP_STR_MAX               256
 +
 +struct thread_options_pack {
 +      uint8_t description[FIO_TOP_STR_MAX];
 +      uint8_t name[FIO_TOP_STR_MAX];
 +      uint8_t directory[FIO_TOP_STR_MAX];
 +      uint8_t filename[FIO_TOP_STR_MAX];
 +      uint8_t opendir[FIO_TOP_STR_MAX];
 +      uint8_t ioengine[FIO_TOP_STR_MAX];
 +      uint8_t mmapfile[FIO_TOP_STR_MAX];
 +      uint32_t td_ddir;
 +      uint32_t rw_seq;
 +      uint32_t kb_base;
 +      uint32_t ddir_seq_nr;
 +      uint64_t ddir_seq_add;
 +      uint32_t iodepth;
 +      uint32_t iodepth_low;
 +      uint32_t iodepth_batch;
 +      uint32_t iodepth_batch_complete;
 +
 +      uint64_t size;
 +      uint32_t size_percent;
 +      uint32_t fill_device;
 +      uint64_t file_size_low;
 +      uint64_t file_size_high;
 +      uint64_t start_offset;
 +
 +      uint32_t bs[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 create_only;
 +      uint32_t end_fsync;
 +      uint32_t pre_read;
 +      uint32_t sync_io;
 +      uint32_t verify;
 +      uint32_t do_verify;
 +      uint32_t verifysort;
 +      uint32_t verify_interval;
 +      uint32_t verify_offset;
 +      uint8_t verify_pattern[MAX_PATTERN_SIZE];
 +      uint32_t verify_pattern_bytes;
 +      uint32_t verify_fatal;
 +      uint32_t verify_dump;
 +      uint32_t verify_async;
 +      uint64_t verify_backlog;
 +      uint32_t verify_batch;
 +      uint32_t use_thread;
 +      uint32_t unlink;
 +      uint32_t do_disk_util;
 +      uint32_t override_sync;
 +      uint32_t rand_repeatable;
 +      uint32_t use_os_rand;
 +      uint32_t log_avg_msec;
 +      uint32_t norandommap;
 +      uint32_t softrandommap;
 +      uint32_t bs_unaligned;
 +      uint32_t fsync_on_close;
 +
 +      uint32_t hugepage_size;
 +      uint32_t rw_min_bs;
 +      uint32_t thinktime;
 +      uint32_t thinktime_spin;
 +      uint32_t thinktime_blocks;
 +      uint32_t fsync_blocks;
 +      uint32_t fdatasync_blocks;
 +      uint32_t barrier_blocks;
 +      uint64_t start_delay;
 +      uint64_t timeout;
 +      uint64_t ramp_time;
 +      uint32_t overwrite;
 +      uint32_t bw_avg_time;
 +      uint32_t iops_avg_time;
 +      uint32_t loops;
 +      uint64_t zone_range;
 +      uint64_t zone_size;
 +      uint64_t zone_skip;
 +      uint64_t lockmem;
 +      uint32_t mem_type;
 +      uint32_t mem_align;
 +
 +      uint32_t stonewall;
 +      uint32_t new_group;
 +      uint32_t numjobs;
 +      uint8_t cpumask[FIO_TOP_STR_MAX];
 +      uint32_t cpumask_set;
 +      uint8_t verify_cpumask[FIO_TOP_STR_MAX];
 +      uint32_t verify_cpumask_set;
 +      uint32_t iolog;
 +      uint32_t rwmixcycle;
 +      uint32_t rwmix[2];
 +      uint32_t nice;
 +      uint32_t ioprio;
 +      uint32_t ioprio_class;
 +      uint32_t file_service_type;
 +      uint32_t group_reporting;
 +      uint32_t fadvise_hint;
 +      uint32_t fallocate_mode;
 +      uint32_t zero_buffers;
 +      uint32_t refill_buffers;
 +      uint32_t scramble_buffers;
 +      unsigned int compress_percentage;
 +      unsigned int compress_chunk;
 +      uint32_t time_based;
 +      uint32_t disable_lat;
 +      uint32_t disable_clat;
 +      uint32_t disable_slat;
 +      uint32_t disable_bw;
 +      uint32_t gtod_reduce;
 +      uint32_t gtod_cpu;
 +      uint32_t gtod_offload;
 +      uint32_t clocksource;
 +      uint32_t no_stall;
 +      uint32_t trim_percentage;
 +      uint32_t trim_batch;
 +      uint32_t trim_zero;
 +      uint64_t trim_backlog;
 +      uint32_t clat_percentiles;
 +      uint32_t overwrite_plist;
 +      fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 +
 +      uint8_t read_iolog_file[FIO_TOP_STR_MAX];
 +      uint8_t write_iolog_file[FIO_TOP_STR_MAX];
 +      uint8_t bw_log_file[FIO_TOP_STR_MAX];
 +      uint8_t lat_log_file[FIO_TOP_STR_MAX];
 +      uint8_t iops_log_file[FIO_TOP_STR_MAX];
 +      uint8_t replay_redirect[FIO_TOP_STR_MAX];
 +
 +      /*
 +       * Pre-run and post-run shell
 +       */
 +      uint8_t exec_prerun[FIO_TOP_STR_MAX];
 +      uint8_t exec_postrun[FIO_TOP_STR_MAX];
 +
 +      uint32_t rate[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];
 +
 +      /*
 +       * I/O Error handling
 +       */
 +      uint32_t continue_on_error;
 +
 +      /*
 +       * Benchmark profile type
 +       */
 +      uint8_t profile[FIO_TOP_STR_MAX];
 +
 +      /*
 +       * blkio cgroup support
 +       */
 +      uint8_t cgroup[FIO_TOP_STR_MAX];
 +      uint32_t cgroup_weight;
 +      uint32_t cgroup_nodelete;
 +
 +      uint32_t uid;
 +      uint32_t gid;
 +
 +      int32_t flow_id;
 +      int32_t flow;
 +      int32_t flow_watermark;
 +      uint32_t flow_sleep;
 +
 +      uint64_t offset_increment;
 +
 +      uint32_t sync_file_range;
 +} __attribute__((packed));
 +
 +extern void convert_thread_options_to_cpu(struct thread_options *o, struct thread_options_pack *top);
 +extern void convert_thread_options_to_net(struct thread_options_pack *top, struct thread_options *);
 +extern int fio_test_cconv(struct thread_options *);
 +extern void options_default_fill(struct thread_options *o);
 +
 +#endif