gfio.c revision 789f4ccdf725e6da8bde98afd004c31e16241cfd
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 "graph.h"
37
38static int gfio_server_running;
39static const char *gfio_graph_font;
40static unsigned int gfio_graph_limit = 100;
41static GdkColor white;
42
43static void view_log(GtkWidget *w, gpointer data);
44
45typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
46
47static void connect_clicked(GtkWidget *widget, gpointer data);
48static void start_job_clicked(GtkWidget *widget, gpointer data);
49static void send_clicked(GtkWidget *widget, gpointer data);
50
51static struct button_spec {
52	const char *buttontext;
53	clickfunction f;
54	const char *tooltiptext[2];
55	const int start_sensitive;
56} buttonspeclist[] = {
57	{
58	  .buttontext		= "Connect",
59	  .f			= connect_clicked,
60	  .tooltiptext		= { "Disconnect from host", "Connect to host" },
61	  .start_sensitive	= 1,
62	},
63	{
64	  .buttontext		= "Send",
65	  .f			= send_clicked,
66	  .tooltiptext		= { "Send job description to host", NULL },
67	  .start_sensitive	= 0,
68	},
69	{
70	  .buttontext		= "Start Job",
71	  .f			= start_job_clicked,
72	  .tooltiptext		= { "Start the current job on the server", NULL },
73	  .start_sensitive	= 0,
74	},
75};
76
77static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc);
78static void gfio_update_thread_status_all(char *status_message, double perc);
79static void report_error(GError *error);
80
81static struct graph *setup_iops_graph(void)
82{
83	struct graph *g;
84
85	g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
86	graph_title(g, "IOPS (IOs/sec)");
87	graph_x_title(g, "Time (secs)");
88	graph_add_label(g, "Read IOPS");
89	graph_add_label(g, "Write IOPS");
90	graph_set_color(g, "Read IOPS", 0.13, 0.54, 0.13);
91	graph_set_color(g, "Write IOPS", 1.0, 0.0, 0.0);
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	return g;
95}
96
97static struct graph *setup_bandwidth_graph(void)
98{
99	struct graph *g;
100
101	g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
102	graph_title(g, "Bandwidth (bytes/sec)");
103	graph_x_title(g, "Time (secs)");
104	graph_add_label(g, "Read Bandwidth");
105	graph_add_label(g, "Write Bandwidth");
106	graph_set_color(g, "Read Bandwidth", 0.13, 0.54, 0.13);
107	graph_set_color(g, "Write Bandwidth", 1.0, 0.0, 0.0);
108	graph_set_base_offset(g, 1);
109	line_graph_set_data_count_limit(g, 100);
110	graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
111	return g;
112}
113
114static void setup_graphs(struct gfio_graphs *g)
115{
116	g->iops_graph = setup_iops_graph();
117	g->bandwidth_graph = setup_bandwidth_graph();
118}
119
120static void clear_ge_ui_info(struct gui_entry *ge)
121{
122	gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
123	gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
124	gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
125	gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
126#if 0
127	/* should we empty it... */
128	gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
129#endif
130	multitext_update_entry(&ge->eta.iotype, 0, "");
131	multitext_update_entry(&ge->eta.bs, 0, "");
132	multitext_update_entry(&ge->eta.ioengine, 0, "");
133	multitext_update_entry(&ge->eta.iodepth, 0, "");
134	gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
135	gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
136	gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
137	gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
138	gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
139	gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
140}
141
142static void show_info_dialog(struct gui *ui, const char *title,
143			     const char *message)
144{
145	GtkWidget *dialog, *content, *label;
146
147	dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window),
148			GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
149			GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
150
151	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
152	label = gtk_label_new(message);
153	gtk_container_add(GTK_CONTAINER(content), label);
154	gtk_widget_show_all(dialog);
155	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
156	gtk_dialog_run(GTK_DIALOG(dialog));
157	gtk_widget_destroy(dialog);
158}
159
160static void set_menu_entry_text(struct gui *ui, const char *path,
161				const char *text)
162{
163	GtkWidget *w;
164
165	w = gtk_ui_manager_get_widget(ui->uimanager, path);
166	if (w)
167		gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
168	else
169		fprintf(stderr, "gfio: can't find path %s\n", path);
170}
171
172
173static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
174{
175	GtkWidget *w;
176
177	w = gtk_ui_manager_get_widget(ui->uimanager, path);
178	if (w)
179		gtk_widget_set_sensitive(w, show);
180	else
181		fprintf(stderr, "gfio: can't find path %s\n", path);
182}
183
184static void set_job_menu_visible(struct gui *ui, int visible)
185{
186	set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
187}
188
189static void set_view_results_visible(struct gui *ui, int visible)
190{
191	set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
192}
193
194static const char *get_button_tooltip(struct button_spec *s, int sensitive)
195{
196	if (s->tooltiptext[sensitive])
197		return s->tooltiptext[sensitive];
198
199	return s->tooltiptext[0];
200}
201
202static GtkWidget *add_button(GtkWidget *buttonbox,
203			     struct button_spec *buttonspec, gpointer data)
204{
205	GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
206	gboolean sens = buttonspec->start_sensitive;
207
208	g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
209	gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
210
211	sens = buttonspec->start_sensitive;
212	gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
213	gtk_widget_set_sensitive(button, sens);
214
215	return button;
216}
217
218static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
219			int nbuttons)
220{
221	int i;
222
223	for (i = 0; i < nbuttons; i++)
224		ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
225}
226
227/*
228 * Update sensitivity of job buttons and job menu items, based on the
229 * state of the client.
230 */
231static void update_button_states(struct gui *ui, struct gui_entry *ge)
232{
233	unsigned int connect_state, send_state, start_state, edit_state;
234	const char *connect_str = NULL;
235
236	switch (ge->state) {
237	default: {
238		char tmp[80];
239
240		sprintf(tmp, "Bad client state: %u\n", ge->state);
241		show_info_dialog(ui, "Error", tmp);
242		/* fall through to new state */
243		}
244
245	case GE_STATE_NEW:
246		connect_state = 1;
247		edit_state = 1;
248		connect_str = "Connect";
249		send_state = 0;
250		start_state = 0;
251		break;
252	case GE_STATE_CONNECTED:
253		connect_state = 1;
254		edit_state = 1;
255		connect_str = "Disconnect";
256		send_state = 1;
257		start_state = 0;
258		break;
259	case GE_STATE_JOB_SENT:
260		connect_state = 1;
261		edit_state = 1;
262		connect_str = "Disconnect";
263		send_state = 0;
264		start_state = 1;
265		break;
266	case GE_STATE_JOB_STARTED:
267		connect_state = 1;
268		edit_state = 1;
269		connect_str = "Disconnect";
270		send_state = 0;
271		start_state = 1;
272		break;
273	case GE_STATE_JOB_RUNNING:
274		connect_state = 1;
275		edit_state = 0;
276		connect_str = "Disconnect";
277		send_state = 0;
278		start_state = 0;
279		break;
280	case GE_STATE_JOB_DONE:
281		connect_state = 1;
282		edit_state = 0;
283		connect_str = "Connect";
284		send_state = 0;
285		start_state = 0;
286		break;
287	}
288
289	gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
290	gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
291	gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
292	gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
293	gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
294
295	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
296	set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
297
298	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
299	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
300	set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
301
302	if (ge->client && ge->client->nr_results)
303		set_view_results_visible(ui, 1);
304	else
305		set_view_results_visible(ui, 0);
306}
307
308static void gfio_set_state(struct gui_entry *ge, unsigned int state)
309{
310	ge->state = state;
311	update_button_states(ge->ui, ge);
312}
313
314static void gfio_ui_setup_log(struct gui *ui)
315{
316	GtkTreeSelection *selection;
317	GtkListStore *model;
318	GtkWidget *tree_view;
319
320	model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
321
322	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
323	gtk_widget_set_can_focus(tree_view, FALSE);
324
325	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
326	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
327	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
328		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
329
330	tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
331	tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
332	tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
333	tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
334
335	ui->log_model = model;
336	ui->log_tree = tree_view;
337}
338
339static struct graph *setup_clat_graph(char *title, unsigned int *ovals,
340				      fio_fp64_t *plist,
341				      unsigned int len,
342				      double xdim, double ydim)
343{
344	struct graph *g;
345	int i;
346
347	g = graph_new(xdim, ydim, gfio_graph_font);
348	graph_title(g, title);
349	graph_x_title(g, "Percentile");
350
351	for (i = 0; i < len; i++) {
352		char fbuf[8];
353
354		sprintf(fbuf, "%2.2f%%", plist[i].u.f);
355		graph_add_label(g, fbuf);
356		graph_add_data(g, fbuf, (double) ovals[i]);
357	}
358
359	return g;
360}
361
362static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
363					       fio_fp64_t *plist,
364					       unsigned int len,
365					       const char *base,
366					       unsigned int scale)
367{
368	GType types[FIO_IO_U_LIST_MAX_LEN];
369	GtkWidget *tree_view;
370	GtkTreeSelection *selection;
371	GtkListStore *model;
372	GtkTreeIter iter;
373	int i;
374
375	for (i = 0; i < len; i++)
376		types[i] = G_TYPE_INT;
377
378	model = gtk_list_store_newv(len, types);
379
380	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
381	gtk_widget_set_can_focus(tree_view, FALSE);
382
383	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
384		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
385
386	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
387	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
388
389	for (i = 0; i < len; i++) {
390		char fbuf[8];
391
392		sprintf(fbuf, "%2.2f%%", plist[i].u.f);
393		tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
394	}
395
396	gtk_list_store_append(model, &iter);
397
398	for (i = 0; i < len; i++) {
399		if (scale)
400			ovals[i] = (ovals[i] + 999) / 1000;
401		gtk_list_store_set(model, &iter, i, ovals[i], -1);
402	}
403
404	return tree_view;
405}
406
407static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
408{
409	struct graph *g = p;
410	cairo_t *cr;
411
412	cr = gdk_cairo_create(w->window);
413#if 0
414	if (graph_has_tooltips(g)) {
415		g_object_set(w, "has-tooltip", TRUE, NULL);
416		g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g);
417	}
418#endif
419	cairo_set_source_rgb(cr, 0, 0, 0);
420	bar_graph_draw(g, cr);
421	cairo_destroy(cr);
422
423	return FALSE;
424}
425
426static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event,
427				       gpointer data)
428{
429	struct graph *g = data;
430
431	graph_set_size(g, w->allocation.width, w->allocation.height);
432	graph_set_size(g, w->allocation.width, w->allocation.height);
433	graph_set_position(g, 0, 0);
434	return TRUE;
435}
436
437static void gfio_show_clat_percentiles(struct gfio_client *gc,
438				       GtkWidget *vbox, struct thread_stat *ts,
439				       int ddir)
440{
441	unsigned int *io_u_plat = ts->io_u_plat[ddir];
442	unsigned long nr = ts->clat_stat[ddir].samples;
443	fio_fp64_t *plist = ts->percentile_list;
444	unsigned int *ovals, len, minv, maxv, scale_down;
445	const char *base;
446	GtkWidget *tree_view, *frame, *hbox, *drawing_area, *completion_vbox;
447	struct gui_entry *ge = gc->ge;
448	char tmp[64];
449
450	len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
451	if (!len)
452		goto out;
453
454	/*
455	 * We default to usecs, but if the value range is such that we
456	 * should scale down to msecs, do that.
457	 */
458	if (minv > 2000 && maxv > 99999) {
459		scale_down = 1;
460		base = "msec";
461	} else {
462		scale_down = 0;
463		base = "usec";
464	}
465
466	sprintf(tmp, "Completion percentiles (%s)", base);
467	tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
468	ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0);
469
470	frame = gtk_frame_new(tmp);
471	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
472
473	completion_vbox = gtk_vbox_new(FALSE, 3);
474	gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
475	hbox = gtk_hbox_new(FALSE, 3);
476	gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
477	drawing_area = gtk_drawing_area_new();
478	gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
479	gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &white);
480	gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
481	g_signal_connect(G_OBJECT(drawing_area), "expose_event", G_CALLBACK(on_expose_lat_drawing_area), ge->clat_graph);
482	g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->clat_graph);
483
484	gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
485out:
486	if (ovals)
487		free(ovals);
488}
489
490static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
491			  unsigned long max, double mean, double dev)
492{
493	const char *base = "(usec)";
494	GtkWidget *hbox, *label, *frame;
495	char *minp, *maxp;
496	char tmp[64];
497
498	if (!usec_to_msec(&min, &max, &mean, &dev))
499		base = "(msec)";
500
501	minp = num2str(min, 6, 1, 0);
502	maxp = num2str(max, 6, 1, 0);
503
504	sprintf(tmp, "%s %s", name, base);
505	frame = gtk_frame_new(tmp);
506	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
507
508	hbox = gtk_hbox_new(FALSE, 3);
509	gtk_container_add(GTK_CONTAINER(frame), hbox);
510
511	label = new_info_label_in_frame(hbox, "Minimum");
512	gtk_label_set_text(GTK_LABEL(label), minp);
513	label = new_info_label_in_frame(hbox, "Maximum");
514	gtk_label_set_text(GTK_LABEL(label), maxp);
515	label = new_info_label_in_frame(hbox, "Average");
516	sprintf(tmp, "%5.02f", mean);
517	gtk_label_set_text(GTK_LABEL(label), tmp);
518	label = new_info_label_in_frame(hbox, "Standard deviation");
519	sprintf(tmp, "%5.02f", dev);
520	gtk_label_set_text(GTK_LABEL(label), tmp);
521
522	free(minp);
523	free(maxp);
524
525}
526
527#define GFIO_CLAT	1
528#define GFIO_SLAT	2
529#define GFIO_LAT	4
530
531static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
532				  struct group_run_stats *rs,
533				  struct thread_stat *ts, int ddir)
534{
535	const char *ddir_label[2] = { "Read", "Write" };
536	GtkWidget *frame, *label, *box, *vbox, *main_vbox;
537	unsigned long min[3], max[3], runt;
538	unsigned long long bw, iops;
539	unsigned int flags = 0;
540	double mean[3], dev[3];
541	char *io_p, *bw_p, *iops_p;
542	int i2p;
543
544	if (!ts->runtime[ddir])
545		return;
546
547	i2p = is_power_of_2(rs->kb_base);
548	runt = ts->runtime[ddir];
549
550	bw = (1000 * ts->io_bytes[ddir]) / runt;
551	io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
552	bw_p = num2str(bw, 6, 1, i2p);
553
554	iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
555	iops_p = num2str(iops, 6, 1, 0);
556
557	box = gtk_hbox_new(FALSE, 3);
558	gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
559
560	frame = gtk_frame_new(ddir_label[ddir]);
561	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
562
563	main_vbox = gtk_vbox_new(FALSE, 3);
564	gtk_container_add(GTK_CONTAINER(frame), main_vbox);
565
566	box = gtk_hbox_new(FALSE, 3);
567	gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
568
569	label = new_info_label_in_frame(box, "IO");
570	gtk_label_set_text(GTK_LABEL(label), io_p);
571	label = new_info_label_in_frame(box, "Bandwidth");
572	gtk_label_set_text(GTK_LABEL(label), bw_p);
573	label = new_info_label_in_frame(box, "IOPS");
574	gtk_label_set_text(GTK_LABEL(label), iops_p);
575	label = new_info_label_in_frame(box, "Runtime (msec)");
576	label_set_int_value(label, ts->runtime[ddir]);
577
578	if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
579		double p_of_agg = 100.0;
580		const char *bw_str = "KB";
581		char tmp[32];
582
583		if (rs->agg[ddir]) {
584			p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
585			if (p_of_agg > 100.0)
586				p_of_agg = 100.0;
587		}
588
589		if (mean[0] > 999999.9) {
590			min[0] /= 1000.0;
591			max[0] /= 1000.0;
592			mean[0] /= 1000.0;
593			dev[0] /= 1000.0;
594			bw_str = "MB";
595		}
596
597		sprintf(tmp, "Bandwidth (%s)", bw_str);
598		frame = gtk_frame_new(tmp);
599		gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
600
601		box = gtk_hbox_new(FALSE, 3);
602		gtk_container_add(GTK_CONTAINER(frame), box);
603
604		label = new_info_label_in_frame(box, "Minimum");
605		label_set_int_value(label, min[0]);
606		label = new_info_label_in_frame(box, "Maximum");
607		label_set_int_value(label, max[0]);
608		label = new_info_label_in_frame(box, "Percentage of jobs");
609		sprintf(tmp, "%3.2f%%", p_of_agg);
610		gtk_label_set_text(GTK_LABEL(label), tmp);
611		label = new_info_label_in_frame(box, "Average");
612		sprintf(tmp, "%5.02f", mean[0]);
613		gtk_label_set_text(GTK_LABEL(label), tmp);
614		label = new_info_label_in_frame(box, "Standard deviation");
615		sprintf(tmp, "%5.02f", dev[0]);
616		gtk_label_set_text(GTK_LABEL(label), tmp);
617	}
618
619	if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
620		flags |= GFIO_SLAT;
621	if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
622		flags |= GFIO_CLAT;
623	if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
624		flags |= GFIO_LAT;
625
626	if (flags) {
627		frame = gtk_frame_new("Latency");
628		gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
629
630		vbox = gtk_vbox_new(FALSE, 3);
631		gtk_container_add(GTK_CONTAINER(frame), vbox);
632
633		if (flags & GFIO_SLAT)
634			gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
635		if (flags & GFIO_CLAT)
636			gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
637		if (flags & GFIO_LAT)
638			gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
639	}
640
641	if (ts->clat_percentiles)
642		gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
643
644	free(io_p);
645	free(bw_p);
646	free(iops_p);
647}
648
649static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
650					    const char **labels,
651					    unsigned int len,
652					    double xdim, double ydim)
653{
654	struct graph *g;
655	int i;
656
657	g = graph_new(xdim, ydim, gfio_graph_font);
658	graph_title(g, title);
659	graph_x_title(g, "Buckets");
660
661	for (i = 0; i < len; i++) {
662		graph_add_label(g, labels[i]);
663		graph_add_data(g, labels[i], lat[i]);
664	}
665
666	return g;
667}
668
669static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels,
670					  int num)
671{
672	GtkWidget *tree_view;
673	GtkTreeSelection *selection;
674	GtkListStore *model;
675	GtkTreeIter iter;
676	GType *types;
677	int i;
678
679	types = malloc(num * sizeof(GType));
680
681	for (i = 0; i < num; i++)
682		types[i] = G_TYPE_STRING;
683
684	model = gtk_list_store_newv(num, types);
685	free(types);
686	types = NULL;
687
688	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
689	gtk_widget_set_can_focus(tree_view, FALSE);
690
691	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
692		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
693
694	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
695	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
696
697	for (i = 0; i < num; i++)
698		tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
699
700	gtk_list_store_append(model, &iter);
701
702	for (i = 0; i < num; i++) {
703		char fbuf[32];
704
705		if (lat[i] <= 0.0)
706			sprintf(fbuf, "0.00");
707		else
708			sprintf(fbuf, "%3.2f%%", lat[i]);
709
710		gtk_list_store_set(model, &iter, i, fbuf, -1);
711	}
712
713	return tree_view;
714}
715
716static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox,
717				      struct thread_stat *ts)
718{
719	double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR];
720	const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u",
721				 "250u", "500u", "750u", "1m", "2m",
722				 "4m", "10m", "20m", "50m", "100m",
723				 "250m", "500m", "750m", "1s", "2s", ">= 2s" };
724	int start, end, i;
725	const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR;
726	GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area;
727	struct gui_entry *ge = gc->ge;
728
729	stat_calc_lat_u(ts, io_u_lat);
730	stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]);
731
732	/*
733	 * Found out which first bucket has entries, and which last bucket
734	 */
735	start = end = -1U;
736	for (i = 0; i < total; i++) {
737		if (io_u_lat[i] == 0.00)
738			continue;
739
740		if (start == -1U)
741			start = i;
742		end = i;
743	}
744
745	/*
746	 * No entries...
747	 */
748	if (start == -1U)
749		return;
750
751	tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1);
752	ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0);
753
754	frame = gtk_frame_new("Latency buckets");
755	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
756
757	completion_vbox = gtk_vbox_new(FALSE, 3);
758	gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
759	hbox = gtk_hbox_new(FALSE, 3);
760	gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
761
762	drawing_area = gtk_drawing_area_new();
763	gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
764	gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &white);
765	gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
766	g_signal_connect(G_OBJECT(drawing_area), "expose_event", G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph);
767        g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph);
768
769	gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
770}
771
772static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
773{
774	GtkWidget *box, *frame, *entry;
775	double usr_cpu, sys_cpu;
776	unsigned long runtime;
777	char tmp[32];
778
779	runtime = ts->total_run_time;
780	if (runtime) {
781		double runt = (double) runtime;
782
783		usr_cpu = (double) ts->usr_time * 100 / runt;
784		sys_cpu = (double) ts->sys_time * 100 / runt;
785	} else {
786		usr_cpu = 0;
787		sys_cpu = 0;
788	}
789
790	frame = gtk_frame_new("OS resources");
791	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
792
793	box = gtk_hbox_new(FALSE, 3);
794	gtk_container_add(GTK_CONTAINER(frame), box);
795
796	entry = new_info_entry_in_frame(box, "User CPU");
797	sprintf(tmp, "%3.2f%%", usr_cpu);
798	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
799	entry = new_info_entry_in_frame(box, "System CPU");
800	sprintf(tmp, "%3.2f%%", sys_cpu);
801	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
802	entry = new_info_entry_in_frame(box, "Context switches");
803	entry_set_int_value(entry, ts->ctx);
804	entry = new_info_entry_in_frame(box, "Major faults");
805	entry_set_int_value(entry, ts->majf);
806	entry = new_info_entry_in_frame(box, "Minor faults");
807	entry_set_int_value(entry, ts->minf);
808}
809static void gfio_add_sc_depths_tree(GtkListStore *model,
810				    struct thread_stat *ts, unsigned int len,
811				    int submit)
812{
813	double io_u_dist[FIO_IO_U_MAP_NR];
814	GtkTreeIter iter;
815	/* Bits 0, and 3-8 */
816	const int add_mask = 0x1f9;
817	int i, j;
818
819	if (submit)
820		stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
821	else
822		stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
823
824	gtk_list_store_append(model, &iter);
825
826	gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
827
828	for (i = 1, j = 0; i < len; i++) {
829		char fbuf[32];
830
831		if (!(add_mask & (1UL << (i - 1))))
832			sprintf(fbuf, "0.0%%");
833		else {
834			sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
835			j++;
836		}
837
838		gtk_list_store_set(model, &iter, i, fbuf, -1);
839	}
840
841}
842
843static void gfio_add_total_depths_tree(GtkListStore *model,
844				       struct thread_stat *ts, unsigned int len)
845{
846	double io_u_dist[FIO_IO_U_MAP_NR];
847	GtkTreeIter iter;
848	/* Bits 1-6, and 8 */
849	const int add_mask = 0x17e;
850	int i, j;
851
852	stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
853
854	gtk_list_store_append(model, &iter);
855
856	gtk_list_store_set(model, &iter, 0, "Total", -1);
857
858	for (i = 1, j = 0; i < len; i++) {
859		char fbuf[32];
860
861		if (!(add_mask & (1UL << (i - 1))))
862			sprintf(fbuf, "0.0%%");
863		else {
864			sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
865			j++;
866		}
867
868		gtk_list_store_set(model, &iter, i, fbuf, -1);
869	}
870
871}
872
873static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
874{
875	GtkWidget *frame, *box, *tree_view;
876	GtkTreeSelection *selection;
877	GtkListStore *model;
878	GType types[FIO_IO_U_MAP_NR + 1];
879	int i;
880#define NR_LABELS	10
881	const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
882
883	frame = gtk_frame_new("IO depths");
884	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
885
886	box = gtk_hbox_new(FALSE, 3);
887	gtk_container_add(GTK_CONTAINER(frame), box);
888
889	for (i = 0; i < NR_LABELS; i++)
890		types[i] = G_TYPE_STRING;
891
892	model = gtk_list_store_newv(NR_LABELS, types);
893
894	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
895	gtk_widget_set_can_focus(tree_view, FALSE);
896
897	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
898		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
899
900	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
901	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
902
903	for (i = 0; i < NR_LABELS; i++)
904		tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
905
906	gfio_add_total_depths_tree(model, ts, NR_LABELS);
907	gfio_add_sc_depths_tree(model, ts, NR_LABELS, 1);
908	gfio_add_sc_depths_tree(model, ts, NR_LABELS, 0);
909
910	gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
911}
912
913static gboolean results_window_delete(GtkWidget *w, gpointer data)
914{
915	struct gui_entry *ge = (struct gui_entry *) data;
916
917	gtk_widget_destroy(w);
918	ge->results_window = NULL;
919	ge->results_notebook = NULL;
920	return TRUE;
921}
922
923static void results_close(GtkWidget *w, gpointer *data)
924{
925	struct gui_entry *ge = (struct gui_entry *) data;
926
927	gtk_widget_destroy(ge->results_window);
928}
929
930static GtkActionEntry results_menu_items[] = {
931	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
932	{ "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
933	{ "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(results_close) },
934};
935static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]);
936
937static const gchar *results_ui_string = " \
938	<ui> \
939		<menubar name=\"MainMenu\"> \
940			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
941				<menuitem name=\"Close\" action=\"CloseFile\" /> \
942			</menu> \
943			<menu name=\"GraphMenu\" action=\"GraphMenuAction\"> \
944			</menu>\
945		</menubar> \
946	</ui> \
947";
948
949static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge)
950{
951	GtkActionGroup *action_group;
952	GtkWidget *widget;
953	GError *error = 0;
954
955	ge->results_uimanager = gtk_ui_manager_new();
956
957	action_group = gtk_action_group_new("ResultsMenu");
958	gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge);
959
960	gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0);
961	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error);
962
963	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager));
964
965	widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu");
966	return widget;
967}
968
969static GtkWidget *get_results_window(struct gui_entry *ge)
970{
971	GtkWidget *win, *notebook, *vbox;
972
973	if (ge->results_window)
974		return ge->results_notebook;
975
976	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
977	gtk_window_set_title(GTK_WINDOW(win), "Results");
978	gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
979	g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
980	g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
981
982	vbox = gtk_vbox_new(FALSE, 0);
983	gtk_container_add(GTK_CONTAINER(win), vbox);
984
985	ge->results_menu = get_results_menubar(win, ge);
986	gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0);
987
988	notebook = gtk_notebook_new();
989	gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
990	gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
991	gtk_container_add(GTK_CONTAINER(vbox), notebook);
992
993	ge->results_window = win;
994	ge->results_notebook = notebook;
995	return ge->results_notebook;
996}
997
998static void disk_util_destroy(GtkWidget *w, gpointer data)
999{
1000	struct gui_entry *ge = (struct gui_entry *) data;
1001
1002	ge->disk_util_vbox = NULL;
1003	gtk_widget_destroy(w);
1004}
1005
1006static GtkWidget *get_scrolled_window(gint border_width)
1007{
1008	GtkWidget *scroll;
1009
1010	scroll = gtk_scrolled_window_new(NULL, NULL);
1011	gtk_container_set_border_width(GTK_CONTAINER(scroll), border_width);
1012	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1013
1014	return scroll;
1015}
1016
1017static GtkWidget *gfio_disk_util_get_vbox(struct gui_entry *ge)
1018{
1019	GtkWidget *vbox, *box, *scroll, *res_notebook;
1020
1021	if (ge->disk_util_vbox)
1022		return ge->disk_util_vbox;
1023
1024	scroll = get_scrolled_window(5);
1025	vbox = gtk_vbox_new(FALSE, 3);
1026	box = gtk_hbox_new(FALSE, 0);
1027	gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
1028
1029	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1030	res_notebook = get_results_window(ge);
1031
1032	gtk_notebook_append_page(GTK_NOTEBOOK(res_notebook), scroll, gtk_label_new("Disk utilization"));
1033	ge->disk_util_vbox = box;
1034	g_signal_connect(vbox, "destroy", G_CALLBACK(disk_util_destroy), ge);
1035
1036	return ge->disk_util_vbox;
1037}
1038
1039static int __gfio_disk_util_show(GtkWidget *res_notebook,
1040				 struct gfio_client *gc, struct cmd_du_pdu *p)
1041{
1042	GtkWidget *box, *frame, *entry, *vbox, *util_vbox;
1043	struct gui_entry *ge = gc->ge;
1044	double util;
1045	char tmp[16];
1046
1047	util_vbox = gfio_disk_util_get_vbox(ge);
1048
1049	vbox = gtk_vbox_new(FALSE, 3);
1050	gtk_container_add(GTK_CONTAINER(util_vbox), vbox);
1051
1052	frame = gtk_frame_new((char *) p->dus.name);
1053	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
1054
1055	box = gtk_vbox_new(FALSE, 3);
1056	gtk_container_add(GTK_CONTAINER(frame), box);
1057
1058	frame = gtk_frame_new("Read");
1059	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1060	vbox = gtk_hbox_new(TRUE, 3);
1061	gtk_container_add(GTK_CONTAINER(frame), vbox);
1062	entry = new_info_entry_in_frame(vbox, "IOs");
1063	entry_set_int_value(entry, p->dus.ios[0]);
1064	entry = new_info_entry_in_frame(vbox, "Merges");
1065	entry_set_int_value(entry, p->dus.merges[0]);
1066	entry = new_info_entry_in_frame(vbox, "Sectors");
1067	entry_set_int_value(entry, p->dus.sectors[0]);
1068	entry = new_info_entry_in_frame(vbox, "Ticks");
1069	entry_set_int_value(entry, p->dus.ticks[0]);
1070
1071	frame = gtk_frame_new("Write");
1072	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1073	vbox = gtk_hbox_new(TRUE, 3);
1074	gtk_container_add(GTK_CONTAINER(frame), vbox);
1075	entry = new_info_entry_in_frame(vbox, "IOs");
1076	entry_set_int_value(entry, p->dus.ios[1]);
1077	entry = new_info_entry_in_frame(vbox, "Merges");
1078	entry_set_int_value(entry, p->dus.merges[1]);
1079	entry = new_info_entry_in_frame(vbox, "Sectors");
1080	entry_set_int_value(entry, p->dus.sectors[1]);
1081	entry = new_info_entry_in_frame(vbox, "Ticks");
1082	entry_set_int_value(entry, p->dus.ticks[1]);
1083
1084	frame = gtk_frame_new("Shared");
1085	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1086	vbox = gtk_hbox_new(TRUE, 3);
1087	gtk_container_add(GTK_CONTAINER(frame), vbox);
1088	entry = new_info_entry_in_frame(vbox, "IO ticks");
1089	entry_set_int_value(entry, p->dus.io_ticks);
1090	entry = new_info_entry_in_frame(vbox, "Time in queue");
1091	entry_set_int_value(entry, p->dus.time_in_queue);
1092
1093	util = 0.0;
1094	if (p->dus.msec)
1095		util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec;
1096	if (util > 100.0)
1097		util = 100.0;
1098
1099	sprintf(tmp, "%3.2f%%", util);
1100	entry = new_info_entry_in_frame(vbox, "Disk utilization");
1101	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1102
1103	gtk_widget_show_all(ge->results_window);
1104	return 0;
1105}
1106
1107static int gfio_disk_util_show(struct gfio_client *gc)
1108{
1109	struct gui_entry *ge = gc->ge;
1110	GtkWidget *res_notebook;
1111	int i;
1112
1113	if (!gc->nr_du)
1114		return 1;
1115
1116	res_notebook = get_results_window(ge);
1117
1118	for (i = 0; i < gc->nr_du; i++) {
1119		struct cmd_du_pdu *p = &gc->du[i];
1120
1121		__gfio_disk_util_show(res_notebook, gc, p);
1122	}
1123
1124	gtk_widget_show_all(ge->results_window);
1125	return 0;
1126}
1127
1128static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts,
1129				 struct group_run_stats *rs)
1130{
1131	unsigned int nr = gc->nr_results;
1132
1133	gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results));
1134	memcpy(&gc->results[nr].ts, ts, sizeof(*ts));
1135	memcpy(&gc->results[nr].gs, rs, sizeof(*rs));
1136	gc->nr_results++;
1137}
1138
1139static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc,
1140				       struct thread_stat *ts,
1141				       struct group_run_stats *rs)
1142{
1143	GtkWidget *box, *vbox, *entry, *scroll;
1144
1145	scroll = gtk_scrolled_window_new(NULL, NULL);
1146	gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1147	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1148
1149	vbox = gtk_vbox_new(FALSE, 3);
1150
1151	box = gtk_hbox_new(FALSE, 0);
1152	gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
1153
1154	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1155
1156	gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name));
1157
1158	entry = new_info_entry_in_frame(box, "Name");
1159	gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
1160	if (strlen(ts->description)) {
1161		entry = new_info_entry_in_frame(box, "Description");
1162		gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
1163	}
1164	entry = new_info_entry_in_frame(box, "Group ID");
1165	entry_set_int_value(entry, ts->groupid);
1166	entry = new_info_entry_in_frame(box, "Jobs");
1167	entry_set_int_value(entry, ts->members);
1168	gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
1169	entry_set_int_value(entry, ts->error);
1170	entry = new_info_entry_in_frame(box, "PID");
1171	entry_set_int_value(entry, ts->pid);
1172
1173	if (ts->io_bytes[DDIR_READ])
1174		gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_READ);
1175	if (ts->io_bytes[DDIR_WRITE])
1176		gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_WRITE);
1177
1178	gfio_show_latency_buckets(gc, vbox, ts);
1179	gfio_show_cpu_usage(vbox, ts);
1180	gfio_show_io_depths(vbox, ts);
1181}
1182
1183static void gfio_display_end_results(struct gfio_client *gc)
1184{
1185	struct gui_entry *ge = gc->ge;
1186	GtkWidget *res_notebook;
1187	int i;
1188
1189	res_notebook = get_results_window(ge);
1190
1191	for (i = 0; i < gc->nr_results; i++) {
1192		struct end_results *e = &gc->results[i];
1193
1194		__gfio_display_end_results(res_notebook, gc, &e->ts, &e->gs);
1195	}
1196
1197	if (gfio_disk_util_show(gc))
1198		gtk_widget_show_all(ge->results_window);
1199}
1200
1201static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
1202			    struct group_run_stats *rs)
1203{
1204	struct gfio_client *gc = client->client_data;
1205	struct gui_entry *ge = gc->ge;
1206
1207	gfio_add_end_results(gc, ts, rs);
1208
1209	gdk_threads_enter();
1210	if (ge->results_window)
1211		__gfio_display_end_results(ge->results_notebook, gc, ts, rs);
1212	else
1213		gfio_display_end_results(gc);
1214	gdk_threads_leave();
1215}
1216
1217static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
1218{
1219	struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
1220	struct gui *ui = &main_ui;
1221	GtkTreeIter iter;
1222	struct tm *tm;
1223	time_t sec;
1224	char tmp[64], timebuf[80];
1225
1226	sec = p->log_sec;
1227	tm = localtime(&sec);
1228	strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm);
1229	sprintf(timebuf, "%s.%03ld", tmp, p->log_usec / 1000);
1230
1231	gdk_threads_enter();
1232
1233	gtk_list_store_append(ui->log_model, &iter);
1234	gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
1235	gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
1236	gtk_list_store_set(ui->log_model, &iter, 2, p->level, -1);
1237	gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
1238
1239	if (p->level == FIO_LOG_ERR)
1240		view_log(NULL, (gpointer) ui);
1241
1242	gdk_threads_leave();
1243}
1244
1245static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
1246{
1247	struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
1248	struct gfio_client *gc = client->client_data;
1249	struct gui_entry *ge = gc->ge;
1250	unsigned int nr = gc->nr_du;
1251
1252	gc->du = realloc(gc->du, (nr + 1) * sizeof(struct cmd_du_pdu));
1253	memcpy(&gc->du[nr], p, sizeof(*p));
1254	gc->nr_du++;
1255
1256	gdk_threads_enter();
1257	if (ge->results_window)
1258		__gfio_disk_util_show(ge->results_notebook, gc, p);
1259	else
1260		gfio_disk_util_show(gc);
1261	gdk_threads_leave();
1262}
1263
1264extern int sum_stat_clients;
1265extern struct thread_stat client_ts;
1266extern struct group_run_stats client_gs;
1267
1268static int sum_stat_nr;
1269
1270static void gfio_thread_status_op(struct fio_client *client,
1271				  struct fio_net_cmd *cmd)
1272{
1273	struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
1274
1275	gfio_display_ts(client, &p->ts, &p->rs);
1276
1277	if (sum_stat_clients == 1)
1278		return;
1279
1280	sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
1281	sum_group_stats(&client_gs, &p->rs);
1282
1283	client_ts.members++;
1284	client_ts.thread_number = p->ts.thread_number;
1285	client_ts.groupid = p->ts.groupid;
1286
1287	if (++sum_stat_nr == sum_stat_clients) {
1288		strcpy(client_ts.name, "All clients");
1289		gfio_display_ts(client, &client_ts, &client_gs);
1290	}
1291}
1292
1293static void gfio_group_stats_op(struct fio_client *client,
1294				struct fio_net_cmd *cmd)
1295{
1296	/* We're ignoring group stats for now */
1297}
1298
1299static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
1300				   gpointer data)
1301{
1302	struct gfio_graphs *g = data;
1303
1304	graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
1305	graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
1306	graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
1307	graph_set_position(g->bandwidth_graph, 0, 0);
1308	return TRUE;
1309}
1310
1311static void draw_graph(struct graph *g, cairo_t *cr)
1312{
1313	line_graph_draw(g, cr);
1314	cairo_stroke(cr);
1315}
1316
1317static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
1318			      gboolean keyboard_mode, GtkTooltip *tooltip,
1319			      gpointer data)
1320{
1321	struct gfio_graphs *g = data;
1322	const char *text = NULL;
1323
1324	if (graph_contains_xy(g->iops_graph, x, y))
1325		text = graph_find_tooltip(g->iops_graph, x, y);
1326	else if (graph_contains_xy(g->bandwidth_graph, x, y))
1327		text = graph_find_tooltip(g->bandwidth_graph, x, y);
1328
1329	if (text) {
1330		gtk_tooltip_set_text(tooltip, text);
1331		return TRUE;
1332	}
1333
1334	return FALSE;
1335}
1336
1337static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
1338{
1339	struct gfio_graphs *g = p;
1340	cairo_t *cr;
1341
1342	cr = gdk_cairo_create(w->window);
1343
1344	if (graph_has_tooltips(g->iops_graph) ||
1345	    graph_has_tooltips(g->bandwidth_graph)) {
1346		g_object_set(w, "has-tooltip", TRUE, NULL);
1347		g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
1348	}
1349
1350	cairo_set_source_rgb(cr, 0, 0, 0);
1351	draw_graph(g->iops_graph, cr);
1352	draw_graph(g->bandwidth_graph, cr);
1353	cairo_destroy(cr);
1354
1355	return FALSE;
1356}
1357
1358/*
1359 * Client specific ETA
1360 */
1361static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
1362{
1363	struct gfio_client *gc = client->client_data;
1364	struct gui_entry *ge = gc->ge;
1365	static int eta_good;
1366	char eta_str[128];
1367	char output[256];
1368	char tmp[32];
1369	double perc = 0.0;
1370	int i2p = 0;
1371
1372	gdk_threads_enter();
1373
1374	eta_str[0] = '\0';
1375	output[0] = '\0';
1376
1377	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1378		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1379		eta_to_str(eta_str, je->eta_sec);
1380	}
1381
1382	sprintf(tmp, "%u", je->nr_running);
1383	gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
1384	sprintf(tmp, "%u", je->files_open);
1385	gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
1386
1387#if 0
1388	if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1389	if (je->m_rate || je->t_rate) {
1390		char *tr, *mr;
1391
1392		mr = num2str(je->m_rate, 4, 0, i2p);
1393		tr = num2str(je->t_rate, 4, 0, i2p);
1394		gtk_entry_set_text(GTK_ENTRY(ge->eta);
1395		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1396		free(tr);
1397		free(mr);
1398	} else if (je->m_iops || je->t_iops)
1399		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1400
1401	gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
1402	gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
1403	gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
1404	gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
1405#endif
1406
1407	if (je->eta_sec != INT_MAX && je->nr_running) {
1408		char *iops_str[2];
1409		char *rate_str[2];
1410
1411		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1412			strcpy(output, "-.-% done");
1413		else {
1414			eta_good = 1;
1415			perc *= 100.0;
1416			sprintf(output, "%3.1f%% done", perc);
1417		}
1418
1419		rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1420		rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1421
1422		iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1423		iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1424
1425		gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
1426		gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
1427		gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
1428		gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
1429
1430		graph_add_xy_data(ge->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1431		graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1432		graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1433		graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1434
1435		free(rate_str[0]);
1436		free(rate_str[1]);
1437		free(iops_str[0]);
1438		free(iops_str[1]);
1439	}
1440
1441	if (eta_str[0]) {
1442		char *dst = output + strlen(output);
1443
1444		sprintf(dst, " - %s", eta_str);
1445	}
1446
1447	gfio_update_thread_status(ge, output, perc);
1448	gdk_threads_leave();
1449}
1450
1451/*
1452 * Update ETA in main window for all clients
1453 */
1454static void gfio_update_all_eta(struct jobs_eta *je)
1455{
1456	struct gui *ui = &main_ui;
1457	static int eta_good;
1458	char eta_str[128];
1459	char output[256];
1460	double perc = 0.0;
1461	int i2p = 0;
1462
1463	gdk_threads_enter();
1464
1465	eta_str[0] = '\0';
1466	output[0] = '\0';
1467
1468	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1469		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1470		eta_to_str(eta_str, je->eta_sec);
1471	}
1472
1473#if 0
1474	if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1475	if (je->m_rate || je->t_rate) {
1476		char *tr, *mr;
1477
1478		mr = num2str(je->m_rate, 4, 0, i2p);
1479		tr = num2str(je->t_rate, 4, 0, i2p);
1480		gtk_entry_set_text(GTK_ENTRY(ui->eta);
1481		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1482		free(tr);
1483		free(mr);
1484	} else if (je->m_iops || je->t_iops)
1485		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1486
1487	gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
1488	gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
1489	gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
1490	gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
1491#endif
1492
1493	entry_set_int_value(ui->eta.jobs, je->nr_running);
1494
1495	if (je->eta_sec != INT_MAX && je->nr_running) {
1496		char *iops_str[2];
1497		char *rate_str[2];
1498
1499		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1500			strcpy(output, "-.-% done");
1501		else {
1502			eta_good = 1;
1503			perc *= 100.0;
1504			sprintf(output, "%3.1f%% done", perc);
1505		}
1506
1507		rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1508		rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1509
1510		iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1511		iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1512
1513		gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
1514		gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
1515		gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
1516		gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
1517
1518		graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1519		graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1520		graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1521		graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1522
1523		free(rate_str[0]);
1524		free(rate_str[1]);
1525		free(iops_str[0]);
1526		free(iops_str[1]);
1527	}
1528
1529	if (eta_str[0]) {
1530		char *dst = output + strlen(output);
1531
1532		sprintf(dst, " - %s", eta_str);
1533	}
1534
1535	gfio_update_thread_status_all(output, perc);
1536	gdk_threads_leave();
1537}
1538
1539static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
1540{
1541	struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
1542	struct gfio_client *gc = client->client_data;
1543	struct gui_entry *ge = gc->ge;
1544	const char *os, *arch;
1545	char buf[64];
1546
1547	os = fio_get_os_string(probe->os);
1548	if (!os)
1549		os = "unknown";
1550
1551	arch = fio_get_arch_string(probe->arch);
1552	if (!arch)
1553		os = "unknown";
1554
1555	if (!client->name)
1556		client->name = strdup((char *) probe->hostname);
1557
1558	gdk_threads_enter();
1559
1560	gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
1561	gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
1562	gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
1563	sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
1564	gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
1565
1566	gfio_set_state(ge, GE_STATE_CONNECTED);
1567
1568	gdk_threads_leave();
1569}
1570
1571static void gfio_update_thread_status(struct gui_entry *ge,
1572				      char *status_message, double perc)
1573{
1574	static char message[100];
1575	const char *m = message;
1576
1577	strncpy(message, status_message, sizeof(message) - 1);
1578	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
1579	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
1580	gtk_widget_queue_draw(main_ui.window);
1581}
1582
1583static void gfio_update_thread_status_all(char *status_message, double perc)
1584{
1585	struct gui *ui = &main_ui;
1586	static char message[100];
1587	const char *m = message;
1588
1589	strncpy(message, status_message, sizeof(message) - 1);
1590	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
1591	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
1592	gtk_widget_queue_draw(ui->window);
1593}
1594
1595static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd)
1596{
1597	struct gfio_client *gc = client->client_data;
1598
1599	gdk_threads_enter();
1600	gfio_set_state(gc->ge, GE_STATE_NEW);
1601	gdk_threads_leave();
1602}
1603
1604static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
1605{
1606	struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
1607	struct gfio_client *gc = client->client_data;
1608	struct thread_options *o = &gc->o;
1609	struct gui_entry *ge = gc->ge;
1610	char *c1, *c2, *c3, *c4;
1611	char tmp[80];
1612
1613	p->thread_number = le32_to_cpu(p->thread_number);
1614	p->groupid = le32_to_cpu(p->groupid);
1615	convert_thread_options_to_cpu(o, &p->top);
1616
1617	gdk_threads_enter();
1618
1619	gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
1620
1621	gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
1622	gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
1623
1624	sprintf(tmp, "%s %s", o->odirect ? "direct" : "buffered", ddir_str(o->td_ddir));
1625	multitext_add_entry(&ge->eta.iotype, tmp);
1626
1627	c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
1628	c2 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
1629	c3 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
1630	c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
1631	sprintf(tmp, "%s-%s/%s-%s", c1, c2, c3, c4);
1632	free(c1);
1633	free(c2);
1634	free(c3);
1635	free(c4);
1636	multitext_add_entry(&ge->eta.bs, tmp);
1637
1638	multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
1639
1640	sprintf(tmp, "%u", o->iodepth);
1641	multitext_add_entry(&ge->eta.iodepth, tmp);
1642
1643	multitext_set_entry(&ge->eta.iotype, 0);
1644	multitext_set_entry(&ge->eta.bs, 0);
1645	multitext_set_entry(&ge->eta.ioengine, 0);
1646	multitext_set_entry(&ge->eta.iodepth, 0);
1647
1648	gfio_set_state(ge, GE_STATE_JOB_SENT);
1649
1650	gdk_threads_leave();
1651}
1652
1653static void gfio_client_timed_out(struct fio_client *client)
1654{
1655	struct gfio_client *gc = client->client_data;
1656	char buf[256];
1657
1658	gdk_threads_enter();
1659
1660	gfio_set_state(gc->ge, GE_STATE_NEW);
1661	clear_ge_ui_info(gc->ge);
1662
1663	sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
1664	show_info_dialog(gc->ge->ui, "Network timeout", buf);
1665
1666	gdk_threads_leave();
1667}
1668
1669static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
1670{
1671	struct gfio_client *gc = client->client_data;
1672
1673	gdk_threads_enter();
1674
1675	gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
1676
1677	if (gc->err_entry)
1678		entry_set_int_value(gc->err_entry, client->error);
1679
1680	gdk_threads_leave();
1681}
1682
1683static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
1684{
1685	struct gfio_client *gc = client->client_data;
1686
1687	gdk_threads_enter();
1688	gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
1689	gdk_threads_leave();
1690}
1691
1692static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
1693{
1694	struct gfio_client *gc = client->client_data;
1695
1696	gdk_threads_enter();
1697	gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
1698	gdk_threads_leave();
1699}
1700
1701static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu)
1702{
1703	printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples);
1704	free(pdu);
1705}
1706
1707struct client_ops gfio_client_ops = {
1708	.text			= gfio_text_op,
1709	.disk_util		= gfio_disk_util_op,
1710	.thread_status		= gfio_thread_status_op,
1711	.group_stats		= gfio_group_stats_op,
1712	.jobs_eta		= gfio_update_client_eta,
1713	.eta			= gfio_update_all_eta,
1714	.probe			= gfio_probe_op,
1715	.quit			= gfio_quit_op,
1716	.add_job		= gfio_add_job_op,
1717	.timed_out		= gfio_client_timed_out,
1718	.stop			= gfio_client_stop,
1719	.start			= gfio_client_start,
1720	.job_start		= gfio_client_job_start,
1721	.iolog			= gfio_client_iolog,
1722	.eta_msec		= FIO_CLIENT_DEF_ETA_MSEC,
1723	.stay_connected		= 1,
1724	.client_type		= FIO_CLIENT_TYPE_GUI,
1725};
1726
1727/*
1728 * FIXME: need more handling here
1729 */
1730static void ge_destroy(struct gui_entry *ge)
1731{
1732	struct gfio_client *gc = ge->client;
1733
1734	if (gc && gc->client) {
1735		if (ge->state >= GE_STATE_CONNECTED)
1736			fio_client_terminate(gc->client);
1737
1738		fio_put_client(gc->client);
1739	}
1740
1741	flist_del(&ge->list);
1742	free(ge);
1743}
1744
1745static void ge_widget_destroy(GtkWidget *w, gpointer data)
1746{
1747}
1748
1749static void gfio_quit(struct gui *ui)
1750{
1751	struct gui_entry *ge;
1752
1753	while (!flist_empty(&ui->list)) {
1754		ge = flist_entry(ui->list.next, struct gui_entry, list);
1755		ge_destroy(ge);
1756	}
1757
1758        gtk_main_quit();
1759}
1760
1761static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1762                __attribute__((unused)) gpointer data)
1763{
1764	gfio_quit(data);
1765}
1766
1767static void *job_thread(void *arg)
1768{
1769	struct gui *ui = arg;
1770
1771	ui->handler_running = 1;
1772	fio_handle_clients(&gfio_client_ops);
1773	ui->handler_running = 0;
1774	return NULL;
1775}
1776
1777static int send_job_files(struct gui_entry *ge)
1778{
1779	struct gfio_client *gc = ge->client;
1780	int i, ret = 0;
1781
1782	for (i = 0; i < ge->nr_job_files; i++) {
1783		ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1784		if (ret < 0) {
1785			GError *error;
1786
1787			error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1788			report_error(error);
1789			g_error_free(error);
1790			break;
1791		} else if (ret)
1792			break;
1793
1794		free(ge->job_files[i]);
1795		ge->job_files[i] = NULL;
1796	}
1797	while (i < ge->nr_job_files) {
1798		free(ge->job_files[i]);
1799		ge->job_files[i] = NULL;
1800		i++;
1801	}
1802
1803	free(ge->job_files);
1804	ge->job_files = NULL;
1805	ge->nr_job_files = 0;
1806	return ret;
1807}
1808
1809static void *server_thread(void *arg)
1810{
1811	is_backend = 1;
1812	gfio_server_running = 1;
1813	fio_start_server(NULL);
1814	gfio_server_running = 0;
1815	return NULL;
1816}
1817
1818static void gfio_start_server(void)
1819{
1820	struct gui *ui = &main_ui;
1821
1822	if (!gfio_server_running) {
1823		gfio_server_running = 1;
1824		pthread_create(&ui->server_t, NULL, server_thread, NULL);
1825		pthread_detach(ui->server_t);
1826	}
1827}
1828
1829static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1830                gpointer data)
1831{
1832	struct gui_entry *ge = data;
1833	struct gfio_client *gc = ge->client;
1834
1835	if (gc)
1836		fio_start_client(gc->client);
1837}
1838
1839static void file_open(GtkWidget *w, gpointer data);
1840
1841static void connect_clicked(GtkWidget *widget, gpointer data)
1842{
1843	struct gui_entry *ge = data;
1844	struct gfio_client *gc = ge->client;
1845
1846	if (ge->state == GE_STATE_NEW) {
1847		int ret;
1848
1849		if (!ge->nr_job_files)
1850			file_open(widget, ge->ui);
1851		if (!ge->nr_job_files)
1852			return;
1853
1854		gc = ge->client;
1855
1856		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
1857		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1858		ret = fio_client_connect(gc->client);
1859		if (!ret) {
1860			if (!ge->ui->handler_running)
1861				pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
1862			gfio_set_state(ge, GE_STATE_CONNECTED);
1863		} else {
1864			GError *error;
1865
1866			error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
1867			report_error(error);
1868			g_error_free(error);
1869		}
1870	} else {
1871		fio_client_terminate(gc->client);
1872		gfio_set_state(ge, GE_STATE_NEW);
1873		clear_ge_ui_info(ge);
1874	}
1875}
1876
1877static void send_clicked(GtkWidget *widget, gpointer data)
1878{
1879	struct gui_entry *ge = data;
1880
1881	if (send_job_files(ge)) {
1882		GError *error;
1883
1884		error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send one or more job files for client %s", ge->client->client->hostname);
1885		report_error(error);
1886		g_error_free(error);
1887
1888		gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
1889	}
1890}
1891
1892static void on_info_bar_response(GtkWidget *widget, gint response,
1893                                 gpointer data)
1894{
1895	struct gui *ui = &main_ui;
1896
1897	if (response == GTK_RESPONSE_OK) {
1898		gtk_widget_destroy(widget);
1899		ui->error_info_bar = NULL;
1900	}
1901}
1902
1903static void report_error(GError *error)
1904{
1905	struct gui *ui = &main_ui;
1906
1907	if (ui->error_info_bar == NULL) {
1908		ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1909		                                               GTK_RESPONSE_OK,
1910		                                               NULL);
1911		g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1912		gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
1913		                              GTK_MESSAGE_ERROR);
1914
1915		ui->error_label = gtk_label_new(error->message);
1916		GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
1917		gtk_container_add(GTK_CONTAINER(container), ui->error_label);
1918
1919		gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
1920		gtk_widget_show_all(ui->vbox);
1921	} else {
1922		char buffer[256];
1923		snprintf(buffer, sizeof(buffer), "Failed to open file.");
1924		gtk_label_set(GTK_LABEL(ui->error_label), buffer);
1925	}
1926}
1927
1928struct connection_widgets
1929{
1930	GtkWidget *hentry;
1931	GtkWidget *combo;
1932	GtkWidget *button;
1933};
1934
1935static void hostname_cb(GtkEntry *entry, gpointer data)
1936{
1937	struct connection_widgets *cw = data;
1938	int uses_net = 0, is_localhost = 0;
1939	const gchar *text;
1940	gchar *ctext;
1941
1942	/*
1943	 * Check whether to display the 'auto start backend' box
1944	 * or not. Show it if we are a localhost and using network,
1945	 * or using a socket.
1946	 */
1947	ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1948	if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1949		uses_net = 1;
1950	g_free(ctext);
1951
1952	if (uses_net) {
1953		text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1954		if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1955		    !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1956		    !strcmp(text, "ip6-loopback"))
1957			is_localhost = 1;
1958	}
1959
1960	if (!uses_net || is_localhost) {
1961		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1962		gtk_widget_set_sensitive(cw->button, 1);
1963	} else {
1964		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1965		gtk_widget_set_sensitive(cw->button, 0);
1966	}
1967}
1968
1969static int get_connection_details(char **host, int *port, int *type,
1970				  int *server_start)
1971{
1972	GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1973	struct connection_widgets cw;
1974	char *typeentry;
1975
1976	dialog = gtk_dialog_new_with_buttons("Connection details",
1977			GTK_WINDOW(main_ui.window),
1978			GTK_DIALOG_DESTROY_WITH_PARENT,
1979			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1980			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1981
1982	frame = gtk_frame_new("Hostname / socket name");
1983	/* gtk_dialog_get_content_area() is 2.14 and newer */
1984	vbox = GTK_DIALOG(dialog)->vbox;
1985	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1986
1987	box = gtk_vbox_new(FALSE, 6);
1988	gtk_container_add(GTK_CONTAINER(frame), box);
1989
1990	hbox = gtk_hbox_new(TRUE, 10);
1991	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1992	cw.hentry = gtk_entry_new();
1993	gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1994	gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
1995
1996	frame = gtk_frame_new("Port");
1997	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1998	box = gtk_vbox_new(FALSE, 10);
1999	gtk_container_add(GTK_CONTAINER(frame), box);
2000
2001	hbox = gtk_hbox_new(TRUE, 4);
2002	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2003	pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
2004
2005	frame = gtk_frame_new("Type");
2006	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2007	box = gtk_vbox_new(FALSE, 10);
2008	gtk_container_add(GTK_CONTAINER(frame), box);
2009
2010	hbox = gtk_hbox_new(TRUE, 4);
2011	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2012
2013	cw.combo = gtk_combo_box_new_text();
2014	gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
2015	gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
2016	gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
2017	gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
2018
2019	gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
2020
2021	frame = gtk_frame_new("Options");
2022	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2023	box = gtk_vbox_new(FALSE, 10);
2024	gtk_container_add(GTK_CONTAINER(frame), box);
2025
2026	hbox = gtk_hbox_new(TRUE, 4);
2027	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2028
2029	cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
2030	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
2031	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.");
2032	gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2033
2034	/*
2035	 * Connect edit signal, so we can show/not-show the auto start button
2036	 */
2037	g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
2038	g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2039
2040	gtk_widget_show_all(dialog);
2041
2042	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2043		gtk_widget_destroy(dialog);
2044		return 1;
2045	}
2046
2047	*host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
2048	*port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2049
2050	typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
2051	if (!typeentry || !strncmp(typeentry, "IPv4", 4))
2052		*type = Fio_client_ipv4;
2053	else if (!strncmp(typeentry, "IPv6", 4))
2054		*type = Fio_client_ipv6;
2055	else
2056		*type = Fio_client_socket;
2057	g_free(typeentry);
2058
2059	*server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2060
2061	gtk_widget_destroy(dialog);
2062	return 0;
2063}
2064
2065static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
2066{
2067	struct gfio_client *gc;
2068
2069	gc = malloc(sizeof(*gc));
2070	memset(gc, 0, sizeof(*gc));
2071	gc->ge = ge;
2072	gc->client = fio_get_client(client);
2073
2074	ge->client = gc;
2075
2076	client->client_data = gc;
2077}
2078
2079static GtkWidget *new_client_page(struct gui_entry *ge);
2080
2081static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2082{
2083	struct gui_entry *ge;
2084
2085	ge = malloc(sizeof(*ge));
2086	memset(ge, 0, sizeof(*ge));
2087	ge->state = GE_STATE_NEW;
2088	INIT_FLIST_HEAD(&ge->list);
2089	flist_add_tail(&ge->list, &ui->list);
2090	ge->ui = ui;
2091	return ge;
2092}
2093
2094static struct gui_entry *get_new_ge_with_tab(const char *name)
2095{
2096	struct gui_entry *ge;
2097
2098	ge = alloc_new_gui_entry(&main_ui);
2099
2100	ge->vbox = new_client_page(ge);
2101	g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
2102
2103	ge->page_label = gtk_label_new(name);
2104	ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
2105
2106	gtk_widget_show_all(main_ui.window);
2107	return ge;
2108}
2109
2110static void file_new(GtkWidget *w, gpointer data)
2111{
2112	struct gui *ui = (struct gui *) data;
2113	struct gui_entry *ge;
2114
2115	ge = get_new_ge_with_tab("Untitled");
2116	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2117}
2118
2119/*
2120 * Return the 'ge' corresponding to the tab. If the active tab is the
2121 * main tab, open a new tab.
2122 */
2123static struct gui_entry *get_ge_from_page(gint cur_page, int *created)
2124{
2125	struct flist_head *entry;
2126	struct gui_entry *ge;
2127
2128	if (!cur_page) {
2129		if (created)
2130			*created = 1;
2131		return get_new_ge_with_tab("Untitled");
2132	}
2133
2134	if (created)
2135		*created = 0;
2136
2137	flist_for_each(entry, &main_ui.list) {
2138		ge = flist_entry(entry, struct gui_entry, list);
2139		if (ge->page_num == cur_page)
2140			return ge;
2141	}
2142
2143	return NULL;
2144}
2145
2146static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2147{
2148	gint cur_page;
2149
2150	/*
2151	 * Main tab is tab 0, so any current page other than 0 holds
2152	 * a ge entry.
2153	 */
2154	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2155	if (cur_page)
2156		return get_ge_from_page(cur_page, NULL);
2157
2158	return NULL;
2159}
2160
2161static void file_close(GtkWidget *w, gpointer data)
2162{
2163	struct gui *ui = (struct gui *) data;
2164	struct gui_entry *ge;
2165
2166	/*
2167	 * Can't close the main tab
2168	 */
2169	ge = get_ge_from_cur_tab(ui);
2170	if (ge) {
2171		gtk_widget_destroy(ge->vbox);
2172		return;
2173	}
2174
2175	if (!flist_empty(&ui->list)) {
2176		show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2177		return;
2178	}
2179
2180	gfio_quit(ui);
2181}
2182
2183static void file_add_recent(struct gui *ui, const gchar *uri)
2184{
2185	GtkRecentData grd;
2186
2187	memset(&grd, 0, sizeof(grd));
2188	grd.display_name = strdup("gfio");
2189	grd.description = strdup("Fio job file");
2190	grd.mime_type = strdup(GFIO_MIME);
2191	grd.app_name = strdup(g_get_application_name());
2192	grd.app_exec = strdup("gfio %f/%u");
2193
2194	gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2195}
2196
2197static gchar *get_filename_from_uri(const gchar *uri)
2198{
2199	if (strncmp(uri, "file://", 7))
2200		return strdup(uri);
2201
2202	return strdup(uri + 7);
2203}
2204
2205static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2206			int type, int port)
2207{
2208	struct fio_client *client;
2209	gchar *filename;
2210
2211	filename = get_filename_from_uri(uri);
2212
2213	ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2214	ge->job_files[ge->nr_job_files] = strdup(filename);
2215	ge->nr_job_files++;
2216
2217	client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2218	if (!client) {
2219		GError *error;
2220
2221		error = g_error_new(g_quark_from_string("fio"), 1,
2222				"Failed to add client %s", host);
2223		report_error(error);
2224		g_error_free(error);
2225		return 1;
2226	}
2227
2228	gfio_client_added(ge, client);
2229	file_add_recent(ge->ui, uri);
2230	return 0;
2231}
2232
2233static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2234{
2235	int port, type, server_start;
2236	struct gui_entry *ge;
2237	gint cur_page;
2238	char *host;
2239	int ret, ge_is_new = 0;
2240
2241	/*
2242	 * Creates new tab if current tab is the main window, or the
2243	 * current tab already has a client.
2244	 */
2245	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2246	ge = get_ge_from_page(cur_page, &ge_is_new);
2247	if (ge->client) {
2248		ge = get_new_ge_with_tab("Untitled");
2249		ge_is_new = 1;
2250	}
2251
2252	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2253
2254	if (get_connection_details(&host, &port, &type, &server_start)) {
2255		if (ge_is_new)
2256			gtk_widget_destroy(ge->vbox);
2257
2258		return 1;
2259	}
2260
2261	ret = do_file_open(ge, uri, host, type, port);
2262
2263	free(host);
2264
2265	if (!ret) {
2266		if (server_start)
2267			gfio_start_server();
2268	} else {
2269		if (ge_is_new)
2270			gtk_widget_destroy(ge->vbox);
2271	}
2272
2273	return ret;
2274}
2275
2276static void recent_open(GtkAction *action, gpointer data)
2277{
2278	struct gui *ui = (struct gui *) data;
2279	GtkRecentInfo *info;
2280	const gchar *uri;
2281
2282	info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2283	uri = gtk_recent_info_get_uri(info);
2284
2285	do_file_open_with_tab(ui, uri);
2286}
2287
2288static void file_open(GtkWidget *w, gpointer data)
2289{
2290	struct gui *ui = data;
2291	GtkWidget *dialog;
2292	GSList *filenames, *fn_glist;
2293	GtkFileFilter *filter;
2294
2295	dialog = gtk_file_chooser_dialog_new("Open File",
2296		GTK_WINDOW(ui->window),
2297		GTK_FILE_CHOOSER_ACTION_OPEN,
2298		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2299		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2300		NULL);
2301	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2302
2303	filter = gtk_file_filter_new();
2304	gtk_file_filter_add_pattern(filter, "*.fio");
2305	gtk_file_filter_add_pattern(filter, "*.job");
2306	gtk_file_filter_add_pattern(filter, "*.ini");
2307	gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2308	gtk_file_filter_set_name(filter, "Fio job file");
2309	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2310
2311	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2312		gtk_widget_destroy(dialog);
2313		return;
2314	}
2315
2316	fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2317
2318	gtk_widget_destroy(dialog);
2319
2320	filenames = fn_glist;
2321	while (filenames != NULL) {
2322		if (do_file_open_with_tab(ui, filenames->data))
2323			break;
2324		filenames = g_slist_next(filenames);
2325	}
2326
2327	g_slist_free(fn_glist);
2328}
2329
2330static void file_save(GtkWidget *w, gpointer data)
2331{
2332	struct gui *ui = data;
2333	GtkWidget *dialog;
2334
2335	dialog = gtk_file_chooser_dialog_new("Save File",
2336		GTK_WINDOW(ui->window),
2337		GTK_FILE_CHOOSER_ACTION_SAVE,
2338		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2339		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2340		NULL);
2341
2342	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2343	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2344
2345	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2346		char *filename;
2347
2348		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2349		// save_job_file(filename);
2350		g_free(filename);
2351	}
2352	gtk_widget_destroy(dialog);
2353}
2354
2355static void view_log_destroy(GtkWidget *w, gpointer data)
2356{
2357	struct gui *ui = (struct gui *) data;
2358
2359	gtk_widget_ref(ui->log_tree);
2360	gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2361	gtk_widget_destroy(w);
2362	ui->log_view = NULL;
2363}
2364
2365static void view_log(GtkWidget *w, gpointer data)
2366{
2367	GtkWidget *win, *scroll, *vbox, *box;
2368	struct gui *ui = (struct gui *) data;
2369
2370	if (ui->log_view)
2371		return;
2372
2373	ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2374	gtk_window_set_title(GTK_WINDOW(win), "Log");
2375	gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2376
2377	scroll = gtk_scrolled_window_new(NULL, NULL);
2378
2379	gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2380
2381	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2382
2383	box = gtk_hbox_new(TRUE, 0);
2384	gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2385	g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2386	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2387
2388	vbox = gtk_vbox_new(TRUE, 5);
2389	gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2390
2391	gtk_container_add(GTK_CONTAINER(win), vbox);
2392	gtk_widget_show_all(win);
2393}
2394
2395static void connect_job_entry(GtkWidget *w, gpointer data)
2396{
2397	struct gui *ui = (struct gui *) data;
2398	struct gui_entry *ge;
2399
2400	ge = get_ge_from_cur_tab(ui);
2401	if (ge)
2402		connect_clicked(w, ge);
2403}
2404
2405static void send_job_entry(GtkWidget *w, gpointer data)
2406{
2407	struct gui *ui = (struct gui *) data;
2408	struct gui_entry *ge;
2409
2410	ge = get_ge_from_cur_tab(ui);
2411	if (ge)
2412		send_clicked(w, ge);
2413
2414}
2415
2416static void edit_job_entry(GtkWidget *w, gpointer data)
2417{
2418	struct gui *ui = (struct gui *) data;
2419	struct gui_entry *ge;
2420
2421	ge = get_ge_from_cur_tab(ui);
2422	if (ge && ge->client)
2423		gopt_get_options_window(ui->window, &ge->client->o);
2424}
2425
2426static void start_job_entry(GtkWidget *w, gpointer data)
2427{
2428	struct gui *ui = (struct gui *) data;
2429	struct gui_entry *ge;
2430
2431	ge = get_ge_from_cur_tab(ui);
2432	if (ge)
2433		start_job_clicked(w, ge);
2434}
2435
2436static void view_results(GtkWidget *w, gpointer data)
2437{
2438	struct gui *ui = (struct gui *) data;
2439	struct gfio_client *gc;
2440	struct gui_entry *ge;
2441
2442	ge = get_ge_from_cur_tab(ui);
2443	if (!ge)
2444		return;
2445
2446	if (ge->results_window)
2447		return;
2448
2449	gc = ge->client;
2450	if (gc && gc->nr_results)
2451		gfio_display_end_results(gc);
2452}
2453
2454static void __update_graph_limits(struct gfio_graphs *g)
2455{
2456	line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2457	line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2458}
2459
2460static void update_graph_limits(void)
2461{
2462	struct flist_head *entry;
2463	struct gui_entry *ge;
2464
2465	__update_graph_limits(&main_ui.graphs);
2466
2467	flist_for_each(entry, &main_ui.list) {
2468		ge = flist_entry(entry, struct gui_entry, list);
2469		__update_graph_limits(&ge->graphs);
2470	}
2471}
2472
2473static void preferences(GtkWidget *w, gpointer data)
2474{
2475	GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2476	GtkWidget *hbox, *spin, *entry, *spin_int;
2477	int i;
2478
2479	dialog = gtk_dialog_new_with_buttons("Preferences",
2480		GTK_WINDOW(main_ui.window),
2481		GTK_DIALOG_DESTROY_WITH_PARENT,
2482		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2483		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2484		NULL);
2485
2486	frame = gtk_frame_new("Graphing");
2487	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2488	vbox = gtk_vbox_new(FALSE, 6);
2489	gtk_container_add(GTK_CONTAINER(frame), vbox);
2490
2491	hbox = gtk_hbox_new(FALSE, 5);
2492	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2493	entry = gtk_label_new("Font face to use for graph labels");
2494	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2495
2496	font = gtk_font_button_new();
2497	gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2498
2499	box = gtk_vbox_new(FALSE, 6);
2500	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2501
2502	hbox = gtk_hbox_new(FALSE, 5);
2503	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2504	entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2505	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2506
2507	spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2508
2509	box = gtk_vbox_new(FALSE, 6);
2510	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2511
2512	hbox = gtk_hbox_new(FALSE, 5);
2513	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2514	entry = gtk_label_new("Client ETA request interval (msec)");
2515	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2516
2517	spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2518	frame = gtk_frame_new("Debug logging");
2519	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2520	vbox = gtk_vbox_new(FALSE, 6);
2521	gtk_container_add(GTK_CONTAINER(frame), vbox);
2522
2523	box = gtk_hbox_new(FALSE, 6);
2524	gtk_container_add(GTK_CONTAINER(vbox), box);
2525
2526	buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2527
2528	for (i = 0; i < FD_DEBUG_MAX; i++) {
2529		if (i == 7) {
2530			box = gtk_hbox_new(FALSE, 6);
2531			gtk_container_add(GTK_CONTAINER(vbox), box);
2532		}
2533
2534
2535		buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2536		gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2537		gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2538	}
2539
2540	gtk_widget_show_all(dialog);
2541
2542	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2543		gtk_widget_destroy(dialog);
2544		return;
2545	}
2546
2547	for (i = 0; i < FD_DEBUG_MAX; i++) {
2548		int set;
2549
2550		set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2551		if (set)
2552			fio_debug |= (1UL << i);
2553	}
2554
2555	gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2556	gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2557	update_graph_limits();
2558	gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2559
2560	gtk_widget_destroy(dialog);
2561}
2562
2563static void about_dialog(GtkWidget *w, gpointer data)
2564{
2565	const char *authors[] = {
2566		"Jens Axboe <axboe@kernel.dk>",
2567		"Stephen Carmeron <stephenmcameron@gmail.com>",
2568		NULL
2569	};
2570	const char *license[] = {
2571		"Fio is free software; you can redistribute it and/or modify "
2572		"it under the terms of the GNU General Public License as published by "
2573		"the Free Software Foundation; either version 2 of the License, or "
2574		"(at your option) any later version.\n",
2575		"Fio is distributed in the hope that it will be useful, "
2576		"but WITHOUT ANY WARRANTY; without even the implied warranty of "
2577		"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2578		"GNU General Public License for more details.\n",
2579		"You should have received a copy of the GNU General Public License "
2580		"along with Fio; if not, write to the Free Software Foundation, Inc., "
2581		"51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2582	};
2583	char *license_trans;
2584
2585	license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2586				     license[2], "\n", NULL);
2587
2588	gtk_show_about_dialog(NULL,
2589		"program-name", "gfio",
2590		"comments", "Gtk2 UI for fio",
2591		"license", license_trans,
2592		"website", "http://git.kernel.dk/?p=fio.git;a=summary",
2593		"authors", authors,
2594		"version", fio_version_string,
2595		"copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2596		"logo-icon-name", "fio",
2597		/* Must be last: */
2598		"wrap-license", TRUE,
2599		NULL);
2600
2601	g_free(license_trans);
2602}
2603
2604static GtkActionEntry menu_items[] = {
2605	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2606	{ "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2607	{ "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2608	{ "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2609	{ "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2610	{ "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2611	{ "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2612	{ "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2613	{ "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2614	{ "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2615	{ "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
2616	{ "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
2617	{ "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2618	{ "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2619	{ "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2620	{ "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2621	{ "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2622};
2623static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2624
2625static const gchar *ui_string = " \
2626	<ui> \
2627		<menubar name=\"MainMenu\"> \
2628			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2629				<menuitem name=\"New\" action=\"NewFile\" /> \
2630				<menuitem name=\"Open\" action=\"OpenFile\" /> \
2631				<menuitem name=\"Close\" action=\"CloseFile\" /> \
2632				<separator name=\"Separator1\"/> \
2633				<menuitem name=\"Save\" action=\"SaveFile\" /> \
2634				<separator name=\"Separator2\"/> \
2635				<menuitem name=\"Preferences\" action=\"Preferences\" /> \
2636				<separator name=\"Separator3\"/> \
2637				<placeholder name=\"FileRecentFiles\"/> \
2638				<separator name=\"Separator4\"/> \
2639				<menuitem name=\"Quit\" action=\"Quit\" /> \
2640			</menu> \
2641			<menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2642				<menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2643				<separator name=\"Separator5\"/> \
2644				<menuitem name=\"Edit job\" action=\"EditJob\" /> \
2645				<menuitem name=\"Send job\" action=\"SendJob\" /> \
2646				<separator name=\"Separator6\"/> \
2647				<menuitem name=\"Start job\" action=\"StartJob\" /> \
2648			</menu>\
2649			<menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2650				<menuitem name=\"Results\" action=\"ViewResults\" /> \
2651				<separator name=\"Separator7\"/> \
2652				<menuitem name=\"Log\" action=\"ViewLog\" /> \
2653			</menu>\
2654			<menu name=\"Help\" action=\"HelpMenuAction\"> \
2655				<menuitem name=\"About\" action=\"About\" /> \
2656			</menu> \
2657		</menubar> \
2658	</ui> \
2659";
2660
2661static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2662				   struct gui *ui)
2663{
2664	GtkActionGroup *action_group;
2665	GError *error = 0;
2666
2667	action_group = gtk_action_group_new("Menu");
2668	gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2669
2670	gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2671	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2672
2673	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2674
2675	return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2676}
2677
2678void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2679                   GtkWidget *vbox, GtkUIManager *ui_manager)
2680{
2681        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2682}
2683
2684static void combo_entry_changed(GtkComboBox *box, gpointer data)
2685{
2686	struct gui_entry *ge = (struct gui_entry *) data;
2687	gint index;
2688
2689	index = gtk_combo_box_get_active(box);
2690
2691	multitext_set_entry(&ge->eta.iotype, index);
2692	multitext_set_entry(&ge->eta.bs, index);
2693	multitext_set_entry(&ge->eta.ioengine, index);
2694	multitext_set_entry(&ge->eta.iodepth, index);
2695}
2696
2697static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2698{
2699	struct gui_entry *ge = (struct gui_entry *) data;
2700
2701	multitext_free(&ge->eta.iotype);
2702	multitext_free(&ge->eta.bs);
2703	multitext_free(&ge->eta.ioengine);
2704	multitext_free(&ge->eta.iodepth);
2705}
2706
2707static GtkWidget *new_client_page(struct gui_entry *ge)
2708{
2709	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2710	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2711
2712	main_vbox = gtk_vbox_new(FALSE, 3);
2713
2714	top_align = gtk_alignment_new(0, 0, 1, 0);
2715	top_vbox = gtk_vbox_new(FALSE, 3);
2716	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2717	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2718
2719	probe = gtk_frame_new("Job");
2720	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2721	probe_frame = gtk_vbox_new(FALSE, 3);
2722	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2723
2724	probe_box = gtk_hbox_new(FALSE, 3);
2725	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2726	ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2727	ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2728	ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2729	ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2730
2731	probe_box = gtk_hbox_new(FALSE, 3);
2732	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2733
2734	ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2735	g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2736	g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2737	ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2738	ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
2739	ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2740	ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2741	ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2742	ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2743
2744	probe_box = gtk_hbox_new(FALSE, 3);
2745	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2746	ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2747	ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2748	ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2749	ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2750
2751	/*
2752	 * Only add this if we have a commit rate
2753	 */
2754#if 0
2755	probe_box = gtk_hbox_new(FALSE, 3);
2756	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2757
2758	ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2759	ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2760
2761	ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2762	ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2763#endif
2764
2765	/*
2766	 * Set up a drawing area and IOPS and bandwidth graphs
2767	 */
2768	ge->graphs.drawing_area = gtk_drawing_area_new();
2769	gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2770		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2771	gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2772	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2773				G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2774	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2775				G_CALLBACK(on_config_drawing_area), &ge->graphs);
2776	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2777	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2778					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2779	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2780					ge->graphs.drawing_area);
2781	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2782
2783	setup_graphs(&ge->graphs);
2784
2785	/*
2786	 * Set up alignments for widgets at the bottom of ui,
2787	 * align bottom left, expand horizontally but not vertically
2788	 */
2789	bottom_align = gtk_alignment_new(0, 1, 1, 0);
2790	ge->buttonbox = gtk_hbox_new(FALSE, 0);
2791	gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2792	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2793
2794	add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2795
2796	/*
2797	 * Set up thread status progress bar
2798	 */
2799	ge->thread_status_pb = gtk_progress_bar_new();
2800	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2801	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2802	gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2803
2804
2805	return main_vbox;
2806}
2807
2808static GtkWidget *new_main_page(struct gui *ui)
2809{
2810	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2811	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2812
2813	main_vbox = gtk_vbox_new(FALSE, 3);
2814
2815	/*
2816	 * Set up alignments for widgets at the top of ui,
2817	 * align top left, expand horizontally but not vertically
2818	 */
2819	top_align = gtk_alignment_new(0, 0, 1, 0);
2820	top_vbox = gtk_vbox_new(FALSE, 0);
2821	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2822	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2823
2824	probe = gtk_frame_new("Run statistics");
2825	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2826	probe_frame = gtk_vbox_new(FALSE, 3);
2827	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2828
2829	probe_box = gtk_hbox_new(FALSE, 3);
2830	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2831	ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2832	ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2833	ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2834	ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2835	ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2836
2837	/*
2838	 * Only add this if we have a commit rate
2839	 */
2840#if 0
2841	probe_box = gtk_hbox_new(FALSE, 3);
2842	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2843
2844	ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2845	ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2846
2847	ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2848	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2849#endif
2850
2851	/*
2852	 * Set up a drawing area and IOPS and bandwidth graphs
2853	 */
2854	ui->graphs.drawing_area = gtk_drawing_area_new();
2855	gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2856		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2857	gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2858	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2859			G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2860	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2861			G_CALLBACK(on_config_drawing_area), &ui->graphs);
2862	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2863	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2864					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2865	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2866					ui->graphs.drawing_area);
2867	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2868			TRUE, TRUE, 0);
2869
2870	setup_graphs(&ui->graphs);
2871
2872	/*
2873	 * Set up alignments for widgets at the bottom of ui,
2874	 * align bottom left, expand horizontally but not vertically
2875	 */
2876	bottom_align = gtk_alignment_new(0, 1, 1, 0);
2877	ui->buttonbox = gtk_hbox_new(FALSE, 0);
2878	gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2879	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2880
2881	/*
2882	 * Set up thread status progress bar
2883	 */
2884	ui->thread_status_pb = gtk_progress_bar_new();
2885	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2886	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2887	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2888
2889	return main_vbox;
2890}
2891
2892static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2893				     guint page, gpointer data)
2894
2895{
2896	struct gui *ui = (struct gui *) data;
2897	struct gui_entry *ge;
2898
2899	if (!page) {
2900		set_job_menu_visible(ui, 0);
2901		set_view_results_visible(ui, 0);
2902		return TRUE;
2903	}
2904
2905	set_job_menu_visible(ui, 1);
2906	ge = get_ge_from_page(page, NULL);
2907	if (ge)
2908		update_button_states(ui, ge);
2909
2910	return TRUE;
2911}
2912
2913static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2914{
2915	time_t time_a = gtk_recent_info_get_visited(a);
2916	time_t time_b = gtk_recent_info_get_visited(b);
2917
2918	return time_b - time_a;
2919}
2920
2921static void add_recent_file_items(struct gui *ui)
2922{
2923	const gchar *gfio = g_get_application_name();
2924	GList *items, *item;
2925	int i = 0;
2926
2927	if (ui->recent_ui_id) {
2928		gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2929		gtk_ui_manager_ensure_update(ui->uimanager);
2930	}
2931	ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2932
2933	if (ui->actiongroup) {
2934		gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2935		g_object_unref(ui->actiongroup);
2936	}
2937	ui->actiongroup = gtk_action_group_new("RecentFileActions");
2938
2939	gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2940
2941	items = gtk_recent_manager_get_items(ui->recentmanager);
2942	items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2943
2944	for (item = items; item && item->data; item = g_list_next(item)) {
2945		GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2946		gchar *action_name;
2947		const gchar *label;
2948		GtkAction *action;
2949
2950		if (!gtk_recent_info_has_application(info, gfio))
2951			continue;
2952
2953		/*
2954		 * We only support local files for now
2955		 */
2956		if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2957			continue;
2958
2959		action_name = g_strdup_printf("RecentFile%u", i++);
2960		label = gtk_recent_info_get_display_name(info);
2961
2962		action = g_object_new(GTK_TYPE_ACTION,
2963					"name", action_name,
2964					"label", label, NULL);
2965
2966		g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2967					gtk_recent_info_ref(info),
2968					(GDestroyNotify) gtk_recent_info_unref);
2969
2970
2971		g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2972
2973		gtk_action_group_add_action(ui->actiongroup, action);
2974		g_object_unref(action);
2975
2976		gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2977					"/MainMenu/FileMenu/FileRecentFiles",
2978					label, action_name,
2979					GTK_UI_MANAGER_MENUITEM, FALSE);
2980
2981		g_free(action_name);
2982
2983		if (i == 8)
2984			break;
2985	}
2986
2987	g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2988	g_list_free(items);
2989}
2990
2991static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2992				   gint x, gint y, GtkSelectionData *data,
2993				   guint info, guint time)
2994{
2995	struct gui *ui = &main_ui;
2996	gchar **uris;
2997	GtkWidget *source;
2998	int i;
2999
3000	source = gtk_drag_get_source_widget(ctx);
3001	if (source && widget == gtk_widget_get_toplevel(source)) {
3002		gtk_drag_finish(ctx, FALSE, FALSE, time);
3003		return;
3004	}
3005
3006	uris = gtk_selection_data_get_uris(data);
3007	if (!uris) {
3008		gtk_drag_finish(ctx, FALSE, FALSE, time);
3009		return;
3010	}
3011
3012	i = 0;
3013	while (uris[i]) {
3014		if (do_file_open_with_tab(ui, uris[i]))
3015			break;
3016		i++;
3017	}
3018
3019	gtk_drag_finish(ctx, TRUE, FALSE, time);
3020	g_strfreev(uris);
3021}
3022
3023static void init_ui(int *argc, char **argv[], struct gui *ui)
3024{
3025	GtkSettings *settings;
3026	GtkWidget *vbox;
3027
3028	/* Magical g*thread incantation, you just need this thread stuff.
3029	 * Without it, the update that happens in gfio_update_thread_status
3030	 * doesn't really happen in a timely fashion, you need expose events
3031	 */
3032	if (!g_thread_supported())
3033		g_thread_init(NULL);
3034	gdk_threads_init();
3035
3036	gtk_init(argc, argv);
3037	settings = gtk_settings_get_default();
3038	gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
3039	g_type_init();
3040	gdk_color_parse("white", &white);
3041
3042	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3043	gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
3044	gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
3045
3046	g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
3047	g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
3048
3049	ui->vbox = gtk_vbox_new(FALSE, 0);
3050	gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3051
3052	ui->uimanager = gtk_ui_manager_new();
3053	ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3054	gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3055
3056	ui->recentmanager = gtk_recent_manager_get_default();
3057	add_recent_file_items(ui);
3058
3059	ui->notebook = gtk_notebook_new();
3060	g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3061	gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3062	gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3063	gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3064
3065	vbox = new_main_page(ui);
3066	gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3067	gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3068	g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3069
3070	gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3071
3072	gfio_ui_setup_log(ui);
3073
3074	gtk_widget_show_all(ui->window);
3075}
3076
3077int main(int argc, char *argv[], char *envp[])
3078{
3079	if (initialize_fio(envp))
3080		return 1;
3081	if (fio_init_options())
3082		return 1;
3083
3084	memset(&main_ui, 0, sizeof(main_ui));
3085	INIT_FLIST_HEAD(&main_ui.list);
3086
3087	init_ui(&argc, &argv, &main_ui);
3088
3089	gdk_threads_enter();
3090	gtk_main();
3091	gdk_threads_leave();
3092	return 0;
3093}
3094