gfio: split some parts into gclient.c
authorJens Axboe <axboe@kernel.dk>
Wed, 21 Mar 2012 10:13:31 +0000 (11:13 +0100)
committerJens Axboe <axboe@kernel.dk>
Wed, 21 Mar 2012 10:13:31 +0000 (11:13 +0100)
We'll probably want to split gclient.c into op code handling and
results display/view.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
Makefile
gclient.c [new file with mode: 0644]
gclient.h [new file with mode: 0644]
gerror.c
gerror.h
gfio.c
gfio.h
ghelpers.c
ghelpers.h

index 2b20a29..1d3cc85 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,8 @@ endif
 
 OBJS = $(SOURCE:.c=.o)
 FIO_OBJS = $(OBJS) fio.o
-GFIO_OBJS = $(OBJS) gfio.o graph.o tickmarks.o ghelpers.o goptions.o gerror.o
+GFIO_OBJS = $(OBJS) gfio.o graph.o tickmarks.o ghelpers.o goptions.o gerror.o \
+                       gclient.o
 
 T_SMALLOC_OBJS = t/stest.o
 T_SMALLOC_OBJS += mutex.o smalloc.o t/log.o
@@ -113,6 +114,9 @@ ghelpers.o: ghelpers.c ghelpers.h
 gerror.o: gerror.c gerror.h
        $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gerror.c
 
+gclient.o: gclient.c gclient.h
+       $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gclient.c
+
 gfio.o: gfio.c ghelpers.c
        $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gfio.c
 
diff --git a/gclient.c b/gclient.c
new file mode 100644 (file)
index 0000000..dfdd870
--- /dev/null
+++ b/gclient.c
@@ -0,0 +1,1349 @@
+#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"
+
+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 GtkActionEntry results_menu_items[] = {
+       { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
+       { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
+       { "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=\"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, p->log_usec / 1000);
+
+       gdk_threads_enter();
+
+       gtk_list_store_append(ui->log_model, &iter);
+       gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
+       gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
+       gtk_list_store_set(ui->log_model, &iter, 2, p->level, -1);
+       gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
+
+       if (p->level == FIO_LOG_ERR)
+               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, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
+               graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
+               graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
+
+               free(rate_str[0]);
+               free(rate_str[1]);
+               free(iops_str[0]);
+               free(iops_str[1]);
+       }
+
+       if (eta_str[0]) {
+               char *dst = output + strlen(output);
+
+               sprintf(dst, " - %s", eta_str);
+       }
+               
+       gfio_update_thread_status(ge, output, perc);
+       gdk_threads_leave();
+}
+
+/*
+ * Update ETA in main window for all clients
+ */
+static void gfio_update_all_eta(struct jobs_eta *je)
+{
+       struct gui *ui = &main_ui;
+       static int eta_good;
+       char eta_str[128];
+       char output[256];
+       double perc = 0.0;
+       int i2p = 0;
+
+       gdk_threads_enter();
+
+       eta_str[0] = '\0';
+       output[0] = '\0';
+
+       if (je->eta_sec != INT_MAX && je->elapsed_sec) {
+               perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
+               eta_to_str(eta_str, je->eta_sec);
+       }
+
+#if 0
+       if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
+       if (je->m_rate || je->t_rate) {
+               char *tr, *mr;
+
+               mr = num2str(je->m_rate, 4, 0, i2p);
+               tr = num2str(je->t_rate, 4, 0, i2p);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta);
+               p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
+               free(tr);
+               free(mr);
+       } else if (je->m_iops || je->t_iops)
+               p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
+
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
+       gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
+#endif
+
+       entry_set_int_value(ui->eta.jobs, je->nr_running);
+
+       if (je->eta_sec != INT_MAX && je->nr_running) {
+               char *iops_str[2];
+               char *rate_str[2];
+
+               if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
+                       strcpy(output, "-.-% done");
+               else {
+                       eta_good = 1;
+                       perc *= 100.0;
+                       sprintf(output, "%3.1f%% done", perc);
+               }
+
+               rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
+               rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
+
+               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+               iops_str[1] = num2str(je->iops[1], 4, 1, 0);
+
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
+               gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
+
+               graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
+               graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
+               graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
+
+               free(rate_str[0]);
+               free(rate_str[1]);
+               free(iops_str[0]);
+               free(iops_str[1]);
+       }
+
+       if (eta_str[0]) {
+               char *dst = output + strlen(output);
+
+               sprintf(dst, " - %s", eta_str);
+       }
+               
+       gfio_update_thread_status_all(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;
+       char buf[64];
+
+       os = fio_get_os_string(probe->os);
+       if (!os)
+               os = "unknown";
+
+       arch = fio_get_arch_string(probe->arch);
+       if (!arch)
+               os = "unknown";
+
+       if (!client->name)
+               client->name = strdup((char *) probe->hostname);
+
+       gdk_threads_enter();
+
+       gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
+       gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
+       gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
+       sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
+       gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
+
+       gfio_set_state(ge, GE_STATE_CONNECTED);
+
+       gdk_threads_leave();
+}
+
+static void gfio_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 void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+       struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       struct thread_options *o = &gc->o;
+       struct gui_entry *ge = gc->ge;
+       char *c1, *c2, *c3, *c4;
+       char tmp[80];
+
+       p->thread_number = le32_to_cpu(p->thread_number);
+       p->groupid = le32_to_cpu(p->groupid);
+       convert_thread_options_to_cpu(o, &p->top);
+
+       gdk_threads_enter();
+
+       gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
+
+       gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
+       gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
+
+       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_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, ts_total_io_u(ts), io_u_dist);
+
+       gtk_list_store_append(model, &iter);
+
+       gtk_list_store_set(model, &iter, 0, "Total", -1);
+
+       for (i = 1, j = 0; i < len; i++) {
+               char fbuf[32];
+
+               if (!(add_mask & (1UL << (i - 1))))
+                       sprintf(fbuf, "0.0%%");
+               else {
+                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
+                       j++;
+               }
+
+               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       }
+
+}
+
+static void gfio_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;
+       GType types[FIO_IO_U_MAP_NR + 1];
+       int i;
+       const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
+       const int nr_labels = ARRAYSIZE(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, FALSE, 3);
+}
+
+static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
+{
+       GtkWidget *box, *frame, *entry;
+       double usr_cpu, sys_cpu;
+       unsigned long runtime;
+       char tmp[32];
+
+       runtime = ts->total_run_time;
+       if (runtime) {
+               double runt = (double) runtime;
+
+               usr_cpu = (double) ts->usr_time * 100 / runt;
+               sys_cpu = (double) ts->sys_time * 100 / runt;
+       } else {
+               usr_cpu = 0;
+               sys_cpu = 0;
+       }
+
+       frame = gtk_frame_new("OS resources");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       entry = new_info_entry_in_frame(box, "User CPU");
+       sprintf(tmp, "%3.2f%%", usr_cpu);
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+       entry = new_info_entry_in_frame(box, "System CPU");
+       sprintf(tmp, "%3.2f%%", sys_cpu);
+       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
+       entry = new_info_entry_in_frame(box, "Context switches");
+       entry_set_int_value(entry, ts->ctx);
+       entry = new_info_entry_in_frame(box, "Major faults");
+       entry_set_int_value(entry, ts->majf);
+       entry = new_info_entry_in_frame(box, "Minor faults");
+       entry_set_int_value(entry, ts->minf);
+}
+
+static 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_add_label(g, labels[i]);
+               graph_add_data(g, labels[i], 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(w->window);
+#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)
+{
+       struct graph *g = data;
+
+       graph_set_size(g, w->allocation.width, w->allocation.height);
+       graph_set_size(g, w->allocation.width, w->allocation.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), "expose_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, FALSE, 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++) {
+               char fbuf[8];
+
+               sprintf(fbuf, "%2.2f%%", plist[i].u.f);
+               graph_add_label(g, fbuf);
+               graph_add_data(g, fbuf, (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), "expose_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, FALSE, 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, FALSE, FALSE, 5);
+
+       main_vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), main_vbox);
+
+       box = gtk_hbox_new(FALSE, 3);
+       gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
+
+       label = new_info_label_in_frame(box, "IO");
+       gtk_label_set_text(GTK_LABEL(label), io_p);
+       label = new_info_label_in_frame(box, "Bandwidth");
+       gtk_label_set_text(GTK_LABEL(label), bw_p);
+       label = new_info_label_in_frame(box, "IOPS");
+       gtk_label_set_text(GTK_LABEL(label), iops_p);
+       label = new_info_label_in_frame(box, "Runtime (msec)");
+       label_set_int_value(label, ts->runtime[ddir]);
+
+       if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
+               double p_of_agg = 100.0;
+               const char *bw_str = "KB";
+               char tmp[32];
+
+               if (rs->agg[ddir]) {
+                       p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
+                       if (p_of_agg > 100.0)
+                               p_of_agg = 100.0;
+               }
+
+               if (mean[0] > 999999.9) {
+                       min[0] /= 1000.0;
+                       max[0] /= 1000.0;
+                       mean[0] /= 1000.0;
+                       dev[0] /= 1000.0;
+                       bw_str = "MB";
+               }
+
+               sprintf(tmp, "Bandwidth (%s)", bw_str);
+               frame = gtk_frame_new(tmp);
+               gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
+
+               box = gtk_hbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), box);
+
+               label = new_info_label_in_frame(box, "Minimum");
+               label_set_int_value(label, min[0]);
+               label = new_info_label_in_frame(box, "Maximum");
+               label_set_int_value(label, max[0]);
+               label = new_info_label_in_frame(box, "Percentage of jobs");
+               sprintf(tmp, "%3.2f%%", p_of_agg);
+               gtk_label_set_text(GTK_LABEL(label), tmp);
+               label = new_info_label_in_frame(box, "Average");
+               sprintf(tmp, "%5.02f", mean[0]);
+               gtk_label_set_text(GTK_LABEL(label), tmp);
+               label = new_info_label_in_frame(box, "Standard deviation");
+               sprintf(tmp, "%5.02f", dev[0]);
+               gtk_label_set_text(GTK_LABEL(label), tmp);
+       }
+
+       if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
+               flags |= GFIO_SLAT;
+       if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
+               flags |= GFIO_CLAT;
+       if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
+               flags |= GFIO_LAT;
+
+       if (flags) {
+               frame = gtk_frame_new("Latency");
+               gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
+
+               vbox = gtk_vbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+               if (flags & GFIO_SLAT)
+                       gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
+               if (flags & GFIO_CLAT)
+                       gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
+               if (flags & GFIO_LAT)
+                       gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
+       }
+
+       if (ts->clat_percentiles)
+               gfio_show_clat_percentiles(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,
+       .timed_out              = gfio_client_timed_out,
+       .stop                   = gfio_client_stop,
+       .start                  = gfio_client_start,
+       .job_start              = gfio_client_job_start,
+       .iolog                  = gfio_client_iolog,
+       .removed                = gfio_client_removed,
+       .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
+       .stay_connected         = 1,
+       .client_type            = FIO_CLIENT_TYPE_GUI,
+};
diff --git a/gclient.h b/gclient.h
new file mode 100644 (file)
index 0000000..1fe86e7
--- /dev/null
+++ b/gclient.h
@@ -0,0 +1,8 @@
+#ifndef GFIO_CLIENT_H
+#define GFIO_CLIENT_H
+
+extern struct client_ops gfio_client_ops;
+
+extern void gfio_display_end_results(struct gfio_client *);
+
+#endif
index 82e0dd2..c0a4679 100644 (file)
--- a/gerror.c
+++ b/gerror.c
@@ -57,4 +57,19 @@ void gfio_report_error(struct gui_entry *ge, const char *format, ...)
        g_error_free(error);
 }
 
+void gfio_report_info(struct gui *ui, const char *title, const char *message)
+{
+       GtkWidget *dialog, *content, *label;
+
+       dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window),
+                       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
 
+       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       label = gtk_label_new(message);
+       gtk_container_add(GTK_CONTAINER(content), label);
+       gtk_widget_show_all(dialog);
+       gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+       gtk_dialog_run(GTK_DIALOG(dialog));
+       gtk_widget_destroy(dialog);
+}
index 69b1219..3767f92 100644 (file)
--- a/gerror.h
+++ b/gerror.h
@@ -2,5 +2,6 @@
 #define GFIO_ERROR_H
 
 extern void gfio_report_error(struct gui_entry *ge, const char *format, ...);
+extern void gfio_report_info(struct gui *ui, const char *title, const char *message);
 
 #endif
diff --git a/gfio.c b/gfio.c
index 4bee83b..d1d1313 100644 (file)
--- a/gfio.c
+++ b/gfio.c
 #include "ghelpers.h"
 #include "goptions.h"
 #include "gerror.h"
+#include "gclient.h"
 #include "graph.h"
 
 static int gfio_server_running;
-static const char *gfio_graph_font;
 static unsigned int gfio_graph_limit = 100;
-static GdkColor white;
 
-static void view_log(GtkWidget *w, gpointer data);
+GdkColor gfio_color_white;
+const char *gfio_graph_font;
 
 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
 
@@ -75,9 +75,6 @@ static struct button_spec {
        },
 };
 
-static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc);
-static void gfio_update_thread_status_all(struct gui *ui, char *status_message, double perc);
-
 static struct graph *setup_iops_graph(void)
 {
        struct graph *g;
@@ -117,7 +114,7 @@ static void setup_graphs(struct gfio_graphs *g)
        g->bandwidth_graph = setup_bandwidth_graph();
 }
 
-static void clear_ge_ui_info(struct gui_entry *ge)
+void clear_ge_ui_info(struct gui_entry *ge)
 {
        gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
        gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
@@ -139,24 +136,6 @@ static void clear_ge_ui_info(struct gui_entry *ge)
        gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
 }
 
-static void show_info_dialog(struct gui *ui, const char *title,
-                            const char *message)
-{
-       GtkWidget *dialog, *content, *label;
-
-       dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window),
-                       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
-                       GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
-
-       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
-       label = gtk_label_new(message);
-       gtk_container_add(GTK_CONTAINER(content), label);
-       gtk_widget_show_all(dialog);
-       gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
-       gtk_dialog_run(GTK_DIALOG(dialog));
-       gtk_widget_destroy(dialog);
-}
-
 static void set_menu_entry_text(struct gui *ui, const char *path,
                                const char *text)
 {
@@ -234,14 +213,9 @@ static void update_button_states(struct gui *ui, struct gui_entry *ge)
        const char *connect_str = NULL;
 
        switch (ge->state) {
-       default: {
-               char tmp[80];
-
-               sprintf(tmp, "Bad client state: %u\n", ge->state);
-               show_info_dialog(ui, "Error", tmp);
+       default:
+               gfio_report_error(ge, "Bad client state: %u\n", ge->state);
                /* fall through to new state */
-               }
-
        case GE_STATE_NEW:
                connect_state = 1;
                edit_state = 1;
@@ -305,7 +279,7 @@ static void update_button_states(struct gui *ui, struct gui_entry *ge)
                set_view_results_visible(ui, 0);
 }
 
-static void gfio_set_state(struct gui_entry *ge, unsigned int state)
+void gfio_set_state(struct gui_entry *ge, unsigned int state)
 {
        ge->state = state;
        update_button_states(ge->ui, ge);
@@ -336,1983 +310,640 @@ static void gfio_ui_setup_log(struct gui *ui)
        ui->log_tree = tree_view;
 }
 
-static struct graph *setup_clat_graph(char *title, unsigned int *ovals,
-                                     fio_fp64_t *plist,
-                                     unsigned int len,
-                                     double xdim, double ydim)
+static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
+                                  gpointer data)
 {
-       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++) {
-               char fbuf[8];
-
-               sprintf(fbuf, "%2.2f%%", plist[i].u.f);
-               graph_add_label(g, fbuf);
-               graph_add_data(g, fbuf, (double) ovals[i]);
-       }
+       struct gfio_graphs *g = data;
 
-       return g;
+       graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
+       graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
+       graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
+       graph_set_position(g->bandwidth_graph, 0, 0);
+       return TRUE;
 }
 
-static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
-                                              fio_fp64_t *plist,
-                                              unsigned int len,
-                                              const char *base,
-                                              unsigned int scale)
+static void draw_graph(struct graph *g, cairo_t *cr)
 {
-       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];
+       line_graph_draw(g, cr);
+       cairo_stroke(cr);
+}
 
-               sprintf(fbuf, "%2.2f%%", plist[i].u.f);
-               tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
-       }
+static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
+                             gboolean keyboard_mode, GtkTooltip *tooltip,
+                             gpointer data)
+{
+       struct gfio_graphs *g = data;
+       const char *text = NULL;
 
-       gtk_list_store_append(model, &iter);
+       if (graph_contains_xy(g->iops_graph, x, y))
+               text = graph_find_tooltip(g->iops_graph, x, y);
+       else if (graph_contains_xy(g->bandwidth_graph, x, y))
+               text = graph_find_tooltip(g->bandwidth_graph, x, y);
 
-       for (i = 0; i < len; i++) {
-               if (scale)
-                       ovals[i] = (ovals[i] + 999) / 1000;
-               gtk_list_store_set(model, &iter, i, ovals[i], -1);
+       if (text) {
+               gtk_tooltip_set_text(tooltip, text);
+               return TRUE;
        }
 
-       return tree_view;
+       return FALSE;
 }
 
-static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
+static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
 {
-       struct graph *g = p;
+       struct gfio_graphs *g = p;
        cairo_t *cr;
 
        cr = gdk_cairo_create(w->window);
-#if 0
-       if (graph_has_tooltips(g)) {
+
+       if (graph_has_tooltips(g->iops_graph) ||
+           graph_has_tooltips(g->bandwidth_graph)) {
                g_object_set(w, "has-tooltip", TRUE, NULL);
-               g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g);
+               g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
        }
-#endif
+
        cairo_set_source_rgb(cr, 0, 0, 0);
-       bar_graph_draw(g, cr);
+       draw_graph(g->iops_graph, cr);
+       draw_graph(g->bandwidth_graph, cr);
        cairo_destroy(cr);
 
        return FALSE;
 }
 
-static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event,
-                                      gpointer data)
+/*
+ * FIXME: need more handling here
+ */
+static void ge_destroy(struct gui_entry *ge)
 {
-       struct graph *g = data;
-
-       graph_set_size(g, w->allocation.width, w->allocation.height);
-       graph_set_size(g, w->allocation.width, w->allocation.height);
-       graph_set_position(g, 0, 0);
-       return TRUE;
-}
+       struct gfio_client *gc = ge->client;
 
-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;
+       if (gc && gc->client) {
+               if (ge->state >= GE_STATE_CONNECTED)
+                       fio_client_terminate(gc->client);
 
-       /*
-        * 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";
+               fio_put_client(gc->client);
        }
 
-       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, &white);
-       gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
-       g_signal_connect(G_OBJECT(drawing_area), "expose_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, FALSE, 3);
-out:
-       if (ovals)
-               free(ovals);
+       free(ge->job_file);
+       free(ge->host);
+       flist_del(&ge->list);
+       free(ge);
 }
 
-static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
-                         unsigned long max, double mean, double dev)
+static void ge_widget_destroy(GtkWidget *w, gpointer data)
 {
-       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);
+       struct gui_entry *ge = (struct gui_entry *) data;
 
+       ge_destroy(ge);
 }
 
-#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)
+static void gfio_quit(struct gui *ui)
 {
-       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;
+       struct gui_entry *ge;
 
-       i2p = is_power_of_2(rs->kb_base);
-       runt = ts->runtime[ddir];
+       while (!flist_empty(&ui->list)) {
+               ge = flist_entry(ui->list.next, struct gui_entry, list);
+               ge_destroy(ge);
+       }
 
-       bw = (1000 * ts->io_bytes[ddir]) / runt;
-       io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
-       bw_p = num2str(bw, 6, 1, i2p);
+        gtk_main_quit();
+}
 
-       iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
-       iops_p = num2str(iops, 6, 1, 0);
+static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
+                __attribute__((unused)) gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
 
-       box = gtk_hbox_new(FALSE, 3);
-       gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
+       gfio_quit(ui);
+}
 
-       frame = gtk_frame_new(ddir_label[ddir]);
-       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
+static void *job_thread(void *arg)
+{
+       struct gui *ui = arg;
 
-       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;
-               }
+       ui->handler_running = 1;
+       fio_handle_clients(&gfio_client_ops);
+       ui->handler_running = 0;
+       return NULL;
+}
 
-               if (mean[0] > 999999.9) {
-                       min[0] /= 1000.0;
-                       max[0] /= 1000.0;
-                       mean[0] /= 1000.0;
-                       dev[0] /= 1000.0;
-                       bw_str = "MB";
-               }
+static int send_job_file(struct gui_entry *ge)
+{
+       struct gfio_client *gc = ge->client;
+       int ret = 0;
 
-               sprintf(tmp, "Bandwidth (%s)", bw_str);
-               frame = gtk_frame_new(tmp);
-               gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
+       ret = fio_client_send_ini(gc->client, ge->job_file);
+       if (!ret)
+               return 0;
 
-               box = gtk_hbox_new(FALSE, 3);
-               gtk_container_add(GTK_CONTAINER(frame), box);
+       gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
+       return 1;
+}
 
-               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);
-       }
+static void *server_thread(void *arg)
+{
+       is_backend = 1;
+       gfio_server_running = 1;
+       fio_start_server(NULL);
+       gfio_server_running = 0;
+       return NULL;
+}
 
-       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]);
+static void gfio_start_server(struct gui *ui)
+{
+       if (!gfio_server_running) {
+               gfio_server_running = 1;
+               pthread_create(&ui->server_t, NULL, server_thread, NULL);
+               pthread_detach(ui->server_t);
        }
-
-       if (ts->clat_percentiles)
-               gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
-
-       free(io_p);
-       free(bw_p);
-       free(iops_p);
 }
 
-static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
-                                           const char **labels,
-                                           unsigned int len,
-                                           double xdim, double ydim)
+static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
+                gpointer data)
 {
-       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_add_label(g, labels[i]);
-               graph_add_data(g, labels[i], lat[i]);
-       }
+       struct gui_entry *ge = data;
+       struct gfio_client *gc = ge->client;
 
-       return g;
+       if (gc)
+               fio_start_client(gc->client);
 }
 
-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;
+static void file_open(GtkWidget *w, gpointer data);
 
-       types = malloc(num * sizeof(GType));
+struct connection_widgets
+{
+       GtkWidget *hentry;
+       GtkWidget *combo;
+       GtkWidget *button;
+};
 
-       for (i = 0; i < num; i++)
-               types[i] = G_TYPE_STRING;
+static void hostname_cb(GtkEntry *entry, gpointer data)
+{
+       struct connection_widgets *cw = data;
+       int uses_net = 0, is_localhost = 0;
+       const gchar *text;
+       gchar *ctext;
 
-       model = gtk_list_store_newv(num, types);
-       free(types);
-       types = NULL;
+       /*
+        * Check whether to display the 'auto start backend' box
+        * or not. Show it if we are a localhost and using network,
+        * or using a socket.
+        */
+       ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
+       if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
+               uses_net = 1;
+       g_free(ctext);
 
-       tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
-       gtk_widget_set_can_focus(tree_view, FALSE);
+       if (uses_net) {
+               text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
+               if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
+                   !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
+                   !strcmp(text, "ip6-loopback"))
+                       is_localhost = 1;
+       }
 
-       g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
-               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+       if (!uses_net || is_localhost) {
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
+               gtk_widget_set_sensitive(cw->button, 1);
+       } else {
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
+               gtk_widget_set_sensitive(cw->button, 0);
+       }
+       }
 
-       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
-       gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+       static int get_connection_details(struct gui_entry *ge)
+       {
+               GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
+               struct connection_widgets cw;
+               struct gui *ui = ge->ui;
+               char *typeentry;
+
+               if (ge->host)
+                       return 0;
+
+               dialog = gtk_dialog_new_with_buttons("Connection details",
+                               GTK_WINDOW(ui->window),
+                               GTK_DIALOG_DESTROY_WITH_PARENT,
+                               GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                               GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+
+               frame = gtk_frame_new("Hostname / socket name");
+               /* gtk_dialog_get_content_area() is 2.14 and newer */
+               vbox = GTK_DIALOG(dialog)->vbox;
+               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+               box = gtk_vbox_new(FALSE, 6);
+               gtk_container_add(GTK_CONTAINER(frame), box);
 
-       for (i = 0; i < num; i++)
-               tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
+               hbox = gtk_hbox_new(TRUE, 10);
+               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+               cw.hentry = gtk_entry_new();
+               gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
+               gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
 
-       gtk_list_store_append(model, &iter);
+               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);
 
-       for (i = 0; i < num; i++) {
-               char fbuf[32];
+               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);
 
-               if (lat[i] <= 0.0)
-                       sprintf(fbuf, "0.00");
-               else
-                       sprintf(fbuf, "%3.2f%%", lat[i]);
+               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);
 
-               gtk_list_store_set(model, &iter, i, fbuf, -1);
-       }
+               hbox = gtk_hbox_new(TRUE, 4);
+               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
 
-       return tree_view;
-}
+               cw.combo = gtk_combo_box_new_text();
+               gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
+               gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
+               gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
+               gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
 
-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]);
+               gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
 
-       /*
-        * 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;
+               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);
 
-               if (start == -1U)
-                       start = i;
-               end = i;
-       }
+               hbox = gtk_hbox_new(TRUE, 4);
+               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
 
-       /*
-        * 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, &white);
-       gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
-       g_signal_connect(G_OBJECT(drawing_area), "expose_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, FALSE, 3);
-}
+               cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
+               gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
+               gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
 
-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];
+               /*
+                * Connect edit signal, so we can show/not-show the auto start button
+                */
+               g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
+               g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
 
-       runtime = ts->total_run_time;
-       if (runtime) {
-               double runt = (double) runtime;
+               gtk_widget_show_all(dialog);
 
-               usr_cpu = (double) ts->usr_time * 100 / runt;
-               sys_cpu = (double) ts->sys_time * 100 / runt;
-       } else {
-               usr_cpu = 0;
-               sys_cpu = 0;
-       }
+               if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+                       gtk_widget_destroy(dialog);
+                       return 1;
+               }
 
-       frame = gtk_frame_new("OS resources");
-       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-
-       box = gtk_hbox_new(FALSE, 3);
-       gtk_container_add(GTK_CONTAINER(frame), box);
-
-       entry = new_info_entry_in_frame(box, "User CPU");
-       sprintf(tmp, "%3.2f%%", usr_cpu);
-       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
-       entry = new_info_entry_in_frame(box, "System CPU");
-       sprintf(tmp, "%3.2f%%", sys_cpu);
-       gtk_entry_set_text(GTK_ENTRY(entry), tmp);
-       entry = new_info_entry_in_frame(box, "Context switches");
-       entry_set_int_value(entry, ts->ctx);
-       entry = new_info_entry_in_frame(box, "Major faults");
-       entry_set_int_value(entry, ts->majf);
-       entry = new_info_entry_in_frame(box, "Minor faults");
-       entry_set_int_value(entry, ts->minf);
-}
-static void gfio_add_sc_depths_tree(GtkListStore *model,
-                                   struct thread_stat *ts, unsigned int len,
-                                   int submit)
-{
-       double io_u_dist[FIO_IO_U_MAP_NR];
-       GtkTreeIter iter;
-       /* Bits 0, and 3-8 */
-       const int add_mask = 0x1f9;
-       int i, j;
-
-       if (submit)
-               stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
-       else
-               stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
+               ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
+               ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
 
-       gtk_list_store_append(model, &iter);
+               typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
+               if (!typeentry || !strncmp(typeentry, "IPv4", 4))
+                       ge->type = Fio_client_ipv4;
+               else if (!strncmp(typeentry, "IPv6", 4))
+                       ge->type = Fio_client_ipv6;
+               else
+                       ge->type = Fio_client_socket;
+               g_free(typeentry);
 
-       gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
+               ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
 
-       for (i = 1, j = 0; i < len; i++) {
-               char fbuf[32];
+               gtk_widget_destroy(dialog);
+               return 0;
+       }
 
-               if (!(add_mask & (1UL << (i - 1))))
-                       sprintf(fbuf, "0.0%%");
-               else {
-                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
-                       j++;
-               }
+       static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
+       {
+               gc->client = fio_get_client(client);
+               client->client_data = gc;
+       }
 
-               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
+       {
+               struct gfio_client *gc;
+
+               gc = malloc(sizeof(*gc));
+               memset(gc, 0, sizeof(*gc));
+               options_default_fill(&gc->o);
+               gc->ge = ge;
+               ge->client = gc;
+               gfio_set_client(gc, client);
        }
 
-}
+       static void connect_clicked(GtkWidget *widget, gpointer data)
+       {
+               struct gui_entry *ge = data;
+               struct gfio_client *gc = ge->client;
 
-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;
+               if (ge->state == GE_STATE_NEW) {
+                       int ret;
+
+                       if (!ge->job_file)
+                               file_open(widget, ge->ui);
+                       if (!ge->job_file)
+                               return;
 
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+                       gc = ge->client;
 
-       gtk_list_store_append(model, &iter);
+                       if (!gc->client) {
+                               struct fio_client *client;
 
-       gtk_list_store_set(model, &iter, 0, "Total", -1);
+                               if (get_connection_details(ge)) {
+                                       gfio_report_error(ge, "Failed to get connection details\n");
+                                       return;
+                               }
 
-       for (i = 1, j = 0; i < len; i++) {
-               char fbuf[32];
+                               client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
+                               if (!client) {
+                                       gfio_report_error(ge, "Failed to add client %s\n", ge->host);
+                                       free(ge->host);
+                                       ge->host = NULL;
+                                       return;
+                               }
+                               gfio_set_client(gc, client);
+                       }
 
-               if (!(add_mask & (1UL << (i - 1))))
-                       sprintf(fbuf, "0.0%%");
-               else {
-                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
-                       j++;
+                       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
+                       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
+                       ret = fio_client_connect(gc->client);
+                       if (!ret) {
+                               if (!ge->ui->handler_running)
+                                       pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
+                               gfio_set_state(ge, GE_STATE_CONNECTED);
+                       } else {
+                               gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
+                       }
+               } else {
+                       fio_client_terminate(gc->client);
+                       gfio_set_state(ge, GE_STATE_NEW);
+                       clear_ge_ui_info(ge);
                }
-
-               gtk_list_store_set(model, &iter, i, fbuf, -1);
        }
 
-}
+       static void send_clicked(GtkWidget *widget, gpointer data)
+       {
+               struct gui_entry *ge = data;
 
-static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
-{
-       GtkWidget *frame, *box, *tree_view = NULL;
-       GtkTreeSelection *selection;
-       GtkListStore *model;
-       GType types[FIO_IO_U_MAP_NR + 1];
-       int i;
-       const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
-       const int nr_labels = ARRAYSIZE(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, FALSE, 3);
-}
-
-static gboolean results_window_delete(GtkWidget *w, gpointer data)
-{
-       struct gui_entry *ge = (struct gui_entry *) data;
-
-       gtk_widget_destroy(w);
-       ge->results_window = NULL;
-       ge->results_notebook = NULL;
-       return TRUE;
-}
-
-static void results_close(GtkWidget *w, gpointer *data)
-{
-       struct gui_entry *ge = (struct gui_entry *) data;
-
-       gtk_widget_destroy(ge->results_window);
-}
-
-static GtkActionEntry results_menu_items[] = {
-       { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
-       { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
-       { "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=\"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 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 *get_scrolled_window(gint border_width)
-{
-       GtkWidget *scroll;
-
-       scroll = gtk_scrolled_window_new(NULL, NULL);
-       gtk_container_set_border_width(GTK_CONTAINER(scroll), border_width);
-       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-
-       return scroll;
-}
-
-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_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_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);
-}
-
-static 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_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, p->log_usec / 1000);
-
-       gdk_threads_enter();
-
-       gtk_list_store_append(ui->log_model, &iter);
-       gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
-       gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
-       gtk_list_store_set(ui->log_model, &iter, 2, p->level, -1);
-       gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
-
-       if (p->level == FIO_LOG_ERR)
-               view_log(NULL, (gpointer) ui);
-
-       gdk_threads_leave();
-}
-
-static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
-{
-       struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
-       struct gfio_client *gc = client->client_data;
-       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 gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
-                                  gpointer data)
-{
-       struct gfio_graphs *g = data;
-
-       graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
-       graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
-       graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
-       graph_set_position(g->bandwidth_graph, 0, 0);
-       return TRUE;
-}
-
-static void draw_graph(struct graph *g, cairo_t *cr)
-{
-       line_graph_draw(g, cr);
-       cairo_stroke(cr);
-}
-
-static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
-                             gboolean keyboard_mode, GtkTooltip *tooltip,
-                             gpointer data)
-{
-       struct gfio_graphs *g = data;
-       const char *text = NULL;
-
-       if (graph_contains_xy(g->iops_graph, x, y))
-               text = graph_find_tooltip(g->iops_graph, x, y);
-       else if (graph_contains_xy(g->bandwidth_graph, x, y))
-               text = graph_find_tooltip(g->bandwidth_graph, x, y);
-
-       if (text) {
-               gtk_tooltip_set_text(tooltip, text);
-               return TRUE;
-       }
-
-       return FALSE;
-}
-
-static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
-{
-       struct gfio_graphs *g = p;
-       cairo_t *cr;
-
-       cr = gdk_cairo_create(w->window);
-
-       if (graph_has_tooltips(g->iops_graph) ||
-           graph_has_tooltips(g->bandwidth_graph)) {
-               g_object_set(w, "has-tooltip", TRUE, NULL);
-               g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
-       }
-
-       cairo_set_source_rgb(cr, 0, 0, 0);
-       draw_graph(g->iops_graph, cr);
-       draw_graph(g->bandwidth_graph, cr);
-       cairo_destroy(cr);
-
-       return FALSE;
-}
-
-/*
- * Client specific ETA
- */
-static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
-{
-       struct gfio_client *gc = client->client_data;
-       struct gui_entry *ge = gc->ge;
-       static int eta_good;
-       char eta_str[128];
-       char output[256];
-       char tmp[32];
-       double perc = 0.0;
-       int i2p = 0;
-
-       gdk_threads_enter();
-
-       eta_str[0] = '\0';
-       output[0] = '\0';
-
-       if (je->eta_sec != INT_MAX && je->elapsed_sec) {
-               perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
-               eta_to_str(eta_str, je->eta_sec);
-       }
-
-       sprintf(tmp, "%u", je->nr_running);
-       gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
-       sprintf(tmp, "%u", je->files_open);
-       gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
-
-#if 0
-       if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
-       if (je->m_rate || je->t_rate) {
-               char *tr, *mr;
-
-               mr = num2str(je->m_rate, 4, 0, i2p);
-               tr = num2str(je->t_rate, 4, 0, i2p);
-               gtk_entry_set_text(GTK_ENTRY(ge->eta);
-               p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
-               free(tr);
-               free(mr);
-       } else if (je->m_iops || je->t_iops)
-               p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
-
-       gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
-       gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
-       gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
-       gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
-#endif
-
-       if (je->eta_sec != INT_MAX && je->nr_running) {
-               char *iops_str[2];
-               char *rate_str[2];
-
-               if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
-                       strcpy(output, "-.-% done");
-               else {
-                       eta_good = 1;
-                       perc *= 100.0;
-                       sprintf(output, "%3.1f%% done", perc);
-               }
-
-               rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
-               rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
-
-               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
-               iops_str[1] = num2str(je->iops[1], 4, 1, 0);
-
-               gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
-               gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
-               gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
-               gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
-
-               graph_add_xy_data(ge->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
-               graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
-               graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
-               graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
-
-               free(rate_str[0]);
-               free(rate_str[1]);
-               free(iops_str[0]);
-               free(iops_str[1]);
-       }
-
-       if (eta_str[0]) {
-               char *dst = output + strlen(output);
-
-               sprintf(dst, " - %s", eta_str);
-       }
-               
-       gfio_update_thread_status(ge, output, perc);
-       gdk_threads_leave();
-}
-
-/*
- * Update ETA in main window for all clients
- */
-static void gfio_update_all_eta(struct jobs_eta *je)
-{
-       struct gui *ui = &main_ui;
-       static int eta_good;
-       char eta_str[128];
-       char output[256];
-       double perc = 0.0;
-       int i2p = 0;
-
-       gdk_threads_enter();
-
-       eta_str[0] = '\0';
-       output[0] = '\0';
-
-       if (je->eta_sec != INT_MAX && je->elapsed_sec) {
-               perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
-               eta_to_str(eta_str, je->eta_sec);
-       }
-
-#if 0
-       if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
-       if (je->m_rate || je->t_rate) {
-               char *tr, *mr;
-
-               mr = num2str(je->m_rate, 4, 0, i2p);
-               tr = num2str(je->t_rate, 4, 0, i2p);
-               gtk_entry_set_text(GTK_ENTRY(ui->eta);
-               p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
-               free(tr);
-               free(mr);
-       } else if (je->m_iops || je->t_iops)
-               p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
-
-       gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
-       gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
-       gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
-       gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
-#endif
-
-       entry_set_int_value(ui->eta.jobs, je->nr_running);
-
-       if (je->eta_sec != INT_MAX && je->nr_running) {
-               char *iops_str[2];
-               char *rate_str[2];
-
-               if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
-                       strcpy(output, "-.-% done");
-               else {
-                       eta_good = 1;
-                       perc *= 100.0;
-                       sprintf(output, "%3.1f%% done", perc);
-               }
-
-               rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
-               rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
-
-               iops_str[0] = num2str(je->iops[0], 4, 1, 0);
-               iops_str[1] = num2str(je->iops[1], 4, 1, 0);
-
-               gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
-               gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
-               gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
-               gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
-
-               graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
-               graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
-               graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
-               graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
-
-               free(rate_str[0]);
-               free(rate_str[1]);
-               free(iops_str[0]);
-               free(iops_str[1]);
-       }
-
-       if (eta_str[0]) {
-               char *dst = output + strlen(output);
-
-               sprintf(dst, " - %s", eta_str);
-       }
-               
-       gfio_update_thread_status_all(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;
-       char buf[64];
-
-       os = fio_get_os_string(probe->os);
-       if (!os)
-               os = "unknown";
-
-       arch = fio_get_arch_string(probe->arch);
-       if (!arch)
-               os = "unknown";
-
-       if (!client->name)
-               client->name = strdup((char *) probe->hostname);
-
-       gdk_threads_enter();
-
-       gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
-       gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
-       gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
-       sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
-       gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
-
-       gfio_set_state(ge, GE_STATE_CONNECTED);
-
-       gdk_threads_leave();
-}
-
-static void gfio_update_thread_status(struct gui_entry *ge,
-                                     char *status_message, double perc)
-{
-       static char message[100];
-       const char *m = message;
-
-       strncpy(message, status_message, sizeof(message) - 1);
-       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
-       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
-       gtk_widget_queue_draw(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);
-}
-
-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 void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
-{
-       struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
-       struct gfio_client *gc = client->client_data;
-       struct thread_options *o = &gc->o;
-       struct gui_entry *ge = gc->ge;
-       char *c1, *c2, *c3, *c4;
-       char tmp[80];
-
-       p->thread_number = le32_to_cpu(p->thread_number);
-       p->groupid = le32_to_cpu(p->groupid);
-       convert_thread_options_to_cpu(o, &p->top);
-
-       gdk_threads_enter();
-
-       gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
-
-       gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
-       gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
-
-       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_client_timed_out(struct fio_client *client)
-{
-       struct gfio_client *gc = client->client_data;
-       char buf[256];
-
-       gdk_threads_enter();
-
-       gfio_set_state(gc->ge, GE_STATE_NEW);
-       clear_ge_ui_info(gc->ge);
-
-       sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
-       show_info_dialog(gc->ge->ui, "Network timeout", buf);
-
-       gdk_threads_leave();
-}
-
-static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
-{
-       struct gfio_client *gc = client->client_data;
-
-       gdk_threads_enter();
-
-       gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
-
-       if (gc->err_entry)
-               entry_set_int_value(gc->err_entry, client->error);
-
-       gdk_threads_leave();
-}
-
-static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
-{
-       struct gfio_client *gc = client->client_data;
-
-       gdk_threads_enter();
-       gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
-       gdk_threads_leave();
-}
-
-static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
-{
-       struct gfio_client *gc = client->client_data;
-
-       gdk_threads_enter();
-       gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
-       gdk_threads_leave();
-}
-
-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_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,
-       .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,
-};
-
-/*
- * FIXME: need more handling here
- */
-static void ge_destroy(struct gui_entry *ge)
-{
-       struct gfio_client *gc = ge->client;
-
-       if (gc && gc->client) {
-               if (ge->state >= GE_STATE_CONNECTED)
-                       fio_client_terminate(gc->client);
-
-               fio_put_client(gc->client);
-       }
-
-       free(ge->job_file);
-       free(ge->host);
-       flist_del(&ge->list);
-       free(ge);
-}
-
-static void ge_widget_destroy(GtkWidget *w, gpointer data)
-{
-       struct gui_entry *ge = (struct gui_entry *) data;
-
-       ge_destroy(ge);
-}
-
-static void gfio_quit(struct gui *ui)
-{
-       struct gui_entry *ge;
-
-       while (!flist_empty(&ui->list)) {
-               ge = flist_entry(ui->list.next, struct gui_entry, list);
-               ge_destroy(ge);
-       }
-
-        gtk_main_quit();
-}
-
-static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
-                __attribute__((unused)) gpointer data)
-{
-       struct gui *ui = (struct gui *) data;
-
-       gfio_quit(ui);
-}
-
-static void *job_thread(void *arg)
-{
-       struct gui *ui = arg;
-
-       ui->handler_running = 1;
-       fio_handle_clients(&gfio_client_ops);
-       ui->handler_running = 0;
-       return NULL;
-}
-
-static int send_job_file(struct gui_entry *ge)
-{
-       struct gfio_client *gc = ge->client;
-       int ret = 0;
-
-       ret = fio_client_send_ini(gc->client, ge->job_file);
-       if (!ret)
-               return 0;
-
-       gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
-       return 1;
-}
-
-static void *server_thread(void *arg)
-{
-       is_backend = 1;
-       gfio_server_running = 1;
-       fio_start_server(NULL);
-       gfio_server_running = 0;
-       return NULL;
-}
-
-static void gfio_start_server(struct gui *ui)
-{
-       if (!gfio_server_running) {
-               gfio_server_running = 1;
-               pthread_create(&ui->server_t, NULL, server_thread, NULL);
-               pthread_detach(ui->server_t);
+               if (send_job_file(ge))
+                       gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
        }
-}
-
-static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
-                gpointer data)
-{
-       struct gui_entry *ge = data;
-       struct gfio_client *gc = ge->client;
-
-       if (gc)
-               fio_start_client(gc->client);
-}
-
-static void file_open(GtkWidget *w, gpointer data);
-
-struct connection_widgets
-{
-       GtkWidget *hentry;
-       GtkWidget *combo;
-       GtkWidget *button;
-};
-
-static void hostname_cb(GtkEntry *entry, gpointer data)
-{
-       struct connection_widgets *cw = data;
-       int uses_net = 0, is_localhost = 0;
-       const gchar *text;
-       gchar *ctext;
-
-       /*
-        * Check whether to display the 'auto start backend' box
-        * or not. Show it if we are a localhost and using network,
-        * or using a socket.
-        */
-       ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
-       if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
-               uses_net = 1;
-       g_free(ctext);
 
-       if (uses_net) {
-               text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
-               if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
-                   !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
-                   !strcmp(text, "ip6-loopback"))
-                       is_localhost = 1;
-       }
+       static GtkWidget *new_client_page(struct gui_entry *ge);
 
-       if (!uses_net || is_localhost) {
-               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
-               gtk_widget_set_sensitive(cw->button, 1);
-       } else {
-               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
-               gtk_widget_set_sensitive(cw->button, 0);
+       static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
+       {
+               struct gui_entry *ge;
+
+               ge = malloc(sizeof(*ge));
+               memset(ge, 0, sizeof(*ge));
+               ge->state = GE_STATE_NEW;
+               INIT_FLIST_HEAD(&ge->list);
+               flist_add_tail(&ge->list, &ui->list);
+               ge->ui = ui;
+               return ge;
        }
-}
-
-static int get_connection_details(struct gui_entry *ge)
-{
-       GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
-       struct connection_widgets cw;
-       struct gui *ui = ge->ui;
-       char *typeentry;
-
-       if (ge->host)
-               return 0;
-
-       dialog = gtk_dialog_new_with_buttons("Connection details",
-                       GTK_WINDOW(ui->window),
-                       GTK_DIALOG_DESTROY_WITH_PARENT,
-                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
-                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
-
-       frame = gtk_frame_new("Hostname / socket name");
-       /* gtk_dialog_get_content_area() is 2.14 and newer */
-       vbox = GTK_DIALOG(dialog)->vbox;
-       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-
-       box = gtk_vbox_new(FALSE, 6);
-       gtk_container_add(GTK_CONTAINER(frame), box);
-
-       hbox = gtk_hbox_new(TRUE, 10);
-       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
-       cw.hentry = gtk_entry_new();
-       gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
-       gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
 
-       frame = gtk_frame_new("Port");
-       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-       box = gtk_vbox_new(FALSE, 10);
-       gtk_container_add(GTK_CONTAINER(frame), box);
-
-       hbox = gtk_hbox_new(TRUE, 4);
-       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
-       pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
-
-       frame = gtk_frame_new("Type");
-       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-       box = gtk_vbox_new(FALSE, 10);
-       gtk_container_add(GTK_CONTAINER(frame), box);
+       static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
+       {
+               struct gui_entry *ge;
 
-       hbox = gtk_hbox_new(TRUE, 4);
-       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+               ge = alloc_new_gui_entry(ui);
 
-       cw.combo = gtk_combo_box_new_text();
-       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
-       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
-       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
-       gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
+               ge->vbox = new_client_page(ge);
+               g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
 
-       gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
+               ge->page_label = gtk_label_new(name);
+               ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
 
-       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);
+               gtk_widget_show_all(ui->window);
+               return ge;
+       }
 
-       hbox = gtk_hbox_new(TRUE, 4);
-       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       static void file_new(GtkWidget *w, gpointer data)
+       {
+               struct gui *ui = (struct gui *) data;
+               struct gui_entry *ge;
 
-       cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
-       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
-       gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
-       gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
+               ge = get_new_ge_with_tab(ui, "Untitled");
+               gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+       }
 
        /*
-        * Connect edit signal, so we can show/not-show the auto start button
+        * Return the 'ge' corresponding to the tab. If the active tab is the
+        * main tab, open a new tab.
         */
-       g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
-       g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
-
-       gtk_widget_show_all(dialog);
-
-       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
-               gtk_widget_destroy(dialog);
-               return 1;
-       }
-
-       ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
-       ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
+       static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
+                                                 int *created)
+       {
+               struct flist_head *entry;
+               struct gui_entry *ge;
 
-       typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
-       if (!typeentry || !strncmp(typeentry, "IPv4", 4))
-               ge->type = Fio_client_ipv4;
-       else if (!strncmp(typeentry, "IPv6", 4))
-               ge->type = Fio_client_ipv6;
-       else
-               ge->type = Fio_client_socket;
-       g_free(typeentry);
+               if (!cur_page) {
+                       if (created)
+                               *created = 1;
+                       return get_new_ge_with_tab(ui, "Untitled");
+               }
 
-       ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
+               if (created)
+                       *created = 0;
 
-       gtk_widget_destroy(dialog);
-       return 0;
-}
+               flist_for_each(entry, &ui->list) {
+                       ge = flist_entry(entry, struct gui_entry, list);
+                       if (ge->page_num == cur_page)
+                               return ge;
+               }
 
-static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
-{
-       gc->client = fio_get_client(client);
-       client->client_data = gc;
-}
+               return NULL;
+       }
 
-static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
-{
-       struct gfio_client *gc;
+       static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
+       {
+               gint cur_page;
 
-       gc = malloc(sizeof(*gc));
-       memset(gc, 0, sizeof(*gc));
-       options_default_fill(&gc->o);
-       gc->ge = ge;
-       ge->client = gc;
-       gfio_set_client(gc, client);
-}
+               /*
+                * Main tab is tab 0, so any current page other than 0 holds
+                * a ge entry.
+                */
+               cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
+               if (cur_page)
+                       return get_ge_from_page(ui, cur_page, NULL);
 
-static void connect_clicked(GtkWidget *widget, gpointer data)
-{
-       struct gui_entry *ge = data;
-       struct gfio_client *gc = ge->client;
+               return NULL;
+       }
 
-       if (ge->state == GE_STATE_NEW) {
-               int ret;
+       static void file_close(GtkWidget *w, gpointer data)
+       {
+               struct gui *ui = (struct gui *) data;
+               struct gui_entry *ge;
 
-               if (!ge->job_file)
-                       file_open(widget, ge->ui);
-               if (!ge->job_file)
+               /*
+                * Can't close the main tab
+                */
+               ge = get_ge_from_cur_tab(ui);
+               if (ge) {
+                       gtk_widget_destroy(ge->vbox);
                        return;
-
-               gc = ge->client;
-
-               if (!gc->client) {
-                       struct fio_client *client;
-
-                       if (get_connection_details(ge)) {
-                               gfio_report_error(ge, "Failed to get connection details\n");
-                               return;
-                       }
-
-                       client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
-                       if (!client) {
-                               gfio_report_error(ge, "Failed to add client %s\n", ge->host);
-                               free(ge->host);
-                               ge->host = NULL;
-                               return;
-                       }
-                       gfio_set_client(gc, client);
                }
 
-               gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
-               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
-               ret = fio_client_connect(gc->client);
-               if (!ret) {
-                       if (!ge->ui->handler_running)
-                               pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
-                       gfio_set_state(ge, GE_STATE_CONNECTED);
-               } else {
-                       gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
+               if (!flist_empty(&ui->list)) {
+                       gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
+                       return;
                }
-       } else {
-               fio_client_terminate(gc->client);
-               gfio_set_state(ge, GE_STATE_NEW);
-               clear_ge_ui_info(ge);
-       }
-}
-
-static void send_clicked(GtkWidget *widget, gpointer data)
-{
-       struct gui_entry *ge = data;
-
-       if (send_job_file(ge))
-               gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
-}
-
-static GtkWidget *new_client_page(struct gui_entry *ge);
-
-static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
-{
-       struct gui_entry *ge;
-
-       ge = malloc(sizeof(*ge));
-       memset(ge, 0, sizeof(*ge));
-       ge->state = GE_STATE_NEW;
-       INIT_FLIST_HEAD(&ge->list);
-       flist_add_tail(&ge->list, &ui->list);
-       ge->ui = ui;
-       return ge;
-}
-
-static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
-{
-       struct gui_entry *ge;
-
-       ge = alloc_new_gui_entry(ui);
-
-       ge->vbox = new_client_page(ge);
-       g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
-
-       ge->page_label = gtk_label_new(name);
-       ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
-
-       gtk_widget_show_all(ui->window);
-       return ge;
-}
 
-static void file_new(GtkWidget *w, gpointer data)
-{
-       struct gui *ui = (struct gui *) data;
-       struct gui_entry *ge;
+               gfio_quit(ui);
+       }
 
-       ge = get_new_ge_with_tab(ui, "Untitled");
-       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
-}
+       static void file_add_recent(struct gui *ui, const gchar *uri)
+       {
+               GtkRecentData grd;
 
-/*
- * Return the 'ge' corresponding to the tab. If the active tab is the
- * main tab, open a new tab.
- */
-static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
-                                         int *created)
-{
-       struct flist_head *entry;
-       struct gui_entry *ge;
+               memset(&grd, 0, sizeof(grd));
+               grd.display_name = strdup("gfio");
+               grd.description = strdup("Fio job file");
+               grd.mime_type = strdup(GFIO_MIME);
+               grd.app_name = strdup(g_get_application_name());
+               grd.app_exec = strdup("gfio %f/%u");
 
-       if (!cur_page) {
-               if (created)
-                       *created = 1;
-               return get_new_ge_with_tab(ui, "Untitled");
+               gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
        }
 
-       if (created)
-               *created = 0;
+       static gchar *get_filename_from_uri(const gchar *uri)
+       {
+               if (strncmp(uri, "file://", 7))
+                       return strdup(uri);
 
-       flist_for_each(entry, &ui->list) {
-               ge = flist_entry(entry, struct gui_entry, list);
-               if (ge->page_num == cur_page)
-                       return ge;
+               return strdup(uri + 7);
        }
 
-       return NULL;
-}
-
-static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
-{
-       gint cur_page;
-
-       /*
-        * Main tab is tab 0, so any current page other than 0 holds
-        * a ge entry.
-        */
-       cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
-       if (cur_page)
-               return get_ge_from_page(ui, cur_page, NULL);
+       static int do_file_open(struct gui_entry *ge, const gchar *uri)
+       {
+               struct fio_client *client;
 
-       return NULL;
-}
+               assert(!ge->job_file);
 
-static void file_close(GtkWidget *w, gpointer data)
-{
-       struct gui *ui = (struct gui *) data;
-       struct gui_entry *ge;
+               ge->job_file = get_filename_from_uri(uri);
 
-       /*
-        * Can't close the main tab
-        */
-       ge = get_ge_from_cur_tab(ui);
-       if (ge) {
-               gtk_widget_destroy(ge->vbox);
-               return;
-       }
+               client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
+               if (client) {
+                       gfio_client_added(ge, client);
+                       file_add_recent(ge->ui, uri);
+                       return 0;
+               }
 
-       if (!flist_empty(&ui->list)) {
-               show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
-               return;
+               gfio_report_error(ge, "Failed to add client %s\n", ge->host);
+               free(ge->host);
+               ge->host = NULL;
+               return 1;
        }
 
-       gfio_quit(ui);
-}
-
-static void file_add_recent(struct gui *ui, const gchar *uri)
-{
-       GtkRecentData grd;
-
-       memset(&grd, 0, sizeof(grd));
-       grd.display_name = strdup("gfio");
-       grd.description = strdup("Fio job file");
-       grd.mime_type = strdup(GFIO_MIME);
-       grd.app_name = strdup(g_get_application_name());
-       grd.app_exec = strdup("gfio %f/%u");
-
-       gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
-}
-
-static gchar *get_filename_from_uri(const gchar *uri)
-{
-       if (strncmp(uri, "file://", 7))
-               return strdup(uri);
-
-       return strdup(uri + 7);
-}
-
-static int do_file_open(struct gui_entry *ge, const gchar *uri)
-{
-       struct fio_client *client;
+       static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
+       {
+               struct gui_entry *ge;
+               gint cur_page;
+               int ret, ge_is_new = 0;
 
-       assert(!ge->job_file);
+               /*
+                * Creates new tab if current tab is the main window, or the
+                * current tab already has a client.
+                */
+               cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
+               ge = get_ge_from_page(ui, cur_page, &ge_is_new);
+               if (ge->client) {
+                       ge = get_new_ge_with_tab(ui, "Untitled");
+                       ge_is_new = 1;
+               }
 
-       ge->job_file = get_filename_from_uri(uri);
+               gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
 
-       client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
-       if (client) {
-               gfio_client_added(ge, client);
-               file_add_recent(ge->ui, uri);
-               return 0;
-       }
+               if (get_connection_details(ge)) {
+                       if (ge_is_new)
+                               gtk_widget_destroy(ge->vbox);
+                               
+                       return 1;
+               }
 
-       gfio_report_error(ge, "Failed to add client %s\n", ge->host);
-       free(ge->host);
-       ge->host = NULL;
-       return 1;
-}
+               ret = do_file_open(ge, uri);
 
-static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
-{
-       struct gui_entry *ge;
-       gint cur_page;
-       int ret, ge_is_new = 0;
+               if (!ret) {
+                       if (ge->server_start)
+                               gfio_start_server(ui);
+               } else {
+                       if (ge_is_new)
+                               gtk_widget_destroy(ge->vbox);
+               }
 
-       /*
-        * Creates new tab if current tab is the main window, or the
-        * current tab already has a client.
-        */
-       cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
-       ge = get_ge_from_page(ui, cur_page, &ge_is_new);
-       if (ge->client) {
-               ge = get_new_ge_with_tab(ui, "Untitled");
-               ge_is_new = 1;
+               return ret;
        }
 
-       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
-
-       if (get_connection_details(ge)) {
-               if (ge_is_new)
-                       gtk_widget_destroy(ge->vbox);
-                       
-               return 1;
-       }
+       static void recent_open(GtkAction *action, gpointer data)
+       {
+               struct gui *ui = (struct gui *) data;
+               GtkRecentInfo *info;
+               const gchar *uri;
 
-       ret = do_file_open(ge, uri);
+               info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
+               uri = gtk_recent_info_get_uri(info);
 
-       if (!ret) {
-               if (ge->server_start)
-                       gfio_start_server(ui);
-       } else {
-               if (ge_is_new)
-                       gtk_widget_destroy(ge->vbox);
+               do_file_open_with_tab(ui, uri);
        }
 
-       return ret;
-}
-
-static void recent_open(GtkAction *action, gpointer data)
-{
-       struct gui *ui = (struct gui *) data;
-       GtkRecentInfo *info;
-       const gchar *uri;
-
-       info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
-       uri = gtk_recent_info_get_uri(info);
-
-       do_file_open_with_tab(ui, uri);
-}
-
-static void file_open(GtkWidget *w, gpointer data)
-{
-       struct gui *ui = data;
-       GtkWidget *dialog;
-       GtkFileFilter *filter;
-       gchar *filename;
+       static void file_open(GtkWidget *w, gpointer data)
+       {
+               struct gui *ui = data;
+               GtkWidget *dialog;
+               GtkFileFilter *filter;
+               gchar *filename;
 
-       dialog = gtk_file_chooser_dialog_new("Open File",
-               GTK_WINDOW(ui->window),
-               GTK_FILE_CHOOSER_ACTION_OPEN,
-               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-               GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
-               NULL);
-       gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+               dialog = gtk_file_chooser_dialog_new("Open File",
+                       GTK_WINDOW(ui->window),
+                       GTK_FILE_CHOOSER_ACTION_OPEN,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                       GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+                       NULL);
+               gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+
+               filter = gtk_file_filter_new();
+               gtk_file_filter_add_pattern(filter, "*.fio");
+               gtk_file_filter_add_pattern(filter, "*.job");
+               gtk_file_filter_add_pattern(filter, "*.ini");
+               gtk_file_filter_add_mime_type(filter, GFIO_MIME);
+               gtk_file_filter_set_name(filter, "Fio job file");
+               gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
+
+               if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+                       gtk_widget_destroy(dialog);
+                       return;
+               }
 
-       filter = gtk_file_filter_new();
-       gtk_file_filter_add_pattern(filter, "*.fio");
-       gtk_file_filter_add_pattern(filter, "*.job");
-       gtk_file_filter_add_pattern(filter, "*.ini");
-       gtk_file_filter_add_mime_type(filter, GFIO_MIME);
-       gtk_file_filter_set_name(filter, "Fio job file");
-       gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
+               filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
 
-       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
                gtk_widget_destroy(dialog);
-               return;
-       }
-
-       filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
-
-       gtk_widget_destroy(dialog);
 
-       do_file_open_with_tab(ui, filename);
-       g_free(filename);
-}
+               do_file_open_with_tab(ui, filename);
+               g_free(filename);
+       }
 
-static void file_save(GtkWidget *w, gpointer data)
-{
-       struct gui *ui = data;
-       GtkWidget *dialog;
+       static void file_save(GtkWidget *w, gpointer data)
+       {
+               struct gui *ui = data;
+               GtkWidget *dialog;
 
-       dialog = gtk_file_chooser_dialog_new("Save File",
-               GTK_WINDOW(ui->window),
-               GTK_FILE_CHOOSER_ACTION_SAVE,
-               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-               GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
-               NULL);
+               dialog = gtk_file_chooser_dialog_new("Save File",
+                       GTK_WINDOW(ui->window),
+                       GTK_FILE_CHOOSER_ACTION_SAVE,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+                       NULL);
 
-       gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
-       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
+               gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
+               gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
 
-       if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
-               char *filename;
+               if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+                       char *filename;
 
-               filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
-               // save_job_file(filename);
-               g_free(filename);
+                       filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+                       // save_job_file(filename);
+                       g_free(filename);
+               }
+               gtk_widget_destroy(dialog);
        }
-       gtk_widget_destroy(dialog);
-}
 
 static void view_log_destroy(GtkWidget *w, gpointer data)
 {
@@ -2324,10 +955,9 @@ static void view_log_destroy(GtkWidget *w, gpointer data)
        ui->log_view = NULL;
 }
 
-static void view_log(GtkWidget *w, gpointer data)
+void gfio_view_log(struct gui *ui)
 {
        GtkWidget *win, *scroll, *vbox, *box;
-       struct gui *ui = (struct gui *) data;
 
        if (ui->log_view)
                return;
@@ -2354,6 +984,13 @@ static void view_log(GtkWidget *w, gpointer data)
        gtk_widget_show_all(win);
 }
 
+static void view_log(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+
+       gfio_view_log(ui);
+}
+
 static void connect_job_entry(GtkWidget *w, gpointer data)
 {
        struct gui *ui = (struct gui *) data;
@@ -2372,7 +1009,6 @@ static void send_job_entry(GtkWidget *w, gpointer data)
        ge = get_ge_from_cur_tab(ui);
        if (ge)
                send_clicked(w, ge);
-
 }
 
 static void edit_job_entry(GtkWidget *w, gpointer data)
@@ -2639,9 +1275,9 @@ static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
 }
 
 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
-                   GtkWidget *vbox, GtkUIManager *ui_manager)
+                  GtkWidget *vbox, GtkUIManager *ui_manager)
 {
-        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
 }
 
 static void combo_entry_changed(GtkComboBox *box, gpointer data)
@@ -2731,7 +1367,7 @@ static GtkWidget *new_client_page(struct gui_entry *ge)
        ge->graphs.drawing_area = gtk_drawing_area_new();
        gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
                DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
-       gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
+       gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
        g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
                                G_CALLBACK(on_expose_drawing_area), &ge->graphs);
        g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
@@ -2817,7 +1453,7 @@ static GtkWidget *new_main_page(struct gui *ui)
        ui->graphs.drawing_area = gtk_drawing_area_new();
        gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
                DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
-       gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
+       gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
        g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
                        G_CALLBACK(on_expose_drawing_area), &ui->graphs);
        g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
@@ -2995,7 +1631,7 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
        settings = gtk_settings_get_default();
        gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
        g_type_init();
-       gdk_color_parse("white", &white);
+       gdk_color_parse("white", &gfio_color_white);
        
        ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
diff --git a/gfio.h b/gfio.h
index 6760675..4a60292 100644 (file)
--- a/gfio.h
+++ b/gfio.h
@@ -150,4 +150,11 @@ struct gfio_client {
 
 #define GFIO_MIME      "text/fio"
 
+extern void gfio_view_log(struct gui *ui);
+extern void gfio_set_state(struct gui_entry *ge, unsigned int state);
+extern void clear_ge_ui_info(struct gui_entry *ge);
+
+extern const char *gfio_graph_font;
+extern GdkColor gfio_color_white;
+
 #endif
index e2e8192..843bfeb 100644 (file)
@@ -161,3 +161,14 @@ void multitext_free(struct multitext_widget *mt)
        mt->cur_text = -1;
        mt->max_text = 0;
 }
+
+GtkWidget *get_scrolled_window(gint border_width)
+{
+       GtkWidget *scroll;
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
+       gtk_container_set_border_width(GTK_CONTAINER(scroll), border_width);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       return scroll;
+}
index dec147f..f5aabec 100644 (file)
@@ -8,6 +8,7 @@ GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double def
 void label_set_int_value(GtkWidget *entry, unsigned int val);
 void entry_set_int_value(GtkWidget *entry, unsigned int val);
 
+GtkWidget *get_scrolled_window(gint border_width);
 
 struct multitext_widget {
        GtkWidget *entry;