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