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