parser: relax restriction on postfix ranges
[fio.git] / gfio.c
diff --git a/gfio.c b/gfio.c
index 186c9b31694d9a6556d0ff616eb9a92a4148d7d4..a52d84d33e3a8221bec40767760f3cab73a667b3 100644 (file)
--- a/gfio.c
+++ b/gfio.c
@@ -2,6 +2,7 @@
  * gfio - gui front end for fio - the flexible io tester
  *
  * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com> 
+ * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
  *
  * The license below covers all files distributed with fio unless otherwise
  * noted in the file itself.
 #include <malloc.h>
 
 #include <glib.h>
+#include <cairo.h>
 #include <gtk/gtk.h>
 
 #include "fio.h"
+#include "graph.h"
+
+static int gfio_server_running;
 
 static void gfio_update_thread_status(char *status_message, double perc);
 
@@ -85,20 +90,66 @@ struct gui {
        GtkWidget *buttonbox;
        GtkWidget *button[ARRAYSIZE(buttonspeclist)];
        GtkWidget *scrolled_window;
-       GtkWidget *textview;
+#define DRAWING_AREA_XDIM 1000
+#define DRAWING_AREA_YDIM 400
+       GtkWidget *drawing_area;
        GtkWidget *error_info_bar;
        GtkWidget *error_label;
+       GtkWidget *results_notebook;
+       GtkWidget *results_window;
+       GtkListStore *log_model;
+       GtkWidget *log_tree;
+       GtkWidget *log_view;
        GtkTextBuffer *text;
        struct probe_widget probe;
        struct eta_widget eta;
        int connected;
        pthread_t t;
+       pthread_t server_t;
 
+       struct graph *iops_graph;
+       struct graph *bandwidth_graph;
        struct fio_client *client;
        int nr_job_files;
        char **job_files;
 } ui;
 
+struct gfio_client {
+       struct gui *ui;
+       GtkWidget *results_widget;
+       GtkWidget *disk_util_frame;
+};
+
+static void setup_iops_graph(struct gui *ui)
+{
+       if (ui->iops_graph)
+               graph_free(ui->iops_graph);
+       ui->iops_graph = graph_new((int) DRAWING_AREA_XDIM / 2.0,
+                                       (int) DRAWING_AREA_YDIM);
+       graph_title(ui->iops_graph, "IOPS");
+       graph_x_title(ui->iops_graph, "Time");
+       graph_y_title(ui->iops_graph, "IOPS");
+       graph_add_label(ui->iops_graph, "Read IOPS");
+       graph_add_label(ui->iops_graph, "Write IOPS");
+       graph_set_color(ui->iops_graph, "Read IOPS", 0.7, 0.0, 0.0);
+       graph_set_color(ui->iops_graph, "Write IOPS", 0.0, 0.0, 0.7);
+}
+
+static void setup_bandwidth_graph(struct gui *ui)
+{
+       if (ui->bandwidth_graph)
+               graph_free(ui->bandwidth_graph);
+       ui->bandwidth_graph = graph_new((int) DRAWING_AREA_XDIM / 2.0,
+                                       (int) DRAWING_AREA_YDIM);
+       graph_title(ui->bandwidth_graph, "Bandwidth");
+       graph_x_title(ui->bandwidth_graph, "Time");
+       graph_y_title(ui->bandwidth_graph, "Bandwidth");
+       graph_add_label(ui->bandwidth_graph, "Read Bandwidth");
+       graph_add_label(ui->bandwidth_graph, "Write Bandwidth");
+       graph_set_color(ui->bandwidth_graph, "Read Bandwidth", 0.7, 0.0, 0.0);
+       graph_set_color(ui->bandwidth_graph, "Write Bandwidth", 0.0, 0.0, 0.7);
+}
+
 static void clear_ui_info(struct gui *ui)
 {
        gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
@@ -165,10 +216,12 @@ static void gfio_set_connected(struct gui *ui, int connected)
                gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
                ui->connected = 1;
                gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
+               gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 1);
        } else {
                ui->connected = 0;
                gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
                gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
+               gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 1);
        }
 }
 
@@ -233,6 +286,31 @@ GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char
        return col;
 }
 
+static void gfio_ui_setup_log(struct gui *ui)
+{
+       GtkTreeSelection *selection;
+       GtkListStore *model;
+       GtkWidget *tree_view;
+
+       model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
+
+       tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+       gtk_widget_set_can_focus(tree_view, FALSE);
+
+       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+       gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+       g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
+       tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
+       tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
+       tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
+       tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
+
+       ui->log_model = model;
+       ui->log_tree = tree_view;
+}
+
 static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
                                               fio_fp64_t *plist,
                                               unsigned int len,
@@ -254,6 +332,9 @@ static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
        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);
 
@@ -266,8 +347,11 @@ static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
 
        gtk_list_store_append(model, &iter);
 
-       for (i = 0; i < len; i++)
+       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;
 }
@@ -360,10 +444,10 @@ static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
 {
        const char *ddir_label[2] = { "Read", "Write" };
        GtkWidget *frame, *label, *box, *vbox, *main_vbox;
-       unsigned long min, max, runt;
+       unsigned long min[3], max[3], runt;
        unsigned long long bw, iops;
        unsigned int flags = 0;
-       double mean, dev;
+       double mean[3], dev[3];
        char *io_p, *bw_p, *iops_p;
        int i2p;
 
@@ -401,47 +485,22 @@ static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
        label = new_info_label_in_frame(box, "Runtime (msec)");
        label_set_int_value(label, ts->runtime[ddir]);
 
-       if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
-               flags |= GFIO_SLAT;
-       if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
-               flags |= GFIO_CLAT;
-       if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
-               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, max, mean, dev);
-               if (flags & GFIO_CLAT)
-                       gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
-               if (flags & GFIO_LAT)
-                       gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
-       }
-
-       if (ts->clat_percentiles)
-               gfio_show_clat_percentiles(main_vbox, ts, ddir);
-
-       if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
+       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 * 100 / (double) 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 > 999999.9) {
-                       min /= 1000.0;
-                       max /= 1000.0;
-                       mean /= 1000.0;
-                       dev /= 1000.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";
                }
 
@@ -453,20 +512,46 @@ static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
                gtk_container_add(GTK_CONTAINER(frame), box);
 
                label = new_info_label_in_frame(box, "Minimum");
-               label_set_int_value(label, min);
+               label_set_int_value(label, min[0]);
                label = new_info_label_in_frame(box, "Maximum");
-               label_set_int_value(label, max);
+               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);
+               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);
+               sprintf(tmp, "%5.02f", dev[0]);
                gtk_label_set_text(GTK_LABEL(label), tmp);
        }
 
+       if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
+               flags |= GFIO_SLAT;
+       if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
+               flags |= GFIO_CLAT;
+       if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
+               flags |= GFIO_LAT;
+
+       if (flags) {
+               frame = gtk_frame_new("Latency");
+               gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
+
+               vbox = gtk_vbox_new(FALSE, 3);
+               gtk_container_add(GTK_CONTAINER(frame), vbox);
+
+               if (flags & GFIO_SLAT)
+                       gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
+               if (flags & GFIO_CLAT)
+                       gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
+               if (flags & GFIO_LAT)
+                       gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
+       }
+
+       if (ts->clat_percentiles)
+               gfio_show_clat_percentiles(main_vbox, ts, ddir);
+
+
        free(io_p);
        free(bw_p);
        free(iops_p);
@@ -504,6 +589,9 @@ static GtkWidget *gfio_output_lat_buckets(double *lat, unsigned int num,
        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);
 
@@ -598,23 +686,79 @@ static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
        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;
 
-static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
+       if (submit)
+               stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
+       else
+               stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
+
+       gtk_list_store_append(model, &iter);
+
+       gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
+
+       for (i = 1, j = 0; i < len; i++) {
+               char fbuf[32];
+
+               if (!(add_mask & (1UL << (i - 1))))
+                       sprintf(fbuf, "0.0%%");
+               else {
+                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
+                       j++;
+               }
+
+               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       }
+
+}
+
+static void gfio_add_total_depths_tree(GtkListStore *model,
+                                      struct thread_stat *ts, unsigned int len)
 {
        double io_u_dist[FIO_IO_U_MAP_NR];
-       double io_u_dist_s[FIO_IO_U_MAP_NR];
-       double io_u_dist_c[FIO_IO_U_MAP_NR];
+       GtkTreeIter iter;
+       /* Bits 1-6, and 8 */
+       const int add_mask = 0x17e;
+       int i, j;
+
+       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
+
+       gtk_list_store_append(model, &iter);
+
+       gtk_list_store_set(model, &iter, 0, "Total", -1);
+
+       for (i = 1, j = 0; i < len; i++) {
+               char fbuf[32];
+
+               if (!(add_mask & (1UL << (i - 1))))
+                       sprintf(fbuf, "0.0%%");
+               else {
+                       sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
+                       j++;
+               }
+
+               gtk_list_store_set(model, &iter, i, fbuf, -1);
+       }
+
+}
+
+static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
+{
        GtkWidget *frame, *box, *tree_view;
        GtkTreeSelection *selection;
        GtkListStore *model;
-       GtkTreeIter iter;
        GType types[FIO_IO_U_MAP_NR + 1];
        int i;
-       const char *labels[] = { "Type", "0", "4", "8", "16", "32", "64", ">= 64" };
-
-       stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
-       stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist_s);
-       stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist_c);
+#define NR_LABELS      10
+       const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
 
        frame = gtk_frame_new("IO depths");
        gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
@@ -622,89 +766,79 @@ static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
        box = gtk_hbox_new(FALSE, 3);
        gtk_container_add(GTK_CONTAINER(frame), box);
 
-       for (i = 0; i < FIO_IO_U_MAP_NR + 1; i++)
+       for (i = 0; i < NR_LABELS; i++)
                types[i] = G_TYPE_STRING;
 
-       model = gtk_list_store_newv(FIO_IO_U_MAP_NR + 1, types);
+       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 < FIO_IO_U_MAP_NR + 1; i++)
+       for (i = 0; i < NR_LABELS; i++)
                tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
 
-       gtk_list_store_append(model, &iter);
+       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);
 
-       for (i = 0; i < FIO_IO_U_MAP_NR + 1; i++) {
-               char fbuf[32];
-
-               if (i == 0) {
-                       gtk_list_store_set(model, &iter, i, "Total", -1);
-                       continue;
-               }
-
-               sprintf(fbuf, "%3.1f%%", io_u_dist[i - 1]);
-               gtk_list_store_set(model, &iter, i, fbuf, -1);
-       }
-
-       gtk_list_store_append(model, &iter);
-
-       for (i = 0; i < FIO_IO_U_MAP_NR + 1; i++) {
-               char fbuf[32];
+       gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
+}
 
-               if (i == 0) {
-                       gtk_list_store_set(model, &iter, i, "Submit", -1);
-                       continue;
-               }
+static gboolean results_window_delete(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
 
-               sprintf(fbuf, "%3.1f%%", io_u_dist_s[i - 1]);
-               gtk_list_store_set(model, &iter, i, fbuf, -1);
-       }
+       gtk_widget_destroy(w);
+       ui->results_window = NULL;
+       ui->results_notebook = NULL;
+       return TRUE;
+}
 
-       gtk_list_store_append(model, &iter);
+static GtkWidget *get_results_window(struct gui *ui)
+{
+       GtkWidget *win, *notebook;
 
-       for (i = 0; i < FIO_IO_U_MAP_NR + 1; i++) {
-               char fbuf[32];
+       if (ui->results_window)
+               return ui->results_notebook;
 
-               if (i == 0) {
-                       gtk_list_store_set(model, &iter, i, "Complete", -1);
-                       continue;
-               }
+       win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_title(GTK_WINDOW(win), "Results");
+       g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ui);
+       g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ui);
 
-               sprintf(fbuf, "%3.1f%%", io_u_dist_c[i - 1]);
-               gtk_list_store_set(model, &iter, i, fbuf, -1);
-       }
+       notebook = gtk_notebook_new();
+       gtk_container_add(GTK_CONTAINER(win), notebook);
 
-       gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
+       ui->results_window = win;
+       ui->results_notebook = notebook;
+       return ui->results_notebook;
 }
 
 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
                            struct group_run_stats *rs)
 {
-       GtkWidget *dialog, *box, *vbox, *entry, *content;
-       struct gui *ui = client->client_data;
+       GtkWidget *res_win, *box, *vbox, *entry;
+       struct gfio_client *gc = client->client_data;
 
        gdk_threads_enter();
 
-       dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
-                       GTK_DIALOG_DESTROY_WITH_PARENT,
-                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
-
-       g_signal_connect_swapped(dialog, "response",
-                             G_CALLBACK(gtk_widget_destroy),
-                             dialog);
-
-       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       res_win = get_results_window(gc->ui);
 
        vbox = gtk_vbox_new(FALSE, 3);
-       gtk_container_add(GTK_CONTAINER(content), vbox);
 
        box = gtk_hbox_new(TRUE, 3);
        gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
 
+       gtk_notebook_append_page(GTK_NOTEBOOK(res_win), vbox, gtk_label_new(ts->name));
+
+       gc->results_widget = vbox;
+
        entry = new_info_entry_in_frame(box, "Name");
        gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
        if (strlen(ts->description)) {
@@ -729,32 +863,100 @@ static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
        gfio_show_cpu_usage(vbox, ts);
        gfio_show_io_depths(vbox, ts);
 
-       gtk_widget_show_all(dialog);
+       gtk_widget_show_all(gc->ui->results_window);
        gdk_threads_leave();
 }
 
 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
 {
-#if 0
-       GtkTextBuffer *buffer;
-       GtkTextIter end;
+       struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       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);
 
-       buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
        gdk_threads_enter();
-       gtk_text_buffer_get_end_iter(buffer, &end);
-       gtk_text_buffer_insert(buffer, &end, buf, -1);
+
+       gtk_list_store_append(gc->ui->log_model, &iter);
+       gtk_list_store_set(gc->ui->log_model, &iter, 0, timebuf, -1);
+       gtk_list_store_set(gc->ui->log_model, &iter, 1, client->hostname, -1);
+       gtk_list_store_set(gc->ui->log_model, &iter, 2, p->level, -1);
+       gtk_list_store_set(gc->ui->log_model, &iter, 3, p->buf, -1);
+
        gdk_threads_leave();
-       gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
-                                       &end, 0.0, FALSE, 0.0,0.0);
-#else
-       fio_client_ops.text_op(client, cmd);
-#endif
 }
 
 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
 {
-       printf("gfio_disk_util_op called\n");
-       fio_client_ops.disk_util(client, cmd);
+       struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
+       struct gfio_client *gc = client->client_data;
+       GtkWidget *box, *frame, *entry, *vbox;
+
+       gdk_threads_enter();
+
+       if (!gc->results_widget) {
+               printf("no results!\n");
+               goto out;
+       }
+
+       if (!gc->disk_util_frame) {
+               gc->disk_util_frame = gtk_frame_new("Disk utilization");
+               gtk_box_pack_start(GTK_BOX(gc->results_widget), gc->disk_util_frame, FALSE, FALSE, 5);
+       }
+
+       vbox = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(gc->disk_util_frame), vbox);
+
+       frame = gtk_frame_new((char *) p->dus.name);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
+
+       box = gtk_vbox_new(FALSE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       frame = gtk_frame_new("Read");
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
+       vbox = gtk_hbox_new(TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       entry = new_info_entry_in_frame(vbox, "IOs");
+       entry_set_int_value(entry, p->dus.ios[0]);
+       entry = new_info_entry_in_frame(vbox, "Merges");
+       entry_set_int_value(entry, p->dus.merges[0]);
+       entry = new_info_entry_in_frame(vbox, "Sectors");
+       entry_set_int_value(entry, p->dus.sectors[0]);
+       entry = new_info_entry_in_frame(vbox, "Ticks");
+       entry_set_int_value(entry, p->dus.ticks[0]);
+
+       frame = gtk_frame_new("Write");
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
+       vbox = gtk_hbox_new(TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       entry = new_info_entry_in_frame(vbox, "IOs");
+       entry_set_int_value(entry, p->dus.ios[1]);
+       entry = new_info_entry_in_frame(vbox, "Merges");
+       entry_set_int_value(entry, p->dus.merges[1]);
+       entry = new_info_entry_in_frame(vbox, "Sectors");
+       entry_set_int_value(entry, p->dus.sectors[1]);
+       entry = new_info_entry_in_frame(vbox, "Ticks");
+       entry_set_int_value(entry, p->dus.ticks[1]);
+
+       frame = gtk_frame_new("Shared");
+       gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
+       vbox = gtk_hbox_new(TRUE, 3);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       entry = new_info_entry_in_frame(vbox, "IO ticks");
+       entry_set_int_value(entry, p->dus.io_ticks);
+       entry = new_info_entry_in_frame(vbox, "Time in queue");
+       entry_set_int_value(entry, p->dus.time_in_queue);
+
+       gtk_widget_show_all(gc->results_widget);
+out:
+       gdk_threads_leave();
 }
 
 extern int sum_stat_clients;
@@ -788,8 +990,36 @@ static void gfio_thread_status_op(struct fio_client *client,
 static void gfio_group_stats_op(struct fio_client *client,
                                struct fio_net_cmd *cmd)
 {
+       gdk_threads_enter();
        printf("gfio_group_stats_op called\n");
        fio_client_ops.group_stats(client, cmd);
+       gdk_threads_leave();
+}
+
+static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
+{
+       struct gui *ui = (struct gui *) p;
+       cairo_t *cr;
+
+       cr = gdk_cairo_create(w->window);
+
+       cairo_set_source_rgb(cr, 0, 0, 0);
+
+       cairo_save(cr);
+       cairo_translate(cr, 0, 0);
+       line_graph_draw(ui->bandwidth_graph, cr);
+       cairo_stroke(cr);
+       cairo_restore(cr);
+
+       cairo_save(cr);
+       cairo_translate(cr, DRAWING_AREA_XDIM / 2.0, 0);
+                               // DRAWING_AREA_YDIM * 0.05);
+       line_graph_draw(ui->iops_graph, cr);
+       cairo_stroke(cr);
+       cairo_restore(cr);
+       cairo_destroy(cr);
+
+       return FALSE;
 }
 
 static void gfio_update_eta(struct jobs_eta *je)
@@ -801,6 +1031,8 @@ static void gfio_update_eta(struct jobs_eta *je)
        double perc = 0.0;
        int i2p = 0;
 
+       gdk_threads_enter();
+
        eta_str[0] = '\0';
        output[0] = '\0';
 
@@ -857,6 +1089,11 @@ static void gfio_update_eta(struct jobs_eta *je)
                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.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0]);
+               graph_add_xy_data(ui.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1]);
+               graph_add_xy_data(ui.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0]);
+               graph_add_xy_data(ui.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1]);
+
                free(rate_str[0]);
                free(rate_str[1]);
                free(iops_str[0]);
@@ -870,11 +1107,14 @@ static void gfio_update_eta(struct jobs_eta *je)
        }
                
        gfio_update_thread_status(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 *ui = gc->ui;
        const char *os, *arch;
        char buf[64];
 
@@ -889,11 +1129,17 @@ static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
        if (!client->name)
                client->name = strdup((char *) probe->hostname);
 
-       gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
-       gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
-       gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
+       gdk_threads_enter();
+
+       gtk_label_set_text(GTK_LABEL(ui->probe.hostname), (char *) probe->hostname);
+       gtk_label_set_text(GTK_LABEL(ui->probe.os), os);
+       gtk_label_set_text(GTK_LABEL(ui->probe.arch), arch);
        sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
-       gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
+       gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), buf);
+
+       gfio_set_connected(ui, 1);
+
+       gdk_threads_leave();
 }
 
 static void gfio_update_thread_status(char *status_message, double perc)
@@ -906,22 +1152,23 @@ static void gfio_update_thread_status(char *status_message, double perc)
                GTK_PROGRESS_BAR(ui.thread_status_pb), m);
        gtk_progress_bar_set_fraction(
                GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
-       gdk_threads_enter();
        gtk_widget_queue_draw(ui.window);
-       gdk_threads_leave();
 }
 
 static void gfio_quit_op(struct fio_client *client)
 {
-       struct gui *ui = client->client_data;
+       struct gfio_client *gc = client->client_data;
 
-       gfio_set_connected(ui, 0);
+       gdk_threads_enter();
+       gfio_set_connected(gc->ui, 0);
+       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 gui *ui = client->client_data;
+       struct gfio_client *gc = client->client_data;
+       struct gui *ui = gc->ui;
        char tmp[8];
        int i;
 
@@ -936,29 +1183,33 @@ static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
        p->numjobs              = le32_to_cpu(p->numjobs);
        p->group_reporting      = le32_to_cpu(p->group_reporting);
 
+       gdk_threads_enter();
+
        gtk_entry_set_text(GTK_ENTRY(ui->eta.name), (gchar *) p->jobname);
        gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), ddir_str(p->rw));
        gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), (gchar *) p->ioengine);
 
        sprintf(tmp, "%u", p->iodepth);
        gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
+
+       gdk_threads_leave();
 }
 
 static void gfio_client_timed_out(struct fio_client *client)
 {
-       struct gui *ui = client->client_data;
+       struct gfio_client *gc = client->client_data;
        GtkWidget *dialog, *label, *content;
        char buf[256];
 
        gdk_threads_enter();
 
-       gfio_set_connected(ui, 0);
-       clear_ui_info(ui);
+       gfio_set_connected(gc->ui, 0);
+       clear_ui_info(gc->ui);
 
        sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
 
        dialog = gtk_dialog_new_with_buttons("Timed out!",
-                       GTK_WINDOW(ui->window),
+                       GTK_WINDOW(gc->ui->window),
                        GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                        GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
 
@@ -1029,6 +1280,24 @@ static void start_job_thread(struct gui *ui)
        }
 }
 
+static void *server_thread(void *arg)
+{
+       is_backend = 1;
+       gfio_server_running = 1;
+       fio_start_server(NULL);
+       gfio_server_running = 0;
+       return NULL;
+}
+
+static void gfio_start_server(struct gui *ui)
+{
+       if (!gfio_server_running) {
+               gfio_server_running = 1;
+               pthread_create(&ui->server_t, NULL, server_thread, NULL);
+               pthread_detach(ui->server_t);
+       }
+}
+
 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
                 gpointer data)
 {
@@ -1048,9 +1317,11 @@ static void connect_clicked(GtkWidget *widget, gpointer data)
                if (!ui->nr_job_files)
                        file_open(widget, data);
                gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
-               fio_clients_connect();
-               pthread_create(&ui->t, NULL, job_thread, NULL);
-               gfio_set_connected(ui, 1);
+               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
+               if (!fio_clients_connect()) {
+                       pthread_create(&ui->t, NULL, job_thread, NULL);
+                       gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 0);
+               }
        } else {
                fio_clients_terminate();
                gfio_set_connected(ui, 0);
@@ -1153,10 +1424,10 @@ static int get_connection_details(char **host, int *port, int *type,
        hbox = gtk_hbox_new(TRUE, 4);
        gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
 
-       combo = gtk_combo_box_text_new();
-       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
-       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
-       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
+       combo = gtk_combo_box_new_text();
+       gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "IPv4");
+       gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "IPv6");
+       gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "local socket");
        gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
 
        gtk_container_add(GTK_CONTAINER(hbox), combo);
@@ -1184,7 +1455,7 @@ static int get_connection_details(char **host, int *port, int *type,
        *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
        *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
 
-       typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
+       typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo));
        if (!typeentry || !strncmp(typeentry, "IPv4", 4))
                *type = Fio_client_ipv4;
        else if (!strncmp(typeentry, "IPv6", 4))
@@ -1199,16 +1470,28 @@ static int get_connection_details(char **host, int *port, int *type,
        return 0;
 }
 
+static void gfio_client_added(struct gui *ui, struct fio_client *client)
+{
+       struct gfio_client *gc;
+
+       gc = malloc(sizeof(*gc));
+       memset(gc, 0, sizeof(*gc));
+       gc->ui = ui;
+
+       client->client_data = gc;
+}
+
 static void file_open(GtkWidget *w, gpointer data)
 {
        GtkWidget *dialog;
+       struct gui *ui = data;
        GSList *filenames, *fn_glist;
        GtkFileFilter *filter;
        char *host;
        int port, type, server_start;
 
        dialog = gtk_file_chooser_dialog_new("Open File",
-               GTK_WINDOW(ui.window),
+               GTK_WINDOW(ui->window),
                GTK_FILE_CHOOSER_ACTION_OPEN,
                GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
@@ -1218,6 +1501,7 @@ static void file_open(GtkWidget *w, gpointer data)
        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, "text/fio");
        gtk_file_filter_set_name(filter, "Fio job file");
        gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
@@ -1236,12 +1520,14 @@ static void file_open(GtkWidget *w, gpointer data)
 
        filenames = fn_glist;
        while (filenames != NULL) {
-               ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
-               ui.job_files[ui.nr_job_files] = strdup(filenames->data);
-               ui.nr_job_files++;
+               struct fio_client *client;
 
-               ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
-               if (!ui.client) {
+               ui->job_files = realloc(ui->job_files, (ui->nr_job_files + 1) * sizeof(char *));
+               ui->job_files[ui->nr_job_files] = strdup(filenames->data);
+               ui->nr_job_files++;
+
+               client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
+               if (!client) {
                        GError *error;
 
                        error = g_error_new(g_quark_from_string("fio"), 1,
@@ -1249,22 +1535,26 @@ static void file_open(GtkWidget *w, gpointer data)
                        report_error(error);
                        g_error_free(error);
                }
-               ui.client->client_data = &ui;
+               gfio_client_added(ui, client);
                        
                g_free(filenames->data);
                filenames = g_slist_next(filenames);
        }
        free(host);
+
+       if (server_start)
+               gfio_start_server(ui);
 err:
        g_slist_free(fn_glist);
 }
 
 static void file_save(GtkWidget *w, gpointer data)
 {
+       struct gui *ui = data;
        GtkWidget *dialog;
 
        dialog = gtk_file_chooser_dialog_new("Save File",
-               GTK_WINDOW(ui.window),
+               GTK_WINDOW(ui->window),
                GTK_FILE_CHOOSER_ACTION_SAVE,
                GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
@@ -1283,6 +1573,46 @@ static void file_save(GtkWidget *w, gpointer data)
        gtk_widget_destroy(dialog);
 }
 
+static void view_log_destroy(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+
+       gtk_widget_ref(ui->log_tree);
+       gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
+       gtk_widget_destroy(w);
+       ui->log_view = NULL;
+}
+
+static void view_log(GtkWidget *w, gpointer data)
+{
+       GtkWidget *win, *scroll, *vbox, *box;
+       struct gui *ui = (struct gui *) data;
+
+       if (ui->log_view)
+               return;
+
+       ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_title(GTK_WINDOW(win), "Log");
+       gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
+
+       scroll = gtk_scrolled_window_new(NULL, NULL);
+
+       gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
+
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       box = gtk_hbox_new(TRUE, 0);
+       gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
+       g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
+
+       vbox = gtk_vbox_new(TRUE, 5);
+       gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
+
+       gtk_container_add(GTK_CONTAINER(win), vbox);
+       gtk_widget_show_all(win);
+}
+
 static void preferences(GtkWidget *w, gpointer data)
 {
        GtkWidget *dialog, *frame, *box, **buttons;
@@ -1342,10 +1672,12 @@ static void about_dialog(GtkWidget *w, gpointer data)
 
 static GtkActionEntry menu_items[] = {
        { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
+       { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
        { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
        { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
        { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
        { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
+       { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
        { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
        { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
 };
@@ -1362,6 +1694,9 @@ static const gchar *ui_string = " \
                                <separator name=\"Separator2\"/> \
                                <menuitem name=\"Quit\" action=\"Quit\" /> \
                        </menu> \
+                       <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
+                               <menuitem name=\"Log\" action=\"ViewLog\" /> \
+                       </menu>\
                        <menu name=\"Help\" action=\"HelpMenuAction\"> \
                                <menuitem name=\"About\" action=\"About\" /> \
                        </menu> \
@@ -1369,13 +1704,14 @@ static const gchar *ui_string = " \
        </ui> \
 ";
 
-static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
+static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
+                                  struct gui *ui)
 {
        GtkActionGroup *action_group = gtk_action_group_new("Menu");
        GError *error = 0;
 
        action_group = gtk_action_group_new("Menu");
-       gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
+       gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
 
        gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
        gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
@@ -1422,7 +1758,7 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
        gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
 
        uimanager = gtk_ui_manager_new();
-       menu = get_menubar_menu(ui->window, uimanager);
+       menu = get_menubar_menu(ui->window, uimanager, ui);
        gfio_ui_setup(settings, menu, ui->vbox, uimanager);
 
        /*
@@ -1478,20 +1814,24 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
 #endif
 
        /*
-        * Add a text box for text op messages 
+        * Set up a drawing area and IOPS and bandwidth graphs
         */
-       ui->textview = gtk_text_view_new();
-       ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
-       gtk_text_buffer_set_text(ui->text, "", -1);
-       gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
-       gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
+       ui->drawing_area = gtk_drawing_area_new();
+       gtk_widget_set_size_request(GTK_WIDGET(ui->drawing_area), 
+                       DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
+       g_signal_connect(G_OBJECT(ui->drawing_area), "expose_event",
+                               G_CALLBACK (on_expose_drawing_area), ui);
        ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-       gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ui->scrolled_window),
+                                       ui->drawing_area);
        gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
                        TRUE, TRUE, 0);
 
+       setup_iops_graph(ui);
+       setup_bandwidth_graph(ui);
+
        /*
         * Set up alignments for widgets at the bottom of ui, 
         * align bottom left, expand horizontally but not vertically
@@ -1512,6 +1852,7 @@ static void init_ui(int *argc, char **argv[], struct gui *ui)
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
        gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
 
+       gfio_ui_setup_log(ui);
 
        gtk_widget_show_all(ui->window);
 }