gfio.c revision 1c1e4a5bbe3ebb614eacfa902c06564e6739a750
1/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
5 *
6 * The license below covers all files distributed with fio unless otherwise
7 * noted in the file itself.
8 *
9 *  This program is free software; you can redistribute it and/or modify
10 *  it under the terms of the GNU General Public License version 2 as
11 *  published by the Free Software Foundation.
12 *
13 *  This program is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *  GNU General Public License for more details.
17 *
18 *  You should have received a copy of the GNU General Public License
19 *  along with this program; if not, write to the Free Software
20 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 *
22 */
23#include <locale.h>
24#include <malloc.h>
25
26#include <glib.h>
27#include <gtk/gtk.h>
28
29#include "fio.h"
30
31static void gfio_update_thread_status(char *status_message, double perc);
32
33#define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
34
35typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
36
37static void connect_clicked(GtkWidget *widget, gpointer data);
38static void start_job_clicked(GtkWidget *widget, gpointer data);
39
40static struct button_spec {
41	const char *buttontext;
42	clickfunction f;
43	const char *tooltiptext;
44	const int start_insensitive;
45} buttonspeclist[] = {
46#define CONNECT_BUTTON 0
47#define START_JOB_BUTTON 1
48	{ "Connect", connect_clicked, "Connect to host", 0 },
49	{ "Start Job",
50		start_job_clicked,
51		"Send current fio job to fio server to be executed", 1 },
52};
53
54struct probe_widget {
55	GtkWidget *hostname;
56	GtkWidget *os;
57	GtkWidget *arch;
58	GtkWidget *fio_ver;
59};
60
61struct eta_widget {
62	GtkWidget *name;
63	GtkWidget *iotype;
64	GtkWidget *ioengine;
65	GtkWidget *iodepth;
66	GtkWidget *jobs;
67	GtkWidget *files;
68	GtkWidget *read_bw;
69	GtkWidget *read_iops;
70	GtkWidget *cr_bw;
71	GtkWidget *cr_iops;
72	GtkWidget *write_bw;
73	GtkWidget *write_iops;
74	GtkWidget *cw_bw;
75	GtkWidget *cw_iops;
76};
77
78struct gui {
79	GtkWidget *window;
80	GtkWidget *vbox;
81	GtkWidget *topvbox;
82	GtkWidget *topalign;
83	GtkWidget *bottomalign;
84	GtkWidget *thread_status_pb;
85	GtkWidget *buttonbox;
86	GtkWidget *button[ARRAYSIZE(buttonspeclist)];
87	GtkWidget *scrolled_window;
88	GtkWidget *textview;
89	GtkWidget *error_info_bar;
90	GtkWidget *error_label;
91	GtkTextBuffer *text;
92	struct probe_widget probe;
93	struct eta_widget eta;
94	int connected;
95	pthread_t t;
96
97	struct fio_client *client;
98	int nr_job_files;
99	char **job_files;
100} ui;
101
102static void clear_ui_info(struct gui *ui)
103{
104	gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
105	gtk_label_set_text(GTK_LABEL(ui->probe.os), "");
106	gtk_label_set_text(GTK_LABEL(ui->probe.arch), "");
107	gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), "");
108	gtk_label_set_text(GTK_LABEL(ui->eta.name), "");
109	gtk_label_set_text(GTK_LABEL(ui->eta.iotype), "");
110	gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), "");
111	gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), "");
112	gtk_label_set_text(GTK_LABEL(ui->eta.jobs), "");
113	gtk_label_set_text(GTK_LABEL(ui->eta.files), "");
114	gtk_label_set_text(GTK_LABEL(ui->eta.read_bw), "");
115	gtk_label_set_text(GTK_LABEL(ui->eta.read_iops), "");
116	gtk_label_set_text(GTK_LABEL(ui->eta.write_bw), "");
117	gtk_label_set_text(GTK_LABEL(ui->eta.write_iops), "");
118}
119
120static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
121{
122	GtkWidget *entry, *frame;
123
124	frame = gtk_frame_new(label);
125	entry = gtk_entry_new();
126	gtk_entry_set_editable(GTK_ENTRY(entry), 0);
127	gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
128	gtk_container_add(GTK_CONTAINER(frame), entry);
129
130	return entry;
131}
132
133static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
134{
135	GtkWidget *label_widget;
136	GtkWidget *frame;
137
138	frame = gtk_frame_new(label);
139	label_widget = gtk_label_new(NULL);
140	gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
141	gtk_container_add(GTK_CONTAINER(frame), label_widget);
142
143	return label_widget;
144}
145
146static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
147{
148	GtkWidget *button, *box;
149
150	box = gtk_hbox_new(FALSE, 3);
151	gtk_container_add(GTK_CONTAINER(hbox), box);
152
153	button = gtk_spin_button_new_with_range(min, max, 1.0);
154	gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
155
156	gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
157	gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
158
159	return button;
160}
161
162static void gfio_set_connected(struct gui *ui, int connected)
163{
164	if (connected) {
165		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
166		ui->connected = 1;
167		gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
168	} else {
169		ui->connected = 0;
170		gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
171		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
172	}
173}
174
175static void label_set_int_value(GtkWidget *entry, unsigned int val)
176{
177	char tmp[80];
178
179	sprintf(tmp, "%u", val);
180	gtk_label_set_text(GTK_LABEL(entry), tmp);
181}
182
183static void entry_set_int_value(GtkWidget *entry, unsigned int val)
184{
185	char tmp[80];
186
187	sprintf(tmp, "%u", val);
188	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
189}
190
191static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
192			  unsigned long max, double mean, double dev)
193{
194	const char *base = "(usec)";
195	GtkWidget *hbox, *label, *frame;
196	char *minp, *maxp;
197	char tmp[64];
198
199	if (!usec_to_msec(&min, &max, &mean, &dev))
200		base = "(msec)";
201
202	minp = num2str(min, 6, 1, 0);
203	maxp = num2str(max, 6, 1, 0);
204
205	sprintf(tmp, "%s %s", name, base);
206	frame = gtk_frame_new(tmp);
207	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
208
209	hbox = gtk_hbox_new(FALSE, 3);
210	gtk_container_add(GTK_CONTAINER(frame), hbox);
211
212	label = new_info_label_in_frame(hbox, "Minimum");
213	gtk_label_set_text(GTK_LABEL(label), minp);
214	label = new_info_label_in_frame(hbox, "Maximum");
215	gtk_label_set_text(GTK_LABEL(label), maxp);
216	label = new_info_label_in_frame(hbox, "Average");
217	sprintf(tmp, "%5.02f", mean);
218	gtk_label_set_text(GTK_LABEL(label), tmp);
219	label = new_info_label_in_frame(hbox, "Standard deviation");
220	sprintf(tmp, "%5.02f", dev);
221	gtk_label_set_text(GTK_LABEL(label), tmp);
222
223	free(minp);
224	free(maxp);
225
226}
227
228static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
229				  struct thread_stat *ts, int ddir)
230{
231	const char *ddir_label[2] = { "Read", "Write" };
232	GtkWidget *frame, *label, *box, *vbox;
233	unsigned long min, max, runt;
234	unsigned long long bw, iops;
235	double mean, dev;
236	char *io_p, *bw_p, *iops_p;
237	int i2p;
238
239	if (!ts->runtime[ddir])
240		return;
241
242	i2p = is_power_of_2(rs->kb_base);
243	runt = ts->runtime[ddir];
244
245	bw = (1000 * ts->io_bytes[ddir]) / runt;
246	io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
247	bw_p = num2str(bw, 6, 1, i2p);
248
249	iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
250	iops_p = num2str(iops, 6, 1, 0);
251
252	box = gtk_hbox_new(FALSE, 3);
253	gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
254
255	frame = gtk_frame_new(ddir_label[ddir]);
256	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
257
258	vbox = gtk_vbox_new(FALSE, 3);
259	gtk_container_add(GTK_CONTAINER(frame), vbox);
260
261	box = gtk_hbox_new(FALSE, 3);
262	gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 3);
263
264	label = new_info_label_in_frame(box, "IO");
265	gtk_label_set_text(GTK_LABEL(label), io_p);
266	label = new_info_label_in_frame(box, "Bandwidth");
267	gtk_label_set_text(GTK_LABEL(label), bw_p);
268	label = new_info_label_in_frame(box, "IOPS");
269	gtk_label_set_text(GTK_LABEL(label), iops_p);
270	label = new_info_label_in_frame(box, "Runtime (msec)");
271	label_set_int_value(label, ts->runtime[ddir]);
272
273	frame = gtk_frame_new("Latency");
274	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
275
276	vbox = gtk_vbox_new(FALSE, 3);
277	gtk_container_add(GTK_CONTAINER(frame), vbox);
278
279	if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
280		gfio_show_lat(vbox, "Submission latency", min, max, mean, dev);
281	if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
282		gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
283	if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
284		gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
285
286	free(io_p);
287	free(bw_p);
288	free(iops_p);
289}
290
291static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
292			    struct group_run_stats *rs)
293{
294	GtkWidget *dialog, *box, *vbox, *entry, *content;
295	struct gui *ui = client->client_data;
296
297	gdk_threads_enter();
298
299	dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
300			GTK_DIALOG_DESTROY_WITH_PARENT,
301			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
302
303	g_signal_connect_swapped(dialog, "response",
304                             G_CALLBACK(gtk_widget_destroy),
305                             dialog);
306
307	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
308
309	vbox = gtk_vbox_new(FALSE, 3);
310	gtk_container_add(GTK_CONTAINER(content), vbox);
311
312	box = gtk_hbox_new(TRUE, 3);
313	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
314
315	entry = new_info_entry_in_frame(box, "Name");
316	gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
317	if (strlen(ts->description)) {
318		entry = new_info_entry_in_frame(box, "Description");
319		gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
320	}
321	entry = new_info_entry_in_frame(box, "Group ID");
322	entry_set_int_value(entry, ts->groupid);
323	entry = new_info_entry_in_frame(box, "Jobs");
324	entry_set_int_value(entry, ts->members);
325	entry = new_info_entry_in_frame(box, "Error");
326	entry_set_int_value(entry, ts->error);
327	entry = new_info_entry_in_frame(box, "PID");
328	entry_set_int_value(entry, ts->pid);
329
330	if (ts->io_bytes[DDIR_READ])
331		gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
332	if (ts->io_bytes[DDIR_WRITE])
333		gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
334
335	gtk_widget_show_all(dialog);
336
337	gdk_threads_leave();
338}
339
340static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
341{
342#if 0
343	GtkTextBuffer *buffer;
344	GtkTextIter end;
345
346	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
347	gdk_threads_enter();
348	gtk_text_buffer_get_end_iter(buffer, &end);
349	gtk_text_buffer_insert(buffer, &end, buf, -1);
350	gdk_threads_leave();
351	gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
352					&end, 0.0, FALSE, 0.0,0.0);
353#else
354	fio_client_ops.text_op(client, cmd);
355#endif
356}
357
358static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
359{
360	printf("gfio_disk_util_op called\n");
361	fio_client_ops.disk_util(client, cmd);
362}
363
364extern int sum_stat_clients;
365extern struct thread_stat client_ts;
366extern struct group_run_stats client_gs;
367
368static int sum_stat_nr;
369
370static void gfio_thread_status_op(struct fio_client *client,
371				  struct fio_net_cmd *cmd)
372{
373	struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
374
375	gfio_display_ts(client, &p->ts, &p->rs);
376
377	if (sum_stat_clients == 1)
378		return;
379
380	sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
381	sum_group_stats(&client_gs, &p->rs);
382
383	client_ts.members++;
384	client_ts.groupid = p->ts.groupid;
385
386	if (++sum_stat_nr == sum_stat_clients) {
387		strcpy(client_ts.name, "All clients");
388		gfio_display_ts(client, &client_ts, &client_gs);
389	}
390}
391
392static void gfio_group_stats_op(struct fio_client *client,
393				struct fio_net_cmd *cmd)
394{
395	printf("gfio_group_stats_op called\n");
396	fio_client_ops.group_stats(client, cmd);
397}
398
399static void gfio_update_eta(struct jobs_eta *je)
400{
401	static int eta_good;
402	char eta_str[128];
403	char output[256];
404	char tmp[32];
405	double perc = 0.0;
406	int i2p = 0;
407
408	eta_str[0] = '\0';
409	output[0] = '\0';
410
411	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
412		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
413		eta_to_str(eta_str, je->eta_sec);
414	}
415
416	sprintf(tmp, "%u", je->nr_running);
417	gtk_label_set_text(GTK_LABEL(ui.eta.jobs), tmp);
418	sprintf(tmp, "%u", je->files_open);
419	gtk_label_set_text(GTK_LABEL(ui.eta.files), tmp);
420
421#if 0
422	if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
423	if (je->m_rate || je->t_rate) {
424		char *tr, *mr;
425
426		mr = num2str(je->m_rate, 4, 0, i2p);
427		tr = num2str(je->t_rate, 4, 0, i2p);
428		gtk_label_set_text(GTK_LABEL(ui.eta.
429		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
430		free(tr);
431		free(mr);
432	} else if (je->m_iops || je->t_iops)
433		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
434
435	gtk_label_set_text(GTK_LABEL(ui.eta.cr_bw), "---");
436	gtk_label_set_text(GTK_LABEL(ui.eta.cr_iops), "---");
437	gtk_label_set_text(GTK_LABEL(ui.eta.cw_bw), "---");
438	gtk_label_set_text(GTK_LABEL(ui.eta.cw_iops), "---");
439#endif
440
441	if (je->eta_sec != INT_MAX && je->nr_running) {
442		char *iops_str[2];
443		char *rate_str[2];
444
445		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
446			strcpy(output, "-.-% done");
447		else {
448			eta_good = 1;
449			perc *= 100.0;
450			sprintf(output, "%3.1f%% done", perc);
451		}
452
453		rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
454		rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
455
456		iops_str[0] = num2str(je->iops[0], 4, 1, 0);
457		iops_str[1] = num2str(je->iops[1], 4, 1, 0);
458
459		gtk_label_set_text(GTK_LABEL(ui.eta.read_bw), rate_str[0]);
460		gtk_label_set_text(GTK_LABEL(ui.eta.read_iops), iops_str[0]);
461		gtk_label_set_text(GTK_LABEL(ui.eta.write_bw), rate_str[1]);
462		gtk_label_set_text(GTK_LABEL(ui.eta.write_iops), iops_str[1]);
463
464		free(rate_str[0]);
465		free(rate_str[1]);
466		free(iops_str[0]);
467		free(iops_str[1]);
468	}
469
470	if (eta_str[0]) {
471		char *dst = output + strlen(output);
472
473		sprintf(dst, " - %s", eta_str);
474	}
475
476	gfio_update_thread_status(output, perc);
477}
478
479static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
480{
481	struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
482	const char *os, *arch;
483	char buf[64];
484
485	os = fio_get_os_string(probe->os);
486	if (!os)
487		os = "unknown";
488
489	arch = fio_get_arch_string(probe->arch);
490	if (!arch)
491		os = "unknown";
492
493	if (!client->name)
494		client->name = strdup((char *) probe->hostname);
495
496	gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
497	gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
498	gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
499	sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
500	gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
501}
502
503static void gfio_update_thread_status(char *status_message, double perc)
504{
505	static char message[100];
506	const char *m = message;
507
508	strncpy(message, status_message, sizeof(message) - 1);
509	gtk_progress_bar_set_text(
510		GTK_PROGRESS_BAR(ui.thread_status_pb), m);
511	gtk_progress_bar_set_fraction(
512		GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
513	gdk_threads_enter();
514	gtk_widget_queue_draw(ui.window);
515	gdk_threads_leave();
516}
517
518static void gfio_quit_op(struct fio_client *client)
519{
520	struct gui *ui = client->client_data;
521
522	gfio_set_connected(ui, 0);
523}
524
525static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
526{
527	struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
528	struct gui *ui = client->client_data;
529	char tmp[8];
530	int i;
531
532	p->iodepth		= le32_to_cpu(p->iodepth);
533	p->rw			= le32_to_cpu(p->rw);
534
535	for (i = 0; i < 2; i++) {
536		p->min_bs[i] 	= le32_to_cpu(p->min_bs[i]);
537		p->max_bs[i]	= le32_to_cpu(p->max_bs[i]);
538	}
539
540	p->numjobs		= le32_to_cpu(p->numjobs);
541	p->group_reporting	= le32_to_cpu(p->group_reporting);
542
543	gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
544	gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
545	gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
546
547	sprintf(tmp, "%u", p->iodepth);
548	gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
549}
550
551static void gfio_client_timed_out(struct fio_client *client)
552{
553	struct gui *ui = client->client_data;
554	GtkWidget *dialog, *label, *content;
555	char buf[256];
556
557	gdk_threads_enter();
558
559	gfio_set_connected(ui, 0);
560	clear_ui_info(ui);
561
562	sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
563
564	dialog = gtk_dialog_new_with_buttons("Timed out!",
565			GTK_WINDOW(ui->window),
566			GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
567			GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
568
569	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
570	label = gtk_label_new((const gchar *) buf);
571	gtk_container_add(GTK_CONTAINER(content), label);
572	gtk_widget_show_all(dialog);
573	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
574
575	gtk_dialog_run(GTK_DIALOG(dialog));
576	gtk_widget_destroy(dialog);
577
578	gdk_threads_leave();
579}
580
581struct client_ops gfio_client_ops = {
582	.text_op		= gfio_text_op,
583	.disk_util		= gfio_disk_util_op,
584	.thread_status		= gfio_thread_status_op,
585	.group_stats		= gfio_group_stats_op,
586	.eta			= gfio_update_eta,
587	.probe			= gfio_probe_op,
588	.quit			= gfio_quit_op,
589	.add_job		= gfio_add_job_op,
590	.timed_out		= gfio_client_timed_out,
591	.stay_connected		= 1,
592};
593
594static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
595                __attribute__((unused)) gpointer data)
596{
597        gtk_main_quit();
598}
599
600static void *job_thread(void *arg)
601{
602	fio_handle_clients(&gfio_client_ops);
603	return NULL;
604}
605
606static int send_job_files(struct gui *ui)
607{
608	int i, ret = 0;
609
610	for (i = 0; i < ui->nr_job_files; i++) {
611		ret = fio_clients_send_ini(ui->job_files[i]);
612		if (ret)
613			break;
614
615		free(ui->job_files[i]);
616		ui->job_files[i] = NULL;
617	}
618	while (i < ui->nr_job_files) {
619		free(ui->job_files[i]);
620		ui->job_files[i] = NULL;
621		i++;
622	}
623
624	return ret;
625}
626
627static void start_job_thread(struct gui *ui)
628{
629	if (send_job_files(ui)) {
630		printf("Yeah, I didn't really like those options too much.\n");
631		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
632		return;
633	}
634}
635
636static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
637                gpointer data)
638{
639	struct gui *ui = data;
640
641	gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
642	start_job_thread(ui);
643}
644
645static void file_open(GtkWidget *w, gpointer data);
646
647static void connect_clicked(GtkWidget *widget, gpointer data)
648{
649	struct gui *ui = data;
650
651	if (!ui->connected) {
652		if (!ui->nr_job_files)
653			file_open(widget, data);
654		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
655		fio_clients_connect();
656		pthread_create(&ui->t, NULL, job_thread, NULL);
657		gfio_set_connected(ui, 1);
658	} else {
659		fio_clients_terminate();
660		gfio_set_connected(ui, 0);
661		clear_ui_info(ui);
662	}
663}
664
665static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
666			struct button_spec *buttonspec)
667{
668	ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
669	g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
670	gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
671	gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
672	gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
673}
674
675static void add_buttons(struct gui *ui,
676				struct button_spec *buttonlist,
677				int nbuttons)
678{
679	int i;
680
681	for (i = 0; i < nbuttons; i++)
682		add_button(ui, i, ui->buttonbox, &buttonlist[i]);
683}
684
685static void on_info_bar_response(GtkWidget *widget, gint response,
686                                 gpointer data)
687{
688	if (response == GTK_RESPONSE_OK) {
689		gtk_widget_destroy(widget);
690		ui.error_info_bar = NULL;
691	}
692}
693
694void report_error(GError *error)
695{
696	if (ui.error_info_bar == NULL) {
697		ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
698		                                               GTK_RESPONSE_OK,
699		                                               NULL);
700		g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
701		gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
702		                              GTK_MESSAGE_ERROR);
703
704		ui.error_label = gtk_label_new(error->message);
705		GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
706		gtk_container_add(GTK_CONTAINER(container), ui.error_label);
707
708		gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
709		gtk_widget_show_all(ui.vbox);
710	} else {
711		char buffer[256];
712		snprintf(buffer, sizeof(buffer), "Failed to open file.");
713		gtk_label_set(GTK_LABEL(ui.error_label), buffer);
714	}
715}
716
717static int get_connection_details(char **host, int *port, int *type,
718				  int *server_start)
719{
720	GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
721	GtkWidget *button;
722	char *typeentry;
723
724	dialog = gtk_dialog_new_with_buttons("Connection details",
725			GTK_WINDOW(ui.window),
726			GTK_DIALOG_DESTROY_WITH_PARENT,
727			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
728			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
729
730	frame = gtk_frame_new("Hostname / socket name");
731	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
732	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
733
734	box = gtk_vbox_new(FALSE, 6);
735	gtk_container_add(GTK_CONTAINER(frame), box);
736
737	hbox = gtk_hbox_new(TRUE, 10);
738	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
739	hentry = gtk_entry_new();
740	gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
741	gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
742
743	frame = gtk_frame_new("Port");
744	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
745	box = gtk_vbox_new(FALSE, 10);
746	gtk_container_add(GTK_CONTAINER(frame), box);
747
748	hbox = gtk_hbox_new(TRUE, 4);
749	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
750	pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
751
752	frame = gtk_frame_new("Type");
753	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
754	box = gtk_vbox_new(FALSE, 10);
755	gtk_container_add(GTK_CONTAINER(frame), box);
756
757	hbox = gtk_hbox_new(TRUE, 4);
758	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
759
760	combo = gtk_combo_box_text_new();
761	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
762	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
763	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
764	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
765
766	gtk_container_add(GTK_CONTAINER(hbox), combo);
767
768	frame = gtk_frame_new("Options");
769	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
770	box = gtk_vbox_new(FALSE, 10);
771	gtk_container_add(GTK_CONTAINER(frame), box);
772
773	hbox = gtk_hbox_new(TRUE, 4);
774	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
775
776	button = gtk_check_button_new_with_label("Auto-spawn fio backend");
777	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
778	gtk_widget_set_tooltip_text(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.");
779	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
780
781	gtk_widget_show_all(dialog);
782
783	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
784		gtk_widget_destroy(dialog);
785		return 1;
786	}
787
788	*host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
789	*port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
790
791	typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
792	if (!typeentry || !strncmp(typeentry, "IPv4", 4))
793		*type = Fio_client_ipv4;
794	else if (!strncmp(typeentry, "IPv6", 4))
795		*type = Fio_client_ipv6;
796	else
797		*type = Fio_client_socket;
798	g_free(typeentry);
799
800	*server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
801
802	gtk_widget_destroy(dialog);
803	return 0;
804}
805
806static void file_open(GtkWidget *w, gpointer data)
807{
808	GtkWidget *dialog;
809	GSList *filenames, *fn_glist;
810	GtkFileFilter *filter;
811	char *host;
812	int port, type, server_start;
813
814	dialog = gtk_file_chooser_dialog_new("Open File",
815		GTK_WINDOW(ui.window),
816		GTK_FILE_CHOOSER_ACTION_OPEN,
817		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
818		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
819		NULL);
820	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
821
822	filter = gtk_file_filter_new();
823	gtk_file_filter_add_pattern(filter, "*.fio");
824	gtk_file_filter_add_pattern(filter, "*.job");
825	gtk_file_filter_add_mime_type(filter, "text/fio");
826	gtk_file_filter_set_name(filter, "Fio job file");
827	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
828
829	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
830		gtk_widget_destroy(dialog);
831		return;
832	}
833
834	fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
835
836	gtk_widget_destroy(dialog);
837
838	if (get_connection_details(&host, &port, &type, &server_start))
839		goto err;
840
841	filenames = fn_glist;
842	while (filenames != NULL) {
843		ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
844		ui.job_files[ui.nr_job_files] = strdup(filenames->data);
845		ui.nr_job_files++;
846
847		ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
848		if (!ui.client) {
849			GError *error;
850
851			error = g_error_new(g_quark_from_string("fio"), 1,
852					"Failed to add client %s", host);
853			report_error(error);
854			g_error_free(error);
855		}
856		ui.client->client_data = &ui;
857
858		g_free(filenames->data);
859		filenames = g_slist_next(filenames);
860	}
861	free(host);
862err:
863	g_slist_free(fn_glist);
864}
865
866static void file_save(GtkWidget *w, gpointer data)
867{
868	GtkWidget *dialog;
869
870	dialog = gtk_file_chooser_dialog_new("Save File",
871		GTK_WINDOW(ui.window),
872		GTK_FILE_CHOOSER_ACTION_SAVE,
873		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
874		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
875		NULL);
876
877	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
878	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
879
880	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
881		char *filename;
882
883		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
884		// save_job_file(filename);
885		g_free(filename);
886	}
887	gtk_widget_destroy(dialog);
888}
889
890static void preferences(GtkWidget *w, gpointer data)
891{
892	GtkWidget *dialog, *frame, *box, **buttons;
893	int i;
894
895	dialog = gtk_dialog_new_with_buttons("Preferences",
896		GTK_WINDOW(ui.window),
897		GTK_DIALOG_DESTROY_WITH_PARENT,
898		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
899		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
900		NULL);
901
902	frame = gtk_frame_new("Debug logging");
903	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
904	box = gtk_hbox_new(FALSE, 6);
905	gtk_container_add(GTK_CONTAINER(frame), box);
906
907	buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
908
909	for (i = 0; i < FD_DEBUG_MAX; i++) {
910		buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
911		gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
912		gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
913	}
914
915	gtk_widget_show_all(dialog);
916
917	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
918		gtk_widget_destroy(dialog);
919		return;
920	}
921
922	for (i = 0; i < FD_DEBUG_MAX; i++) {
923		int set;
924
925		set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
926		if (set)
927			fio_debug |= (1UL << i);
928	}
929
930	gtk_widget_destroy(dialog);
931}
932
933static void about_dialog(GtkWidget *w, gpointer data)
934{
935	gtk_show_about_dialog(NULL,
936		"program-name", "gfio",
937		"comments", "Gtk2 UI for fio",
938		"license", "GPLv2",
939		"version", fio_version_string,
940		"copyright", "Jens Axboe <axboe@kernel.dk> 2012",
941		"logo-icon-name", "fio",
942		/* Must be last: */
943		NULL, NULL,
944		NULL);
945}
946
947static GtkActionEntry menu_items[] = {
948	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
949	{ "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
950	{ "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
951	{ "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
952	{ "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
953	{ "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
954	{ "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
955};
956static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
957
958static const gchar *ui_string = " \
959	<ui> \
960		<menubar name=\"MainMenu\"> \
961			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
962				<menuitem name=\"Open\" action=\"OpenFile\" /> \
963				<menuitem name=\"Save\" action=\"SaveFile\" /> \
964				<separator name=\"Separator\"/> \
965				<menuitem name=\"Preferences\" action=\"Preferences\" /> \
966				<separator name=\"Separator2\"/> \
967				<menuitem name=\"Quit\" action=\"Quit\" /> \
968			</menu> \
969			<menu name=\"Help\" action=\"HelpMenuAction\"> \
970				<menuitem name=\"About\" action=\"About\" /> \
971			</menu> \
972		</menubar> \
973	</ui> \
974";
975
976static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
977{
978	GtkActionGroup *action_group = gtk_action_group_new("Menu");
979	GError *error = 0;
980
981	action_group = gtk_action_group_new("Menu");
982	gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
983
984	gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
985	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
986
987	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
988	return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
989}
990
991void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
992                   GtkWidget *vbox, GtkUIManager *ui_manager)
993{
994        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
995}
996
997static void init_ui(int *argc, char **argv[], struct gui *ui)
998{
999	GtkSettings *settings;
1000	GtkUIManager *uimanager;
1001	GtkWidget *menu, *probe, *probe_frame, *probe_box;
1002
1003	memset(ui, 0, sizeof(*ui));
1004
1005	/* Magical g*thread incantation, you just need this thread stuff.
1006	 * Without it, the update that happens in gfio_update_thread_status
1007	 * doesn't really happen in a timely fashion, you need expose events
1008	 */
1009	if (!g_thread_supported())
1010		g_thread_init(NULL);
1011	gdk_threads_init();
1012
1013	gtk_init(argc, argv);
1014	settings = gtk_settings_get_default();
1015	gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1016	g_type_init();
1017
1018	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1019        gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1020	gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
1021
1022	g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1023	g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1024
1025	ui->vbox = gtk_vbox_new(FALSE, 0);
1026	gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1027
1028	uimanager = gtk_ui_manager_new();
1029	menu = get_menubar_menu(ui->window, uimanager);
1030	gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1031
1032	/*
1033	 * Set up alignments for widgets at the top of ui,
1034	 * align top left, expand horizontally but not vertically
1035	 */
1036	ui->topalign = gtk_alignment_new(0, 0, 1, 0);
1037	ui->topvbox = gtk_vbox_new(FALSE, 3);
1038	gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1039	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1040
1041	probe = gtk_frame_new("Job");
1042	gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1043	probe_frame = gtk_vbox_new(FALSE, 3);
1044	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1045
1046	probe_box = gtk_hbox_new(FALSE, 3);
1047	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1048	ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1049	ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1050	ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1051	ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1052
1053	probe_box = gtk_hbox_new(FALSE, 3);
1054	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1055
1056	ui->eta.name = new_info_label_in_frame(probe_box, "Name");
1057	ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
1058	ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
1059	ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
1060	ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
1061	ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
1062
1063	probe_box = gtk_hbox_new(FALSE, 3);
1064	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1065	ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
1066	ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
1067	ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
1068	ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
1069
1070	/*
1071	 * Only add this if we have a commit rate
1072	 */
1073#if 0
1074	probe_box = gtk_hbox_new(FALSE, 3);
1075	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1076
1077	ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1078	ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1079
1080	ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1081	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1082#endif
1083
1084	/*
1085	 * Add a text box for text op messages
1086	 */
1087	ui->textview = gtk_text_view_new();
1088	ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
1089	gtk_text_buffer_set_text(ui->text, "", -1);
1090	gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
1091	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
1092	ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1093	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1094					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1095	gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
1096	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1097			TRUE, TRUE, 0);
1098
1099	/*
1100	 * Set up alignments for widgets at the bottom of ui,
1101	 * align bottom left, expand horizontally but not vertically
1102	 */
1103	ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
1104	ui->buttonbox = gtk_hbox_new(FALSE, 0);
1105	gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
1106	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1107					FALSE, FALSE, 0);
1108
1109	add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
1110
1111	/*
1112	 * Set up thread status progress bar
1113	 */
1114	ui->thread_status_pb = gtk_progress_bar_new();
1115	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1116	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1117	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1118
1119
1120	gtk_widget_show_all(ui->window);
1121}
1122
1123int main(int argc, char *argv[], char *envp[])
1124{
1125	if (initialize_fio(envp))
1126		return 1;
1127	if (fio_init_options())
1128		return 1;
1129
1130	init_ui(&argc, &argv, &ui);
1131
1132	gdk_threads_enter();
1133	gtk_main();
1134	gdk_threads_leave();
1135	return 0;
1136}
1137