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