gfio.c revision 41666588eed4ed830b1fabd0458eb2b0a93f0147
1/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
5 * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
6 *
7 * The license below covers all files distributed with fio unless otherwise
8 * noted in the file itself.
9 *
10 *  This program is free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License version 2 as
12 *  published by the Free Software Foundation.
13 *
14 *  This program is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *  GNU General Public License for more details.
18 *
19 *  You should have received a copy of the GNU General Public License
20 *  along with this program; if not, write to the Free Software
21 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 *
23 */
24#include <locale.h>
25#include <malloc.h>
26#include <string.h>
27
28#include <glib.h>
29#include <cairo.h>
30#include <gtk/gtk.h>
31
32#include "fio.h"
33#include "gfio.h"
34#include "ghelpers.h"
35#include "goptions.h"
36#include "gerror.h"
37#include "graph.h"
38
39static int gfio_server_running;
40static const char *gfio_graph_font;
41static unsigned int gfio_graph_limit = 100;
42static GdkColor white;
43
44static void view_log(GtkWidget *w, gpointer data);
45
46typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
47
48static void connect_clicked(GtkWidget *widget, gpointer data);
49static void start_job_clicked(GtkWidget *widget, gpointer data);
50static void send_clicked(GtkWidget *widget, gpointer data);
51
52static struct button_spec {
53	const char *buttontext;
54	clickfunction f;
55	const char *tooltiptext[2];
56	const int start_sensitive;
57} buttonspeclist[] = {
58	{
59	  .buttontext		= "Connect",
60	  .f			= connect_clicked,
61	  .tooltiptext		= { "Disconnect from host", "Connect to host" },
62	  .start_sensitive	= 1,
63	},
64	{
65	  .buttontext		= "Send",
66	  .f			= send_clicked,
67	  .tooltiptext		= { "Send job description to host", NULL },
68	  .start_sensitive	= 0,
69	},
70	{
71	  .buttontext		= "Start Job",
72	  .f			= start_job_clicked,
73	  .tooltiptext		= { "Start the current job on the server", NULL },
74	  .start_sensitive	= 0,
75	},
76};
77
78static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc);
79static void gfio_update_thread_status_all(struct gui *ui, char *status_message, double perc);
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 = NULL;
878	GtkTreeSelection *selection;
879	GtkListStore *model;
880	GType types[FIO_IO_U_MAP_NR + 1];
881	int i;
882	const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
883	const int nr_labels = ARRAYSIZE(labels);
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
1711static void gfio_client_removed(struct fio_client *client)
1712{
1713	struct gfio_client *gc = client->client_data;
1714
1715	assert(gc->client == client);
1716	fio_put_client(gc->client);
1717	gc->client = NULL;
1718}
1719
1720struct client_ops gfio_client_ops = {
1721	.text			= gfio_text_op,
1722	.disk_util		= gfio_disk_util_op,
1723	.thread_status		= gfio_thread_status_op,
1724	.group_stats		= gfio_group_stats_op,
1725	.jobs_eta		= gfio_update_client_eta,
1726	.eta			= gfio_update_all_eta,
1727	.probe			= gfio_probe_op,
1728	.quit			= gfio_quit_op,
1729	.add_job		= gfio_add_job_op,
1730	.timed_out		= gfio_client_timed_out,
1731	.stop			= gfio_client_stop,
1732	.start			= gfio_client_start,
1733	.job_start		= gfio_client_job_start,
1734	.iolog			= gfio_client_iolog,
1735	.removed		= gfio_client_removed,
1736	.eta_msec		= FIO_CLIENT_DEF_ETA_MSEC,
1737	.stay_connected		= 1,
1738	.client_type		= FIO_CLIENT_TYPE_GUI,
1739};
1740
1741/*
1742 * FIXME: need more handling here
1743 */
1744static void ge_destroy(struct gui_entry *ge)
1745{
1746	struct gfio_client *gc = ge->client;
1747
1748	if (gc && gc->client) {
1749		if (ge->state >= GE_STATE_CONNECTED)
1750			fio_client_terminate(gc->client);
1751
1752		fio_put_client(gc->client);
1753	}
1754
1755	free(ge->job_file);
1756	free(ge->host);
1757	flist_del(&ge->list);
1758	free(ge);
1759}
1760
1761static void ge_widget_destroy(GtkWidget *w, gpointer data)
1762{
1763	struct gui_entry *ge = (struct gui_entry *) data;
1764
1765	ge_destroy(ge);
1766}
1767
1768static void gfio_quit(struct gui *ui)
1769{
1770	struct gui_entry *ge;
1771
1772	while (!flist_empty(&ui->list)) {
1773		ge = flist_entry(ui->list.next, struct gui_entry, list);
1774		ge_destroy(ge);
1775	}
1776
1777        gtk_main_quit();
1778}
1779
1780static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1781                __attribute__((unused)) gpointer data)
1782{
1783	struct gui *ui = (struct gui *) data;
1784
1785	gfio_quit(ui);
1786}
1787
1788static void *job_thread(void *arg)
1789{
1790	struct gui *ui = arg;
1791
1792	ui->handler_running = 1;
1793	fio_handle_clients(&gfio_client_ops);
1794	ui->handler_running = 0;
1795	return NULL;
1796}
1797
1798static int send_job_file(struct gui_entry *ge)
1799{
1800	struct gfio_client *gc = ge->client;
1801	int ret = 0;
1802
1803	ret = fio_client_send_ini(gc->client, ge->job_file);
1804	if (!ret)
1805		return 0;
1806
1807	gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
1808	return 1;
1809}
1810
1811static void *server_thread(void *arg)
1812{
1813	is_backend = 1;
1814	gfio_server_running = 1;
1815	fio_start_server(NULL);
1816	gfio_server_running = 0;
1817	return NULL;
1818}
1819
1820static void gfio_start_server(struct gui *ui)
1821{
1822	if (!gfio_server_running) {
1823		gfio_server_running = 1;
1824		pthread_create(&ui->server_t, NULL, server_thread, NULL);
1825		pthread_detach(ui->server_t);
1826	}
1827}
1828
1829static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1830                gpointer data)
1831{
1832	struct gui_entry *ge = data;
1833	struct gfio_client *gc = ge->client;
1834
1835	if (gc)
1836		fio_start_client(gc->client);
1837}
1838
1839static void file_open(GtkWidget *w, gpointer data);
1840
1841struct connection_widgets
1842{
1843	GtkWidget *hentry;
1844	GtkWidget *combo;
1845	GtkWidget *button;
1846};
1847
1848static void hostname_cb(GtkEntry *entry, gpointer data)
1849{
1850	struct connection_widgets *cw = data;
1851	int uses_net = 0, is_localhost = 0;
1852	const gchar *text;
1853	gchar *ctext;
1854
1855	/*
1856	 * Check whether to display the 'auto start backend' box
1857	 * or not. Show it if we are a localhost and using network,
1858	 * or using a socket.
1859	 */
1860	ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1861	if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1862		uses_net = 1;
1863	g_free(ctext);
1864
1865	if (uses_net) {
1866		text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1867		if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1868		    !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1869		    !strcmp(text, "ip6-loopback"))
1870			is_localhost = 1;
1871	}
1872
1873	if (!uses_net || is_localhost) {
1874		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1875		gtk_widget_set_sensitive(cw->button, 1);
1876	} else {
1877		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1878		gtk_widget_set_sensitive(cw->button, 0);
1879	}
1880}
1881
1882static int get_connection_details(struct gui_entry *ge)
1883{
1884	GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1885	struct connection_widgets cw;
1886	struct gui *ui = ge->ui;
1887	char *typeentry;
1888
1889	if (ge->host)
1890		return 0;
1891
1892	dialog = gtk_dialog_new_with_buttons("Connection details",
1893			GTK_WINDOW(ui->window),
1894			GTK_DIALOG_DESTROY_WITH_PARENT,
1895			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1896			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1897
1898	frame = gtk_frame_new("Hostname / socket name");
1899	/* gtk_dialog_get_content_area() is 2.14 and newer */
1900	vbox = GTK_DIALOG(dialog)->vbox;
1901	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1902
1903	box = gtk_vbox_new(FALSE, 6);
1904	gtk_container_add(GTK_CONTAINER(frame), box);
1905
1906	hbox = gtk_hbox_new(TRUE, 10);
1907	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1908	cw.hentry = gtk_entry_new();
1909	gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1910	gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
1911
1912	frame = gtk_frame_new("Port");
1913	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1914	box = gtk_vbox_new(FALSE, 10);
1915	gtk_container_add(GTK_CONTAINER(frame), box);
1916
1917	hbox = gtk_hbox_new(TRUE, 4);
1918	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1919	pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
1920
1921	frame = gtk_frame_new("Type");
1922	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1923	box = gtk_vbox_new(FALSE, 10);
1924	gtk_container_add(GTK_CONTAINER(frame), box);
1925
1926	hbox = gtk_hbox_new(TRUE, 4);
1927	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1928
1929	cw.combo = gtk_combo_box_new_text();
1930	gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
1931	gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
1932	gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
1933	gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
1934
1935	gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
1936
1937	frame = gtk_frame_new("Options");
1938	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1939	box = gtk_vbox_new(FALSE, 10);
1940	gtk_container_add(GTK_CONTAINER(frame), box);
1941
1942	hbox = gtk_hbox_new(TRUE, 4);
1943	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1944
1945	cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
1946	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
1947	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.");
1948	gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
1949
1950	/*
1951	 * Connect edit signal, so we can show/not-show the auto start button
1952	 */
1953	g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
1954	g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
1955
1956	gtk_widget_show_all(dialog);
1957
1958	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1959		gtk_widget_destroy(dialog);
1960		return 1;
1961	}
1962
1963	ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
1964	ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
1965
1966	typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
1967	if (!typeentry || !strncmp(typeentry, "IPv4", 4))
1968		ge->type = Fio_client_ipv4;
1969	else if (!strncmp(typeentry, "IPv6", 4))
1970		ge->type = Fio_client_ipv6;
1971	else
1972		ge->type = Fio_client_socket;
1973	g_free(typeentry);
1974
1975	ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
1976
1977	gtk_widget_destroy(dialog);
1978	return 0;
1979}
1980
1981static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
1982{
1983	gc->client = fio_get_client(client);
1984	client->client_data = gc;
1985}
1986
1987static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
1988{
1989	struct gfio_client *gc;
1990
1991	gc = malloc(sizeof(*gc));
1992	memset(gc, 0, sizeof(*gc));
1993	options_default_fill(&gc->o);
1994	gc->ge = ge;
1995	ge->client = gc;
1996	gfio_set_client(gc, client);
1997}
1998
1999static void connect_clicked(GtkWidget *widget, gpointer data)
2000{
2001	struct gui_entry *ge = data;
2002	struct gfio_client *gc = ge->client;
2003
2004	if (ge->state == GE_STATE_NEW) {
2005		int ret;
2006
2007		if (!ge->job_file)
2008			file_open(widget, ge->ui);
2009		if (!ge->job_file)
2010			return;
2011
2012		gc = ge->client;
2013
2014		if (!gc->client) {
2015			struct fio_client *client;
2016
2017			if (get_connection_details(ge)) {
2018				gfio_report_error(ge, "Failed to get connection details\n");
2019				return;
2020			}
2021
2022			client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
2023			if (!client) {
2024				gfio_report_error(ge, "Failed to add client %s\n", ge->host);
2025				free(ge->host);
2026				ge->host = NULL;
2027				return;
2028			}
2029			gfio_set_client(gc, client);
2030		}
2031
2032		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
2033		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2034		ret = fio_client_connect(gc->client);
2035		if (!ret) {
2036			if (!ge->ui->handler_running)
2037				pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
2038			gfio_set_state(ge, GE_STATE_CONNECTED);
2039		} else {
2040			gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
2041		}
2042	} else {
2043		fio_client_terminate(gc->client);
2044		gfio_set_state(ge, GE_STATE_NEW);
2045		clear_ge_ui_info(ge);
2046	}
2047}
2048
2049static void send_clicked(GtkWidget *widget, gpointer data)
2050{
2051	struct gui_entry *ge = data;
2052
2053	if (send_job_file(ge))
2054		gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
2055}
2056
2057static GtkWidget *new_client_page(struct gui_entry *ge);
2058
2059static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2060{
2061	struct gui_entry *ge;
2062
2063	ge = malloc(sizeof(*ge));
2064	memset(ge, 0, sizeof(*ge));
2065	ge->state = GE_STATE_NEW;
2066	INIT_FLIST_HEAD(&ge->list);
2067	flist_add_tail(&ge->list, &ui->list);
2068	ge->ui = ui;
2069	return ge;
2070}
2071
2072static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
2073{
2074	struct gui_entry *ge;
2075
2076	ge = alloc_new_gui_entry(ui);
2077
2078	ge->vbox = new_client_page(ge);
2079	g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
2080
2081	ge->page_label = gtk_label_new(name);
2082	ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
2083
2084	gtk_widget_show_all(ui->window);
2085	return ge;
2086}
2087
2088static void file_new(GtkWidget *w, gpointer data)
2089{
2090	struct gui *ui = (struct gui *) data;
2091	struct gui_entry *ge;
2092
2093	ge = get_new_ge_with_tab(ui, "Untitled");
2094	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2095}
2096
2097/*
2098 * Return the 'ge' corresponding to the tab. If the active tab is the
2099 * main tab, open a new tab.
2100 */
2101static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
2102					  int *created)
2103{
2104	struct flist_head *entry;
2105	struct gui_entry *ge;
2106
2107	if (!cur_page) {
2108		if (created)
2109			*created = 1;
2110		return get_new_ge_with_tab(ui, "Untitled");
2111	}
2112
2113	if (created)
2114		*created = 0;
2115
2116	flist_for_each(entry, &ui->list) {
2117		ge = flist_entry(entry, struct gui_entry, list);
2118		if (ge->page_num == cur_page)
2119			return ge;
2120	}
2121
2122	return NULL;
2123}
2124
2125static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2126{
2127	gint cur_page;
2128
2129	/*
2130	 * Main tab is tab 0, so any current page other than 0 holds
2131	 * a ge entry.
2132	 */
2133	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2134	if (cur_page)
2135		return get_ge_from_page(ui, cur_page, NULL);
2136
2137	return NULL;
2138}
2139
2140static void file_close(GtkWidget *w, gpointer data)
2141{
2142	struct gui *ui = (struct gui *) data;
2143	struct gui_entry *ge;
2144
2145	/*
2146	 * Can't close the main tab
2147	 */
2148	ge = get_ge_from_cur_tab(ui);
2149	if (ge) {
2150		gtk_widget_destroy(ge->vbox);
2151		return;
2152	}
2153
2154	if (!flist_empty(&ui->list)) {
2155		show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2156		return;
2157	}
2158
2159	gfio_quit(ui);
2160}
2161
2162static void file_add_recent(struct gui *ui, const gchar *uri)
2163{
2164	GtkRecentData grd;
2165
2166	memset(&grd, 0, sizeof(grd));
2167	grd.display_name = strdup("gfio");
2168	grd.description = strdup("Fio job file");
2169	grd.mime_type = strdup(GFIO_MIME);
2170	grd.app_name = strdup(g_get_application_name());
2171	grd.app_exec = strdup("gfio %f/%u");
2172
2173	gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2174}
2175
2176static gchar *get_filename_from_uri(const gchar *uri)
2177{
2178	if (strncmp(uri, "file://", 7))
2179		return strdup(uri);
2180
2181	return strdup(uri + 7);
2182}
2183
2184static int do_file_open(struct gui_entry *ge, const gchar *uri)
2185{
2186	struct fio_client *client;
2187
2188	assert(!ge->job_file);
2189
2190	ge->job_file = get_filename_from_uri(uri);
2191
2192	client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
2193	if (client) {
2194		gfio_client_added(ge, client);
2195		file_add_recent(ge->ui, uri);
2196		return 0;
2197	}
2198
2199	gfio_report_error(ge, "Failed to add client %s\n", ge->host);
2200	free(ge->host);
2201	ge->host = NULL;
2202	return 1;
2203}
2204
2205static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2206{
2207	struct gui_entry *ge;
2208	gint cur_page;
2209	int ret, ge_is_new = 0;
2210
2211	/*
2212	 * Creates new tab if current tab is the main window, or the
2213	 * current tab already has a client.
2214	 */
2215	cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2216	ge = get_ge_from_page(ui, cur_page, &ge_is_new);
2217	if (ge->client) {
2218		ge = get_new_ge_with_tab(ui, "Untitled");
2219		ge_is_new = 1;
2220	}
2221
2222	gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2223
2224	if (get_connection_details(ge)) {
2225		if (ge_is_new)
2226			gtk_widget_destroy(ge->vbox);
2227
2228		return 1;
2229	}
2230
2231	ret = do_file_open(ge, uri);
2232
2233	if (!ret) {
2234		if (ge->server_start)
2235			gfio_start_server(ui);
2236	} else {
2237		if (ge_is_new)
2238			gtk_widget_destroy(ge->vbox);
2239	}
2240
2241	return ret;
2242}
2243
2244static void recent_open(GtkAction *action, gpointer data)
2245{
2246	struct gui *ui = (struct gui *) data;
2247	GtkRecentInfo *info;
2248	const gchar *uri;
2249
2250	info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2251	uri = gtk_recent_info_get_uri(info);
2252
2253	do_file_open_with_tab(ui, uri);
2254}
2255
2256static void file_open(GtkWidget *w, gpointer data)
2257{
2258	struct gui *ui = data;
2259	GtkWidget *dialog;
2260	GtkFileFilter *filter;
2261	gchar *filename;
2262
2263	dialog = gtk_file_chooser_dialog_new("Open File",
2264		GTK_WINDOW(ui->window),
2265		GTK_FILE_CHOOSER_ACTION_OPEN,
2266		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2267		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2268		NULL);
2269	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
2270
2271	filter = gtk_file_filter_new();
2272	gtk_file_filter_add_pattern(filter, "*.fio");
2273	gtk_file_filter_add_pattern(filter, "*.job");
2274	gtk_file_filter_add_pattern(filter, "*.ini");
2275	gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2276	gtk_file_filter_set_name(filter, "Fio job file");
2277	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2278
2279	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2280		gtk_widget_destroy(dialog);
2281		return;
2282	}
2283
2284	filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2285
2286	gtk_widget_destroy(dialog);
2287
2288	do_file_open_with_tab(ui, filename);
2289	g_free(filename);
2290}
2291
2292static void file_save(GtkWidget *w, gpointer data)
2293{
2294	struct gui *ui = data;
2295	GtkWidget *dialog;
2296
2297	dialog = gtk_file_chooser_dialog_new("Save File",
2298		GTK_WINDOW(ui->window),
2299		GTK_FILE_CHOOSER_ACTION_SAVE,
2300		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2301		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2302		NULL);
2303
2304	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2305	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2306
2307	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2308		char *filename;
2309
2310		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2311		// save_job_file(filename);
2312		g_free(filename);
2313	}
2314	gtk_widget_destroy(dialog);
2315}
2316
2317static void view_log_destroy(GtkWidget *w, gpointer data)
2318{
2319	struct gui *ui = (struct gui *) data;
2320
2321	gtk_widget_ref(ui->log_tree);
2322	gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2323	gtk_widget_destroy(w);
2324	ui->log_view = NULL;
2325}
2326
2327static void view_log(GtkWidget *w, gpointer data)
2328{
2329	GtkWidget *win, *scroll, *vbox, *box;
2330	struct gui *ui = (struct gui *) data;
2331
2332	if (ui->log_view)
2333		return;
2334
2335	ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2336	gtk_window_set_title(GTK_WINDOW(win), "Log");
2337	gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2338
2339	scroll = gtk_scrolled_window_new(NULL, NULL);
2340
2341	gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2342
2343	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2344
2345	box = gtk_hbox_new(TRUE, 0);
2346	gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2347	g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2348	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2349
2350	vbox = gtk_vbox_new(TRUE, 5);
2351	gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2352
2353	gtk_container_add(GTK_CONTAINER(win), vbox);
2354	gtk_widget_show_all(win);
2355}
2356
2357static void connect_job_entry(GtkWidget *w, gpointer data)
2358{
2359	struct gui *ui = (struct gui *) data;
2360	struct gui_entry *ge;
2361
2362	ge = get_ge_from_cur_tab(ui);
2363	if (ge)
2364		connect_clicked(w, ge);
2365}
2366
2367static void send_job_entry(GtkWidget *w, gpointer data)
2368{
2369	struct gui *ui = (struct gui *) data;
2370	struct gui_entry *ge;
2371
2372	ge = get_ge_from_cur_tab(ui);
2373	if (ge)
2374		send_clicked(w, ge);
2375
2376}
2377
2378static void edit_job_entry(GtkWidget *w, gpointer data)
2379{
2380	struct gui *ui = (struct gui *) data;
2381	struct gui_entry *ge;
2382
2383	ge = get_ge_from_cur_tab(ui);
2384	if (ge && ge->client)
2385		gopt_get_options_window(ui->window, &ge->client->o);
2386}
2387
2388static void start_job_entry(GtkWidget *w, gpointer data)
2389{
2390	struct gui *ui = (struct gui *) data;
2391	struct gui_entry *ge;
2392
2393	ge = get_ge_from_cur_tab(ui);
2394	if (ge)
2395		start_job_clicked(w, ge);
2396}
2397
2398static void view_results(GtkWidget *w, gpointer data)
2399{
2400	struct gui *ui = (struct gui *) data;
2401	struct gfio_client *gc;
2402	struct gui_entry *ge;
2403
2404	ge = get_ge_from_cur_tab(ui);
2405	if (!ge)
2406		return;
2407
2408	if (ge->results_window)
2409		return;
2410
2411	gc = ge->client;
2412	if (gc && gc->nr_results)
2413		gfio_display_end_results(gc);
2414}
2415
2416static void __update_graph_limits(struct gfio_graphs *g)
2417{
2418	line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2419	line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2420}
2421
2422static void update_graph_limits(void)
2423{
2424	struct flist_head *entry;
2425	struct gui_entry *ge;
2426
2427	__update_graph_limits(&main_ui.graphs);
2428
2429	flist_for_each(entry, &main_ui.list) {
2430		ge = flist_entry(entry, struct gui_entry, list);
2431		__update_graph_limits(&ge->graphs);
2432	}
2433}
2434
2435static void preferences(GtkWidget *w, gpointer data)
2436{
2437	GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2438	GtkWidget *hbox, *spin, *entry, *spin_int;
2439	struct gui *ui = (struct gui *) data;
2440	int i;
2441
2442	dialog = gtk_dialog_new_with_buttons("Preferences",
2443		GTK_WINDOW(ui->window),
2444		GTK_DIALOG_DESTROY_WITH_PARENT,
2445		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2446		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2447		NULL);
2448
2449	frame = gtk_frame_new("Graphing");
2450	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2451	vbox = gtk_vbox_new(FALSE, 6);
2452	gtk_container_add(GTK_CONTAINER(frame), vbox);
2453
2454	hbox = gtk_hbox_new(FALSE, 5);
2455	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2456	entry = gtk_label_new("Font face to use for graph labels");
2457	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2458
2459	font = gtk_font_button_new();
2460	gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2461
2462	box = gtk_vbox_new(FALSE, 6);
2463	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2464
2465	hbox = gtk_hbox_new(FALSE, 5);
2466	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2467	entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2468	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2469
2470	spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2471
2472	box = gtk_vbox_new(FALSE, 6);
2473	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2474
2475	hbox = gtk_hbox_new(FALSE, 5);
2476	gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2477	entry = gtk_label_new("Client ETA request interval (msec)");
2478	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2479
2480	spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2481	frame = gtk_frame_new("Debug logging");
2482	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2483	vbox = gtk_vbox_new(FALSE, 6);
2484	gtk_container_add(GTK_CONTAINER(frame), vbox);
2485
2486	box = gtk_hbox_new(FALSE, 6);
2487	gtk_container_add(GTK_CONTAINER(vbox), box);
2488
2489	buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2490
2491	for (i = 0; i < FD_DEBUG_MAX; i++) {
2492		if (i == 7) {
2493			box = gtk_hbox_new(FALSE, 6);
2494			gtk_container_add(GTK_CONTAINER(vbox), box);
2495		}
2496
2497
2498		buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2499		gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2500		gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2501	}
2502
2503	gtk_widget_show_all(dialog);
2504
2505	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2506		gtk_widget_destroy(dialog);
2507		return;
2508	}
2509
2510	for (i = 0; i < FD_DEBUG_MAX; i++) {
2511		int set;
2512
2513		set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2514		if (set)
2515			fio_debug |= (1UL << i);
2516	}
2517
2518	gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2519	gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2520	update_graph_limits();
2521	gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2522
2523	gtk_widget_destroy(dialog);
2524}
2525
2526static void about_dialog(GtkWidget *w, gpointer data)
2527{
2528	const char *authors[] = {
2529		"Jens Axboe <axboe@kernel.dk>",
2530		"Stephen Carmeron <stephenmcameron@gmail.com>",
2531		NULL
2532	};
2533	const char *license[] = {
2534		"Fio is free software; you can redistribute it and/or modify "
2535		"it under the terms of the GNU General Public License as published by "
2536		"the Free Software Foundation; either version 2 of the License, or "
2537		"(at your option) any later version.\n",
2538		"Fio is distributed in the hope that it will be useful, "
2539		"but WITHOUT ANY WARRANTY; without even the implied warranty of "
2540		"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2541		"GNU General Public License for more details.\n",
2542		"You should have received a copy of the GNU General Public License "
2543		"along with Fio; if not, write to the Free Software Foundation, Inc., "
2544		"51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2545	};
2546	char *license_trans;
2547
2548	license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2549				     license[2], "\n", NULL);
2550
2551	gtk_show_about_dialog(NULL,
2552		"program-name", "gfio",
2553		"comments", "Gtk2 UI for fio",
2554		"license", license_trans,
2555		"website", "http://git.kernel.dk/?p=fio.git;a=summary",
2556		"authors", authors,
2557		"version", fio_version_string,
2558		"copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2559		"logo-icon-name", "fio",
2560		/* Must be last: */
2561		"wrap-license", TRUE,
2562		NULL);
2563
2564	g_free(license_trans);
2565}
2566
2567static GtkActionEntry menu_items[] = {
2568	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2569	{ "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2570	{ "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2571	{ "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2572	{ "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2573	{ "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2574	{ "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2575	{ "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2576	{ "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2577	{ "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2578	{ "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
2579	{ "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
2580	{ "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2581	{ "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2582	{ "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2583	{ "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2584	{ "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2585};
2586static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2587
2588static const gchar *ui_string = " \
2589	<ui> \
2590		<menubar name=\"MainMenu\"> \
2591			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2592				<menuitem name=\"New\" action=\"NewFile\" /> \
2593				<menuitem name=\"Open\" action=\"OpenFile\" /> \
2594				<menuitem name=\"Close\" action=\"CloseFile\" /> \
2595				<separator name=\"Separator1\"/> \
2596				<menuitem name=\"Save\" action=\"SaveFile\" /> \
2597				<separator name=\"Separator2\"/> \
2598				<menuitem name=\"Preferences\" action=\"Preferences\" /> \
2599				<separator name=\"Separator3\"/> \
2600				<placeholder name=\"FileRecentFiles\"/> \
2601				<separator name=\"Separator4\"/> \
2602				<menuitem name=\"Quit\" action=\"Quit\" /> \
2603			</menu> \
2604			<menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2605				<menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2606				<separator name=\"Separator5\"/> \
2607				<menuitem name=\"Edit job\" action=\"EditJob\" /> \
2608				<menuitem name=\"Send job\" action=\"SendJob\" /> \
2609				<separator name=\"Separator6\"/> \
2610				<menuitem name=\"Start job\" action=\"StartJob\" /> \
2611			</menu>\
2612			<menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2613				<menuitem name=\"Results\" action=\"ViewResults\" /> \
2614				<separator name=\"Separator7\"/> \
2615				<menuitem name=\"Log\" action=\"ViewLog\" /> \
2616			</menu>\
2617			<menu name=\"Help\" action=\"HelpMenuAction\"> \
2618				<menuitem name=\"About\" action=\"About\" /> \
2619			</menu> \
2620		</menubar> \
2621	</ui> \
2622";
2623
2624static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2625				   struct gui *ui)
2626{
2627	GtkActionGroup *action_group;
2628	GError *error = 0;
2629
2630	action_group = gtk_action_group_new("Menu");
2631	gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2632
2633	gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2634	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2635
2636	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2637
2638	return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2639}
2640
2641void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2642                   GtkWidget *vbox, GtkUIManager *ui_manager)
2643{
2644        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2645}
2646
2647static void combo_entry_changed(GtkComboBox *box, gpointer data)
2648{
2649	struct gui_entry *ge = (struct gui_entry *) data;
2650	gint index;
2651
2652	index = gtk_combo_box_get_active(box);
2653
2654	multitext_set_entry(&ge->eta.iotype, index);
2655	multitext_set_entry(&ge->eta.bs, index);
2656	multitext_set_entry(&ge->eta.ioengine, index);
2657	multitext_set_entry(&ge->eta.iodepth, index);
2658}
2659
2660static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2661{
2662	struct gui_entry *ge = (struct gui_entry *) data;
2663
2664	multitext_free(&ge->eta.iotype);
2665	multitext_free(&ge->eta.bs);
2666	multitext_free(&ge->eta.ioengine);
2667	multitext_free(&ge->eta.iodepth);
2668}
2669
2670static GtkWidget *new_client_page(struct gui_entry *ge)
2671{
2672	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2673	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2674
2675	main_vbox = gtk_vbox_new(FALSE, 3);
2676
2677	top_align = gtk_alignment_new(0, 0, 1, 0);
2678	top_vbox = gtk_vbox_new(FALSE, 3);
2679	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2680	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2681
2682	probe = gtk_frame_new("Job");
2683	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2684	probe_frame = gtk_vbox_new(FALSE, 3);
2685	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2686
2687	probe_box = gtk_hbox_new(FALSE, 3);
2688	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2689	ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2690	ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2691	ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2692	ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2693
2694	probe_box = gtk_hbox_new(FALSE, 3);
2695	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2696
2697	ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2698	g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2699	g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2700	ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2701	ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
2702	ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2703	ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2704	ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2705	ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2706
2707	probe_box = gtk_hbox_new(FALSE, 3);
2708	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2709	ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2710	ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2711	ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2712	ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2713
2714	/*
2715	 * Only add this if we have a commit rate
2716	 */
2717#if 0
2718	probe_box = gtk_hbox_new(FALSE, 3);
2719	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2720
2721	ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2722	ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2723
2724	ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2725	ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2726#endif
2727
2728	/*
2729	 * Set up a drawing area and IOPS and bandwidth graphs
2730	 */
2731	ge->graphs.drawing_area = gtk_drawing_area_new();
2732	gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2733		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2734	gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2735	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2736				G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2737	g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2738				G_CALLBACK(on_config_drawing_area), &ge->graphs);
2739	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2740	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2741					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2742	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2743					ge->graphs.drawing_area);
2744	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2745
2746	setup_graphs(&ge->graphs);
2747
2748	/*
2749	 * Set up alignments for widgets at the bottom of ui,
2750	 * align bottom left, expand horizontally but not vertically
2751	 */
2752	bottom_align = gtk_alignment_new(0, 1, 1, 0);
2753	ge->buttonbox = gtk_hbox_new(FALSE, 0);
2754	gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2755	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2756
2757	add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2758
2759	/*
2760	 * Set up thread status progress bar
2761	 */
2762	ge->thread_status_pb = gtk_progress_bar_new();
2763	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2764	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2765	gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2766
2767
2768	return main_vbox;
2769}
2770
2771static GtkWidget *new_main_page(struct gui *ui)
2772{
2773	GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2774	GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2775
2776	main_vbox = gtk_vbox_new(FALSE, 3);
2777
2778	/*
2779	 * Set up alignments for widgets at the top of ui,
2780	 * align top left, expand horizontally but not vertically
2781	 */
2782	top_align = gtk_alignment_new(0, 0, 1, 0);
2783	top_vbox = gtk_vbox_new(FALSE, 0);
2784	gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2785	gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2786
2787	probe = gtk_frame_new("Run statistics");
2788	gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2789	probe_frame = gtk_vbox_new(FALSE, 3);
2790	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2791
2792	probe_box = gtk_hbox_new(FALSE, 3);
2793	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2794	ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2795	ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2796	ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2797	ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2798	ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2799
2800	/*
2801	 * Only add this if we have a commit rate
2802	 */
2803#if 0
2804	probe_box = gtk_hbox_new(FALSE, 3);
2805	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2806
2807	ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2808	ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2809
2810	ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2811	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2812#endif
2813
2814	/*
2815	 * Set up a drawing area and IOPS and bandwidth graphs
2816	 */
2817	ui->graphs.drawing_area = gtk_drawing_area_new();
2818	gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2819		DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2820	gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2821	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2822			G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2823	g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2824			G_CALLBACK(on_config_drawing_area), &ui->graphs);
2825	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2826	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2827					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2828	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2829					ui->graphs.drawing_area);
2830	gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2831			TRUE, TRUE, 0);
2832
2833	setup_graphs(&ui->graphs);
2834
2835	/*
2836	 * Set up alignments for widgets at the bottom of ui,
2837	 * align bottom left, expand horizontally but not vertically
2838	 */
2839	bottom_align = gtk_alignment_new(0, 1, 1, 0);
2840	ui->buttonbox = gtk_hbox_new(FALSE, 0);
2841	gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2842	gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2843
2844	/*
2845	 * Set up thread status progress bar
2846	 */
2847	ui->thread_status_pb = gtk_progress_bar_new();
2848	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2849	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2850	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2851
2852	return main_vbox;
2853}
2854
2855static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2856				     guint page, gpointer data)
2857
2858{
2859	struct gui *ui = (struct gui *) data;
2860	struct gui_entry *ge;
2861
2862	if (!page) {
2863		set_job_menu_visible(ui, 0);
2864		set_view_results_visible(ui, 0);
2865		return TRUE;
2866	}
2867
2868	set_job_menu_visible(ui, 1);
2869	ge = get_ge_from_page(ui, page, NULL);
2870	if (ge)
2871		update_button_states(ui, ge);
2872
2873	return TRUE;
2874}
2875
2876static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2877{
2878	time_t time_a = gtk_recent_info_get_visited(a);
2879	time_t time_b = gtk_recent_info_get_visited(b);
2880
2881	return time_b - time_a;
2882}
2883
2884static void add_recent_file_items(struct gui *ui)
2885{
2886	const gchar *gfio = g_get_application_name();
2887	GList *items, *item;
2888	int i = 0;
2889
2890	if (ui->recent_ui_id) {
2891		gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2892		gtk_ui_manager_ensure_update(ui->uimanager);
2893	}
2894	ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2895
2896	if (ui->actiongroup) {
2897		gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2898		g_object_unref(ui->actiongroup);
2899	}
2900	ui->actiongroup = gtk_action_group_new("RecentFileActions");
2901
2902	gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2903
2904	items = gtk_recent_manager_get_items(ui->recentmanager);
2905	items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2906
2907	for (item = items; item && item->data; item = g_list_next(item)) {
2908		GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2909		gchar *action_name;
2910		const gchar *label;
2911		GtkAction *action;
2912
2913		if (!gtk_recent_info_has_application(info, gfio))
2914			continue;
2915
2916		/*
2917		 * We only support local files for now
2918		 */
2919		if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2920			continue;
2921
2922		action_name = g_strdup_printf("RecentFile%u", i++);
2923		label = gtk_recent_info_get_display_name(info);
2924
2925		action = g_object_new(GTK_TYPE_ACTION,
2926					"name", action_name,
2927					"label", label, NULL);
2928
2929		g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2930					gtk_recent_info_ref(info),
2931					(GDestroyNotify) gtk_recent_info_unref);
2932
2933
2934		g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2935
2936		gtk_action_group_add_action(ui->actiongroup, action);
2937		g_object_unref(action);
2938
2939		gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2940					"/MainMenu/FileMenu/FileRecentFiles",
2941					label, action_name,
2942					GTK_UI_MANAGER_MENUITEM, FALSE);
2943
2944		g_free(action_name);
2945
2946		if (i == 8)
2947			break;
2948	}
2949
2950	g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2951	g_list_free(items);
2952}
2953
2954static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2955				   gint x, gint y, GtkSelectionData *seldata,
2956				   guint info, guint time, gpointer *data)
2957{
2958	struct gui *ui = (struct gui *) data;
2959	gchar **uris;
2960	GtkWidget *source;
2961
2962	source = gtk_drag_get_source_widget(ctx);
2963	if (source && widget == gtk_widget_get_toplevel(source)) {
2964		gtk_drag_finish(ctx, FALSE, FALSE, time);
2965		return;
2966	}
2967
2968	uris = gtk_selection_data_get_uris(seldata);
2969	if (!uris) {
2970		gtk_drag_finish(ctx, FALSE, FALSE, time);
2971		return;
2972	}
2973
2974	if (uris[0])
2975		do_file_open_with_tab(ui, uris[0]);
2976
2977	gtk_drag_finish(ctx, TRUE, FALSE, time);
2978	g_strfreev(uris);
2979}
2980
2981static void init_ui(int *argc, char **argv[], struct gui *ui)
2982{
2983	GtkSettings *settings;
2984	GtkWidget *vbox;
2985
2986	/* Magical g*thread incantation, you just need this thread stuff.
2987	 * Without it, the update that happens in gfio_update_thread_status
2988	 * doesn't really happen in a timely fashion, you need expose events
2989	 */
2990	if (!g_thread_supported())
2991		g_thread_init(NULL);
2992	gdk_threads_init();
2993
2994	gtk_init(argc, argv);
2995	settings = gtk_settings_get_default();
2996	gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
2997	g_type_init();
2998	gdk_color_parse("white", &white);
2999
3000	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3001	gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
3002	gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
3003
3004	g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
3005	g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
3006
3007	ui->vbox = gtk_vbox_new(FALSE, 0);
3008	gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3009
3010	ui->uimanager = gtk_ui_manager_new();
3011	ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3012	gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3013
3014	ui->recentmanager = gtk_recent_manager_get_default();
3015	add_recent_file_items(ui);
3016
3017	ui->notebook = gtk_notebook_new();
3018	g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3019	gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3020	gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3021	gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3022
3023	vbox = new_main_page(ui);
3024	gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
3025	gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3026	g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3027
3028	gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3029
3030	gfio_ui_setup_log(ui);
3031
3032	gtk_widget_show_all(ui->window);
3033}
3034
3035int main(int argc, char *argv[], char *envp[])
3036{
3037	if (initialize_fio(envp))
3038		return 1;
3039	if (fio_init_options())
3040		return 1;
3041
3042	memset(&main_ui, 0, sizeof(main_ui));
3043	INIT_FLIST_HEAD(&main_ui.list);
3044
3045	init_ui(&argc, &argv, &main_ui);
3046
3047	gdk_threads_enter();
3048	gtk_main();
3049	gdk_threads_leave();
3050	return 0;
3051}
3052