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_first_entry(&gc->o_list, 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, false);
    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 	fio_server_create_sk_key();
    463 	is_backend = 1;
    464 	gfio_server_running = 1;
    465 	fio_start_server(NULL);
    466 	gfio_server_running = 0;
    467 	fio_server_destroy_sk_key();
    468 	return NULL;
    469 }
    470 
    471 static void gfio_start_server(struct gui *ui)
    472 {
    473 	if (!gfio_server_running) {
    474 		gfio_server_running = 1;
    475 		pthread_create(&ui->server_t, NULL, server_thread, NULL);
    476 		pthread_detach(ui->server_t);
    477 	}
    478 }
    479 
    480 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
    481 			      gpointer data)
    482 {
    483 	struct gui_entry *ge = data;
    484 	struct gfio_client *gc = ge->client;
    485 
    486 	if (gc)
    487 		fio_start_client(gc->client);
    488 }
    489 
    490 static void file_open(GtkWidget *w, gpointer data);
    491 
    492 struct connection_widgets
    493 {
    494 	GtkWidget *hentry;
    495 	GtkWidget *combo;
    496 	GtkWidget *button;
    497 };
    498 
    499 static void hostname_cb(GtkEntry *entry, gpointer data)
    500 {
    501 	struct connection_widgets *cw = data;
    502 	int uses_net = 0, is_localhost = 0;
    503 	const gchar *text;
    504 	gchar *ctext;
    505 
    506 	/*
    507 	 * Check whether to display the 'auto start backend' box
    508 	 * or not. Show it if we are a localhost and using network,
    509 	 * or using a socket.
    510 	 */
    511 	ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
    512 	if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
    513 		uses_net = 1;
    514 	g_free(ctext);
    515 
    516 	if (uses_net) {
    517 		text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
    518 		if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
    519 		    !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
    520 		    !strcmp(text, "ip6-loopback"))
    521 			is_localhost = 1;
    522 	}
    523 
    524 	if (!uses_net || is_localhost) {
    525 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
    526 		gtk_widget_set_sensitive(cw->button, 1);
    527 	} else {
    528 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
    529 		gtk_widget_set_sensitive(cw->button, 0);
    530 	}
    531 }
    532 
    533 static int get_connection_details(struct gui_entry *ge)
    534 {
    535 	GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
    536 	struct connection_widgets cw;
    537 	struct gui *ui = ge->ui;
    538 	char *typeentry;
    539 
    540 	if (ge->host)
    541 		return 0;
    542 
    543 	dialog = gtk_dialog_new_with_buttons("Connection details",
    544 			GTK_WINDOW(ui->window),
    545 			GTK_DIALOG_DESTROY_WITH_PARENT,
    546 			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
    547 			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
    548 
    549 	frame = gtk_frame_new("Hostname / socket name");
    550 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
    551 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    552 
    553 	box = gtk_vbox_new(FALSE, 6);
    554 	gtk_container_add(GTK_CONTAINER(frame), box);
    555 
    556 	hbox = gtk_hbox_new(TRUE, 10);
    557 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    558 	cw.hentry = gtk_entry_new();
    559 	gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
    560 	gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
    561 
    562 	frame = gtk_frame_new("Port");
    563 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    564 	box = gtk_vbox_new(FALSE, 10);
    565 	gtk_container_add(GTK_CONTAINER(frame), box);
    566 
    567 	hbox = gtk_hbox_new(TRUE, 4);
    568 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    569 	pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
    570 
    571 	frame = gtk_frame_new("Type");
    572 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    573 	box = gtk_vbox_new(FALSE, 10);
    574 	gtk_container_add(GTK_CONTAINER(frame), box);
    575 
    576 	hbox = gtk_hbox_new(TRUE, 4);
    577 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    578 
    579 	cw.combo = gtk_combo_box_text_new();
    580 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
    581 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
    582 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
    583 	gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
    584 
    585 	gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
    586 
    587 	frame = gtk_frame_new("Options");
    588 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    589 	box = gtk_vbox_new(FALSE, 10);
    590 	gtk_container_add(GTK_CONTAINER(frame), box);
    591 
    592 	hbox = gtk_hbox_new(TRUE, 4);
    593 	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
    594 
    595 	cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
    596 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
    597 	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.");
    598 	gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
    599 
    600 	/*
    601 	 * Connect edit signal, so we can show/not-show the auto start button
    602 	 */
    603 	g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
    604 	g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
    605 
    606 	gtk_widget_show_all(dialog);
    607 
    608 	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
    609 		gtk_widget_destroy(dialog);
    610 		return 1;
    611 	}
    612 
    613 	ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
    614 	ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
    615 
    616 	typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
    617 	if (!typeentry || !strncmp(typeentry, "IPv4", 4))
    618 		ge->type = Fio_client_ipv4;
    619 	else if (!strncmp(typeentry, "IPv6", 4))
    620 		ge->type = Fio_client_ipv6;
    621 	else
    622 		ge->type = Fio_client_socket;
    623 	g_free(typeentry);
    624 
    625 	ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
    626 
    627 	gtk_widget_destroy(dialog);
    628 	return 0;
    629 }
    630 
    631 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
    632 {
    633 	gc->client = fio_get_client(client);
    634 	client->client_data = gc;
    635 }
    636 
    637 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
    638 {
    639 	struct gfio_client_options *gco;
    640 	struct gfio_client *gc;
    641 
    642 	gc = calloc(1, sizeof(*gc));
    643 	INIT_FLIST_HEAD(&gc->o_list);
    644 	gc->ge = ge;
    645 	ge->client = gc;
    646 	gfio_set_client(gc, client);
    647 
    648 	/*
    649 	 * Just add a default set of options, need to consider how best
    650 	 * to handle this
    651 	 */
    652 	gco = calloc(1, sizeof(*gco));
    653 	INIT_FLIST_HEAD(&gco->list);
    654 	options_default_fill(&gco->o);
    655 	flist_add_tail(&gco->list, &gc->o_list);
    656 	gc->o_list_nr++;
    657 }
    658 
    659 static void gfio_clear_graph_data(struct gfio_graphs *g)
    660 {
    661 	graph_clear_values(g->iops_graph);
    662 	graph_clear_values(g->bandwidth_graph);
    663 }
    664 
    665 static void connect_clicked(GtkWidget *widget, gpointer data)
    666 {
    667 	struct gui_entry *ge = data;
    668 	struct gfio_client *gc = ge->client;
    669 
    670 	if (ge->state == GE_STATE_NEW) {
    671 		int ret;
    672 
    673 		if (!ge->job_file)
    674 			file_open(widget, ge->ui);
    675 		if (!ge->job_file)
    676 			return;
    677 
    678 		gc = ge->client;
    679 
    680 		if (!gc->client) {
    681 			struct fio_client *client;
    682 
    683 			if (get_connection_details(ge)) {
    684 				gfio_report_error(ge, "Failed to get connection details\n");
    685 				return;
    686 			}
    687 
    688 			client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
    689 			if (!client) {
    690 				gfio_report_error(ge, "Failed to add client %s\n", ge->host);
    691 				free(ge->host);
    692 				ge->host = NULL;
    693 				return;
    694 			}
    695 			gfio_set_client(gc, client);
    696 		}
    697 
    698 		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
    699 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
    700 		ret = fio_client_connect(gc->client);
    701 		if (!ret) {
    702 			if (!ge->ui->handler_running)
    703 				pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
    704 			gfio_set_state(ge, GE_STATE_CONNECTED);
    705 			gfio_clear_graph_data(&ge->graphs);
    706 		} else {
    707 			gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
    708 		}
    709 	} else {
    710 		fio_client_terminate(gc->client);
    711 		gfio_set_state(ge, GE_STATE_NEW);
    712 		clear_ge_ui_info(ge);
    713 	}
    714 }
    715 
    716 static void send_clicked(GtkWidget *widget, gpointer data)
    717 {
    718 	struct gui_entry *ge = data;
    719 
    720 	if (send_job_file(ge))
    721 		gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
    722 }
    723 
    724 static GtkWidget *new_client_page(struct gui_entry *ge);
    725 
    726 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
    727 {
    728 	struct gui_entry *ge;
    729 
    730 	ge = malloc(sizeof(*ge));
    731 	memset(ge, 0, sizeof(*ge));
    732 	ge->state = GE_STATE_NEW;
    733 	ge->ui = ui;
    734 	return ge;
    735 }
    736 
    737 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
    738 {
    739 	struct gui_entry *ge;
    740 
    741 	ge = alloc_new_gui_entry(ui);
    742 
    743 	ge->vbox = new_client_page(ge);
    744 	g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
    745 
    746 	ge->page_label = gtk_label_new(name);
    747 	ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
    748 
    749 	g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
    750 
    751 	gtk_widget_show_all(ui->window);
    752 	return ge;
    753 }
    754 
    755 static void file_new(GtkWidget *w, gpointer data)
    756 {
    757 	struct gui *ui = (struct gui *) data;
    758 	struct gui_entry *ge;
    759 
    760 	ge = get_new_ge_with_tab(ui, "Untitled");
    761 	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
    762 }
    763 
    764 /*
    765  * Return the 'ge' corresponding to the tab. If the active tab is the
    766  * main tab, open a new tab.
    767  */
    768 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
    769 					  int *created)
    770 {
    771 	if (!cur_page) {
    772 		if (created)
    773 			*created = 1;
    774 		return get_new_ge_with_tab(ui, "Untitled");
    775 	}
    776 
    777 	if (created)
    778 		*created = 0;
    779 
    780 	return g_hash_table_lookup(ui->ge_hash, &cur_page);
    781 }
    782 
    783 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
    784 {
    785 	gint cur_page;
    786 
    787 	/*
    788 	 * Main tab is tab 0, so any current page other than 0 holds
    789 	 * a ge entry.
    790 	 */
    791 	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
    792 	if (cur_page)
    793 		return get_ge_from_page(ui, cur_page, NULL);
    794 
    795 	return NULL;
    796 }
    797 
    798 static void file_close(GtkWidget *w, gpointer data)
    799 {
    800 	struct gui *ui = (struct gui *) data;
    801 	struct gui_entry *ge;
    802 
    803 	/*
    804 	 * Can't close the main tab
    805 	 */
    806 	ge = get_ge_from_cur_tab(ui);
    807 	if (ge) {
    808 		gtk_widget_destroy(ge->vbox);
    809 		return;
    810 	}
    811 
    812 	if (g_hash_table_size(ui->ge_hash)) {
    813 		gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
    814 		return;
    815 	}
    816 
    817 	gfio_quit(ui);
    818 }
    819 
    820 static void file_add_recent(struct gui *ui, const gchar *uri)
    821 {
    822 	GtkRecentData grd;
    823 
    824 	memset(&grd, 0, sizeof(grd));
    825 	grd.display_name = strdup("gfio");
    826 	grd.description = strdup("Fio job file");
    827 	grd.mime_type = strdup(GFIO_MIME);
    828 	grd.app_name = strdup(g_get_application_name());
    829 	grd.app_exec = strdup("gfio %f/%u");
    830 
    831 	gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
    832 }
    833 
    834 static gchar *get_filename_from_uri(const gchar *uri)
    835 {
    836 	if (strncmp(uri, "file://", 7))
    837 		return strdup(uri);
    838 
    839 	return strdup(uri + 7);
    840 }
    841 
    842 static int do_file_open(struct gui_entry *ge, const gchar *uri)
    843 {
    844 	struct fio_client *client;
    845 
    846 	assert(!ge->job_file);
    847 
    848 	ge->job_file = get_filename_from_uri(uri);
    849 
    850 	client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
    851 	if (client) {
    852 		char *label = strdup(uri);
    853 
    854 		basename(label);
    855 		gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
    856 		free(label);
    857 
    858 		gfio_client_added(ge, client);
    859 		file_add_recent(ge->ui, uri);
    860 		return 0;
    861 	}
    862 
    863 	gfio_report_error(ge, "Failed to add client %s\n", ge->host);
    864 	free(ge->host);
    865 	ge->host = NULL;
    866 	free(ge->job_file);
    867 	ge->job_file = NULL;
    868 	return 1;
    869 }
    870 
    871 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
    872 {
    873 	struct gui_entry *ge;
    874 	gint cur_page;
    875 	int ret, ge_is_new = 0;
    876 
    877 	/*
    878 	 * Creates new tab if current tab is the main window, or the
    879 	 * current tab already has a client.
    880 	 */
    881 	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
    882 	ge = get_ge_from_page(ui, cur_page, &ge_is_new);
    883 	if (ge->client) {
    884 		ge = get_new_ge_with_tab(ui, "Untitled");
    885 		ge_is_new = 1;
    886 	}
    887 
    888 	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
    889 
    890 	if (get_connection_details(ge)) {
    891 		if (ge_is_new)
    892 			gtk_widget_destroy(ge->vbox);
    893 
    894 		return 1;
    895 	}
    896 
    897 	ret = do_file_open(ge, uri);
    898 
    899 	if (!ret) {
    900 		if (ge->server_start)
    901 			gfio_start_server(ui);
    902 	} else {
    903 		if (ge_is_new)
    904 			gtk_widget_destroy(ge->vbox);
    905 	}
    906 
    907 	return ret;
    908 }
    909 
    910 static void recent_open(GtkAction *action, gpointer data)
    911 {
    912 	struct gui *ui = (struct gui *) data;
    913 	GtkRecentInfo *info;
    914 	const gchar *uri;
    915 
    916 	info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
    917 	uri = gtk_recent_info_get_uri(info);
    918 
    919 	do_file_open_with_tab(ui, uri);
    920 }
    921 
    922 static void file_open(GtkWidget *w, gpointer data)
    923 {
    924 	struct gui *ui = data;
    925 	GtkWidget *dialog;
    926 	GtkFileFilter *filter;
    927 	gchar *filename;
    928 
    929 	dialog = gtk_file_chooser_dialog_new("Open File",
    930 		GTK_WINDOW(ui->window),
    931 		GTK_FILE_CHOOSER_ACTION_OPEN,
    932 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    933 		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
    934 		NULL);
    935 	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
    936 
    937 	filter = gtk_file_filter_new();
    938 	gtk_file_filter_add_pattern(filter, "*.fio");
    939 	gtk_file_filter_add_pattern(filter, "*.job");
    940 	gtk_file_filter_add_pattern(filter, "*.ini");
    941 	gtk_file_filter_add_mime_type(filter, GFIO_MIME);
    942 	gtk_file_filter_set_name(filter, "Fio job file");
    943 	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
    944 
    945 	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
    946 		gtk_widget_destroy(dialog);
    947 		return;
    948 	}
    949 
    950 	filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
    951 
    952 	gtk_widget_destroy(dialog);
    953 
    954 	do_file_open_with_tab(ui, filename);
    955 	g_free(filename);
    956 }
    957 
    958 static void file_save(GtkWidget *w, gpointer data)
    959 {
    960 	struct gui *ui = data;
    961 	GtkWidget *dialog;
    962 
    963 	dialog = gtk_file_chooser_dialog_new("Save File",
    964 		GTK_WINDOW(ui->window),
    965 		GTK_FILE_CHOOSER_ACTION_SAVE,
    966 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    967 		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
    968 		NULL);
    969 
    970 	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
    971 	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
    972 
    973 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
    974 		char *filename;
    975 
    976 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
    977 		// save_job_file(filename);
    978 		g_free(filename);
    979 	}
    980 	gtk_widget_destroy(dialog);
    981 }
    982 
    983 static void view_log_destroy(GtkWidget *w, gpointer data)
    984 {
    985 	struct gui *ui = (struct gui *) data;
    986 
    987 	g_object_ref(G_OBJECT(ui->log_tree));
    988 	gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
    989 	gtk_widget_destroy(w);
    990 	ui->log_view = NULL;
    991 }
    992 
    993 void gfio_view_log(struct gui *ui)
    994 {
    995 	GtkWidget *win, *scroll, *vbox, *box;
    996 
    997 	if (ui->log_view)
    998 		return;
    999 
   1000 	ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   1001 	gtk_window_set_title(GTK_WINDOW(win), "Log");
   1002 	gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
   1003 
   1004 	scroll = gtk_scrolled_window_new(NULL, NULL);
   1005 
   1006 	gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
   1007 
   1008 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1009 
   1010 	box = gtk_hbox_new(TRUE, 0);
   1011 	gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
   1012 	g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
   1013 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
   1014 
   1015 	vbox = gtk_vbox_new(TRUE, 5);
   1016 	gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
   1017 
   1018 	gtk_container_add(GTK_CONTAINER(win), vbox);
   1019 	gtk_widget_show_all(win);
   1020 }
   1021 
   1022 static void view_log(GtkWidget *w, gpointer data)
   1023 {
   1024 	struct gui *ui = (struct gui *) data;
   1025 
   1026 	gfio_view_log(ui);
   1027 }
   1028 
   1029 static void connect_job_entry(GtkWidget *w, gpointer data)
   1030 {
   1031 	struct gui *ui = (struct gui *) data;
   1032 	struct gui_entry *ge;
   1033 
   1034 	ge = get_ge_from_cur_tab(ui);
   1035 	if (ge)
   1036 		connect_clicked(w, ge);
   1037 }
   1038 
   1039 static void send_job_entry(GtkWidget *w, gpointer data)
   1040 {
   1041 	struct gui *ui = (struct gui *) data;
   1042 	struct gui_entry *ge;
   1043 
   1044 	ge = get_ge_from_cur_tab(ui);
   1045 	if (ge)
   1046 		send_clicked(w, ge);
   1047 }
   1048 
   1049 static void edit_job_entry(GtkWidget *w, gpointer data)
   1050 {
   1051 	struct gui *ui = (struct gui *) data;
   1052 	struct gui_entry *ge;
   1053 
   1054 	ge = get_ge_from_cur_tab(ui);
   1055 	if (ge && ge->client)
   1056 		gopt_get_options_window(ui->window, ge->client);
   1057 }
   1058 
   1059 static void start_job_entry(GtkWidget *w, gpointer data)
   1060 {
   1061 	struct gui *ui = (struct gui *) data;
   1062 	struct gui_entry *ge;
   1063 
   1064 	ge = get_ge_from_cur_tab(ui);
   1065 	if (ge)
   1066 		start_job_clicked(w, ge);
   1067 }
   1068 
   1069 static void view_results(GtkWidget *w, gpointer data)
   1070 {
   1071 	struct gui *ui = (struct gui *) data;
   1072 	struct gfio_client *gc;
   1073 	struct gui_entry *ge;
   1074 
   1075 	ge = get_ge_from_cur_tab(ui);
   1076 	if (!ge)
   1077 		return;
   1078 
   1079 	if (ge->results_window)
   1080 		return;
   1081 
   1082 	gc = ge->client;
   1083 	if (gc && gc->nr_results)
   1084 		gfio_display_end_results(gc);
   1085 }
   1086 
   1087 static void __update_graph_settings(struct gfio_graphs *g)
   1088 {
   1089 	line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
   1090 	graph_set_font(g->iops_graph, gfio_graph_font);
   1091 	line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
   1092 	graph_set_font(g->bandwidth_graph, gfio_graph_font);
   1093 }
   1094 
   1095 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
   1096 {
   1097 	struct gui_entry *ge = (struct gui_entry *) value;
   1098 	GdkEvent *ev;
   1099 
   1100 	__update_graph_settings(&ge->graphs);
   1101 
   1102 	ev = gdk_event_new(GDK_EXPOSE);
   1103 	g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
   1104 	gdk_event_free(ev);
   1105 }
   1106 
   1107 static void update_graph_limits(void)
   1108 {
   1109 	struct gui *ui = &main_ui;
   1110 	GdkEvent *ev;
   1111 
   1112 	__update_graph_settings(&ui->graphs);
   1113 
   1114 	ev = gdk_event_new(GDK_EXPOSE);
   1115 	g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
   1116 	gdk_event_free(ev);
   1117 
   1118 	g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
   1119 }
   1120 
   1121 static void preferences(GtkWidget *w, gpointer data)
   1122 {
   1123 	GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
   1124 	GtkWidget *hbox, *spin, *entry, *spin_int;
   1125 	struct gui *ui = (struct gui *) data;
   1126 	int i;
   1127 
   1128 	dialog = gtk_dialog_new_with_buttons("Preferences",
   1129 		GTK_WINDOW(ui->window),
   1130 		GTK_DIALOG_DESTROY_WITH_PARENT,
   1131 		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
   1132 		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
   1133 		NULL);
   1134 
   1135 	frame = gtk_frame_new("Graphing");
   1136 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
   1137 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
   1138 	vbox = gtk_vbox_new(FALSE, 6);
   1139 	gtk_container_add(GTK_CONTAINER(frame), vbox);
   1140 
   1141 	hbox = gtk_hbox_new(FALSE, 5);
   1142 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
   1143 	entry = gtk_label_new("Font face to use for graph labels");
   1144 	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
   1145 
   1146 	font = gtk_font_button_new_with_font(gfio_graph_font);
   1147 	gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
   1148 
   1149 	box = gtk_vbox_new(FALSE, 6);
   1150 	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
   1151 
   1152 	hbox = gtk_hbox_new(FALSE, 5);
   1153 	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
   1154 	entry = gtk_label_new("Maximum number of data points in graph (seconds)");
   1155 	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
   1156 
   1157 	spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
   1158 
   1159 	box = gtk_vbox_new(FALSE, 6);
   1160 	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
   1161 
   1162 	hbox = gtk_hbox_new(FALSE, 5);
   1163 	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
   1164 	entry = gtk_label_new("Client ETA request interval (msec)");
   1165 	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
   1166 
   1167 	spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
   1168 	frame = gtk_frame_new("Debug logging");
   1169 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
   1170 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
   1171 	vbox = gtk_vbox_new(FALSE, 6);
   1172 	gtk_container_add(GTK_CONTAINER(frame), vbox);
   1173 
   1174 	box = gtk_hbox_new(FALSE, 6);
   1175 	gtk_container_add(GTK_CONTAINER(vbox), box);
   1176 
   1177 	buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
   1178 
   1179 	for (i = 0; i < FD_DEBUG_MAX; i++) {
   1180 		if (i == 7) {
   1181 			box = gtk_hbox_new(FALSE, 6);
   1182 			gtk_container_add(GTK_CONTAINER(vbox), box);
   1183 		}
   1184 
   1185 
   1186 		buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
   1187 		gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
   1188 		gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
   1189 	}
   1190 
   1191 	gtk_widget_show_all(dialog);
   1192 
   1193 	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
   1194 		gtk_widget_destroy(dialog);
   1195 		return;
   1196 	}
   1197 
   1198 	for (i = 0; i < FD_DEBUG_MAX; i++) {
   1199 		int set;
   1200 
   1201 		set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
   1202 		if (set)
   1203 			fio_debug |= (1UL << i);
   1204 	}
   1205 
   1206 	gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
   1207 	gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
   1208 	update_graph_limits();
   1209 	gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
   1210 
   1211 	gtk_widget_destroy(dialog);
   1212 }
   1213 
   1214 static void about_dialog(GtkWidget *w, gpointer data)
   1215 {
   1216 	const char *authors[] = {
   1217 		"Jens Axboe <axboe (at) kernel.dk>",
   1218 		"Stephen Cameron <stephenmcameron (at) gmail.com>",
   1219 		NULL
   1220 	};
   1221 	const char *license[] = {
   1222 		"Fio is free software; you can redistribute it and/or modify "
   1223 		"it under the terms of the GNU General Public License as published by "
   1224 		"the Free Software Foundation; either version 2 of the License, or "
   1225 		"(at your option) any later version.\n",
   1226 		"Fio is distributed in the hope that it will be useful, "
   1227 		"but WITHOUT ANY WARRANTY; without even the implied warranty of "
   1228 		"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
   1229 		"GNU General Public License for more details.\n",
   1230 		"You should have received a copy of the GNU General Public License "
   1231 		"along with Fio; if not, write to the Free Software Foundation, Inc., "
   1232 		"51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
   1233 	};
   1234 	char *license_trans;
   1235 
   1236 	license_trans = g_strconcat(license[0], "\n", license[1], "\n",
   1237 				     license[2], "\n", NULL);
   1238 
   1239 	gtk_show_about_dialog(NULL,
   1240 		"program-name", "gfio",
   1241 		"comments", "Gtk2 UI for fio",
   1242 		"license", license_trans,
   1243 		"website", "http://git.kernel.dk/cgit/fio/",
   1244 		"authors", authors,
   1245 		"version", fio_version_string,
   1246 		"copyright", " 2012 Jens Axboe <axboe (at) kernel.dk>",
   1247 		"logo-icon-name", "fio",
   1248 		/* Must be last: */
   1249 		"wrap-license", TRUE,
   1250 		NULL);
   1251 
   1252 	g_free(license_trans);
   1253 }
   1254 
   1255 static GtkActionEntry menu_items[] = {
   1256 	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
   1257 	{ "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
   1258 	{ "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
   1259 	{ "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
   1260 	{ "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
   1261 	{ "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
   1262 	{ "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
   1263 	{ "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
   1264 	{ "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
   1265 	{ "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
   1266 	{ "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
   1267 	{ "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
   1268 	{ "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
   1269 	{ "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
   1270 	{ "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
   1271 	{ "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
   1272 	{ "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
   1273 };
   1274 static gint nmenu_items = ARRAY_SIZE(menu_items);
   1275 
   1276 static const gchar *ui_string = " \
   1277 	<ui> \
   1278 		<menubar name=\"MainMenu\"> \
   1279 			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
   1280 				<menuitem name=\"New\" action=\"NewFile\" /> \
   1281 				<menuitem name=\"Open\" action=\"OpenFile\" /> \
   1282 				<menuitem name=\"Close\" action=\"CloseFile\" /> \
   1283 				<separator name=\"Separator1\"/> \
   1284 				<menuitem name=\"Save\" action=\"SaveFile\" /> \
   1285 				<separator name=\"Separator2\"/> \
   1286 				<menuitem name=\"Preferences\" action=\"Preferences\" /> \
   1287 				<separator name=\"Separator3\"/> \
   1288 				<placeholder name=\"FileRecentFiles\"/> \
   1289 				<separator name=\"Separator4\"/> \
   1290 				<menuitem name=\"Quit\" action=\"Quit\" /> \
   1291 			</menu> \
   1292 			<menu name=\"JobMenu\" action=\"JobMenuAction\"> \
   1293 				<menuitem name=\"Connect\" action=\"ConnectJob\" /> \
   1294 				<separator name=\"Separator5\"/> \
   1295 				<menuitem name=\"Edit job\" action=\"EditJob\" /> \
   1296 				<menuitem name=\"Send job\" action=\"SendJob\" /> \
   1297 				<separator name=\"Separator6\"/> \
   1298 				<menuitem name=\"Start job\" action=\"StartJob\" /> \
   1299 			</menu>\
   1300 			<menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
   1301 				<menuitem name=\"Results\" action=\"ViewResults\" /> \
   1302 				<separator name=\"Separator7\"/> \
   1303 				<menuitem name=\"Log\" action=\"ViewLog\" /> \
   1304 			</menu>\
   1305 			<menu name=\"Help\" action=\"HelpMenuAction\"> \
   1306 				<menuitem name=\"About\" action=\"About\" /> \
   1307 			</menu> \
   1308 		</menubar> \
   1309 	</ui> \
   1310 ";
   1311 
   1312 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
   1313 				   struct gui *ui)
   1314 {
   1315 	GtkActionGroup *action_group;
   1316 	GError *error = 0;
   1317 
   1318 	action_group = gtk_action_group_new("Menu");
   1319 	gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
   1320 
   1321 	gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
   1322 	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
   1323 
   1324 	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
   1325 
   1326 	return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
   1327 }
   1328 
   1329 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
   1330 		   GtkWidget *vbox, GtkUIManager *ui_manager)
   1331 {
   1332 	gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
   1333 }
   1334 
   1335 static void combo_entry_changed(GtkComboBox *box, gpointer data)
   1336 {
   1337 	struct gui_entry *ge = (struct gui_entry *) data;
   1338 	gint index;
   1339 
   1340 	index = gtk_combo_box_get_active(box);
   1341 
   1342 	multitext_set_entry(&ge->eta.iotype, index);
   1343 	multitext_set_entry(&ge->eta.bs, index);
   1344 	multitext_set_entry(&ge->eta.ioengine, index);
   1345 	multitext_set_entry(&ge->eta.iodepth, index);
   1346 }
   1347 
   1348 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
   1349 {
   1350 	struct gui_entry *ge = (struct gui_entry *) data;
   1351 
   1352 	multitext_free(&ge->eta.iotype);
   1353 	multitext_free(&ge->eta.bs);
   1354 	multitext_free(&ge->eta.ioengine);
   1355 	multitext_free(&ge->eta.iodepth);
   1356 }
   1357 
   1358 static GtkWidget *new_client_page(struct gui_entry *ge)
   1359 {
   1360 	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
   1361 	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
   1362 
   1363 	main_vbox = gtk_vbox_new(FALSE, 3);
   1364 
   1365 	top_align = gtk_alignment_new(0, 0, 1, 0);
   1366 	top_vbox = gtk_vbox_new(FALSE, 3);
   1367 	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
   1368 	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
   1369 
   1370 	probe = gtk_frame_new("Job");
   1371 	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
   1372 	probe_frame = gtk_vbox_new(FALSE, 3);
   1373 	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
   1374 
   1375 	probe_box = gtk_hbox_new(FALSE, 3);
   1376 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1377 	ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
   1378 	ge->probe.os = new_info_label_in_frame(probe_box, "OS");
   1379 	ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
   1380 	ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
   1381 
   1382 	probe_box = gtk_hbox_new(FALSE, 3);
   1383 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1384 
   1385 	ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
   1386 	g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
   1387 	g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
   1388 	ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
   1389 	ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write/Trim)");
   1390 	ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
   1391 	ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
   1392 	ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
   1393 	ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
   1394 
   1395 	probe_box = gtk_hbox_new(FALSE, 3);
   1396 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1397 	ge->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1398 	ge->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "Read IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1399 	ge->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1400 	ge->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "Write IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1401 	ge->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1402 	ge->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "Trim IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1403 
   1404 	/*
   1405 	 * Only add this if we have a commit rate
   1406 	 */
   1407 #if 0
   1408 	probe_box = gtk_hbox_new(FALSE, 3);
   1409 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
   1410 
   1411 	ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1412 	ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1413 
   1414 	ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1415 	ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1416 #endif
   1417 
   1418 	/*
   1419 	 * Set up a drawing area and IOPS and bandwidth graphs
   1420 	 */
   1421 	ge->graphs.drawing_area = gtk_drawing_area_new();
   1422 	gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
   1423 		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
   1424 	gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
   1425 	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
   1426 				G_CALLBACK(on_expose_drawing_area), &ge->graphs);
   1427 	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
   1428 				G_CALLBACK(on_config_drawing_area), &ge->graphs);
   1429 	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
   1430 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
   1431 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1432 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
   1433 					ge->graphs.drawing_area);
   1434 	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
   1435 
   1436 	setup_graphs(&ge->graphs);
   1437 
   1438 	/*
   1439 	 * Set up alignments for widgets at the bottom of ui,
   1440 	 * align bottom left, expand horizontally but not vertically
   1441 	 */
   1442 	bottom_align = gtk_alignment_new(0, 1, 1, 0);
   1443 	ge->buttonbox = gtk_hbox_new(FALSE, 0);
   1444 	gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
   1445 	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
   1446 
   1447 	add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
   1448 
   1449 	/*
   1450 	 * Set up thread status progress bar
   1451 	 */
   1452 	ge->thread_status_pb = gtk_progress_bar_new();
   1453 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
   1454 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
   1455 	gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
   1456 
   1457 
   1458 	return main_vbox;
   1459 }
   1460 
   1461 static GtkWidget *new_main_page(struct gui *ui)
   1462 {
   1463 	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
   1464 	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
   1465 
   1466 	main_vbox = gtk_vbox_new(FALSE, 3);
   1467 
   1468 	/*
   1469 	 * Set up alignments for widgets at the top of ui,
   1470 	 * align top left, expand horizontally but not vertically
   1471 	 */
   1472 	top_align = gtk_alignment_new(0, 0, 1, 0);
   1473 	top_vbox = gtk_vbox_new(FALSE, 0);
   1474 	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
   1475 	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
   1476 
   1477 	probe = gtk_frame_new("Run statistics");
   1478 	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
   1479 	probe_frame = gtk_vbox_new(FALSE, 3);
   1480 	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
   1481 
   1482 	probe_box = gtk_hbox_new(FALSE, 3);
   1483 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
   1484 	ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
   1485 	ui->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1486 	ui->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
   1487 	ui->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1488 	ui->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
   1489 	ui->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1490 	ui->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
   1491 
   1492 	/*
   1493 	 * Only add this if we have a commit rate
   1494 	 */
   1495 #if 0
   1496 	probe_box = gtk_hbox_new(FALSE, 3);
   1497 	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
   1498 
   1499 	ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1500 	ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1501 
   1502 	ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
   1503 	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
   1504 #endif
   1505 
   1506 	/*
   1507 	 * Set up a drawing area and IOPS and bandwidth graphs
   1508 	 */
   1509 	ui->graphs.drawing_area = gtk_drawing_area_new();
   1510 	gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
   1511 		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
   1512 	gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
   1513 	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
   1514 			G_CALLBACK(on_expose_drawing_area), &ui->graphs);
   1515 	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
   1516 			G_CALLBACK(on_config_drawing_area), &ui->graphs);
   1517 	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
   1518 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
   1519 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1520 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
   1521 					ui->graphs.drawing_area);
   1522 	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
   1523 			TRUE, TRUE, 0);
   1524 
   1525 	setup_graphs(&ui->graphs);
   1526 
   1527 	/*
   1528 	 * Set up alignments for widgets at the bottom of ui,
   1529 	 * align bottom left, expand horizontally but not vertically
   1530 	 */
   1531 	bottom_align = gtk_alignment_new(0, 1, 1, 0);
   1532 	ui->buttonbox = gtk_hbox_new(FALSE, 0);
   1533 	gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
   1534 	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
   1535 
   1536 	/*
   1537 	 * Set up thread status progress bar
   1538 	 */
   1539 	ui->thread_status_pb = gtk_progress_bar_new();
   1540 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
   1541 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
   1542 	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
   1543 
   1544 	return main_vbox;
   1545 }
   1546 
   1547 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
   1548 				     guint page, gpointer data)
   1549 
   1550 {
   1551 	struct gui *ui = (struct gui *) data;
   1552 	struct gui_entry *ge;
   1553 
   1554 	if (!page) {
   1555 		set_job_menu_visible(ui, 0);
   1556 		set_view_results_visible(ui, 0);
   1557 		return TRUE;
   1558 	}
   1559 
   1560 	set_job_menu_visible(ui, 1);
   1561 	ge = get_ge_from_page(ui, page, NULL);
   1562 	if (ge)
   1563 		update_button_states(ui, ge);
   1564 
   1565 	return TRUE;
   1566 }
   1567 
   1568 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
   1569 {
   1570 	time_t time_a = gtk_recent_info_get_visited(a);
   1571 	time_t time_b = gtk_recent_info_get_visited(b);
   1572 
   1573 	return time_b - time_a;
   1574 }
   1575 
   1576 static void add_recent_file_items(struct gui *ui)
   1577 {
   1578 	const gchar *gfio = g_get_application_name();
   1579 	GList *items, *item;
   1580 	int i = 0;
   1581 
   1582 	if (ui->recent_ui_id) {
   1583 		gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
   1584 		gtk_ui_manager_ensure_update(ui->uimanager);
   1585 	}
   1586 	ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
   1587 
   1588 	if (ui->actiongroup) {
   1589 		gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
   1590 		g_object_unref(ui->actiongroup);
   1591 	}
   1592 	ui->actiongroup = gtk_action_group_new("RecentFileActions");
   1593 
   1594 	gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
   1595 
   1596 	items = gtk_recent_manager_get_items(ui->recentmanager);
   1597 	items = g_list_sort(items, (GCompareFunc) compare_recent_items);
   1598 
   1599 	for (item = items; item && item->data; item = g_list_next(item)) {
   1600 		GtkRecentInfo *info = (GtkRecentInfo *) item->data;
   1601 		gchar *action_name;
   1602 		const gchar *label;
   1603 		GtkAction *action;
   1604 
   1605 		if (!gtk_recent_info_has_application(info, gfio))
   1606 			continue;
   1607 
   1608 		/*
   1609 		 * We only support local files for now
   1610 		 */
   1611 		if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
   1612 			continue;
   1613 
   1614 		action_name = g_strdup_printf("RecentFile%u", i++);
   1615 		label = gtk_recent_info_get_display_name(info);
   1616 
   1617 		action = g_object_new(GTK_TYPE_ACTION,
   1618 					"name", action_name,
   1619 					"label", label, NULL);
   1620 
   1621 		g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
   1622 					gtk_recent_info_ref(info),
   1623 					(GDestroyNotify) gtk_recent_info_unref);
   1624 
   1625 
   1626 		g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
   1627 
   1628 		gtk_action_group_add_action(ui->actiongroup, action);
   1629 		g_object_unref(action);
   1630 
   1631 		gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
   1632 					"/MainMenu/FileMenu/FileRecentFiles",
   1633 					label, action_name,
   1634 					GTK_UI_MANAGER_MENUITEM, FALSE);
   1635 
   1636 		g_free(action_name);
   1637 
   1638 		if (i == 8)
   1639 			break;
   1640 	}
   1641 
   1642 	g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
   1643 	g_list_free(items);
   1644 }
   1645 
   1646 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
   1647 				   gint x, gint y, GtkSelectionData *seldata,
   1648 				   guint info, guint time, gpointer *data)
   1649 {
   1650 	struct gui *ui = (struct gui *) data;
   1651 	gchar **uris;
   1652 	GtkWidget *source;
   1653 
   1654 	source = gtk_drag_get_source_widget(ctx);
   1655 	if (source && widget == gtk_widget_get_toplevel(source)) {
   1656 		gtk_drag_finish(ctx, FALSE, FALSE, time);
   1657 		return;
   1658 	}
   1659 
   1660 	uris = gtk_selection_data_get_uris(seldata);
   1661 	if (!uris) {
   1662 		gtk_drag_finish(ctx, FALSE, FALSE, time);
   1663 		return;
   1664 	}
   1665 
   1666 	if (uris[0])
   1667 		do_file_open_with_tab(ui, uris[0]);
   1668 
   1669 	gtk_drag_finish(ctx, TRUE, FALSE, time);
   1670 	g_strfreev(uris);
   1671 }
   1672 
   1673 static void init_ui(int *argc, char **argv[], struct gui *ui)
   1674 {
   1675 	GtkSettings *settings;
   1676 	GtkWidget *vbox;
   1677 
   1678 	/* Magical g*thread incantation, you just need this thread stuff.
   1679 	 * Without it, the update that happens in gfio_update_thread_status
   1680 	 * doesn't really happen in a timely fashion, you need expose events
   1681 	 */
   1682 #if !GLIB_CHECK_VERSION(2, 31, 0)
   1683 	if (!g_thread_supported())
   1684 		g_thread_init(NULL);
   1685 #endif
   1686 
   1687 	gdk_threads_init();
   1688 
   1689 	gtk_init(argc, argv);
   1690 	settings = gtk_settings_get_default();
   1691 	gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
   1692 #if !GLIB_CHECK_VERSION(2, 36, 0)
   1693 	g_type_init();
   1694 #endif
   1695 	gdk_color_parse("#fffff4", &gfio_color_lightyellow);
   1696 	gdk_color_parse("white", &gfio_color_white);
   1697 
   1698 	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   1699 	gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
   1700 	gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
   1701 
   1702 	g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
   1703 	g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
   1704 
   1705 	ui->vbox = gtk_vbox_new(FALSE, 0);
   1706 	gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
   1707 
   1708 	ui->uimanager = gtk_ui_manager_new();
   1709 	ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
   1710 	gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
   1711 
   1712 	ui->recentmanager = gtk_recent_manager_get_default();
   1713 	add_recent_file_items(ui);
   1714 
   1715 	ui->notebook = gtk_notebook_new();
   1716 	g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
   1717 	gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
   1718 	gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
   1719 	gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
   1720 
   1721 	vbox = new_main_page(ui);
   1722 	gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
   1723 	gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
   1724 	g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
   1725 
   1726 	gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
   1727 
   1728 	gfio_ui_setup_log(ui);
   1729 
   1730 	gtk_widget_show_all(ui->window);
   1731 }
   1732 
   1733 int main(int argc, char *argv[], char *envp[])
   1734 {
   1735 	if (initialize_fio(envp))
   1736 		return 1;
   1737 	if (fio_init_options())
   1738 		return 1;
   1739 
   1740 	gopt_init();
   1741 
   1742 	memset(&main_ui, 0, sizeof(main_ui));
   1743 	main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
   1744 
   1745 	init_ui(&argc, &argv, &main_ui);
   1746 
   1747 	gdk_threads_enter();
   1748 	gtk_main();
   1749 	gdk_threads_leave();
   1750 
   1751 	g_hash_table_destroy(main_ui.ge_hash);
   1752 
   1753 	gopt_exit();
   1754 	return 0;
   1755 }
   1756