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