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