Home | History | Annotate | Download | only in fio
      1 /*
      2  * gfio - gui front end for fio - the flexible io tester
      3  *
      4  * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron (at) gmail.com>
      5  * Copyright (C) 2012 Jens Axboe <axboe (at) kernel.dk>
      6  *
      7  * The license below covers all files distributed with fio unless otherwise
      8  * noted in the file itself.
      9  *
     10  *  This program is free software; you can redistribute it and/or modify
     11  *  it under the terms of the GNU General Public License version 2 as
     12  *  published by the Free Software Foundation.
     13  *
     14  *  This program is distributed in the hope that it will be useful,
     15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17  *  GNU General Public License for more details.
     18  *
     19  *  You should have received a copy of the GNU General Public License
     20  *  along with this program; if not, write to the Free Software
     21  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     22  *
     23  */
     24 #include <locale.h>
     25 #include <malloc.h>
     26 #include <string.h>
     27 
     28 #include <glib.h>
     29 #include <cairo.h>
     30 #include <gtk/gtk.h>
     31 
     32 #include "fio.h"
     33 #include "gfio.h"
     34 #include "ghelpers.h"
     35 #include "goptions.h"
     36 #include "gerror.h"
     37 #include "gclient.h"
     38 #include "graph.h"
     39 
     40 static int gfio_server_running;
     41 static unsigned int gfio_graph_limit = 100;
     42 
     43 GdkColor gfio_color_white;
     44 GdkColor gfio_color_lightyellow;
     45 const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
     46 
     47 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
     48 
     49 static void connect_clicked(GtkWidget *widget, gpointer data);
     50 static void start_job_clicked(GtkWidget *widget, gpointer data);
     51 static void send_clicked(GtkWidget *widget, gpointer data);
     52 
     53 static struct button_spec {
     54 	const char *buttontext;
     55 	clickfunction f;
     56 	const char *tooltiptext[2];
     57 	const int start_sensitive;
     58 } buttonspeclist[] = {
     59 	{
     60 	  .buttontext		= "Connect",
     61 	  .f			= connect_clicked,
     62 	  .tooltiptext		= { "Disconnect from host", "Connect to host" },
     63 	  .start_sensitive	= 1,
     64 	},
     65 	{
     66 	  .buttontext		= "Send",
     67 	  .f			= send_clicked,
     68 	  .tooltiptext		= { "Send job description to host", NULL },
     69 	  .start_sensitive	= 0,
     70 	},
     71 	{
     72 	  .buttontext		= "Start Job",
     73 	  .f			= start_job_clicked,
     74 	  .tooltiptext		= { "Start the current job on the server", NULL },
     75 	  .start_sensitive	= 0,
     76 	},
     77 };
     78 
     79 static void setup_iops_graph(struct gfio_graphs *gg)
     80 {
     81 	struct graph *g;
     82 
     83 	g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
     84 	graph_title(g, "IOPS (IOs/sec)");
     85 	graph_x_title(g, "Time (secs)");
     86 	gg->read_iops = graph_add_label(g, "Read IOPS");
     87 	gg->write_iops = graph_add_label(g, "Write IOPS");
     88 	gg->trim_iops = graph_add_label(g, "Trim IOPS");
     89 	graph_set_color(g, gg->read_iops, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
     90 	graph_set_color(g, gg->write_iops, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
     91 	graph_set_color(g, gg->trim_iops, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
     92 	line_graph_set_data_count_limit(g, gfio_graph_limit);
     93 	graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
     94 	graph_set_graph_all_zeroes(g, 0);
     95 	gg->iops_graph = g;
     96 }
     97 
     98 static void setup_bandwidth_graph(struct gfio_graphs *gg)
     99 {
    100 	struct graph *g;
    101 
    102 	g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
    103 	graph_title(g, "Bandwidth (bytes/sec)");
    104 	graph_x_title(g, "Time (secs)");
    105 	gg->read_bw = graph_add_label(g, "Read Bandwidth");
    106 	gg->write_bw = graph_add_label(g, "Write Bandwidth");
    107 	gg->trim_bw = graph_add_label(g, "Trim Bandwidth");
    108 	graph_set_color(g, gg->read_bw, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
    109 	graph_set_color(g, gg->write_bw, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
    110 	graph_set_color(g, gg->trim_bw, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
    111 	graph_set_base_offset(g, 1);
    112 	line_graph_set_data_count_limit(g, 100);
    113 	graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
    114 	graph_set_graph_all_zeroes(g, 0);
    115 	gg->bandwidth_graph = g;
    116 }
    117 
    118 static void setup_graphs(struct gfio_graphs *g)
    119 {
    120 	setup_iops_graph(g);
    121 	setup_bandwidth_graph(g);
    122 }
    123 
    124 void clear_ge_ui_info(struct gui_entry *ge)
    125 {
    126 	gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
    127 	gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
    128 	gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
    129 	gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
    130 #if 0
    131 	/* should we empty it... */
    132 	gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
    133 #endif
    134 	multitext_update_entry(&ge->eta.iotype, 0, "");
    135 	multitext_update_entry(&ge->eta.bs, 0, "");
    136 	multitext_update_entry(&ge->eta.ioengine, 0, "");
    137 	multitext_update_entry(&ge->eta.iodepth, 0, "");
    138 	gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
    139 	gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
    140 	gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
    141 	gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
    142 	gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
    143 	gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
    144 }
    145 
    146 static void set_menu_entry_text(struct gui *ui, const char *path,
    147 				const char *text)
    148 {
    149 	GtkWidget *w;
    150 
    151 	w = gtk_ui_manager_get_widget(ui->uimanager, path);
    152 	if (w)
    153 		gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
    154 	else
    155 		fprintf(stderr, "gfio: can't find path %s\n", path);
    156 }
    157 
    158 
    159 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
    160 {
    161 	GtkWidget *w;
    162 
    163 	w = gtk_ui_manager_get_widget(ui->uimanager, path);
    164 	if (w)
    165 		gtk_widget_set_sensitive(w, show);
    166 	else
    167 		fprintf(stderr, "gfio: can't find path %s\n", path);
    168 }
    169 
    170 static void set_job_menu_visible(struct gui *ui, int visible)
    171 {
    172 	set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
    173 }
    174 
    175 static void set_view_results_visible(struct gui *ui, int visible)
    176 {
    177 	set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
    178 }
    179 
    180 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
    181 {
    182 	if (s->tooltiptext[sensitive])
    183 		return s->tooltiptext[sensitive];
    184 
    185 	return s->tooltiptext[0];
    186 }
    187 
    188 static GtkWidget *add_button(GtkWidget *buttonbox,
    189 			     struct button_spec *buttonspec, gpointer data)
    190 {
    191 	GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
    192 	gboolean sens = buttonspec->start_sensitive;
    193 
    194 	g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
    195 	gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
    196 
    197 	sens = buttonspec->start_sensitive;
    198 	gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
    199 	gtk_widget_set_sensitive(button, sens);
    200 
    201 	return button;
    202 }
    203 
    204 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
    205 			int nbuttons)
    206 {
    207 	int i;
    208 
    209 	for (i = 0; i < nbuttons; i++)
    210 		ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
    211 }
    212 
    213 /*
    214  * Update sensitivity of job buttons and job menu items, based on the
    215  * state of the client.
    216  */
    217 static void update_button_states(struct gui *ui, struct gui_entry *ge)
    218 {
    219 	unsigned int connect_state, send_state, start_state, edit_state;
    220 	const char *connect_str = NULL;
    221 
    222 	switch (ge->state) {
    223 	default:
    224 		gfio_report_error(ge, "Bad client state: %u\n", ge->state);
    225 		/* fall through to new state */
    226 	case GE_STATE_NEW:
    227 		connect_state = 1;
    228 		edit_state = 1;
    229 		connect_str = "Connect";
    230 		send_state = 0;
    231 		start_state = 0;
    232 		break;
    233 	case GE_STATE_CONNECTED:
    234 		connect_state = 1;
    235 		edit_state = 1;
    236 		connect_str = "Disconnect";
    237 		send_state = 1;
    238 		start_state = 0;
    239 		break;
    240 	case GE_STATE_JOB_SENT:
    241 		connect_state = 1;
    242 		edit_state = 1;
    243 		connect_str = "Disconnect";
    244 		send_state = 0;
    245 		start_state = 1;
    246 		break;
    247 	case GE_STATE_JOB_STARTED:
    248 		connect_state = 1;
    249 		edit_state = 1;
    250 		connect_str = "Disconnect";
    251 		send_state = 0;
    252 		start_state = 1;
    253 		break;
    254 	case GE_STATE_JOB_RUNNING:
    255 		connect_state = 1;
    256 		edit_state = 0;
    257 		connect_str = "Disconnect";
    258 		send_state = 0;
    259 		start_state = 0;
    260 		break;
    261 	case GE_STATE_JOB_DONE:
    262 		connect_state = 1;
    263 		edit_state = 0;
    264 		connect_str = "Connect";
    265 		send_state = 0;
    266 		start_state = 0;
    267 		break;
    268 	}
    269 
    270 	gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
    271 	gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
    272 	gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
    273 	gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
    274 	gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
    275 
    276 	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
    277 	set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
    278 
    279 	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
    280 	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
    281 	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
    282 
    283 	if (ge->client && ge->client->nr_results)
    284 		set_view_results_visible(ui, 1);
    285 	else
    286 		set_view_results_visible(ui, 0);
    287 }
    288 
    289 void gfio_set_state(struct gui_entry *ge, unsigned int state)
    290 {
    291 	ge->state = state;
    292 	update_button_states(ge->ui, ge);
    293 }
    294 
    295 static void gfio_ui_setup_log(struct gui *ui)
    296 {
    297 	GtkTreeSelection *selection;
    298 	GtkListStore *model;
    299 	GtkWidget *tree_view;
    300 
    301 	model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
    302 
    303 	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
    304 	gtk_widget_set_can_focus(tree_view, FALSE);
    305 
    306 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
    307 	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
    308 	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
    309 		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
    310 
    311 	tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
    312 	tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
    313 	tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
    314 	tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
    315 
    316 	ui->log_model = model;
    317 	ui->log_tree = tree_view;
    318 }
    319 
    320 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
    321 				   gpointer data)
    322 {
    323 	guint width = gtk_widget_get_allocated_width(w);
    324 	guint height = gtk_widget_get_allocated_height(w);
    325 	struct gfio_graphs *g = data;
    326 
    327 	graph_set_size(g->iops_graph, width / 2.0, height);
    328 	graph_set_position(g->iops_graph, width / 2.0, 0.0);
    329 	graph_set_size(g->bandwidth_graph, width / 2.0, height);
    330 	graph_set_position(g->bandwidth_graph, 0, 0);
    331 	return TRUE;
    332 }
    333 
    334 static void draw_graph(struct graph *g, cairo_t *cr)
    335 {
    336 	line_graph_draw(g, cr);
    337 	cairo_stroke(cr);
    338 }
    339 
    340 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
    341 			      gboolean keyboard_mode, GtkTooltip *tooltip,
    342 			      gpointer data)
    343 {
    344 	struct gfio_graphs *g = data;
    345 	const char *text = NULL;
    346 
    347 	if (graph_contains_xy(g->iops_graph, x, y))
    348 		text = graph_find_tooltip(g->iops_graph, x, y);
    349 	else if (graph_contains_xy(g->bandwidth_graph, x, y))
    350 		text = graph_find_tooltip(g->bandwidth_graph, x, y);
    351 
    352 	if (text) {
    353 		gtk_tooltip_set_text(tooltip, text);
    354 		return TRUE;
    355 	}
    356 
    357 	return FALSE;
    358 }
    359 
    360 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
    361 {
    362 	struct gfio_graphs *g = p;
    363 	cairo_t *cr;
    364 
    365 	cr = gdk_cairo_create(gtk_widget_get_window(w));
    366 
    367 	if (graph_has_tooltips(g->iops_graph) ||
    368 	    graph_has_tooltips(g->bandwidth_graph)) {
    369 		g_object_set(w, "has-tooltip", TRUE, NULL);
    370 		g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
    371 	}
    372 
    373 	cairo_set_source_rgb(cr, 0, 0, 0);
    374 	draw_graph(g->iops_graph, cr);
    375 	draw_graph(g->bandwidth_graph, cr);
    376 	cairo_destroy(cr);
    377 
    378 	return FALSE;
    379 }
    380 
    381 /*
    382  * FIXME: need more handling here
    383  */
    384 static void ge_destroy(struct gui_entry *ge)
    385 {
    386 	struct gfio_client *gc = ge->client;
    387 
    388 	if (gc) {
    389 		if (gc->client) {
    390 			if (ge->state >= GE_STATE_CONNECTED)
    391 				fio_client_terminate(gc->client);
    392 
    393 			fio_put_client(gc->client);
    394 		}
    395 		free(gc);
    396 	}
    397 
    398 	g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
    399 
    400 	free(ge->job_file);
    401 	free(ge->host);
    402 	free(ge);
    403 }
    404 
    405 static void ge_widget_destroy(GtkWidget *w, gpointer data)
    406 {
    407 	struct gui_entry *ge = (struct gui_entry *) data;
    408 
    409 	ge_destroy(ge);
    410 }
    411 
    412 static void gfio_quit(struct gui *ui)
    413 {
    414 	gtk_main_quit();
    415 }
    416 
    417 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
    418 			 gpointer data)
    419 {
    420 	struct gui *ui = (struct gui *) data;
    421 
    422 	gfio_quit(ui);
    423 }
    424 
    425 static void *job_thread(void *arg)
    426 {
    427 	struct gui *ui = arg;
    428 
    429 	ui->handler_running = 1;
    430 	fio_handle_clients(&gfio_client_ops);
    431 	ui->handler_running = 0;
    432 	return NULL;
    433 }
    434 
    435 static int send_job_file(struct gui_entry *ge)
    436 {
    437 	struct gfio_client *gc = ge->client;
    438 	int ret = 0;
    439 
    440 	/*
    441 	 * Prune old options, we are expecting the return options
    442 	 * when the job file is parsed remotely and returned to us.
    443 	 */
    444 	while (!flist_empty(&gc->o_list)) {
    445 		struct gfio_client_options *gco;
    446 
    447 		gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
    448 		flist_del(&gco->list);
    449 		free(gco);
    450 	}
    451 
    452 	ret = fio_client_send_ini(gc->client, ge->job_file);
    453 	if (!ret)
    454 		return 0;
    455 
    456 	gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
    457 	return 1;
    458 }
    459 
    460 static void *server_thread(void *arg)
    461 {
    462 	is_backend = 1;
    463 	gfio_server_running = 1;
    464 	fio_start_server(NULL);
    465 	gfio_server_running = 0;
    466 	return NULL;
    467 }
    468 
    469 static void gfio_start_server(struct gui *ui)
    470 {
    471 	if (!gfio_server_running) {
    472 		gfio_server_running = 1;
    473 		pthread_create(&ui->server_t, NULL, server_thread, NULL);
    474 		pthread_detach(ui->server_t);
    475 	}
    476 }
    477 
    478 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
    479 			      gpointer data)
    480 {
    481 	struct gui_entry *ge = data;
    482 	struct gfio_client *gc = ge->client;
    483 
    484 	if (gc)
    485 		fio_start_client(gc->client);
    486 }
    487 
    488 static void file_open(GtkWidget *w, gpointer data);
    489 
    490 struct connection_widgets
    491 {
    492 	GtkWidget *hentry;
    493 	GtkWidget *combo;
    494 	GtkWidget *button;
    495 };
    496 
    497 static void hostname_cb(GtkEntry *entry, gpointer data)
    498 {
    499 	struct connection_widgets *cw = data;
    500 	int uses_net = 0, is_localhost = 0;
    501 	const gchar *text;
    502 	gchar *ctext;
    503 
    504 	/*
    505 	 * Check whether to display the 'auto start backend' box
    506 	 * or not. Show it if we are a localhost and using network,
    507 	 * or using a socket.
    508 	 */
    509 	ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
    510 	if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
    511 		uses_net = 1;
    512 	g_free(ctext);
    513 
    514 	if (uses_net) {
    515 		text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
    516 		if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
    517 		    !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
    518 		    !strcmp(text, "ip6-loopback"))
    519 			is_localhost = 1;
    520 	}
    521 
    522 	if (!uses_net || is_localhost) {
    523 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
    524 		gtk_widget_set_sensitive(cw->button, 1);
    525 	} else {
    526 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
    527 		gtk_widget_set_sensitive(cw->button, 0);
    528 	}
    529 }
    530 
    531 static int get_connection_details(struct gui_entry *ge)
    532 {
    533 	GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
    534 	struct connection_widgets cw;
    535 	struct gui *ui = ge->ui;
    536 	char *typeentry;
    537 
    538 	if (ge->host)
    539 		return 0;
    540 
    541 	dialog = gtk_dialog_new_with_buttons("Connection details",
    542 			GTK_WINDOW(ui->window),
    543 			GTK_DIALOG_DESTROY_WITH_PARENT,
    544 			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
    545 			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
    546 
    547 	frame = gtk_frame_new("Hostname / socket name");
    548 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
    549 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    550 
    551 	box = gtk_vbox_new(FALSE, 6);
    552 	gtk_container_add(GTK_CONTAINER(frame), box);
    553 
    554 	hbox = gtk_hbox_new(TRUE, 10);
    555 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    556 	cw.hentry = gtk_entry_new();
    557 	gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
    558 	gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
    559 
    560 	frame = gtk_frame_new("Port");
    561 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    562 	box = gtk_vbox_new(FALSE, 10);
    563 	gtk_container_add(GTK_CONTAINER(frame), box);
    564 
    565 	hbox = gtk_hbox_new(TRUE, 4);
    566 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    567 	pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
    568 
    569 	frame = gtk_frame_new("Type");
    570 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    571 	box = gtk_vbox_new(FALSE, 10);
    572 	gtk_container_add(GTK_CONTAINER(frame), box);
    573 
    574 	hbox = gtk_hbox_new(TRUE, 4);
    575 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    576 
    577 	cw.combo = gtk_combo_box_text_new();
    578 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
    579 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
    580 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
    581 	gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
    582 
    583 	gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
    584 
    585 	frame = gtk_frame_new("Options");
    586 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    587 	box = gtk_vbox_new(FALSE, 10);
    588 	gtk_container_add(GTK_CONTAINER(frame), box);
    589 
    590 	hbox = gtk_hbox_new(TRUE, 4);
    591 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    592 
    593 	cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
    594 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
    595 	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.");
    596 	gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
    597 
    598 	/*
    599 	 * Connect edit signal, so we can show/not-show the auto start button
    600 	 */
    601 	g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
    602 	g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
    603 
    604 	gtk_widget_show_all(dialog);
    605 
    606 	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
    607 		gtk_widget_destroy(dialog);
    608 		return 1;
    609 	}
    610 
    611 	ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
    612 	ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
    613 
    614 	typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
    615 	if (!typeentry || !strncmp(typeentry, "IPv4", 4))
    616 		ge->type = Fio_client_ipv4;
    617 	else if (!strncmp(typeentry, "IPv6", 4))
    618 		ge->type = Fio_client_ipv6;
    619 	else
    620 		ge->type = Fio_client_socket;
    621 	g_free(typeentry);
    622 
    623 	ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
    624 
    625 	gtk_widget_destroy(dialog);
    626 	return 0;
    627 }
    628 
    629 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
    630 {
    631 	gc->client = fio_get_client(client);
    632 	client->client_data = gc;
    633 }
    634 
    635 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
    636 {
    637 	struct gfio_client_options *gco;
    638 	struct gfio_client *gc;
    639 
    640 	gc = calloc(1, sizeof(*gc));
    641 	INIT_FLIST_HEAD(&gc->o_list);
    642 	gc->ge = ge;
    643 	ge->client = gc;
    644 	gfio_set_client(gc, client);
    645 
    646 	/*
    647 	 * Just add a default set of options, need to consider how best
    648 	 * to handle this
    649 	 */
    650 	gco = calloc(1, sizeof(*gco));
    651 	INIT_FLIST_HEAD(&gco->list);
    652 	options_default_fill(&gco->o);
    653 	flist_add_tail(&gco->list, &gc->o_list);
    654 	gc->o_list_nr++;
    655 }
    656 
    657 static void gfio_clear_graph_data(struct gfio_graphs *g)
    658 {
    659 	graph_clear_values(g->iops_graph);
    660 	graph_clear_values(g->bandwidth_graph);
    661 }
    662 
    663 static void connect_clicked(GtkWidget *widget, gpointer data)
    664 {
    665 	struct gui_entry *ge = data;
    666 	struct gfio_client *gc = ge->client;
    667 
    668 	if (ge->state == GE_STATE_NEW) {
    669 		int ret;
    670 
    671 		if (!ge->job_file)
    672 			file_open(widget, ge->ui);
    673 		if (!ge->job_file)
    674 			return;
    675 
    676 		gc = ge->client;
    677 
    678 		if (!gc->client) {
    679 			struct fio_client *client;
    680 
    681 			if (get_connection_details(ge)) {
    682 				gfio_report_error(ge, "Failed to get connection details\n");
    683 				return;
    684 			}
    685 
    686 			client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
    687 			if (!client) {
    688 				gfio_report_error(ge, "Failed to add client %s\n", ge->host);
    689 				free(ge->host);
    690 				ge->host = NULL;
    691 				return;
    692 			}
    693 			gfio_set_client(gc, client);
    694 		}
    695 
    696 		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
    697 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
    698 		ret = fio_client_connect(gc->client);
    699 		if (!ret) {
    700 			if (!ge->ui->handler_running)
    701 				pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
    702 			gfio_set_state(ge, GE_STATE_CONNECTED);
    703 			gfio_clear_graph_data(&ge->graphs);
    704 		} else {
    705 			gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
    706 		}
    707 	} else {
    708 		fio_client_terminate(gc->client);
    709 		gfio_set_state(ge, GE_STATE_NEW);
    710 		clear_ge_ui_info(ge);
    711 	}
    712 }
    713 
    714 static void send_clicked(GtkWidget *widget, gpointer data)
    715 {
    716 	struct gui_entry *ge = data;
    717 
    718 	if (send_job_file(ge))
    719 		gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
    720 }
    721 
    722 static GtkWidget *new_client_page(struct gui_entry *ge);
    723 
    724 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
    725 {
    726 	struct gui_entry *ge;
    727 
    728 	ge = malloc(sizeof(*ge));
    729 	memset(ge, 0, sizeof(*ge));
    730 	ge->state = GE_STATE_NEW;
    731 	ge->ui = ui;
    732 	return ge;
    733 }
    734 
    735 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
    736 {
    737 	struct gui_entry *ge;
    738 
    739 	ge = alloc_new_gui_entry(ui);
    740 
    741 	ge->vbox = new_client_page(ge);
    742 	g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
    743 
    744 	ge->page_label = gtk_label_new(name);
    745 	ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
    746 
    747 	g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
    748 
    749 	gtk_widget_show_all(ui->window);
    750 	return ge;
    751 }
    752 
    753 static void file_new(GtkWidget *w, gpointer data)
    754 {
    755 	struct gui *ui = (struct gui *) data;
    756 	struct gui_entry *ge;
    757 
    758 	ge = get_new_ge_with_tab(ui, "Untitled");
    759 	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
    760 }
    761 
    762 /*
    763  * Return the 'ge' corresponding to the tab. If the active tab is the
    764  * main tab, open a new tab.
    765  */
    766 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
    767 					  int *created)
    768 {
    769 	if (!cur_page) {
    770 		if (created)
    771 			*created = 1;
    772 		return get_new_ge_with_tab(ui, "Untitled");
    773 	}
    774 
    775 	if (created)
    776 		*created = 0;
    777 
    778 	return g_hash_table_lookup(ui->ge_hash, &cur_page);
    779 }
    780 
    781 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
    782 {
    783 	gint cur_page;
    784 
    785 	/*
    786 	 * Main tab is tab 0, so any current page other than 0 holds
    787 	 * a ge entry.
    788 	 */
    789 	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
    790 	if (cur_page)
    791 		return get_ge_from_page(ui, cur_page, NULL);
    792 
    793 	return NULL;
    794 }
    795 
    796 static void file_close(GtkWidget *w, gpointer data)
    797 {
    798 	struct gui *ui = (struct gui *) data;
    799 	struct gui_entry *ge;
    800 
    801 	/*
    802 	 * Can't close the main tab
    803 	 */
    804 	ge = get_ge_from_cur_tab(ui);
    805 	if (ge) {
    806 		gtk_widget_destroy(ge->vbox);
    807 		return;
    808 	}
    809 
    810 	if (g_hash_table_size(ui->ge_hash)) {
    811 		gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
    812 		return;
    813 	}
    814 
    815 	gfio_quit(ui);
    816 }
    817 
    818 static void file_add_recent(struct gui *ui, const gchar *uri)
    819 {
    820 	GtkRecentData grd;
    821 
    822 	memset(&grd, 0, sizeof(grd));
    823 	grd.display_name = strdup("gfio");
    824 	grd.description = strdup("Fio job file");
    825 	grd.mime_type = strdup(GFIO_MIME);
    826 	grd.app_name = strdup(g_get_application_name());
    827 	grd.app_exec = strdup("gfio %f/%u");
    828 
    829 	gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
    830 }
    831 
    832 static gchar *get_filename_from_uri(const gchar *uri)
    833 {
    834 	if (strncmp(uri, "file://", 7))
    835 		return strdup(uri);
    836 
    837 	return strdup(uri + 7);
    838 }
    839 
    840 static int do_file_open(struct gui_entry *ge, const gchar *uri)
    841 {
    842 	struct fio_client *client;
    843 
    844 	assert(!ge->job_file);
    845 
    846 	ge->job_file = get_filename_from_uri(uri);
    847 
    848 	client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
    849 	if (client) {
    850 		char *label = strdup(uri);
    851 
    852 		basename(label);
    853 		gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
    854 		free(label);
    855 
    856 		gfio_client_added(ge, client);
    857 		file_add_recent(ge->ui, uri);
    858 		return 0;
    859 	}
    860 
    861 	gfio_report_error(ge, "Failed to add client %s\n", ge->host);
    862 	free(ge->host);
    863 	ge->host = NULL;
    864 	free(ge->job_file);
    865 	ge->job_file = NULL;
    866 	return 1;
    867 }
    868 
    869 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
    870 {
    871 	struct gui_entry *ge;
    872 	gint cur_page;
    873 	int ret, ge_is_new = 0;
    874 
    875 	/*
    876 	 * Creates new tab if current tab is the main window, or the
    877 	 * current tab already has a client.
    878 	 */
    879 	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
    880 	ge = get_ge_from_page(ui, cur_page, &ge_is_new);
    881 	if (ge->client) {
    882 		ge = get_new_ge_with_tab(ui, "Untitled");
    883 		ge_is_new = 1;
    884 	}
    885 
    886 	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
    887 
    888 	if (get_connection_details(ge)) {
    889 		if (ge_is_new)
    890 			gtk_widget_destroy(ge->vbox);
    891 
    892 		return 1;
    893 	}
    894 
    895 	ret = do_file_open(ge, uri);
    896 
    897 	if (!ret) {
    898 		if (ge->server_start)
    899 			gfio_start_server(ui);
    900 	} else {
    901 		if (ge_is_new)
    902 			gtk_widget_destroy(ge->vbox);
    903 	}
    904 
    905 	return ret;
    906 }
    907 
    908 static void recent_open(GtkAction *action, gpointer data)
    909 {
    910 	struct gui *ui = (struct gui *) data;
    911 	GtkRecentInfo *info;
    912 	const gchar *uri;
    913 
    914 	info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
    915 	uri = gtk_recent_info_get_uri(info);
    916 
    917 	do_file_open_with_tab(ui, uri);
    918 }
    919 
    920 static void file_open(GtkWidget *w, gpointer data)
    921 {
    922 	struct gui *ui = data;
    923 	GtkWidget *dialog;
    924 	GtkFileFilter *filter;
    925 	gchar *filename;
    926 
    927 	dialog = gtk_file_chooser_dialog_new("Open File",
    928 		GTK_WINDOW(ui->window),
    929 		GTK_FILE_CHOOSER_ACTION_OPEN,
    930 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    931 		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
    932 		NULL);
    933 	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
    934 
    935 	filter = gtk_file_filter_new();
    936 	gtk_file_filter_add_pattern(filter, "*.fio");
    937 	gtk_file_filter_add_pattern(filter, "*.job");
    938 	gtk_file_filter_add_pattern(filter, "*.ini");
    939 	gtk_file_filter_add_mime_type(filter, GFIO_MIME);
    940 	gtk_file_filter_set_name(filter, "Fio job file");
    941 	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
    942 
    943 	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
    944 		gtk_widget_destroy(dialog);
    945 		return;
    946 	}
    947 
    948 	filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
    949 
    950 	gtk_widget_destroy(dialog);
    951 
    952 	do_file_open_with_tab(ui, filename);
    953 	g_free(filename);
    954 }
    955 
    956 static void file_save(GtkWidget *w, gpointer data)
    957 {
    958 	struct gui *ui = data;
    959 	GtkWidget *dialog;
    960 
    961 	dialog = gtk_file_chooser_dialog_new("Save File",
    962 		GTK_WINDOW(ui->window),
    963 		GTK_FILE_CHOOSER_ACTION_SAVE,
    964 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    965 		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
    966 		NULL);
    967 
    968 	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
    969 	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
    970 
    971 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
    972 		char *filename;
    973 
    974 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
    975 		// save_job_file(filename);
    976 		g_free(filename);
    977 	}
    978 	gtk_widget_destroy(dialog);
    979 }
    980 
    981 static void view_log_destroy(GtkWidget *w, gpointer data)
    982 {
    983 	struct gui *ui = (struct gui *) data;
    984 
    985 	g_object_ref(G_OBJECT(ui->log_tree));
    986 	gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
    987 	gtk_widget_destroy(w);
    988 	ui->log_view = NULL;
    989 }
    990 
    991 void gfio_view_log(struct gui *ui)
    992 {
    993 	GtkWidget *win, *scroll, *vbox, *box;
    994 
    995 	if (ui->log_view)
    996 		return;
    997 
    998 	ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    999 	gtk_window_set_title(GTK_WINDOW(win), "Log");
   1000 	gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
   1001 
   1002 	scroll = gtk_scrolled_window_new(NULL, NULL);
   1003 
   1004 	gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
   1005 
   1006 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1007 
   1008 	box = gtk_hbox_new(TRUE, 0);
   1009 	gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
   1010 	g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
   1011 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
   1012 
   1013 	vbox = gtk_vbox_new(TRUE, 5);
   1014 	gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
   1015 
   1016 	gtk_container_add(GTK_CONTAINER(win), vbox);
   1017 	gtk_widget_show_all(win);
   1018 }
   1019 
   1020 static void view_log(GtkWidget *w, gpointer data)
   1021 {
   1022 	struct gui *ui = (struct gui *) data;
   1023 
   1024 	gfio_view_log(ui);
   1025 }
   1026 
   1027 static void connect_job_entry(GtkWidget *w, gpointer data)
   1028 {
   1029 	struct gui *ui = (struct gui *) data;
   1030 	struct gui_entry *ge;
   1031 
   1032 	ge = get_ge_from_cur_tab(ui);
   1033 	if (ge)
   1034 		connect_clicked(w, ge);
   1035 }
   1036 
   1037 static void send_job_entry(GtkWidget *w, gpointer data)
   1038 {
   1039 	struct gui *ui = (struct gui *) data;
   1040 	struct gui_entry *ge;
   1041 
   1042 	ge = get_ge_from_cur_tab(ui);
   1043 	if (ge)
   1044 		send_clicked(w, ge);
   1045 }
   1046 
   1047 static void edit_job_entry(GtkWidget *w, gpointer data)
   1048 {
   1049 	struct gui *ui = (struct gui *) data;
   1050 	struct gui_entry *ge;
   1051 
   1052 	ge = get_ge_from_cur_tab(ui);
   1053 	if (ge && ge->client)
   1054 		gopt_get_options_window(ui->window, ge->client);
   1055 }
   1056 
   1057 static void start_job_entry(GtkWidget *w, gpointer data)
   1058 {
   1059 	struct gui *ui = (struct gui *) data;
   1060 	struct gui_entry *ge;
   1061 
   1062 	ge = get_ge_from_cur_tab(ui);
   1063 	if (ge)
   1064 		start_job_clicked(w, ge);
   1065 }
   1066 
   1067 static void view_results(GtkWidget *w, gpointer data)
   1068 {
   1069 	struct gui *ui = (struct gui *) data;
   1070 	struct gfio_client *gc;
   1071 	struct gui_entry *ge;
   1072 
   1073 	ge = get_ge_from_cur_tab(ui);
   1074 	if (!ge)
   1075 		return;
   1076 
   1077 	if (ge->results_window)
   1078 		return;
   1079 
   1080 	gc = ge->client;
   1081 	if (gc && gc->nr_results)
   1082 		gfio_display_end_results(gc);
   1083 }
   1084 
   1085 static void __update_graph_settings(struct gfio_graphs *g)
   1086 {
   1087 	line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
   1088 	graph_set_font(g->iops_graph, gfio_graph_font);
   1089 	line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
   1090 	graph_set_font(g->bandwidth_graph, gfio_graph_font);
   1091 }
   1092 
   1093 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
   1094 {
   1095 	struct gui_entry *ge = (struct gui_entry *) value;
   1096 	GdkEvent *ev;
   1097 
   1098 	__update_graph_settings(&ge->graphs);
   1099 
   1100 	ev = gdk_event_new(GDK_EXPOSE);
   1101 	g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
   1102 	gdk_event_free(ev);
   1103 }
   1104 
   1105 static void update_graph_limits(void)
   1106 {
   1107 	struct gui *ui = &main_ui;
   1108 	GdkEvent *ev;
   1109 
   1110 	__update_graph_settings(&ui->graphs);
   1111 
   1112 	ev = gdk_event_new(GDK_EXPOSE);
   1113 	g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
   1114 	gdk_event_free(ev);
   1115 
   1116 	g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
   1117 }
   1118 
   1119 static void preferences(GtkWidget *w, gpointer data)
   1120 {
   1121 	GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
   1122 	GtkWidget *hbox, *spin, *entry, *spin_int;
   1123 	struct gui *ui = (struct gui *) data;
   1124 	int i;
   1125 
   1126 	dialog = gtk_dialog_new_with_buttons("Preferences",
   1127 		GTK_WINDOW(ui->window),
   1128 		GTK_DIALOG_DESTROY_WITH_PARENT,
   1129 		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
   1130 		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
   1131 		NULL);
   1132 
   1133 	frame = gtk_frame_new("Graphing");
   1134 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
   1135 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
   1136 	vbox = gtk_vbox_new(FALSE, 6);
   1137 	gtk_container_add(GTK_CONTAINER(frame), vbox);
   1138 
   1139 	hbox = gtk_hbox_new(FALSE, 5);
   1140 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
   1141 	entry = gtk_label_new("Font face to use for graph labels");
   1142 	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
   1143 
   1144 	font = gtk_font_button_new_with_font(gfio_graph_font);
   1145 	gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
   1146 
   1147 	box = gtk_vbox_new(FALSE, 6);
   1148 	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
   1149 
   1150 	hbox = gtk_hbox_new(FALSE, 5);
   1151 	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
   1152 	entry = gtk_label_new("Maximum number of data points in graph (seconds)");
   1153 	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
   1154 
   1155 	spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
   1156 
   1157 	box = gtk_vbox_new(FALSE, 6);
   1158 	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
   1159 
   1160 	hbox = gtk_hbox_new(FALSE, 5);
   1161 	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
   1162 	entry = gtk_label_new("Client ETA request interval (msec)");
   1163 	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
   1164 
   1165 	spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
   1166 	frame = gtk_frame_new("Debug logging");
   1167 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
   1168 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
   1169 	vbox = gtk_vbox_new(FALSE, 6);
   1170 	gtk_container_add(GTK_CONTAINER(frame), vbox);
   1171 
   1172 	box = gtk_hbox_new(FALSE, 6);
   1173 	gtk_container_add(GTK_CONTAINER(vbox), box);
   1174 
   1175 	buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
   1176 
   1177 	for (i = 0; i < FD_DEBUG_MAX; i++) {
   1178 		if (i == 7) {
   1179 			box = gtk_hbox_new(FALSE, 6);
   1180 			gtk_container_add(GTK_CONTAINER(vbox), box);
   1181 		}
   1182 
   1183 
   1184 		buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
   1185 		gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
   1186 		gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
   1187 	}
   1188 
   1189 	gtk_widget_show_all(dialog);
   1190 
   1191 	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
   1192 		gtk_widget_destroy(dialog);
   1193 		return;
   1194 	}
   1195 
   1196 	for (i = 0; i < FD_DEBUG_MAX; i++) {
   1197 		int set;
   1198 
   1199 		set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
   1200 		if (set)
   1201 			fio_debug |= (1UL << i);
   1202 	}
   1203 
   1204 	gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
   1205 	gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
   1206 	update_graph_limits();
   1207 	gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
   1208 
   1209 	gtk_widget_destroy(dialog);
   1210 }
   1211 
   1212 static void about_dialog(GtkWidget *w, gpointer data)
   1213 {
   1214 	const char *authors[] = {
   1215 		"Jens Axboe <axboe (at) kernel.dk>",
   1216 		"Stephen Carmeron <stephenmcameron (at) gmail.com>",
   1217 		NULL
   1218 	};
   1219 	const char *license[] = {
   1220 		"Fio is free software; you can redistribute it and/or modify "
   1221 		"it under the terms of the GNU General Public License as published by "
   1222 		"the Free Software Foundation; either version 2 of the License, or "
   1223 		"(at your option) any later version.\n",
   1224 		"Fio is distributed in the hope that it will be useful, "
   1225 		"but WITHOUT ANY WARRANTY; without even the implied warranty of "
   1226 		"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
   1227 		"GNU General Public License for more details.\n",
   1228 		"You should have received a copy of the GNU General Public License "
   1229 		"along with Fio; if not, write to the Free Software Foundation, Inc., "
   1230 		"51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
   1231 	};
   1232 	char *license_trans;
   1233 
   1234 	license_trans = g_strconcat(license[0], "\n", license[1], "\n",
   1235 				     license[2], "\n", NULL);
   1236 
   1237 	gtk_show_about_dialog(NULL,
   1238 		"program-name", "gfio",
   1239 		"comments", "Gtk2 UI for fio",
   1240 		"license", license_trans,
   1241 		"website", "http://git.kernel.dk/?p=fio.git;a=summary",
   1242 		"authors", authors,
   1243 		"version", fio_version_string,
   1244 		"copyright", " 2012 Jens Axboe <axboe (at) kernel.dk>",
   1245 		"logo-icon-name", "fio",
   1246 		/* Must be last: */
   1247 		"wrap-license", TRUE,
   1248 		NULL);
   1249 
   1250 	g_free(license_trans);
   1251 }
   1252 
   1253 static GtkActionEntry menu_items[] = {
   1254 	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
   1255 	{ "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
   1256 	{ "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
   1257 	{ "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
   1258 	{ "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
   1259 	{ "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
   1260 	{ "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
   1261 	{ "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
   1262 	{ "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
   1263 	{ "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
   1264 	{ "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
   1265 	{ "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
   1266 	{ "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
   1267 	{ "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
   1268 	{ "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
   1269 	{ "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
   1270 	{ "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
   1271 };
   1272 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
   1273 
   1274 static const gchar *ui_string = " \
   1275 	<ui> \
   1276 		<menubar name=\"MainMenu\"> \
   1277 			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
   1278 				<menuitem name=\"New\" action=\"NewFile\" /> \
   1279 				<menuitem name=\"Open\" action=\"OpenFile\" /> \
   1280 				<menuitem name=\"Close\" action=\"CloseFile\" /> \
   1281 				<separator name=\"Separator1\"/> \
   1282 				<menuitem name=\"Save\" action=\"SaveFile\" /> \
   1283 				<separator name=\"Separator2\"/> \
   1284 				<menuitem name=\"Preferences\" action=\"Preferences\" /> \
   1285 				<separator name=\"Separator3\"/> \
   1286 				<placeholder name=\"FileRecentFiles\"/> \
   1287 				<separator name=\"Separator4\"/> \
   1288 				<menuitem name=\"Quit\" action=\"Quit\" /> \
   1289 			</menu> \
   1290 			<menu name=\"JobMenu\" action=\"JobMenuAction\"> \
   1291 				<menuitem name=\"Connect\" action=\"ConnectJob\" /> \
   1292 				<separator name=\"Separator5\"/> \
   1293 				<menuitem name=\"Edit job\" action=\"EditJob\" /> \
   1294 				<menuitem name=\"Send job\" action=\"SendJob\" /> \
   1295 				<separator name=\"Separator6\"/> \
   1296 				<menuitem name=\"Start job\" action=\"StartJob\" /> \
   1297 			</menu>\
   1298 			<menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
   1299 				<menuitem name=\"Results\" action=\"ViewResults\" /> \
   1300 				<separator name=\"Separator7\"/> \
   1301 				<menuitem name=\"Log\" action=\"ViewLog\" /> \
   1302 			</menu>\
   1303 			<menu name=\"Help\" action=\"HelpMenuAction\"> \
   1304 				<menuitem name=\"About\" action=\"About\" /> \
   1305 			</menu> \
   1306 		</menubar> \
   1307 	</ui> \
   1308 ";
   1309 
   1310 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
   1311 				   struct gui *ui)
   1312 {
   1313 	GtkActionGroup *action_group;
   1314 	GError *error = 0;
   1315 
   1316 	action_group = gtk_action_group_new("Menu");
   1317 	gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
   1318 
   1319 	gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
   1320 	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
   1321 
   1322 	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
   1323 
   1324 	return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
   1325 }
   1326 
   1327 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
   1328 		   GtkWidget *vbox, GtkUIManager *ui_manager)
   1329 {
   1330 	gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
   1331 }
   1332 
   1333 static void combo_entry_changed(GtkComboBox *box, gpointer data)
   1334 {
   1335 	struct gui_entry *ge = (struct gui_entry *) data;
   1336 	gint index;
   1337 
   1338 	index = gtk_combo_box_get_active(box);
   1339 
   1340 	multitext_set_entry(&ge->eta.iotype, index);
   1341 	multitext_set_entry(&ge->eta.bs, index);
   1342 	multitext_set_entry(&ge->eta.ioengine, index);
   1343 	multitext_set_entry(&ge->eta.iodepth, index);
   1344 }
   1345 
   1346 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
   1347 {
   1348 	struct gui_entry *ge = (struct gui_entry *) data;
   1349 
   1350 	multitext_free(&ge->eta.iotype);
   1351 	multitext_free(&ge->eta.bs);
   1352 	multitext_free(&ge->eta.ioengine);
   1353 	multitext_free(&ge->eta.iodepth);
   1354 }
   1355 
   1356 static GtkWidget *new_client_page(struct gui_entry *ge)
   1357 {
   1358 	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
   1359 	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
   1360 
   1361 	main_vbox = gtk_vbox_new(FALSE, 3);
   1362 
   1363 	top_align = gtk_alignment_new(0, 0, 1, 0);
   1364 	top_vbox = gtk_vbox_new(FALSE, 3);
   1365 	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
   1366 	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
   1367 
   1368 	probe = gtk_frame_new("Job");
   1369 	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
   1370 	probe_frame = gtk_vbox_new(FALSE, 3);
   1371 	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
   1372 
   1373 	probe_box = gtk_hbox_new(FALSE, 3);
   1374 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1375 	ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
   1376 	ge->probe.os = new_info_label_in_frame(probe_box, "OS");
   1377 	ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
   1378 	ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
   1379 
   1380 	probe_box = gtk_hbox_new(FALSE, 3);
   1381 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1382 
   1383 	ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
   1384 	g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
   1385 	g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
   1386 	ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
   1387 	ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
   1388 	ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
   1389 	ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
   1390 	ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
   1391 	ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
   1392 
   1393 	probe_box = gtk_hbox_new(FALSE, 3);
   1394 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1395 	ge->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1396 	ge->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1397 	ge->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1398 	ge->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1399 	ge->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1400 	ge->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1401 
   1402 	/*
   1403 	 * Only add this if we have a commit rate
   1404 	 */
   1405 #if 0
   1406 	probe_box = gtk_hbox_new(FALSE, 3);
   1407 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
   1408 
   1409 	ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1410 	ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1411 
   1412 	ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1413 	ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1414 #endif
   1415 
   1416 	/*
   1417 	 * Set up a drawing area and IOPS and bandwidth graphs
   1418 	 */
   1419 	ge->graphs.drawing_area = gtk_drawing_area_new();
   1420 	gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
   1421 		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
   1422 	gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
   1423 	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
   1424 				G_CALLBACK(on_expose_drawing_area), &ge->graphs);
   1425 	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
   1426 				G_CALLBACK(on_config_drawing_area), &ge->graphs);
   1427 	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
   1428 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
   1429 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1430 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
   1431 					ge->graphs.drawing_area);
   1432 	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
   1433 
   1434 	setup_graphs(&ge->graphs);
   1435 
   1436 	/*
   1437 	 * Set up alignments for widgets at the bottom of ui,
   1438 	 * align bottom left, expand horizontally but not vertically
   1439 	 */
   1440 	bottom_align = gtk_alignment_new(0, 1, 1, 0);
   1441 	ge->buttonbox = gtk_hbox_new(FALSE, 0);
   1442 	gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
   1443 	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
   1444 
   1445 	add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
   1446 
   1447 	/*
   1448 	 * Set up thread status progress bar
   1449 	 */
   1450 	ge->thread_status_pb = gtk_progress_bar_new();
   1451 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
   1452 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
   1453 	gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
   1454 
   1455 
   1456 	return main_vbox;
   1457 }
   1458 
   1459 static GtkWidget *new_main_page(struct gui *ui)
   1460 {
   1461 	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
   1462 	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
   1463 
   1464 	main_vbox = gtk_vbox_new(FALSE, 3);
   1465 
   1466 	/*
   1467 	 * Set up alignments for widgets at the top of ui,
   1468 	 * align top left, expand horizontally but not vertically
   1469 	 */
   1470 	top_align = gtk_alignment_new(0, 0, 1, 0);
   1471 	top_vbox = gtk_vbox_new(FALSE, 0);
   1472 	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
   1473 	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
   1474 
   1475 	probe = gtk_frame_new("Run statistics");
   1476 	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
   1477 	probe_frame = gtk_vbox_new(FALSE, 3);
   1478 	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
   1479 
   1480 	probe_box = gtk_hbox_new(FALSE, 3);
   1481 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1482 	ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
   1483 	ui->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1484 	ui->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1485 	ui->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1486 	ui->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1487 	ui->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1488 	ui->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1489 
   1490 	/*
   1491 	 * Only add this if we have a commit rate
   1492 	 */
   1493 #if 0
   1494 	probe_box = gtk_hbox_new(FALSE, 3);
   1495 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
   1496 
   1497 	ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1498 	ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1499 
   1500 	ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1501 	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1502 #endif
   1503 
   1504 	/*
   1505 	 * Set up a drawing area and IOPS and bandwidth graphs
   1506 	 */
   1507 	ui->graphs.drawing_area = gtk_drawing_area_new();
   1508 	gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
   1509 		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
   1510 	gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
   1511 	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
   1512 			G_CALLBACK(on_expose_drawing_area), &ui->graphs);
   1513 	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
   1514 			G_CALLBACK(on_config_drawing_area), &ui->graphs);
   1515 	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
   1516 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
   1517 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1518 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
   1519 					ui->graphs.drawing_area);
   1520 	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
   1521 			TRUE, TRUE, 0);
   1522 
   1523 	setup_graphs(&ui->graphs);
   1524 
   1525 	/*
   1526 	 * Set up alignments for widgets at the bottom of ui,
   1527 	 * align bottom left, expand horizontally but not vertically
   1528 	 */
   1529 	bottom_align = gtk_alignment_new(0, 1, 1, 0);
   1530 	ui->buttonbox = gtk_hbox_new(FALSE, 0);
   1531 	gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
   1532 	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
   1533 
   1534 	/*
   1535 	 * Set up thread status progress bar
   1536 	 */
   1537 	ui->thread_status_pb = gtk_progress_bar_new();
   1538 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
   1539 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
   1540 	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
   1541 
   1542 	return main_vbox;
   1543 }
   1544 
   1545 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
   1546 				     guint page, gpointer data)
   1547 
   1548 {
   1549 	struct gui *ui = (struct gui *) data;
   1550 	struct gui_entry *ge;
   1551 
   1552 	if (!page) {
   1553 		set_job_menu_visible(ui, 0);
   1554 		set_view_results_visible(ui, 0);
   1555 		return TRUE;
   1556 	}
   1557 
   1558 	set_job_menu_visible(ui, 1);
   1559 	ge = get_ge_from_page(ui, page, NULL);
   1560 	if (ge)
   1561 		update_button_states(ui, ge);
   1562 
   1563 	return TRUE;
   1564 }
   1565 
   1566 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
   1567 {
   1568 	time_t time_a = gtk_recent_info_get_visited(a);
   1569 	time_t time_b = gtk_recent_info_get_visited(b);
   1570 
   1571 	return time_b - time_a;
   1572 }
   1573 
   1574 static void add_recent_file_items(struct gui *ui)
   1575 {
   1576 	const gchar *gfio = g_get_application_name();
   1577 	GList *items, *item;
   1578 	int i = 0;
   1579 
   1580 	if (ui->recent_ui_id) {
   1581 		gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
   1582 		gtk_ui_manager_ensure_update(ui->uimanager);
   1583 	}
   1584 	ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
   1585 
   1586 	if (ui->actiongroup) {
   1587 		gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
   1588 		g_object_unref(ui->actiongroup);
   1589 	}
   1590 	ui->actiongroup = gtk_action_group_new("RecentFileActions");
   1591 
   1592 	gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
   1593 
   1594 	items = gtk_recent_manager_get_items(ui->recentmanager);
   1595 	items = g_list_sort(items, (GCompareFunc) compare_recent_items);
   1596 
   1597 	for (item = items; item && item->data; item = g_list_next(item)) {
   1598 		GtkRecentInfo *info = (GtkRecentInfo *) item->data;
   1599 		gchar *action_name;
   1600 		const gchar *label;
   1601 		GtkAction *action;
   1602 
   1603 		if (!gtk_recent_info_has_application(info, gfio))
   1604 			continue;
   1605 
   1606 		/*
   1607 		 * We only support local files for now
   1608 		 */
   1609 		if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
   1610 			continue;
   1611 
   1612 		action_name = g_strdup_printf("RecentFile%u", i++);
   1613 		label = gtk_recent_info_get_display_name(info);
   1614 
   1615 		action = g_object_new(GTK_TYPE_ACTION,
   1616 					"name", action_name,
   1617 					"label", label, NULL);
   1618 
   1619 		g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
   1620 					gtk_recent_info_ref(info),
   1621 					(GDestroyNotify) gtk_recent_info_unref);
   1622 
   1623 
   1624 		g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
   1625 
   1626 		gtk_action_group_add_action(ui->actiongroup, action);
   1627 		g_object_unref(action);
   1628 
   1629 		gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
   1630 					"/MainMenu/FileMenu/FileRecentFiles",
   1631 					label, action_name,
   1632 					GTK_UI_MANAGER_MENUITEM, FALSE);
   1633 
   1634 		g_free(action_name);
   1635 
   1636 		if (i == 8)
   1637 			break;
   1638 	}
   1639 
   1640 	g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
   1641 	g_list_free(items);
   1642 }
   1643 
   1644 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
   1645 				   gint x, gint y, GtkSelectionData *seldata,
   1646 				   guint info, guint time, gpointer *data)
   1647 {
   1648 	struct gui *ui = (struct gui *) data;
   1649 	gchar **uris;
   1650 	GtkWidget *source;
   1651 
   1652 	source = gtk_drag_get_source_widget(ctx);
   1653 	if (source && widget == gtk_widget_get_toplevel(source)) {
   1654 		gtk_drag_finish(ctx, FALSE, FALSE, time);
   1655 		return;
   1656 	}
   1657 
   1658 	uris = gtk_selection_data_get_uris(seldata);
   1659 	if (!uris) {
   1660 		gtk_drag_finish(ctx, FALSE, FALSE, time);
   1661 		return;
   1662 	}
   1663 
   1664 	if (uris[0])
   1665 		do_file_open_with_tab(ui, uris[0]);
   1666 
   1667 	gtk_drag_finish(ctx, TRUE, FALSE, time);
   1668 	g_strfreev(uris);
   1669 }
   1670 
   1671 static void init_ui(int *argc, char **argv[], struct gui *ui)
   1672 {
   1673 	GtkSettings *settings;
   1674 	GtkWidget *vbox;
   1675 
   1676 	/* Magical g*thread incantation, you just need this thread stuff.
   1677 	 * Without it, the update that happens in gfio_update_thread_status
   1678 	 * doesn't really happen in a timely fashion, you need expose events
   1679 	 */
   1680 #if !GTK_CHECK_VERSION(2, 24, 0)
   1681 	if (!g_thread_supported())
   1682 		g_thread_init(NULL);
   1683 #endif
   1684 
   1685 	gdk_threads_init();
   1686 
   1687 	gtk_init(argc, argv);
   1688 	settings = gtk_settings_get_default();
   1689 	gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
   1690 	g_type_init();
   1691 	gdk_color_parse("#fffff4", &gfio_color_lightyellow);
   1692 	gdk_color_parse("white", &gfio_color_white);
   1693 
   1694 	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   1695 	gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
   1696 	gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
   1697 
   1698 	g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
   1699 	g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
   1700 
   1701 	ui->vbox = gtk_vbox_new(FALSE, 0);
   1702 	gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
   1703 
   1704 	ui->uimanager = gtk_ui_manager_new();
   1705 	ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
   1706 	gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
   1707 
   1708 	ui->recentmanager = gtk_recent_manager_get_default();
   1709 	add_recent_file_items(ui);
   1710 
   1711 	ui->notebook = gtk_notebook_new();
   1712 	g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
   1713 	gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
   1714 	gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
   1715 	gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
   1716 
   1717 	vbox = new_main_page(ui);
   1718 	gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
   1719 	gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
   1720 	g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
   1721 
   1722 	gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
   1723 
   1724 	gfio_ui_setup_log(ui);
   1725 
   1726 	gtk_widget_show_all(ui->window);
   1727 }
   1728 
   1729 int main(int argc, char *argv[], char *envp[])
   1730 {
   1731 	if (initialize_fio(envp))
   1732 		return 1;
   1733 	if (fio_init_options())
   1734 		return 1;
   1735 
   1736 	gopt_init();
   1737 
   1738 	memset(&main_ui, 0, sizeof(main_ui));
   1739 	main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
   1740 
   1741 	init_ui(&argc, &argv, &main_ui);
   1742 
   1743 	gdk_threads_enter();
   1744 	gtk_main();
   1745 	gdk_threads_leave();
   1746 
   1747 	g_hash_table_destroy(main_ui.ge_hash);
   1748 
   1749 	gopt_exit();
   1750 	return 0;
   1751 }
   1752