gfio.c revision 084d1c6f817eacaaefa1de4f0637ef6c1405d74b
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 void gfio_set_connected(struct gui *ui, int connected)
121{
122	if (connected) {
123		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
124		ui->connected = 1;
125		gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
126	} else {
127		ui->connected = 0;
128		gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
129		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
130	}
131}
132
133static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
134{
135#if 0
136	GtkTextBuffer *buffer;
137	GtkTextIter end;
138
139	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
140	gdk_threads_enter();
141	gtk_text_buffer_get_end_iter(buffer, &end);
142	gtk_text_buffer_insert(buffer, &end, buf, -1);
143	gdk_threads_leave();
144	gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
145					&end, 0.0, FALSE, 0.0,0.0);
146#else
147	fio_client_ops.text_op(client, cmd);
148#endif
149}
150
151static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
152{
153	printf("gfio_disk_util_op called\n");
154	fio_client_ops.disk_util(client, cmd);
155}
156
157static void gfio_thread_status_op(struct fio_net_cmd *cmd)
158{
159	printf("gfio_thread_status_op called\n");
160	fio_client_ops.thread_status(cmd);
161}
162
163static void gfio_group_stats_op(struct fio_net_cmd *cmd)
164{
165	printf("gfio_group_stats_op called\n");
166	fio_client_ops.group_stats(cmd);
167}
168
169static void gfio_update_eta(struct jobs_eta *je)
170{
171	static int eta_good;
172	char eta_str[128];
173	char output[256];
174	char tmp[32];
175	double perc = 0.0;
176	int i2p = 0;
177
178	eta_str[0] = '\0';
179	output[0] = '\0';
180
181	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
182		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
183		eta_to_str(eta_str, je->eta_sec);
184	}
185
186	sprintf(tmp, "%u", je->nr_running);
187	gtk_label_set_text(GTK_LABEL(ui.eta.jobs), tmp);
188	sprintf(tmp, "%u", je->files_open);
189	gtk_label_set_text(GTK_LABEL(ui.eta.files), tmp);
190
191#if 0
192	if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
193	if (je->m_rate || je->t_rate) {
194		char *tr, *mr;
195
196		mr = num2str(je->m_rate, 4, 0, i2p);
197		tr = num2str(je->t_rate, 4, 0, i2p);
198		gtk_label_set_text(GTK_LABEL(ui.eta.
199		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
200		free(tr);
201		free(mr);
202	} else if (je->m_iops || je->t_iops)
203		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
204
205	gtk_label_set_text(GTK_LABEL(ui.eta.cr_bw), "---");
206	gtk_label_set_text(GTK_LABEL(ui.eta.cr_iops), "---");
207	gtk_label_set_text(GTK_LABEL(ui.eta.cw_bw), "---");
208	gtk_label_set_text(GTK_LABEL(ui.eta.cw_iops), "---");
209#endif
210
211	if (je->eta_sec != INT_MAX && je->nr_running) {
212		char *iops_str[2];
213		char *rate_str[2];
214
215		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
216			strcpy(output, "-.-% done");
217		else {
218			eta_good = 1;
219			perc *= 100.0;
220			sprintf(output, "%3.1f%% done", perc);
221		}
222
223		rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
224		rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
225
226		iops_str[0] = num2str(je->iops[0], 4, 1, 0);
227		iops_str[1] = num2str(je->iops[1], 4, 1, 0);
228
229		gtk_label_set_text(GTK_LABEL(ui.eta.read_bw), rate_str[0]);
230		gtk_label_set_text(GTK_LABEL(ui.eta.read_iops), iops_str[0]);
231		gtk_label_set_text(GTK_LABEL(ui.eta.write_bw), rate_str[1]);
232		gtk_label_set_text(GTK_LABEL(ui.eta.write_iops), iops_str[1]);
233
234		free(rate_str[0]);
235		free(rate_str[1]);
236		free(iops_str[0]);
237		free(iops_str[1]);
238	}
239
240	if (eta_str[0]) {
241		char *dst = output + strlen(output);
242
243		sprintf(dst, " - %s", eta_str);
244	}
245
246	gfio_update_thread_status(output, perc);
247}
248
249static void gfio_eta_op(struct fio_client *client, struct fio_net_cmd *cmd)
250{
251	struct jobs_eta *je = (struct jobs_eta *) cmd->payload;
252	struct client_eta *eta = (struct client_eta *) (uintptr_t) cmd->tag;
253
254	client->eta_in_flight = NULL;
255	flist_del_init(&client->eta_list);
256
257	fio_client_convert_jobs_eta(je);
258	fio_client_sum_jobs_eta(&eta->eta, je);
259	fio_client_dec_jobs_eta(eta, gfio_update_eta);
260}
261
262static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
263{
264	struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
265	const char *os, *arch;
266	char buf[64];
267
268	os = fio_get_os_string(probe->os);
269	if (!os)
270		os = "unknown";
271
272	arch = fio_get_arch_string(probe->arch);
273	if (!arch)
274		os = "unknown";
275
276	if (!client->name)
277		client->name = strdup((char *) probe->hostname);
278
279	gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
280	gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
281	gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
282	sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
283	gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
284}
285
286static void gfio_update_thread_status(char *status_message, double perc)
287{
288	static char message[100];
289	const char *m = message;
290
291	strncpy(message, status_message, sizeof(message) - 1);
292	gtk_progress_bar_set_text(
293		GTK_PROGRESS_BAR(ui.thread_status_pb), m);
294	gtk_progress_bar_set_fraction(
295		GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
296	gdk_threads_enter();
297	gtk_widget_queue_draw(ui.window);
298	gdk_threads_leave();
299}
300
301static void gfio_quit_op(struct fio_client *client)
302{
303	struct gui *ui = client->client_data;
304
305	gfio_set_connected(ui, 0);
306}
307
308static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
309{
310	struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
311	struct gui *ui = client->client_data;
312	char tmp[8];
313	int i;
314
315	p->iodepth		= le32_to_cpu(p->iodepth);
316	p->rw			= le32_to_cpu(p->rw);
317
318	for (i = 0; i < 2; i++) {
319		p->min_bs[i] 	= le32_to_cpu(p->min_bs[i]);
320		p->max_bs[i]	= le32_to_cpu(p->max_bs[i]);
321	}
322
323	p->numjobs		= le32_to_cpu(p->numjobs);
324	p->group_reporting	= le32_to_cpu(p->group_reporting);
325
326	gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
327	gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
328	gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
329
330	sprintf(tmp, "%u", p->iodepth);
331	gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
332}
333
334static void gfio_client_timed_out(struct fio_client *client)
335{
336	struct gui *ui = client->client_data;
337	GtkWidget *dialog, *label, *content;
338	char buf[256];
339
340	gdk_threads_enter();
341
342	gfio_set_connected(ui, 0);
343	clear_ui_info(ui);
344
345	sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
346
347	dialog = gtk_dialog_new_with_buttons("Timed out!",
348			GTK_WINDOW(ui->window),
349			GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
350			GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
351
352	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
353	label = gtk_label_new((const gchar *) buf);
354	gtk_container_add(GTK_CONTAINER(content), label);
355	gtk_widget_show_all(dialog);
356	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
357
358	gtk_dialog_run(GTK_DIALOG(dialog));
359	gtk_widget_destroy(dialog);
360
361	gdk_threads_leave();
362}
363
364struct client_ops gfio_client_ops = {
365	.text_op		= gfio_text_op,
366	.disk_util		= gfio_disk_util_op,
367	.thread_status		= gfio_thread_status_op,
368	.group_stats		= gfio_group_stats_op,
369	.eta			= gfio_eta_op,
370	.probe			= gfio_probe_op,
371	.quit			= gfio_quit_op,
372	.add_job		= gfio_add_job_op,
373	.timed_out		= gfio_client_timed_out,
374	.stay_connected		= 1,
375};
376
377static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
378                __attribute__((unused)) gpointer data)
379{
380        gtk_main_quit();
381}
382
383static void *job_thread(void *arg)
384{
385	fio_handle_clients(&gfio_client_ops);
386	return NULL;
387}
388
389static int send_job_files(struct gui *ui)
390{
391	int i, ret = 0;
392
393	for (i = 0; i < ui->nr_job_files; i++) {
394		ret = fio_clients_send_ini(ui->job_files[i]);
395		if (ret)
396			break;
397
398		free(ui->job_files[i]);
399		ui->job_files[i] = NULL;
400	}
401	while (i < ui->nr_job_files) {
402		free(ui->job_files[i]);
403		ui->job_files[i] = NULL;
404		i++;
405	}
406
407	return ret;
408}
409
410static void start_job_thread(struct gui *ui)
411{
412	if (send_job_files(ui)) {
413		printf("Yeah, I didn't really like those options too much.\n");
414		gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
415		return;
416	}
417}
418
419static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
420{
421	GtkWidget *label_widget;
422	GtkWidget *frame;
423
424	frame = gtk_frame_new(label);
425	label_widget = gtk_label_new(NULL);
426	gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
427	gtk_container_add(GTK_CONTAINER(frame), label_widget);
428
429	return label_widget;
430}
431
432static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
433{
434	GtkWidget *button, *box;
435
436	box = gtk_hbox_new(FALSE, 3);
437	gtk_container_add(GTK_CONTAINER(hbox), box);
438
439	button = gtk_spin_button_new_with_range(min, max, 1.0);
440	gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
441
442	gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
443	gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
444
445	return button;
446}
447
448static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
449                gpointer data)
450{
451	struct gui *ui = data;
452
453	gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
454	start_job_thread(ui);
455}
456
457static void file_open(GtkWidget *w, gpointer data);
458
459static void connect_clicked(GtkWidget *widget, gpointer data)
460{
461	struct gui *ui = data;
462
463	if (!ui->connected) {
464		if (!ui->nr_job_files)
465			file_open(widget, data);
466		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
467		fio_clients_connect();
468		pthread_create(&ui->t, NULL, job_thread, NULL);
469		gfio_set_connected(ui, 1);
470	} else {
471		fio_clients_terminate();
472		gfio_set_connected(ui, 0);
473		clear_ui_info(ui);
474	}
475}
476
477static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
478			struct button_spec *buttonspec)
479{
480	ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
481	g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
482	gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
483	gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
484	gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
485}
486
487static void add_buttons(struct gui *ui,
488				struct button_spec *buttonlist,
489				int nbuttons)
490{
491	int i;
492
493	for (i = 0; i < nbuttons; i++)
494		add_button(ui, i, ui->buttonbox, &buttonlist[i]);
495}
496
497static void on_info_bar_response(GtkWidget *widget, gint response,
498                                 gpointer data)
499{
500	if (response == GTK_RESPONSE_OK) {
501		gtk_widget_destroy(widget);
502		ui.error_info_bar = NULL;
503	}
504}
505
506void report_error(GError *error)
507{
508	if (ui.error_info_bar == NULL) {
509		ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
510		                                               GTK_RESPONSE_OK,
511		                                               NULL);
512		g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
513		gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
514		                              GTK_MESSAGE_ERROR);
515
516		ui.error_label = gtk_label_new(error->message);
517		GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
518		gtk_container_add(GTK_CONTAINER(container), ui.error_label);
519
520		gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
521		gtk_widget_show_all(ui.vbox);
522	} else {
523		char buffer[256];
524		snprintf(buffer, sizeof(buffer), "Failed to open file.");
525		gtk_label_set(GTK_LABEL(ui.error_label), buffer);
526	}
527}
528
529static int get_connection_details(char **host, int *port, int *type,
530				  int *server_start)
531{
532	GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
533	GtkWidget *button;
534	char *typeentry;
535
536	dialog = gtk_dialog_new_with_buttons("Connection details",
537			GTK_WINDOW(ui.window),
538			GTK_DIALOG_DESTROY_WITH_PARENT,
539			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
540			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
541
542	frame = gtk_frame_new("Hostname / socket name");
543	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
544	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
545
546	box = gtk_vbox_new(FALSE, 6);
547	gtk_container_add(GTK_CONTAINER(frame), box);
548
549	hbox = gtk_hbox_new(TRUE, 10);
550	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
551	hentry = gtk_entry_new();
552	gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
553	gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
554
555	frame = gtk_frame_new("Port");
556	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
557	box = gtk_vbox_new(FALSE, 10);
558	gtk_container_add(GTK_CONTAINER(frame), box);
559
560	hbox = gtk_hbox_new(TRUE, 4);
561	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
562	pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
563
564	frame = gtk_frame_new("Type");
565	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
566	box = gtk_vbox_new(FALSE, 10);
567	gtk_container_add(GTK_CONTAINER(frame), box);
568
569	hbox = gtk_hbox_new(TRUE, 4);
570	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
571
572	combo = gtk_combo_box_text_new();
573	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
574	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
575	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
576	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
577
578	gtk_container_add(GTK_CONTAINER(hbox), combo);
579
580	frame = gtk_frame_new("Options");
581	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
582	box = gtk_vbox_new(FALSE, 10);
583	gtk_container_add(GTK_CONTAINER(frame), box);
584
585	hbox = gtk_hbox_new(TRUE, 4);
586	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
587
588	button = gtk_check_button_new_with_label("Auto-spawn fio backend");
589	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
590	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.");
591	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
592
593	gtk_widget_show_all(dialog);
594
595	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
596		gtk_widget_destroy(dialog);
597		return 1;
598	}
599
600	*host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
601	*port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
602
603	typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
604	if (!typeentry || !strncmp(typeentry, "IPv4", 4))
605		*type = Fio_client_ipv4;
606	else if (!strncmp(typeentry, "IPv6", 4))
607		*type = Fio_client_ipv6;
608	else
609		*type = Fio_client_socket;
610	g_free(typeentry);
611
612	*server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
613
614	gtk_widget_destroy(dialog);
615	return 0;
616}
617
618static void file_open(GtkWidget *w, gpointer data)
619{
620	GtkWidget *dialog;
621	GSList *filenames, *fn_glist;
622	GtkFileFilter *filter;
623	char *host;
624	int port, type, server_start;
625
626	dialog = gtk_file_chooser_dialog_new("Open File",
627		GTK_WINDOW(ui.window),
628		GTK_FILE_CHOOSER_ACTION_OPEN,
629		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
630		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
631		NULL);
632	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
633
634	filter = gtk_file_filter_new();
635	gtk_file_filter_add_pattern(filter, "*.fio");
636	gtk_file_filter_add_pattern(filter, "*.job");
637	gtk_file_filter_add_mime_type(filter, "text/fio");
638	gtk_file_filter_set_name(filter, "Fio job file");
639	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
640
641	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
642		gtk_widget_destroy(dialog);
643		return;
644	}
645
646	fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
647
648	gtk_widget_destroy(dialog);
649
650	if (get_connection_details(&host, &port, &type, &server_start))
651		goto err;
652
653	filenames = fn_glist;
654	while (filenames != NULL) {
655		ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
656		ui.job_files[ui.nr_job_files] = strdup(filenames->data);
657		ui.nr_job_files++;
658
659		ui.client = fio_client_add_explicit(host, type, port);
660		if (!ui.client) {
661			GError *error;
662
663			error = g_error_new(g_quark_from_string("fio"), 1,
664					"Failed to add client %s", host);
665			report_error(error);
666			g_error_free(error);
667		}
668		ui.client->client_data = &ui;
669
670		g_free(filenames->data);
671		filenames = g_slist_next(filenames);
672	}
673	free(host);
674err:
675	g_slist_free(fn_glist);
676}
677
678static void file_save(GtkWidget *w, gpointer data)
679{
680	GtkWidget *dialog;
681
682	dialog = gtk_file_chooser_dialog_new("Save File",
683		GTK_WINDOW(ui.window),
684		GTK_FILE_CHOOSER_ACTION_SAVE,
685		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
686		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
687		NULL);
688
689	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
690	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
691
692	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
693		char *filename;
694
695		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
696		// save_job_file(filename);
697		g_free(filename);
698	}
699	gtk_widget_destroy(dialog);
700}
701
702static void preferences(GtkWidget *w, gpointer data)
703{
704	GtkWidget *dialog, *frame, *box, **buttons;
705	int i;
706
707	dialog = gtk_dialog_new_with_buttons("Preferences",
708		GTK_WINDOW(ui.window),
709		GTK_DIALOG_DESTROY_WITH_PARENT,
710		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
711		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
712		NULL);
713
714	frame = gtk_frame_new("Debug logging");
715	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
716	box = gtk_hbox_new(FALSE, 6);
717	gtk_container_add(GTK_CONTAINER(frame), box);
718
719	buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
720
721	for (i = 0; i < FD_DEBUG_MAX; i++) {
722		buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
723		gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
724		gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
725	}
726
727	gtk_widget_show_all(dialog);
728
729	if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
730		gtk_widget_destroy(dialog);
731		return;
732	}
733
734	for (i = 0; i < FD_DEBUG_MAX; i++) {
735		int set;
736
737		set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
738		if (set)
739			fio_debug |= (1UL << i);
740	}
741
742	gtk_widget_destroy(dialog);
743}
744
745static void about_dialog(GtkWidget *w, gpointer data)
746{
747	gtk_show_about_dialog(NULL,
748		"program-name", "gfio",
749		"comments", "Gtk2 UI for fio",
750		"license", "GPLv2",
751		"version", fio_version_string,
752		"copyright", "Jens Axboe <axboe@kernel.dk> 2012",
753		"logo-icon-name", "fio",
754		/* Must be last: */
755		NULL, NULL,
756		NULL);
757}
758
759static GtkActionEntry menu_items[] = {
760	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
761	{ "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
762	{ "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
763	{ "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
764	{ "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
765	{ "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
766	{ "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
767};
768static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
769
770static const gchar *ui_string = " \
771	<ui> \
772		<menubar name=\"MainMenu\"> \
773			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
774				<menuitem name=\"Open\" action=\"OpenFile\" /> \
775				<menuitem name=\"Save\" action=\"SaveFile\" /> \
776				<separator name=\"Separator\"/> \
777				<menuitem name=\"Preferences\" action=\"Preferences\" /> \
778				<separator name=\"Separator2\"/> \
779				<menuitem name=\"Quit\" action=\"Quit\" /> \
780			</menu> \
781			<menu name=\"Help\" action=\"HelpMenuAction\"> \
782				<menuitem name=\"About\" action=\"About\" /> \
783			</menu> \
784		</menubar> \
785	</ui> \
786";
787
788static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
789{
790	GtkActionGroup *action_group = gtk_action_group_new("Menu");
791	GError *error = 0;
792
793	action_group = gtk_action_group_new("Menu");
794	gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
795
796	gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
797	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
798
799	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
800	return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
801}
802
803void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
804                   GtkWidget *vbox, GtkUIManager *ui_manager)
805{
806        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
807}
808
809static void init_ui(int *argc, char **argv[], struct gui *ui)
810{
811	GtkSettings *settings;
812	GtkUIManager *uimanager;
813	GtkWidget *menu, *probe, *probe_frame, *probe_box;
814
815	memset(ui, 0, sizeof(*ui));
816
817	/* Magical g*thread incantation, you just need this thread stuff.
818	 * Without it, the update that happens in gfio_update_thread_status
819	 * doesn't really happen in a timely fashion, you need expose events
820	 */
821	if (!g_thread_supported())
822		g_thread_init(NULL);
823	gdk_threads_init();
824
825	gtk_init(argc, argv);
826	settings = gtk_settings_get_default();
827	gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
828	g_type_init();
829
830	ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
831        gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
832	gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
833
834	g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
835	g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
836
837	ui->vbox = gtk_vbox_new(FALSE, 0);
838	gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
839
840	uimanager = gtk_ui_manager_new();
841	menu = get_menubar_menu(ui->window, uimanager);
842	gfio_ui_setup(settings, menu, ui->vbox, uimanager);
843
844	/*
845	 * Set up alignments for widgets at the top of ui,
846	 * align top left, expand horizontally but not vertically
847	 */
848	ui->topalign = gtk_alignment_new(0, 0, 1, 0);
849	ui->topvbox = gtk_vbox_new(FALSE, 3);
850	gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
851	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
852
853	probe = gtk_frame_new("Job");
854	gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
855	probe_frame = gtk_vbox_new(FALSE, 3);
856	gtk_container_add(GTK_CONTAINER(probe), probe_frame);
857
858	probe_box = gtk_hbox_new(FALSE, 3);
859	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
860	ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
861	ui->probe.os = new_info_label_in_frame(probe_box, "OS");
862	ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
863	ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
864
865	probe_box = gtk_hbox_new(FALSE, 3);
866	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
867
868	ui->eta.name = new_info_label_in_frame(probe_box, "Name");
869	ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
870	ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
871	ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
872	ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
873	ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
874
875	probe_box = gtk_hbox_new(FALSE, 3);
876	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
877	ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
878	ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
879	ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
880	ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
881
882	/*
883	 * Only add this if we have a commit rate
884	 */
885#if 0
886	probe_box = gtk_hbox_new(FALSE, 3);
887	gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
888
889	ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
890	ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
891
892	ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
893	ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
894#endif
895
896	/*
897	 * Add a text box for text op messages
898	 */
899	ui->textview = gtk_text_view_new();
900	ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
901	gtk_text_buffer_set_text(ui->text, "", -1);
902	gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
903	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
904	ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
905	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
906					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
907	gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
908	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
909			TRUE, TRUE, 0);
910
911	/*
912	 * Set up alignments for widgets at the bottom of ui,
913	 * align bottom left, expand horizontally but not vertically
914	 */
915	ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
916	ui->buttonbox = gtk_hbox_new(FALSE, 0);
917	gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
918	gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
919					FALSE, FALSE, 0);
920
921	add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
922
923	/*
924	 * Set up thread status progress bar
925	 */
926	ui->thread_status_pb = gtk_progress_bar_new();
927	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
928	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
929	gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
930
931
932	gtk_widget_show_all(ui->window);
933}
934
935int main(int argc, char *argv[], char *envp[])
936{
937	if (initialize_fio(envp))
938		return 1;
939	if (fio_init_options())
940		return 1;
941
942	init_ui(&argc, &argv, &ui);
943
944	gdk_threads_enter();
945	gtk_main();
946	gdk_threads_leave();
947	return 0;
948}
949