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