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