Add new net command for text logging
[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_convert_jobs_eta(je);
258         fio_client_sum_jobs_eta(&eta->eta, je);
259         fio_client_dec_jobs_eta(eta, gfio_update_eta);
260 }
261
262 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
263 {
264         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
265         const char *os, *arch;
266         char buf[64];
267
268         os = fio_get_os_string(probe->os);
269         if (!os)
270                 os = "unknown";
271
272         arch = fio_get_arch_string(probe->arch);
273         if (!arch)
274                 os = "unknown";
275
276         if (!client->name)
277                 client->name = strdup((char *) probe->hostname);
278
279         gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
280         gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
281         gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
282         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
283         gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
284 }
285
286 static void gfio_update_thread_status(char *status_message, double perc)
287 {
288         static char message[100];
289         const char *m = message;
290
291         strncpy(message, status_message, sizeof(message) - 1);
292         gtk_progress_bar_set_text(
293                 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
294         gtk_progress_bar_set_fraction(
295                 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
296         gdk_threads_enter();
297         gtk_widget_queue_draw(ui.window);
298         gdk_threads_leave();
299 }
300
301 static void gfio_quit_op(struct fio_client *client)
302 {
303         struct gui *ui = client->client_data;
304
305         gfio_set_connected(ui, 0);
306 }
307
308 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
309 {
310         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
311         struct gui *ui = client->client_data;
312         char tmp[8];
313         int i;
314
315         p->iodepth              = le32_to_cpu(p->iodepth);
316         p->rw                   = le32_to_cpu(p->rw);
317
318         for (i = 0; i < 2; i++) {
319                 p->min_bs[i]    = le32_to_cpu(p->min_bs[i]);
320                 p->max_bs[i]    = le32_to_cpu(p->max_bs[i]);
321         }
322
323         p->numjobs              = le32_to_cpu(p->numjobs);
324         p->group_reporting      = le32_to_cpu(p->group_reporting);
325
326         gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
327         gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
328         gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
329
330         sprintf(tmp, "%u", p->iodepth);
331         gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
332 }
333
334 static void gfio_client_timed_out(struct fio_client *client)
335 {
336         struct gui *ui = client->client_data;
337         GtkWidget *dialog, *label, *content;
338         char buf[256];
339
340         gdk_threads_enter();
341
342         gfio_set_connected(ui, 0);
343         clear_ui_info(ui);
344
345         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
346
347         dialog = gtk_dialog_new_with_buttons("Timed out!",
348                         GTK_WINDOW(ui->window),
349                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
350                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
351
352         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
353         label = gtk_label_new((const gchar *) buf);
354         gtk_container_add(GTK_CONTAINER(content), label);
355         gtk_widget_show_all(dialog);
356         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
357
358         gtk_dialog_run(GTK_DIALOG(dialog));
359         gtk_widget_destroy(dialog);
360
361         gdk_threads_leave();
362 }
363
364 struct client_ops gfio_client_ops = {
365         .text_op                = gfio_text_op,
366         .disk_util              = gfio_disk_util_op,
367         .thread_status          = gfio_thread_status_op,
368         .group_stats            = gfio_group_stats_op,
369         .eta                    = gfio_eta_op,
370         .probe                  = gfio_probe_op,
371         .quit                   = gfio_quit_op,
372         .add_job                = gfio_add_job_op,
373         .timed_out              = gfio_client_timed_out,
374         .stay_connected         = 1,
375 };
376
377 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
378                 __attribute__((unused)) gpointer data)
379 {
380         gtk_main_quit();
381 }
382
383 static void *job_thread(void *arg)
384 {
385         fio_handle_clients(&gfio_client_ops);
386         return NULL;
387 }
388
389 static int send_job_files(struct gui *ui)
390 {
391         int i, ret = 0;
392
393         for (i = 0; i < ui->nr_job_files; i++) {
394                 ret = fio_clients_send_ini(ui->job_files[i]);
395                 if (ret)
396                         break;
397
398                 free(ui->job_files[i]);
399                 ui->job_files[i] = NULL;
400         }
401         while (i < ui->nr_job_files) {
402                 free(ui->job_files[i]);
403                 ui->job_files[i] = NULL;
404                 i++;
405         }
406
407         return ret;
408 }
409
410 static void start_job_thread(struct gui *ui)
411 {
412         if (send_job_files(ui)) {
413                 printf("Yeah, I didn't really like those options too much.\n");
414                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
415                 return;
416         }
417 }
418
419 static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
420 {
421         GtkWidget *label_widget;
422         GtkWidget *frame;
423
424         frame = gtk_frame_new(label);
425         label_widget = gtk_label_new(NULL);
426         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
427         gtk_container_add(GTK_CONTAINER(frame), label_widget);
428
429         return label_widget;
430 }
431
432 static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
433 {
434         GtkWidget *button, *box;
435
436         box = gtk_hbox_new(FALSE, 3);
437         gtk_container_add(GTK_CONTAINER(hbox), box);
438
439         button = gtk_spin_button_new_with_range(min, max, 1.0);
440         gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
441
442         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
443         gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
444
445         return button;
446 }
447
448 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
449                 gpointer data)
450 {
451         struct gui *ui = data;
452
453         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
454         start_job_thread(ui);
455 }
456
457 static void file_open(GtkWidget *w, gpointer data);
458
459 static void connect_clicked(GtkWidget *widget, gpointer data)
460 {
461         struct gui *ui = data;
462
463         if (!ui->connected) {
464                 if (!ui->nr_job_files)
465                         file_open(widget, data);
466                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
467                 fio_clients_connect();
468                 pthread_create(&ui->t, NULL, job_thread, NULL);
469                 gfio_set_connected(ui, 1);
470         } else {
471                 fio_clients_terminate();
472                 gfio_set_connected(ui, 0);
473                 clear_ui_info(ui);
474         }
475 }
476
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 preferences(GtkWidget *w, gpointer data)
703 {
704         GtkWidget *dialog, *frame, *box, **buttons;
705         int i;
706
707         dialog = gtk_dialog_new_with_buttons("Preferences",
708                 GTK_WINDOW(ui.window),
709                 GTK_DIALOG_DESTROY_WITH_PARENT,
710                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
711                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
712                 NULL);
713
714         frame = gtk_frame_new("Debug logging");
715         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
716         box = gtk_hbox_new(FALSE, 6);
717         gtk_container_add(GTK_CONTAINER(frame), box);
718
719         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
720
721         for (i = 0; i < FD_DEBUG_MAX; i++) {
722                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
723                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
724                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
725         }
726
727         gtk_widget_show_all(dialog);
728
729         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
730                 gtk_widget_destroy(dialog);
731                 return;
732         }
733
734         for (i = 0; i < FD_DEBUG_MAX; i++) {
735                 int set;
736
737                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
738                 if (set)
739                         fio_debug |= (1UL << i);
740         }
741
742         gtk_widget_destroy(dialog);
743 }
744
745 static void about_dialog(GtkWidget *w, gpointer data)
746 {
747         gtk_show_about_dialog(NULL,
748                 "program-name", "gfio",
749                 "comments", "Gtk2 UI for fio",
750                 "license", "GPLv2",
751                 "version", fio_version_string,
752                 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
753                 "logo-icon-name", "fio",
754                 /* Must be last: */
755                 NULL, NULL,
756                 NULL);
757 }
758
759 static GtkActionEntry menu_items[] = {
760         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
761         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
762         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
763         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
764         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
765         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
766         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
767 };
768 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
769
770 static const gchar *ui_string = " \
771         <ui> \
772                 <menubar name=\"MainMenu\"> \
773                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
774                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
775                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
776                                 <separator name=\"Separator\"/> \
777                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
778                                 <separator name=\"Separator2\"/> \
779                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
780                         </menu> \
781                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
782                                 <menuitem name=\"About\" action=\"About\" /> \
783                         </menu> \
784                 </menubar> \
785         </ui> \
786 ";
787
788 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
789 {
790         GtkActionGroup *action_group = gtk_action_group_new("Menu");
791         GError *error = 0;
792
793         action_group = gtk_action_group_new("Menu");
794         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
795
796         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
797         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
798
799         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
800         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
801 }
802
803 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
804                    GtkWidget *vbox, GtkUIManager *ui_manager)
805 {
806         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
807 }
808
809 static void init_ui(int *argc, char **argv[], struct gui *ui)
810 {
811         GtkSettings *settings;
812         GtkUIManager *uimanager;
813         GtkWidget *menu, *probe, *probe_frame, *probe_box;
814
815         memset(ui, 0, sizeof(*ui));
816
817         /* Magical g*thread incantation, you just need this thread stuff.
818          * Without it, the update that happens in gfio_update_thread_status
819          * doesn't really happen in a timely fashion, you need expose events
820          */
821         if (!g_thread_supported())
822                 g_thread_init(NULL);
823         gdk_threads_init();
824
825         gtk_init(argc, argv);
826         settings = gtk_settings_get_default();
827         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
828         g_type_init();
829         
830         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
831         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
832         gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
833
834         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
835         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
836
837         ui->vbox = gtk_vbox_new(FALSE, 0);
838         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
839
840         uimanager = gtk_ui_manager_new();
841         menu = get_menubar_menu(ui->window, uimanager);
842         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
843
844         /*
845          * Set up alignments for widgets at the top of ui, 
846          * align top left, expand horizontally but not vertically
847          */
848         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
849         ui->topvbox = gtk_vbox_new(FALSE, 3);
850         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
851         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
852
853         probe = gtk_frame_new("Job");
854         gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
855         probe_frame = gtk_vbox_new(FALSE, 3);
856         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
857
858         probe_box = gtk_hbox_new(FALSE, 3);
859         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
860         ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
861         ui->probe.os = new_info_label_in_frame(probe_box, "OS");
862         ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
863         ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
864
865         probe_box = gtk_hbox_new(FALSE, 3);
866         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
867
868         ui->eta.name = new_info_label_in_frame(probe_box, "Name");
869         ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
870         ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
871         ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
872         ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
873         ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
874
875         probe_box = gtk_hbox_new(FALSE, 3);
876         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
877         ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
878         ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
879         ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
880         ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
881
882         /*
883          * Only add this if we have a commit rate
884          */
885 #if 0
886         probe_box = gtk_hbox_new(FALSE, 3);
887         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
888
889         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
890         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
891
892         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
893         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
894 #endif
895
896         /*
897          * Add a text box for text op messages 
898          */
899         ui->textview = gtk_text_view_new();
900         ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
901         gtk_text_buffer_set_text(ui->text, "", -1);
902         gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
903         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
904         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
905         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
906                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
907         gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
908         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
909                         TRUE, TRUE, 0);
910
911         /*
912          * Set up alignments for widgets at the bottom of ui, 
913          * align bottom left, expand horizontally but not vertically
914          */
915         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
916         ui->buttonbox = gtk_hbox_new(FALSE, 0);
917         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
918         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
919                                         FALSE, FALSE, 0);
920
921         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
922
923         /*
924          * Set up thread status progress bar
925          */
926         ui->thread_status_pb = gtk_progress_bar_new();
927         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
928         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
929         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
930
931
932         gtk_widget_show_all(ui->window);
933 }
934
935 int main(int argc, char *argv[], char *envp[])
936 {
937         if (initialize_fio(envp))
938                 return 1;
939         if (fio_init_options())
940                 return 1;
941
942         init_ui(&argc, &argv, &ui);
943
944         gdk_threads_enter();
945         gtk_main();
946         gdk_threads_leave();
947         return 0;
948 }