Start of support for auto-starting the backend
[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                 clear_ui_info(ui);
131         }
132 }
133
134 static void gfio_text_op(struct fio_client *client,
135                 FILE *f, __u16 pdu_len, const char *buf)
136 {
137 #if 0
138         GtkTextBuffer *buffer;
139         GtkTextIter end;
140
141         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
142         gdk_threads_enter();
143         gtk_text_buffer_get_end_iter(buffer, &end);
144         gtk_text_buffer_insert(buffer, &end, buf, -1);
145         gdk_threads_leave();
146         gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
147                                         &end, 0.0, FALSE, 0.0,0.0);
148 #else
149         fio_client_ops.text_op(client, f, pdu_len, buf);
150 #endif
151 }
152
153 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
154 {
155         printf("gfio_disk_util_op called\n");
156         fio_client_ops.disk_util(client, cmd);
157 }
158
159 static void gfio_thread_status_op(struct fio_net_cmd *cmd)
160 {
161         printf("gfio_thread_status_op called\n");
162         fio_client_ops.thread_status(cmd);
163 }
164
165 static void gfio_group_stats_op(struct fio_net_cmd *cmd)
166 {
167         printf("gfio_group_stats_op called\n");
168         fio_client_ops.group_stats(cmd);
169 }
170
171 static void gfio_update_eta(struct jobs_eta *je)
172 {
173         static int eta_good;
174         char eta_str[128];
175         char output[256];
176         char tmp[32];
177         double perc = 0.0;
178         int i2p = 0;
179
180         eta_str[0] = '\0';
181         output[0] = '\0';
182
183         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
184                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
185                 eta_to_str(eta_str, je->eta_sec);
186         }
187
188         sprintf(tmp, "%u", je->nr_running);
189         gtk_label_set_text(GTK_LABEL(ui.eta.jobs), tmp);
190         sprintf(tmp, "%u", je->files_open);
191         gtk_label_set_text(GTK_LABEL(ui.eta.files), tmp);
192
193 #if 0
194         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
195         if (je->m_rate || je->t_rate) {
196                 char *tr, *mr;
197
198                 mr = num2str(je->m_rate, 4, 0, i2p);
199                 tr = num2str(je->t_rate, 4, 0, i2p);
200                 gtk_label_set_text(GTK_LABEL(ui.eta.
201                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
202                 free(tr);
203                 free(mr);
204         } else if (je->m_iops || je->t_iops)
205                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
206
207         gtk_label_set_text(GTK_LABEL(ui.eta.cr_bw), "---");
208         gtk_label_set_text(GTK_LABEL(ui.eta.cr_iops), "---");
209         gtk_label_set_text(GTK_LABEL(ui.eta.cw_bw), "---");
210         gtk_label_set_text(GTK_LABEL(ui.eta.cw_iops), "---");
211 #endif
212
213         if (je->eta_sec != INT_MAX && je->nr_running) {
214                 char *iops_str[2];
215                 char *rate_str[2];
216
217                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
218                         strcpy(output, "-.-% done");
219                 else {
220                         eta_good = 1;
221                         perc *= 100.0;
222                         sprintf(output, "%3.1f%% done", perc);
223                 }
224
225                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
226                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
227
228                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
229                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
230
231                 gtk_label_set_text(GTK_LABEL(ui.eta.read_bw), rate_str[0]);
232                 gtk_label_set_text(GTK_LABEL(ui.eta.read_iops), iops_str[0]);
233                 gtk_label_set_text(GTK_LABEL(ui.eta.write_bw), rate_str[1]);
234                 gtk_label_set_text(GTK_LABEL(ui.eta.write_iops), iops_str[1]);
235
236                 free(rate_str[0]);
237                 free(rate_str[1]);
238                 free(iops_str[0]);
239                 free(iops_str[1]);
240         }
241
242         if (eta_str[0]) {
243                 char *dst = output + strlen(output);
244
245                 sprintf(dst, " - %s", eta_str);
246         }
247                 
248         gfio_update_thread_status(output, perc);
249 }
250
251 static void gfio_eta_op(struct fio_client *client, struct fio_net_cmd *cmd)
252 {
253         struct jobs_eta *je = (struct jobs_eta *) cmd->payload;
254         struct client_eta *eta = (struct client_eta *) (uintptr_t) cmd->tag;
255
256         client->eta_in_flight = NULL;
257         flist_del_init(&client->eta_list);
258
259         fio_client_convert_jobs_eta(je);
260         fio_client_sum_jobs_eta(&eta->eta, je);
261         fio_client_dec_jobs_eta(eta, gfio_update_eta);
262 }
263
264 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
265 {
266         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
267         const char *os, *arch;
268         char buf[64];
269
270         os = fio_get_os_string(probe->os);
271         if (!os)
272                 os = "unknown";
273
274         arch = fio_get_arch_string(probe->arch);
275         if (!arch)
276                 os = "unknown";
277
278         if (!client->name)
279                 client->name = strdup((char *) probe->hostname);
280
281         gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
282         gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
283         gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
284         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
285         gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
286 }
287
288 static void gfio_update_thread_status(char *status_message, double perc)
289 {
290         static char message[100];
291         const char *m = message;
292
293         strncpy(message, status_message, sizeof(message) - 1);
294         gtk_progress_bar_set_text(
295                 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
296         gtk_progress_bar_set_fraction(
297                 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
298         gdk_threads_enter();
299         gtk_widget_queue_draw(ui.window);
300         gdk_threads_leave();
301 }
302
303 static void gfio_quit_op(struct fio_client *client)
304 {
305         struct gui *ui = client->client_data;
306
307         gfio_set_connected(ui, 0);
308 }
309
310 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
311 {
312         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
313         struct gui *ui = client->client_data;
314         char tmp[8];
315         int i;
316
317         p->iodepth              = le32_to_cpu(p->iodepth);
318         p->rw                   = le32_to_cpu(p->rw);
319
320         for (i = 0; i < 2; i++) {
321                 p->min_bs[i]    = le32_to_cpu(p->min_bs[i]);
322                 p->max_bs[i]    = le32_to_cpu(p->max_bs[i]);
323         }
324
325         p->numjobs              = le32_to_cpu(p->numjobs);
326         p->group_reporting      = le32_to_cpu(p->group_reporting);
327
328         gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
329         gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
330         gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
331
332         sprintf(tmp, "%u", p->iodepth);
333         gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
334 }
335
336 static void gfio_client_timed_out(struct fio_client *client)
337 {
338         struct gui *ui = client->client_data;
339         GtkWidget *dialog, *label, *content;
340         char buf[256];
341
342         gdk_threads_enter();
343
344         gfio_set_connected(ui, 0);
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         }
475 }
476
477 static 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
487 static 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
497 static 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
506 void 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
529 static 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
618 static 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);
674 err:
675         g_slist_free(fn_glist);
676 }
677
678 static 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
702 static void about_dialog(GtkWidget *w, gpointer data)
703 {
704         gtk_show_about_dialog(NULL,
705                 "program-name", "gfio",
706                 "comments", "Gtk2 UI for fio",
707                 "license", "GPLv2",
708                 "version", fio_version_string,
709                 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
710                 "logo-icon-name", "fio",
711                 /* Must be last: */
712                 NULL, NULL,
713                 NULL);
714 }
715
716 static GtkActionEntry menu_items[] = {
717         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
718         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
719         { "OpenFile",       GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
720         { "SaveFile",       GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
721         { "Quit",           GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
722         { "About",          GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
723 };
724 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
725
726 static const gchar *ui_string = " \
727         <ui> \
728                 <menubar name=\"MainMenu\"> \
729                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
730                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
731                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
732                                 <separator name=\"Separator\"/> \
733                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
734                         </menu> \
735                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
736                                 <menuitem name=\"About\" action=\"About\" /> \
737                         </menu> \
738                 </menubar> \
739         </ui> \
740 ";
741
742 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
743 {
744         GtkActionGroup *action_group = gtk_action_group_new("Menu");
745         GError *error = 0;
746
747         action_group = gtk_action_group_new("Menu");
748         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
749
750         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
751         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
752
753         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
754         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
755 }
756
757 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
758                    GtkWidget *vbox, GtkUIManager *ui_manager)
759 {
760         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
761 }
762
763 static void init_ui(int *argc, char **argv[], struct gui *ui)
764 {
765         GtkSettings *settings;
766         GtkUIManager *uimanager;
767         GtkWidget *menu, *probe, *probe_frame, *probe_box;
768
769         memset(ui, 0, sizeof(*ui));
770
771         /* Magical g*thread incantation, you just need this thread stuff.
772          * Without it, the update that happens in gfio_update_thread_status
773          * doesn't really happen in a timely fashion, you need expose events
774          */
775         if (!g_thread_supported())
776                 g_thread_init(NULL);
777         gdk_threads_init();
778
779         gtk_init(argc, argv);
780         settings = gtk_settings_get_default();
781         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
782         g_type_init();
783         
784         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
785         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
786         gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
787
788         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
789         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
790
791         ui->vbox = gtk_vbox_new(FALSE, 0);
792         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
793
794         uimanager = gtk_ui_manager_new();
795         menu = get_menubar_menu(ui->window, uimanager);
796         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
797
798         /*
799          * Set up alignments for widgets at the top of ui, 
800          * align top left, expand horizontally but not vertically
801          */
802         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
803         ui->topvbox = gtk_vbox_new(FALSE, 3);
804         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
805         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
806
807         probe = gtk_frame_new("Job");
808         gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
809         probe_frame = gtk_vbox_new(FALSE, 3);
810         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
811
812         probe_box = gtk_hbox_new(FALSE, 3);
813         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
814         ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
815         ui->probe.os = new_info_label_in_frame(probe_box, "OS");
816         ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
817         ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
818
819         probe_box = gtk_hbox_new(FALSE, 3);
820         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
821
822         ui->eta.name = new_info_label_in_frame(probe_box, "Name");
823         ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
824         ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
825         ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
826         ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
827         ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
828
829         probe_box = gtk_hbox_new(FALSE, 3);
830         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
831         ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
832         ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
833         ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
834         ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
835
836         /*
837          * Only add this if we have a commit rate
838          */
839 #if 0
840         probe_box = gtk_hbox_new(FALSE, 3);
841         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
842
843         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
844         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
845
846         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
847         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
848 #endif
849
850         /*
851          * Add a text box for text op messages 
852          */
853         ui->textview = gtk_text_view_new();
854         ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
855         gtk_text_buffer_set_text(ui->text, "", -1);
856         gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
857         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
858         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
859         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
860                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
861         gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
862         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
863                         TRUE, TRUE, 0);
864
865         /*
866          * Set up alignments for widgets at the bottom of ui, 
867          * align bottom left, expand horizontally but not vertically
868          */
869         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
870         ui->buttonbox = gtk_hbox_new(FALSE, 0);
871         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
872         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
873                                         FALSE, FALSE, 0);
874
875         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
876
877         /*
878          * Set up thread status progress bar
879          */
880         ui->thread_status_pb = gtk_progress_bar_new();
881         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
882         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
883         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
884
885
886         gtk_widget_show_all(ui->window);
887 }
888
889 int main(int argc, char *argv[], char *envp[])
890 {
891         if (initialize_fio(envp))
892                 return 1;
893         if (fio_init_options())
894                 return 1;
895
896         fio_debug = ~0UL;
897         init_ui(&argc, &argv, &ui);
898
899         gdk_threads_enter();
900         gtk_main();
901         gdk_threads_leave();
902         return 0;
903 }