Add completion percentiles to results output
[fio.git] / gfio.c
diff --git a/gfio.c b/gfio.c
index 382b54b1296fe2f63353c27bb90b73586889230f..256041261f66f0a2c0e565bc4f6eb9b9ba6ed2e8 100644 (file)
--- a/gfio.c
+++ b/gfio.c
 
 #include "fio.h"
 
+static void gfio_update_thread_status(char *status_message, double perc);
+
 #define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
 
 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
 
-static void quit_clicked(GtkWidget *widget, gpointer data);
+static void connect_clicked(GtkWidget *widget, gpointer data);
 static void start_job_clicked(GtkWidget *widget, gpointer data);
 
 static struct button_spec {
        const char *buttontext;
        clickfunction f;
        const char *tooltiptext;
+       const int start_insensitive;
 } buttonspeclist[] = {
-#define START_JOB_BUTTON 0
+#define CONNECT_BUTTON 0
+#define START_JOB_BUTTON 1
+       { "Connect", connect_clicked, "Connect to host", 0 },
        { "Start Job",
                start_job_clicked,
-               "Send current fio job to fio server to be executed" },
+               "Send current fio job to fio server to be executed", 1 },
 };
 
 struct probe_widget {
@@ -53,6 +58,23 @@ struct probe_widget {
        GtkWidget *fio_ver;
 };
 
+struct eta_widget {
+       GtkWidget *name;
+       GtkWidget *iotype;
+       GtkWidget *ioengine;
+       GtkWidget *iodepth;
+       GtkWidget *jobs;
+       GtkWidget *files;
+       GtkWidget *read_bw;
+       GtkWidget *read_iops;
+       GtkWidget *cr_bw;
+       GtkWidget *cr_iops;
+       GtkWidget *write_bw;
+       GtkWidget *write_iops;
+       GtkWidget *cw_bw;
+       GtkWidget *cw_iops;
+};
+
 struct gui {
        GtkWidget *window;
        GtkWidget *vbox;
@@ -62,28 +84,390 @@ struct gui {
        GtkWidget *thread_status_pb;
        GtkWidget *buttonbox;
        GtkWidget *button[ARRAYSIZE(buttonspeclist)];
-       GtkWidget *hostname_hbox;
-       GtkWidget *hostname_label;
-       GtkWidget *hostname_entry;
-       GtkWidget *port_label;
-       GtkWidget *port_entry;
-       GtkWidget *hostname_combo_box; /* ipv4, ipv6 or socket */
        GtkWidget *scrolled_window;
        GtkWidget *textview;
        GtkWidget *error_info_bar;
        GtkWidget *error_label;
        GtkTextBuffer *text;
        struct probe_widget probe;
+       struct eta_widget eta;
+       int connected;
        pthread_t t;
 
-       void *cookie;
+       struct fio_client *client;
        int nr_job_files;
        char **job_files;
 } ui;
 
-static void gfio_text_op(struct fio_client *client,
-                FILE *f, __u16 pdu_len, const char *buf)
+static void clear_ui_info(struct gui *ui)
+{
+       gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
+       gtk_label_set_text(GTK_LABEL(ui->probe.os), "");
+       gtk_label_set_text(GTK_LABEL(ui->probe.arch), "");
+       gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.name), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.iotype), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.jobs), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.files), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.read_bw), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.read_iops), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.write_bw), "");
+       gtk_label_set_text(GTK_LABEL(ui->eta.write_iops), "");
+}
+
+static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *entry, *frame;
+
+       frame = gtk_frame_new(label);
+       entry = gtk_entry_new();
+       gtk_entry_set_editable(GTK_ENTRY(entry), 0);
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), entry);
+
+       return entry;
+}
+
+static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
+{
+       GtkWidget *label_widget;
+       GtkWidget *frame;
+
+       frame = gtk_frame_new(label);
+       label_widget = gtk_label_new(NULL);
+       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), label_widget);
+
+       return label_widget;
+}
+
+static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
+{
+       GtkWidget *button, *box;
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(hbox), box);
+
+       button = gtk_spin_button_new_with_range(min, max, 1.0);
+       gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
+
+       gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
+
+       return button;
+}
+
+static void gfio_set_connected(struct gui *ui, int connected)
+{
+       if (connected) {
+               gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
+               ui->connected = 1;
+               gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
+       } else {
+               ui->connected = 0;
+               gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
+               gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
+       }
+}
+
+static void label_set_int_value(GtkWidget *entry, unsigned int val)
+{
+       char tmp[80];
+
+       sprintf(tmp, "%u", val);
+       gtk_label_set_text(GTK_LABEL(entry), tmp);
+}
+
+static void entry_set_int_value(GtkWidget *entry, unsigned int val)
+{
+       char tmp[80];
+
+       sprintf(tmp, "%u", val);
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+}
+
+#define ALIGN_LEFT 1
+#define ALIGN_RIGHT 2
+#define INVISIBLE 4
+#define UNSORTABLE 8
+
+GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags)
+{
+       GtkCellRenderer *renderer;
+       GtkTreeViewColumn *col;
+       double xalign = 0.0; /* left as default */
+       PangoAlignment align;
+       gboolean visible;
+
+       align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
+               (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
+               PANGO_ALIGN_CENTER;
+       visible = !(flags & INVISIBLE);
+
+       renderer = gtk_cell_renderer_text_new();
+       col = gtk_tree_view_column_new();
+
+       gtk_tree_view_column_set_title(col, title);
+       if (!(flags & UNSORTABLE))
+               gtk_tree_view_column_set_sort_column_id(col, index);
+       gtk_tree_view_column_set_resizable(col, TRUE);
+       gtk_tree_view_column_pack_start(col, renderer, TRUE);
+       gtk_tree_view_column_add_attribute(col, renderer, "text", index);
+       gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
+       switch (align) {
+       case PANGO_ALIGN_LEFT:
+               xalign = 0.0;
+               break;
+       case PANGO_ALIGN_CENTER:
+               xalign = 0.5;
+               break;
+       case PANGO_ALIGN_RIGHT:
+               xalign = 1.0;
+               break;
+       }
+       gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
+       gtk_tree_view_column_set_visible(col, visible);
+       gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
+       return col;
+}
+
+static 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);
+
+       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++)
+               gtk_list_store_set(model, &iter, i, ovals[i], -1);
+
+       return tree_view;
+}
+
+static void gfio_show_clat_percentiles(GtkWidget *vbox, struct thread_stat *ts,
+                                      int ddir)
+{
+       unsigned int *io_u_plat = ts->io_u_plat[ddir];
+       unsigned long nr = ts->clat_stat[ddir].samples;
+       fio_fp64_t *plist = ts->percentile_list;
+       unsigned int *ovals, len, minv, maxv, scale_down;
+       const char *base;
+       GtkWidget *tree_view, *frame, *hbox;
+       char tmp[64];
+
+       len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
+       if (!len)
+               goto out;
+
+       /*
+        * We default to usecs, but if the value range is such that we
+        * should scale down to msecs, do that.
+        */
+       if (minv > 2000 && maxv > 99999) {
+               scale_down = 1;
+               base = "msec";
+       } else {
+               scale_down = 0;
+               base = "usec";
+       }
+
+       tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
+
+       sprintf(tmp, "Completion percentiles (%s)", base);
+       frame = gtk_frame_new(tmp);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+       gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
+out:
+       if (ovals)
+               free(ovals);
+}
+
+static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
+                         unsigned long max, double mean, double dev)
+{
+       const char *base = "(usec)";
+       GtkWidget *hbox, *label, *frame;
+       char *minp, *maxp;
+       char tmp[64];
+
+       if (!usec_to_msec(&min, &max, &mean, &dev))
+               base = "(msec)";
+
+       minp = num2str(min, 6, 1, 0);
+       maxp = num2str(max, 6, 1, 0);
+
+       sprintf(tmp, "%s %s", name, base);
+       frame = gtk_frame_new(tmp);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       hbox = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+       label = new_info_label_in_frame(hbox, "Minimum");
+       gtk_label_set_text(GTK_LABEL(label), minp);
+       label = new_info_label_in_frame(hbox, "Maximum");
+       gtk_label_set_text(GTK_LABEL(label), maxp);
+       label = new_info_label_in_frame(hbox, "Average");
+       sprintf(tmp, "%5.02f", mean);
+       gtk_label_set_text(GTK_LABEL(label), tmp);
+       label = new_info_label_in_frame(hbox, "Standard deviation");
+       sprintf(tmp, "%5.02f", dev);
+       gtk_label_set_text(GTK_LABEL(label), tmp);
+
+       free(minp);
+       free(maxp);
+
+}
+
+static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
+                                 struct thread_stat *ts, int ddir)
+{
+       const char *ddir_label[2] = { "Read", "Write" };
+       GtkWidget *frame, *label, *box, *vbox;
+       unsigned long min, max, runt;
+       unsigned long long bw, iops;
+       double mean, dev;
+       char *io_p, *bw_p, *iops_p;
+       int i2p;
+
+       if (!ts->runtime[ddir])
+               return;
+
+       i2p = is_power_of_2(rs->kb_base);
+       runt = ts->runtime[ddir];
+
+       bw = (1000 * ts->io_bytes[ddir]) / runt;
+       io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
+       bw_p = num2str(bw, 6, 1, i2p);
+
+       iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
+       iops_p = num2str(iops, 6, 1, 0);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
+
+       frame = gtk_frame_new(ddir_label[ddir]);
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(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]);
+
+       frame = gtk_frame_new("Latency");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+       if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
+               gfio_show_lat(vbox, "Submission latency", min, max, mean, dev);
+       if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
+               gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
+       if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
+               gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
+       if (ts->clat_percentiles)
+               gfio_show_clat_percentiles(vbox, ts, ddir);
+
+       free(io_p);
+       free(bw_p);
+       free(iops_p);
+}
+
+static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
+                           struct group_run_stats *rs)
+{
+       GtkWidget *dialog, *box, *vbox, *entry, *content;
+       struct gui *ui = client->client_data;
+
+       gdk_threads_enter();
+
+       dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
+
+       g_signal_connect_swapped(dialog, "response",
+                             G_CALLBACK(gtk_widget_destroy),
+                             dialog);
+
+       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(content), vbox);
+
+       box = gtk_hbox_new(TRUE, 3);
+       gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
+
+       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);
+       entry = new_info_entry_in_frame(box, "Error");
+       entry_set_int_value(entry, ts->error);
+       entry = new_info_entry_in_frame(box, "PID");
+       entry_set_int_value(entry, ts->pid);
+
+       if (ts->io_bytes[DDIR_READ])
+               gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
+       if (ts->io_bytes[DDIR_WRITE])
+               gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
+
+       gtk_widget_show_all(dialog);
+
+       gdk_threads_leave();
+}
+
+static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
 {
+#if 0
        GtkTextBuffer *buffer;
        GtkTextIter end;
 
@@ -94,6 +478,9 @@ static void gfio_text_op(struct fio_client *client,
        gdk_threads_leave();
        gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
                                        &end, 0.0, FALSE, 0.0,0.0);
+#else
+       fio_client_ops.text_op(client, cmd);
+#endif
 }
 
 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
@@ -102,21 +489,119 @@ static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd
        fio_client_ops.disk_util(client, cmd);
 }
 
-static void gfio_thread_status_op(struct fio_net_cmd *cmd)
+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)
 {
-       printf("gfio_thread_status_op called\n");
-       fio_client_ops.thread_status(cmd);
+       struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+
+       gfio_display_ts(client, &p->ts, &p->rs);
+
+       if (sum_stat_clients == 1)
+               return;
+
+       sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
+       sum_group_stats(&client_gs, &p->rs);
+
+       client_ts.members++;
+       client_ts.groupid = p->ts.groupid;
+
+       if (++sum_stat_nr == sum_stat_clients) {
+               strcpy(client_ts.name, "All clients");
+               gfio_display_ts(client, &client_ts, &client_gs);
+       }
 }
 
-static void gfio_group_stats_op(struct fio_net_cmd *cmd)
+static void gfio_group_stats_op(struct fio_client *client,
+                               struct fio_net_cmd *cmd)
 {
        printf("gfio_group_stats_op called\n");
-       fio_client_ops.group_stats(cmd);
+       fio_client_ops.group_stats(client, cmd);
 }
 
-static void gfio_eta_op(struct fio_client *client, struct fio_net_cmd *cmd)
+static void gfio_update_eta(struct jobs_eta *je)
 {
-       fio_client_ops.eta(client, cmd);
+       static int eta_good;
+       char eta_str[128];
+       char output[256];
+       char tmp[32];
+       double perc = 0.0;
+       int i2p = 0;
+
+       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_label_set_text(GTK_LABEL(ui.eta.jobs), tmp);
+       sprintf(tmp, "%u", je->files_open);
+       gtk_label_set_text(GTK_LABEL(ui.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_label_set_text(GTK_LABEL(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_label_set_text(GTK_LABEL(ui.eta.cr_bw), "---");
+       gtk_label_set_text(GTK_LABEL(ui.eta.cr_iops), "---");
+       gtk_label_set_text(GTK_LABEL(ui.eta.cw_bw), "---");
+       gtk_label_set_text(GTK_LABEL(ui.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_label_set_text(GTK_LABEL(ui.eta.read_bw), rate_str[0]);
+               gtk_label_set_text(GTK_LABEL(ui.eta.read_iops), iops_str[0]);
+               gtk_label_set_text(GTK_LABEL(ui.eta.write_bw), rate_str[1]);
+               gtk_label_set_text(GTK_LABEL(ui.eta.write_iops), iops_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(output, perc);
 }
 
 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
@@ -158,14 +643,80 @@ static void gfio_update_thread_status(char *status_message, double perc)
        gdk_threads_leave();
 }
 
+static void gfio_quit_op(struct fio_client *client)
+{
+       struct gui *ui = client->client_data;
+
+       gfio_set_connected(ui, 0);
+}
+
+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 gui *ui = client->client_data;
+       char tmp[8];
+       int i;
+
+       p->iodepth              = le32_to_cpu(p->iodepth);
+       p->rw                   = le32_to_cpu(p->rw);
+
+       for (i = 0; i < 2; i++) {
+               p->min_bs[i]    = le32_to_cpu(p->min_bs[i]);
+               p->max_bs[i]    = le32_to_cpu(p->max_bs[i]);
+       }
+
+       p->numjobs              = le32_to_cpu(p->numjobs);
+       p->group_reporting      = le32_to_cpu(p->group_reporting);
+
+       gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
+       gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
+       gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
+
+       sprintf(tmp, "%u", p->iodepth);
+       gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
+}
+
+static void gfio_client_timed_out(struct fio_client *client)
+{
+       struct gui *ui = client->client_data;
+       GtkWidget *dialog, *label, *content;
+       char buf[256];
+
+       gdk_threads_enter();
+
+       gfio_set_connected(ui, 0);
+       clear_ui_info(ui);
+
+       sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
+
+       dialog = gtk_dialog_new_with_buttons("Timed out!",
+                       GTK_WINDOW(ui->window),
+                       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+
+       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       label = gtk_label_new((const gchar *) buf);
+       gtk_container_add(GTK_CONTAINER(content), label);
+       gtk_widget_show_all(dialog);
+       gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+
+       gtk_dialog_run(GTK_DIALOG(dialog));
+       gtk_widget_destroy(dialog);
+
+       gdk_threads_leave();
+}
+
 struct client_ops gfio_client_ops = {
        .text_op                = gfio_text_op,
        .disk_util              = gfio_disk_util_op,
        .thread_status          = gfio_thread_status_op,
        .group_stats            = gfio_group_stats_op,
-       .eta                    = gfio_eta_op,
+       .eta                    = gfio_update_eta,
        .probe                  = gfio_probe_op,
-       .thread_status_display  = gfio_update_thread_status,
+       .quit                   = gfio_quit_op,
+       .add_job                = gfio_add_job_op,
+       .timed_out              = gfio_client_timed_out,
+       .stay_connected         = 1,
 };
 
 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
@@ -176,39 +727,38 @@ static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
 
 static void *job_thread(void *arg)
 {
-       struct gui *ui = arg;
-
        fio_handle_clients(&gfio_client_ops);
-       gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
        return NULL;
 }
 
 static int send_job_files(struct gui *ui)
 {
-       int i, ret;
+       int i, ret = 0;
 
        for (i = 0; i < ui->nr_job_files; i++) {
                ret = fio_clients_send_ini(ui->job_files[i]);
+               if (ret)
+                       break;
+
                free(ui->job_files[i]);
                ui->job_files[i] = NULL;
-               if (ret)
-                       return ret;
+       }
+       while (i < ui->nr_job_files) {
+               free(ui->job_files[i]);
+               ui->job_files[i] = NULL;
+               i++;
        }
 
-       return 0;
+       return ret;
 }
 
-static void start_job_thread(pthread_t *t, struct gui *ui)
+static void start_job_thread(struct gui *ui)
 {
-       fio_clients_connect();
-
        if (send_job_files(ui)) {
                printf("Yeah, I didn't really like those options too much.\n");
                gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
                return;
        }
-               
-       pthread_create(t, NULL, job_thread, ui);
 }
 
 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
@@ -216,9 +766,28 @@ static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
 {
        struct gui *ui = data;
 
-       printf("Start job button was clicked.\n");
        gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
-       start_job_thread(&ui->t, ui);
+       start_job_thread(ui);
+}
+
+static void file_open(GtkWidget *w, gpointer data);
+
+static void connect_clicked(GtkWidget *widget, gpointer data)
+{
+       struct gui *ui = data;
+
+       if (!ui->connected) {
+               if (!ui->nr_job_files)
+                       file_open(widget, data);
+               gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
+               fio_clients_connect();
+               pthread_create(&ui->t, NULL, job_thread, NULL);
+               gfio_set_connected(ui, 1);
+       } else {
+               fio_clients_terminate();
+               gfio_set_connected(ui, 0);
+               clear_ui_info(ui);
+       }
 }
 
 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
@@ -226,8 +795,9 @@ static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
 {
        ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
        g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
-       gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], TRUE, TRUE, 0);
+       gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
        gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
+       gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
 }
 
 static void add_buttons(struct gui *ui,
@@ -249,7 +819,7 @@ static void on_info_bar_response(GtkWidget *widget, gint response,
        }
 }
 
-void report_error(GErrorerror)
+void report_error(GError *error)
 {
        if (ui.error_info_bar == NULL) {
                ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
@@ -272,11 +842,102 @@ void report_error(GError* error)
        }
 }
 
+static int get_connection_details(char **host, int *port, int *type,
+                                 int *server_start)
+{
+       GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
+       GtkWidget *button;
+       char *typeentry;
+
+       dialog = gtk_dialog_new_with_buttons("Connection details",
+                       GTK_WINDOW(ui.window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+
+       frame = gtk_frame_new("Hostname / socket name");
+       vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       box = gtk_vbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 10);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       hentry = gtk_entry_new();
+       gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
+       gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
+
+       frame = gtk_frame_new("Port");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
+
+       frame = gtk_frame_new("Type");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+
+       combo = gtk_combo_box_text_new();
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
+       gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
+
+       gtk_container_add(GTK_CONTAINER(hbox), combo);
+
+       frame = gtk_frame_new("Options");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+
+       button = gtk_check_button_new_with_label("Auto-spawn fio backend");
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
+       gtk_widget_set_tooltip_text(button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
+
+       gtk_widget_show_all(dialog);
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+               gtk_widget_destroy(dialog);
+               return 1;
+       }
+
+       *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
+       *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
+
+       typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
+       if (!typeentry || !strncmp(typeentry, "IPv4", 4))
+               *type = Fio_client_ipv4;
+       else if (!strncmp(typeentry, "IPv6", 4))
+               *type = Fio_client_ipv6;
+       else
+               *type = Fio_client_socket;
+       g_free(typeentry);
+
+       *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
+
+       gtk_widget_destroy(dialog);
+       return 0;
+}
+
 static void file_open(GtkWidget *w, gpointer data)
 {
        GtkWidget *dialog;
        GSList *filenames, *fn_glist;
        GtkFileFilter *filter;
+       char *host;
+       int port, type, server_start;
 
        dialog = gtk_file_chooser_dialog_new("Open File",
                GTK_WINDOW(ui.window),
@@ -299,29 +960,35 @@ static void file_open(GtkWidget *w, gpointer data)
        }
 
        fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+
+       gtk_widget_destroy(dialog);
+
+       if (get_connection_details(&host, &port, &type, &server_start))
+               goto err;
+
        filenames = fn_glist;
        while (filenames != NULL) {
-               const char *hostname;
-
                ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
                ui.job_files[ui.nr_job_files] = strdup(filenames->data);
                ui.nr_job_files++;
 
-               hostname = gtk_entry_get_text(GTK_ENTRY(ui.hostname_entry));
-               fio_client_add(hostname, &ui.cookie);
-#if 0
-               if (error) {
+               ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
+               if (!ui.client) {
+                       GError *error;
+
+                       error = g_error_new(g_quark_from_string("fio"), 1,
+                                       "Failed to add client %s", host);
                        report_error(error);
                        g_error_free(error);
-                       error = NULL;
                }
-#endif
+               ui.client->client_data = &ui;
                        
                g_free(filenames->data);
                filenames = g_slist_next(filenames);
        }
+       free(host);
+err:
        g_slist_free(fn_glist);
-       gtk_widget_destroy(dialog);
 }
 
 static void file_save(GtkWidget *w, gpointer data)
@@ -348,6 +1015,49 @@ static void file_save(GtkWidget *w, gpointer data)
        gtk_widget_destroy(dialog);
 }
 
+static void preferences(GtkWidget *w, gpointer data)
+{
+       GtkWidget *dialog, *frame, *box, **buttons;
+       int i;
+
+       dialog = gtk_dialog_new_with_buttons("Preferences",
+               GTK_WINDOW(ui.window),
+               GTK_DIALOG_DESTROY_WITH_PARENT,
+               GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+               NULL);
+
+       frame = gtk_frame_new("Debug logging");
+       gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
+       box = gtk_hbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
+
+       for (i = 0; i < FD_DEBUG_MAX; i++) {
+               buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
+               gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
+               gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
+       }
+
+       gtk_widget_show_all(dialog);
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+               gtk_widget_destroy(dialog);
+               return;
+       }
+
+       for (i = 0; i < FD_DEBUG_MAX; i++) {
+               int set;
+
+               set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
+               if (set)
+                       fio_debug |= (1UL << i);
+       }
+
+       gtk_widget_destroy(dialog);
+}
+
 static void about_dialog(GtkWidget *w, gpointer data)
 {
        gtk_show_about_dialog(NULL,
@@ -363,14 +1073,15 @@ static void about_dialog(GtkWidget *w, gpointer data)
 }
 
 static GtkActionEntry menu_items[] = {
-        { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
-        { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
-       { "OpenFile",       GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
-        { "SaveFile",       GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
-        { "Quit",           GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
-       { "About",          GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
+       { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
+       { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
+       { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
+       { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
+       { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
+       { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
+       { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
 };
-static gint nmenu_items = sizeof (menu_items) / sizeof(menu_items[0]);
+static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
 
 static const gchar *ui_string = " \
        <ui> \
@@ -379,6 +1090,8 @@ static const gchar *ui_string = " \
                                <menuitem name=\"Open\" action=\"OpenFile\" /> \
                                <menuitem name=\"Save\" action=\"SaveFile\" /> \
                                <separator name=\"Separator\"/> \
+                               <menuitem name=\"Preferences\" action=\"Preferences\" /> \
+                               <separator name=\"Separator2\"/> \
                                <menuitem name=\"Quit\" action=\"Quit\" /> \
                        </menu> \
                        <menu name=\"Help\" action=\"HelpMenuAction\"> \
@@ -409,23 +1122,8 @@ void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
 }
 
-static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
-{
-       GtkWidget *label_widget;
-       GtkWidget *frame;
-
-       frame = gtk_frame_new(label);
-       label_widget = gtk_label_new(NULL);
-       gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
-       gtk_container_add(GTK_CONTAINER(frame), label_widget);
-
-       return label_widget;
-}
-
 static void init_ui(int *argc, char **argv[], struct gui *ui)
 {
-       GList *hostname_type_list = NULL;
-       char portnum[20];
        GtkSettings *settings;
        GtkUIManager *uimanager;
        GtkWidget *menu, *probe, *probe_frame, *probe_box;
@@ -436,7 +1134,7 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
         * Without it, the update that happens in gfio_update_thread_status
         * doesn't really happen in a timely fashion, you need expose events
         */
-       if (!g_thread_supported ())
+       if (!g_thread_supported())
                g_thread_init(NULL);
        gdk_threads_init();
 
@@ -464,62 +1162,52 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
         * align top left, expand horizontally but not vertically
         */
        ui->topalign = gtk_alignment_new(0, 0, 1, 0);
-       ui->topvbox = gtk_vbox_new(FALSE, 0);
+       ui->topvbox = gtk_vbox_new(FALSE, 3);
        gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
        gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
 
-       /*
-        * Set up hostname label + entry, port label + entry,
-        */
-       ui->hostname_hbox = gtk_hbox_new(FALSE, 0);
-       ui->hostname_label = gtk_label_new("Host:");
-       ui->hostname_entry = gtk_entry_new();
-       gtk_entry_set_text(GTK_ENTRY(ui->hostname_entry), "localhost");
-       ui->port_label = gtk_label_new("Port:");
-       ui->port_entry = gtk_entry_new();
-       snprintf(portnum, sizeof(portnum) - 1, "%d", FIO_NET_PORT);
-       gtk_entry_set_text(GTK_ENTRY(ui->port_entry), (gchar *) portnum);
-
-       /*
-        * Set up combo box for address type
-        */
-       ui->hostname_combo_box = gtk_combo_new();
-       gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ui->hostname_combo_box)->entry), "IPv4");
-       hostname_type_list = g_list_append(hostname_type_list, (gpointer) "IPv4"); 
-       hostname_type_list = g_list_append(hostname_type_list, (gpointer) "local socket"); 
-       hostname_type_list = g_list_append(hostname_type_list, (gpointer) "IPv6"); 
-       gtk_combo_set_popdown_strings(GTK_COMBO(ui->hostname_combo_box), hostname_type_list);
-       g_list_free(hostname_type_list);
-
-       gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->hostname_label);
-       gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->hostname_entry);
-       gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->port_label);
-       gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->port_entry);
-       gtk_container_add(GTK_CONTAINER (ui->hostname_hbox), ui->hostname_combo_box);
-       gtk_container_add(GTK_CONTAINER (ui->topvbox), ui->hostname_hbox);
-
-       probe = gtk_frame_new("Host");
+       probe = gtk_frame_new("Job");
        gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
        probe_frame = gtk_vbox_new(FALSE, 3);
        gtk_container_add(GTK_CONTAINER(probe), probe_frame);
 
        probe_box = gtk_hbox_new(FALSE, 3);
        gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
-
        ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
        ui->probe.os = new_info_label_in_frame(probe_box, "OS");
        ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
        ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
 
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
+
+       ui->eta.name = new_info_label_in_frame(probe_box, "Name");
+       ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
+       ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
+       ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
+       ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
+       ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
+
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
+       ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
+       ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
+       ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
+       ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
+
        /*
-        * Set up thread status progress bar
+        * Only add this if we have a commit rate
         */
-       ui->thread_status_pb = gtk_progress_bar_new();
-       gtk_progress_bar_set_fraction(
-               GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
-       gtk_progress_bar_set_text(
-               GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
-       gtk_container_add(GTK_CONTAINER (ui->topvbox), ui->thread_status_pb);
+#if 0
+       probe_box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
+
+       ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
+       ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
+
+       ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
+       ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
+#endif
 
        /*
         * Add a text box for text op messages 
@@ -547,6 +1235,16 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
                                        FALSE, FALSE, 0);
 
        add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
+
+       /*
+        * Set up thread status progress bar
+        */
+       ui->thread_status_pb = gtk_progress_bar_new();
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
+       gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
+
+
        gtk_widget_show_all(ui->window);
 }