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