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