Rename t0001 regression job to have .fio file extension
[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  * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
6  *
7  * The license below covers all files distributed with fio unless otherwise
8  * noted in the file itself.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License version 2 as
12  *  published by the Free Software Foundation.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include <locale.h>
25 #include <malloc.h>
26 #include <string.h>
27
28 #include <glib.h>
29 #include <cairo.h>
30 #include <gtk/gtk.h>
31
32 #include "fio.h"
33 #include "graph.h"
34
35 #define GFIO_MIME       "text/fio"
36
37 static int gfio_server_running;
38 static const char *gfio_graph_font;
39 static unsigned int gfio_graph_limit = 100;
40
41 static void view_log(GtkWidget *w, gpointer data);
42
43 #define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
44
45 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
46
47 static void connect_clicked(GtkWidget *widget, gpointer data);
48 static void start_job_clicked(GtkWidget *widget, gpointer data);
49 static void send_clicked(GtkWidget *widget, gpointer data);
50
51 static struct button_spec {
52         const char *buttontext;
53         clickfunction f;
54         const char *tooltiptext;
55         const int start_insensitive;
56 } buttonspeclist[] = {
57 #define CONNECT_BUTTON 0
58 #define SEND_BUTTON 1
59 #define START_JOB_BUTTON 2
60         { "Connect", connect_clicked, "Connect to host", 0 },
61         { "Send", send_clicked, "Send job description to host", 1 },
62         { "Start Job", start_job_clicked,
63                 "Start the current job on the server", 1 },
64 };
65
66 struct probe_widget {
67         GtkWidget *hostname;
68         GtkWidget *os;
69         GtkWidget *arch;
70         GtkWidget *fio_ver;
71 };
72
73 struct multitext_widget {
74         GtkWidget *entry;
75         char **text;
76         unsigned int cur_text;
77         unsigned int max_text;
78 };
79
80 struct eta_widget {
81         GtkWidget *names;
82         struct multitext_widget iotype;
83         struct multitext_widget ioengine;
84         struct multitext_widget iodepth;
85         GtkWidget *jobs;
86         GtkWidget *files;
87         GtkWidget *read_bw;
88         GtkWidget *read_iops;
89         GtkWidget *cr_bw;
90         GtkWidget *cr_iops;
91         GtkWidget *write_bw;
92         GtkWidget *write_iops;
93         GtkWidget *cw_bw;
94         GtkWidget *cw_iops;
95 };
96
97 struct gfio_graphs {
98 #define DRAWING_AREA_XDIM 1000
99 #define DRAWING_AREA_YDIM 400
100         GtkWidget *drawing_area;
101         struct graph *iops_graph;
102         struct graph *bandwidth_graph;
103 };
104
105 /*
106  * Main window widgets and data
107  */
108 struct gui {
109         GtkUIManager *uimanager;
110         GtkRecentManager *recentmanager;
111         GtkActionGroup *actiongroup;
112         guint recent_ui_id;
113         GtkWidget *menu;
114         GtkWidget *window;
115         GtkWidget *vbox;
116         GtkWidget *thread_status_pb;
117         GtkWidget *buttonbox;
118         GtkWidget *notebook;
119         GtkWidget *error_info_bar;
120         GtkWidget *error_label;
121         GtkListStore *log_model;
122         GtkWidget *log_tree;
123         GtkWidget *log_view;
124         struct gfio_graphs graphs;
125         struct probe_widget probe;
126         struct eta_widget eta;
127         pthread_t server_t;
128
129         pthread_t t;
130         int handler_running;
131
132         struct flist_head list;
133 } main_ui;
134
135 enum {
136         GE_STATE_NEW = 1,
137         GE_STATE_CONNECTED,
138         GE_STATE_JOB_SENT,
139         GE_STATE_JOB_STARTED,
140         GE_STATE_JOB_RUNNING,
141         GE_STATE_JOB_DONE,
142 };
143
144 /*
145  * Notebook entry
146  */
147 struct gui_entry {
148         struct flist_head list;
149         struct gui *ui;
150
151         GtkWidget *vbox;
152         GtkWidget *job_notebook;
153         GtkWidget *thread_status_pb;
154         GtkWidget *buttonbox;
155         GtkWidget *button[ARRAYSIZE(buttonspeclist)];
156         GtkWidget *notebook;
157         GtkWidget *error_info_bar;
158         GtkWidget *error_label;
159         GtkWidget *results_notebook;
160         GtkWidget *results_window;
161         GtkListStore *log_model;
162         GtkWidget *log_tree;
163         GtkWidget *log_view;
164         struct gfio_graphs graphs;
165         struct probe_widget probe;
166         struct eta_widget eta;
167         GtkWidget *page_label;
168         gint page_num;
169         unsigned int state;
170
171         struct gfio_client *client;
172         int nr_job_files;
173         char **job_files;
174 };
175
176 struct gfio_client {
177         struct gui_entry *ge;
178         struct fio_client *client;
179         GtkWidget *results_widget;
180         GtkWidget *disk_util_frame;
181         GtkWidget *err_entry;
182         unsigned int job_added;
183         struct thread_options o;
184 };
185
186 static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc);
187 static void gfio_update_thread_status_all(char *status_message, double perc);
188 void report_error(GError *error);
189
190 static void iops_graph_y_axis_unit_change(struct graph *g, int power_of_ten)
191 {
192         switch (power_of_ten) {
193                 case 9: graph_y_title(g, "Billions of IOs / sec");
194                         break;
195                 case 6: graph_y_title(g, "Millions of IOs / sec");
196                         break;
197                 case 3: graph_y_title(g, "Thousands of IOs / sec");
198                         break;
199                 case 0:
200                 default: graph_y_title(g, "IOs / sec");
201                         break;
202         }
203 }
204
205 static struct graph *setup_iops_graph(void)
206 {
207         struct graph *g;
208
209         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
210         graph_title(g, "IOPS");
211         graph_x_title(g, "Time (secs)");
212         graph_y_title(g, "IOs / sec");
213         graph_add_label(g, "Read IOPS");
214         graph_add_label(g, "Write IOPS");
215         graph_set_color(g, "Read IOPS", 0.13, 0.54, 0.13);
216         graph_set_color(g, "Write IOPS", 1.0, 0.0, 0.0);
217         line_graph_set_data_count_limit(g, gfio_graph_limit);
218         graph_y_axis_unit_change_notify(g, iops_graph_y_axis_unit_change);
219         graph_add_extra_space(g, 0.005, 0.005, 0.03, 0.03);
220         return g;
221 }
222
223 static void bandwidth_graph_y_axis_unit_change(struct graph *g, int power_of_ten)
224 {
225         switch (power_of_ten) {
226                 case 9: graph_y_title(g, "Petabytes / sec");
227                         break;
228                 case 6: graph_y_title(g, "Gigabytes / sec");
229                         break;
230                 case 3: graph_y_title(g, "Megabytes / sec");
231                         break;
232                 case 0:
233                 default: graph_y_title(g, "Kilobytes / sec");
234                         break;
235         }
236 }
237
238 static struct graph *setup_bandwidth_graph(void)
239 {
240         struct graph *g;
241
242         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
243         graph_title(g, "Bandwidth");
244         graph_x_title(g, "Time (secs)");
245         graph_y_title(g, "Kbytes / sec");
246         graph_add_label(g, "Read Bandwidth");
247         graph_add_label(g, "Write Bandwidth");
248         graph_set_color(g, "Read Bandwidth", 0.13, 0.54, 0.13);
249         graph_set_color(g, "Write Bandwidth", 1.0, 0.0, 0.0);
250         line_graph_set_data_count_limit(g, 100);
251         graph_y_axis_unit_change_notify(g, bandwidth_graph_y_axis_unit_change);
252         graph_add_extra_space(g, 0.005, 0.005, 0.03, 0.03);
253
254         return g;
255 }
256
257 static void setup_graphs(struct gfio_graphs *g)
258 {
259         g->iops_graph = setup_iops_graph();
260         g->bandwidth_graph = setup_bandwidth_graph();
261 }
262
263 static void multitext_add_entry(struct multitext_widget *mt, const char *text)
264 {
265         mt->text = realloc(mt->text, (mt->max_text + 1) * sizeof(char *));
266         mt->text[mt->max_text] = strdup(text);
267         mt->max_text++;
268 }
269
270 static void multitext_set_entry(struct multitext_widget *mt, unsigned int index)
271 {
272         if (index >= mt->max_text)
273                 return;
274         if (!mt->text || !mt->text[index])
275                 return;
276
277         mt->cur_text = index;
278         gtk_entry_set_text(GTK_ENTRY(mt->entry), mt->text[index]);
279 }
280
281 static void multitext_update_entry(struct multitext_widget *mt,
282                                    unsigned int index, const char *text)
283 {
284         if (!mt->text)
285                 return;
286
287         if (mt->text[index])
288                 free(mt->text[index]);
289
290         mt->text[index] = strdup(text);
291         if (mt->cur_text == index)
292                 gtk_entry_set_text(GTK_ENTRY(mt->entry), mt->text[index]);
293 }
294
295 static void multitext_free(struct multitext_widget *mt)
296 {
297         int i;
298
299         gtk_entry_set_text(GTK_ENTRY(mt->entry), "");
300
301         for (i = 0; i < mt->max_text; i++) {
302                 if (mt->text[i])
303                         free(mt->text[i]);
304         }
305
306         free(mt->text);
307         mt->cur_text = -1;
308         mt->max_text = 0;
309 }
310
311 static void clear_ge_ui_info(struct gui_entry *ge)
312 {
313         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
314         gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
315         gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
316         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
317 #if 0
318         /* should we empty it... */
319         gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
320 #endif
321         multitext_update_entry(&ge->eta.iotype, 0, "");
322         multitext_update_entry(&ge->eta.ioengine, 0, "");
323         multitext_update_entry(&ge->eta.iodepth, 0, "");
324         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
325         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
326         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
327         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
328         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
329         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
330 }
331
332 static GtkWidget *new_combo_entry_in_frame(GtkWidget *box, const char *label)
333 {
334         GtkWidget *entry, *frame;
335
336         frame = gtk_frame_new(label);
337         entry = gtk_combo_box_new_text();
338         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
339         gtk_container_add(GTK_CONTAINER(frame), entry);
340
341         return entry;
342 }
343
344 static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
345 {
346         GtkWidget *entry, *frame;
347
348         frame = gtk_frame_new(label);
349         entry = gtk_entry_new();
350         gtk_entry_set_editable(GTK_ENTRY(entry), 0);
351         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
352         gtk_container_add(GTK_CONTAINER(frame), entry);
353
354         return entry;
355 }
356
357 static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
358 {
359         GtkWidget *label_widget;
360         GtkWidget *frame;
361
362         frame = gtk_frame_new(label);
363         label_widget = gtk_label_new(NULL);
364         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
365         gtk_container_add(GTK_CONTAINER(frame), label_widget);
366
367         return label_widget;
368 }
369
370 static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
371 {
372         GtkWidget *button, *box;
373
374         box = gtk_hbox_new(FALSE, 3);
375         gtk_container_add(GTK_CONTAINER(hbox), box);
376
377         button = gtk_spin_button_new_with_range(min, max, 1.0);
378         gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
379
380         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
381         gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
382
383         return button;
384 }
385
386 static void label_set_int_value(GtkWidget *entry, unsigned int val)
387 {
388         char tmp[80];
389
390         sprintf(tmp, "%u", val);
391         gtk_label_set_text(GTK_LABEL(entry), tmp);
392 }
393
394 static void entry_set_int_value(GtkWidget *entry, unsigned int val)
395 {
396         char tmp[80];
397
398         sprintf(tmp, "%u", val);
399         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
400 }
401
402 static void show_info_dialog(struct gui *ui, const char *title,
403                              const char *message)
404 {
405         GtkWidget *dialog, *content, *label;
406
407         dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window),
408                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
409                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
410
411         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
412         label = gtk_label_new(message);
413         gtk_container_add(GTK_CONTAINER(content), label);
414         gtk_widget_show_all(dialog);
415         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
416         gtk_dialog_run(GTK_DIALOG(dialog));
417         gtk_widget_destroy(dialog);
418 }
419
420 /*
421  * Update sensitivity of job buttons and job menu items, based on the
422  * state of the client.
423  */
424 static void update_button_states(struct gui *ui, struct gui_entry *ge)
425 {
426         unsigned int connect_state, send_state, start_state, edit_state;
427         const char *connect_str = NULL;
428         GtkWidget *w;
429
430         switch (ge->state) {
431         default: {
432                 char tmp[80];
433
434                 sprintf(tmp, "Bad client state: %u\n", ge->state);
435                 show_info_dialog(ui, "Error", tmp);
436                 /* fall through to new state */
437                 }
438
439         case GE_STATE_NEW:
440                 connect_state = 1;
441                 edit_state = 0;
442                 connect_str = "Connect";
443                 send_state = 0;
444                 start_state = 0;
445                 break;
446         case GE_STATE_CONNECTED:
447                 connect_state = 1;
448                 edit_state = 0;
449                 connect_str = "Disconnect";
450                 send_state = 1;
451                 start_state = 0;
452                 break;
453         case GE_STATE_JOB_SENT:
454                 connect_state = 1;
455                 edit_state = 0;
456                 connect_str = "Disconnect";
457                 send_state = 0;
458                 start_state = 1;
459                 break;
460         case GE_STATE_JOB_STARTED:
461                 connect_state = 1;
462                 edit_state = 1;
463                 connect_str = "Disconnect";
464                 send_state = 0;
465                 start_state = 1;
466                 break;
467         case GE_STATE_JOB_RUNNING:
468                 connect_state = 1;
469                 edit_state = 0;
470                 connect_str = "Disconnect";
471                 send_state = 0;
472                 start_state = 0;
473                 break;
474         case GE_STATE_JOB_DONE:
475                 connect_state = 1;
476                 edit_state = 0;
477                 connect_str = "Connect";
478                 send_state = 0;
479                 start_state = 0;
480                 break;
481         }
482
483         gtk_widget_set_sensitive(ge->button[CONNECT_BUTTON], connect_state);
484         gtk_widget_set_sensitive(ge->button[SEND_BUTTON], send_state);
485         gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], start_state);
486         gtk_button_set_label(GTK_BUTTON(ge->button[CONNECT_BUTTON]), connect_str);
487
488         /*
489          * So the below doesn't work at all, how to set those menu items
490          * invisibible...
491          */
492         w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Connect");
493         gtk_widget_set_sensitive(w, connect_state);
494         gtk_menu_item_set_label(GTK_MENU_ITEM(w), connect_str);
495
496         w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Edit job");
497         gtk_widget_set_sensitive(w, edit_state);
498
499         w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Send job");
500         gtk_widget_set_sensitive(w, send_state);
501
502         w = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu/Start job");
503         gtk_widget_set_sensitive(w, start_state);
504 }
505
506 static void gfio_set_state(struct gui_entry *ge, unsigned int state)
507 {
508         ge->state = state;
509         update_button_states(ge->ui, ge);
510 }
511
512 #define ALIGN_LEFT 1
513 #define ALIGN_RIGHT 2
514 #define INVISIBLE 4
515 #define UNSORTABLE 8
516
517 GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags)
518 {
519         GtkCellRenderer *renderer;
520         GtkTreeViewColumn *col;
521         double xalign = 0.0; /* left as default */
522         PangoAlignment align;
523         gboolean visible;
524
525         align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
526                 (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
527                 PANGO_ALIGN_CENTER;
528         visible = !(flags & INVISIBLE);
529
530         renderer = gtk_cell_renderer_text_new();
531         col = gtk_tree_view_column_new();
532
533         gtk_tree_view_column_set_title(col, title);
534         if (!(flags & UNSORTABLE))
535                 gtk_tree_view_column_set_sort_column_id(col, index);
536         gtk_tree_view_column_set_resizable(col, TRUE);
537         gtk_tree_view_column_pack_start(col, renderer, TRUE);
538         gtk_tree_view_column_add_attribute(col, renderer, "text", index);
539         gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
540         switch (align) {
541         case PANGO_ALIGN_LEFT:
542                 xalign = 0.0;
543                 break;
544         case PANGO_ALIGN_CENTER:
545                 xalign = 0.5;
546                 break;
547         case PANGO_ALIGN_RIGHT:
548                 xalign = 1.0;
549                 break;
550         }
551         gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
552         gtk_tree_view_column_set_visible(col, visible);
553         gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
554         return col;
555 }
556
557 static void gfio_ui_setup_log(struct gui *ui)
558 {
559         GtkTreeSelection *selection;
560         GtkListStore *model;
561         GtkWidget *tree_view;
562
563         model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
564
565         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
566         gtk_widget_set_can_focus(tree_view, FALSE);
567
568         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
569         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
570         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
571                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
572
573         tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
574         tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
575         tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
576         tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
577
578         ui->log_model = model;
579         ui->log_tree = tree_view;
580 }
581
582 static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
583                                                fio_fp64_t *plist,
584                                                unsigned int len,
585                                                const char *base,
586                                                unsigned int scale)
587 {
588         GType types[FIO_IO_U_LIST_MAX_LEN];
589         GtkWidget *tree_view;
590         GtkTreeSelection *selection;
591         GtkListStore *model;
592         GtkTreeIter iter;
593         int i;
594
595         for (i = 0; i < len; i++)
596                 types[i] = G_TYPE_INT;
597
598         model = gtk_list_store_newv(len, types);
599
600         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
601         gtk_widget_set_can_focus(tree_view, FALSE);
602
603         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
604                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
605
606         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
607         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
608
609         for (i = 0; i < len; i++) {
610                 char fbuf[8];
611
612                 sprintf(fbuf, "%2.2f%%", plist[i].u.f);
613                 tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
614         }
615
616         gtk_list_store_append(model, &iter);
617
618         for (i = 0; i < len; i++) {
619                 if (scale)
620                         ovals[i] = (ovals[i] + 999) / 1000;
621                 gtk_list_store_set(model, &iter, i, ovals[i], -1);
622         }
623
624         return tree_view;
625 }
626
627 static void gfio_show_clat_percentiles(GtkWidget *vbox, struct thread_stat *ts,
628                                        int ddir)
629 {
630         unsigned int *io_u_plat = ts->io_u_plat[ddir];
631         unsigned long nr = ts->clat_stat[ddir].samples;
632         fio_fp64_t *plist = ts->percentile_list;
633         unsigned int *ovals, len, minv, maxv, scale_down;
634         const char *base;
635         GtkWidget *tree_view, *frame, *hbox;
636         char tmp[64];
637
638         len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
639         if (!len)
640                 goto out;
641
642         /*
643          * We default to usecs, but if the value range is such that we
644          * should scale down to msecs, do that.
645          */
646         if (minv > 2000 && maxv > 99999) {
647                 scale_down = 1;
648                 base = "msec";
649         } else {
650                 scale_down = 0;
651                 base = "usec";
652         }
653
654         tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
655
656         sprintf(tmp, "Completion percentiles (%s)", base);
657         frame = gtk_frame_new(tmp);
658         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
659
660         hbox = gtk_hbox_new(FALSE, 3);
661         gtk_container_add(GTK_CONTAINER(frame), hbox);
662
663         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
664 out:
665         if (ovals)
666                 free(ovals);
667 }
668
669 static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
670                           unsigned long max, double mean, double dev)
671 {
672         const char *base = "(usec)";
673         GtkWidget *hbox, *label, *frame;
674         char *minp, *maxp;
675         char tmp[64];
676
677         if (!usec_to_msec(&min, &max, &mean, &dev))
678                 base = "(msec)";
679
680         minp = num2str(min, 6, 1, 0);
681         maxp = num2str(max, 6, 1, 0);
682
683         sprintf(tmp, "%s %s", name, base);
684         frame = gtk_frame_new(tmp);
685         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
686
687         hbox = gtk_hbox_new(FALSE, 3);
688         gtk_container_add(GTK_CONTAINER(frame), hbox);
689
690         label = new_info_label_in_frame(hbox, "Minimum");
691         gtk_label_set_text(GTK_LABEL(label), minp);
692         label = new_info_label_in_frame(hbox, "Maximum");
693         gtk_label_set_text(GTK_LABEL(label), maxp);
694         label = new_info_label_in_frame(hbox, "Average");
695         sprintf(tmp, "%5.02f", mean);
696         gtk_label_set_text(GTK_LABEL(label), tmp);
697         label = new_info_label_in_frame(hbox, "Standard deviation");
698         sprintf(tmp, "%5.02f", dev);
699         gtk_label_set_text(GTK_LABEL(label), tmp);
700
701         free(minp);
702         free(maxp);
703
704 }
705
706 #define GFIO_CLAT       1
707 #define GFIO_SLAT       2
708 #define GFIO_LAT        4
709
710 static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
711                                   struct thread_stat *ts, int ddir)
712 {
713         const char *ddir_label[2] = { "Read", "Write" };
714         GtkWidget *frame, *label, *box, *vbox, *main_vbox;
715         unsigned long min[3], max[3], runt;
716         unsigned long long bw, iops;
717         unsigned int flags = 0;
718         double mean[3], dev[3];
719         char *io_p, *bw_p, *iops_p;
720         int i2p;
721
722         if (!ts->runtime[ddir])
723                 return;
724
725         i2p = is_power_of_2(rs->kb_base);
726         runt = ts->runtime[ddir];
727
728         bw = (1000 * ts->io_bytes[ddir]) / runt;
729         io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
730         bw_p = num2str(bw, 6, 1, i2p);
731
732         iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
733         iops_p = num2str(iops, 6, 1, 0);
734
735         box = gtk_hbox_new(FALSE, 3);
736         gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
737
738         frame = gtk_frame_new(ddir_label[ddir]);
739         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
740
741         main_vbox = gtk_vbox_new(FALSE, 3);
742         gtk_container_add(GTK_CONTAINER(frame), main_vbox);
743
744         box = gtk_hbox_new(FALSE, 3);
745         gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
746
747         label = new_info_label_in_frame(box, "IO");
748         gtk_label_set_text(GTK_LABEL(label), io_p);
749         label = new_info_label_in_frame(box, "Bandwidth");
750         gtk_label_set_text(GTK_LABEL(label), bw_p);
751         label = new_info_label_in_frame(box, "IOPS");
752         gtk_label_set_text(GTK_LABEL(label), iops_p);
753         label = new_info_label_in_frame(box, "Runtime (msec)");
754         label_set_int_value(label, ts->runtime[ddir]);
755
756         if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
757                 double p_of_agg = 100.0;
758                 const char *bw_str = "KB";
759                 char tmp[32];
760
761                 if (rs->agg[ddir]) {
762                         p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
763                         if (p_of_agg > 100.0)
764                                 p_of_agg = 100.0;
765                 }
766
767                 if (mean[0] > 999999.9) {
768                         min[0] /= 1000.0;
769                         max[0] /= 1000.0;
770                         mean[0] /= 1000.0;
771                         dev[0] /= 1000.0;
772                         bw_str = "MB";
773                 }
774
775                 sprintf(tmp, "Bandwidth (%s)", bw_str);
776                 frame = gtk_frame_new(tmp);
777                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
778
779                 box = gtk_hbox_new(FALSE, 3);
780                 gtk_container_add(GTK_CONTAINER(frame), box);
781
782                 label = new_info_label_in_frame(box, "Minimum");
783                 label_set_int_value(label, min[0]);
784                 label = new_info_label_in_frame(box, "Maximum");
785                 label_set_int_value(label, max[0]);
786                 label = new_info_label_in_frame(box, "Percentage of jobs");
787                 sprintf(tmp, "%3.2f%%", p_of_agg);
788                 gtk_label_set_text(GTK_LABEL(label), tmp);
789                 label = new_info_label_in_frame(box, "Average");
790                 sprintf(tmp, "%5.02f", mean[0]);
791                 gtk_label_set_text(GTK_LABEL(label), tmp);
792                 label = new_info_label_in_frame(box, "Standard deviation");
793                 sprintf(tmp, "%5.02f", dev[0]);
794                 gtk_label_set_text(GTK_LABEL(label), tmp);
795         }
796
797         if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
798                 flags |= GFIO_SLAT;
799         if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
800                 flags |= GFIO_CLAT;
801         if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
802                 flags |= GFIO_LAT;
803
804         if (flags) {
805                 frame = gtk_frame_new("Latency");
806                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
807
808                 vbox = gtk_vbox_new(FALSE, 3);
809                 gtk_container_add(GTK_CONTAINER(frame), vbox);
810
811                 if (flags & GFIO_SLAT)
812                         gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
813                 if (flags & GFIO_CLAT)
814                         gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
815                 if (flags & GFIO_LAT)
816                         gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
817         }
818
819         if (ts->clat_percentiles)
820                 gfio_show_clat_percentiles(main_vbox, ts, ddir);
821
822
823         free(io_p);
824         free(bw_p);
825         free(iops_p);
826 }
827
828 static GtkWidget *gfio_output_lat_buckets(double *lat, unsigned int num,
829                                           const char **labels)
830 {
831         GtkWidget *tree_view;
832         GtkTreeSelection *selection;
833         GtkListStore *model;
834         GtkTreeIter iter;
835         GType *types;
836         int i, skipped;
837
838         /*
839          * Check if all are empty, in which case don't bother
840          */
841         for (i = 0, skipped = 0; i < num; i++)
842                 if (lat[i] <= 0.0)
843                         skipped++;
844
845         if (skipped == num)
846                 return NULL;
847
848         types = malloc(num * sizeof(GType));
849
850         for (i = 0; i < num; i++)
851                 types[i] = G_TYPE_STRING;
852
853         model = gtk_list_store_newv(num, types);
854         free(types);
855         types = NULL;
856
857         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
858         gtk_widget_set_can_focus(tree_view, FALSE);
859
860         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
861                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
862
863         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
864         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
865
866         for (i = 0; i < num; i++)
867                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
868
869         gtk_list_store_append(model, &iter);
870
871         for (i = 0; i < num; i++) {
872                 char fbuf[32];
873
874                 if (lat[i] <= 0.0)
875                         sprintf(fbuf, "0.00");
876                 else
877                         sprintf(fbuf, "%3.2f%%", lat[i]);
878
879                 gtk_list_store_set(model, &iter, i, fbuf, -1);
880         }
881
882         return tree_view;
883 }
884
885 static void gfio_show_latency_buckets(GtkWidget *vbox, struct thread_stat *ts)
886 {
887         GtkWidget *box, *frame, *tree_view;
888         double io_u_lat_u[FIO_IO_U_LAT_U_NR];
889         double io_u_lat_m[FIO_IO_U_LAT_M_NR];
890         const char *uranges[] = { "2", "4", "10", "20", "50", "100",
891                                   "250", "500", "750", "1000", };
892         const char *mranges[] = { "2", "4", "10", "20", "50", "100",
893                                   "250", "500", "750", "1000", "2000",
894                                   ">= 2000", };
895
896         stat_calc_lat_u(ts, io_u_lat_u);
897         stat_calc_lat_m(ts, io_u_lat_m);
898
899         tree_view = gfio_output_lat_buckets(io_u_lat_u, FIO_IO_U_LAT_U_NR, uranges);
900         if (tree_view) {
901                 frame = gtk_frame_new("Latency buckets (usec)");
902                 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
903
904                 box = gtk_hbox_new(FALSE, 3);
905                 gtk_container_add(GTK_CONTAINER(frame), box);
906                 gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
907         }
908
909         tree_view = gfio_output_lat_buckets(io_u_lat_m, FIO_IO_U_LAT_M_NR, mranges);
910         if (tree_view) {
911                 frame = gtk_frame_new("Latency buckets (msec)");
912                 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
913
914                 box = gtk_hbox_new(FALSE, 3);
915                 gtk_container_add(GTK_CONTAINER(frame), box);
916                 gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
917         }
918 }
919
920 static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
921 {
922         GtkWidget *box, *frame, *entry;
923         double usr_cpu, sys_cpu;
924         unsigned long runtime;
925         char tmp[32];
926
927         runtime = ts->total_run_time;
928         if (runtime) {
929                 double runt = (double) runtime;
930
931                 usr_cpu = (double) ts->usr_time * 100 / runt;
932                 sys_cpu = (double) ts->sys_time * 100 / runt;
933         } else {
934                 usr_cpu = 0;
935                 sys_cpu = 0;
936         }
937
938         frame = gtk_frame_new("OS resources");
939         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
940
941         box = gtk_hbox_new(FALSE, 3);
942         gtk_container_add(GTK_CONTAINER(frame), box);
943
944         entry = new_info_entry_in_frame(box, "User CPU");
945         sprintf(tmp, "%3.2f%%", usr_cpu);
946         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
947         entry = new_info_entry_in_frame(box, "System CPU");
948         sprintf(tmp, "%3.2f%%", sys_cpu);
949         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
950         entry = new_info_entry_in_frame(box, "Context switches");
951         entry_set_int_value(entry, ts->ctx);
952         entry = new_info_entry_in_frame(box, "Major faults");
953         entry_set_int_value(entry, ts->majf);
954         entry = new_info_entry_in_frame(box, "Minor faults");
955         entry_set_int_value(entry, ts->minf);
956 }
957 static void gfio_add_sc_depths_tree(GtkListStore *model,
958                                     struct thread_stat *ts, unsigned int len,
959                                     int submit)
960 {
961         double io_u_dist[FIO_IO_U_MAP_NR];
962         GtkTreeIter iter;
963         /* Bits 0, and 3-8 */
964         const int add_mask = 0x1f9;
965         int i, j;
966
967         if (submit)
968                 stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
969         else
970                 stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
971
972         gtk_list_store_append(model, &iter);
973
974         gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
975
976         for (i = 1, j = 0; i < len; i++) {
977                 char fbuf[32];
978
979                 if (!(add_mask & (1UL << (i - 1))))
980                         sprintf(fbuf, "0.0%%");
981                 else {
982                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
983                         j++;
984                 }
985
986                 gtk_list_store_set(model, &iter, i, fbuf, -1);
987         }
988
989 }
990
991 static void gfio_add_total_depths_tree(GtkListStore *model,
992                                        struct thread_stat *ts, unsigned int len)
993 {
994         double io_u_dist[FIO_IO_U_MAP_NR];
995         GtkTreeIter iter;
996         /* Bits 1-6, and 8 */
997         const int add_mask = 0x17e;
998         int i, j;
999
1000         stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
1001
1002         gtk_list_store_append(model, &iter);
1003
1004         gtk_list_store_set(model, &iter, 0, "Total", -1);
1005
1006         for (i = 1, j = 0; i < len; i++) {
1007                 char fbuf[32];
1008
1009                 if (!(add_mask & (1UL << (i - 1))))
1010                         sprintf(fbuf, "0.0%%");
1011                 else {
1012                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
1013                         j++;
1014                 }
1015
1016                 gtk_list_store_set(model, &iter, i, fbuf, -1);
1017         }
1018
1019 }
1020
1021 static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
1022 {
1023         GtkWidget *frame, *box, *tree_view;
1024         GtkTreeSelection *selection;
1025         GtkListStore *model;
1026         GType types[FIO_IO_U_MAP_NR + 1];
1027         int i;
1028 #define NR_LABELS       10
1029         const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
1030
1031         frame = gtk_frame_new("IO depths");
1032         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1033
1034         box = gtk_hbox_new(FALSE, 3);
1035         gtk_container_add(GTK_CONTAINER(frame), box);
1036
1037         for (i = 0; i < NR_LABELS; i++)
1038                 types[i] = G_TYPE_STRING;
1039
1040         model = gtk_list_store_newv(NR_LABELS, types);
1041
1042         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1043         gtk_widget_set_can_focus(tree_view, FALSE);
1044
1045         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
1046                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
1047
1048         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
1049         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
1050
1051         for (i = 0; i < NR_LABELS; i++)
1052                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
1053
1054         gfio_add_total_depths_tree(model, ts, NR_LABELS);
1055         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 1);
1056         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 0);
1057
1058         gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
1059 }
1060
1061 static gboolean results_window_delete(GtkWidget *w, gpointer data)
1062 {
1063         struct gui_entry *ge = (struct gui_entry *) data;
1064
1065         gtk_widget_destroy(w);
1066         ge->results_window = NULL;
1067         ge->results_notebook = NULL;
1068         return TRUE;
1069 }
1070
1071 static GtkWidget *get_results_window(struct gui_entry *ge)
1072 {
1073         GtkWidget *win, *notebook;
1074
1075         if (ge->results_window)
1076                 return ge->results_notebook;
1077
1078         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1079         gtk_window_set_title(GTK_WINDOW(win), "Results");
1080         gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
1081         g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
1082         g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
1083
1084         notebook = gtk_notebook_new();
1085         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
1086         gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
1087         gtk_container_add(GTK_CONTAINER(win), notebook);
1088
1089         ge->results_window = win;
1090         ge->results_notebook = notebook;
1091         return ge->results_notebook;
1092 }
1093
1094 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
1095                             struct group_run_stats *rs)
1096 {
1097         GtkWidget *res_win, *box, *vbox, *entry, *scroll;
1098         struct gfio_client *gc = client->client_data;
1099
1100         gdk_threads_enter();
1101
1102         res_win = get_results_window(gc->ge);
1103
1104         scroll = gtk_scrolled_window_new(NULL, NULL);
1105         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1106         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1107
1108         vbox = gtk_vbox_new(FALSE, 3);
1109
1110         box = gtk_hbox_new(FALSE, 0);
1111         gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
1112
1113         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1114
1115         gtk_notebook_append_page(GTK_NOTEBOOK(res_win), scroll, gtk_label_new(ts->name));
1116
1117         gc->results_widget = vbox;
1118
1119         entry = new_info_entry_in_frame(box, "Name");
1120         gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
1121         if (strlen(ts->description)) {
1122                 entry = new_info_entry_in_frame(box, "Description");
1123                 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
1124         }
1125         entry = new_info_entry_in_frame(box, "Group ID");
1126         entry_set_int_value(entry, ts->groupid);
1127         entry = new_info_entry_in_frame(box, "Jobs");
1128         entry_set_int_value(entry, ts->members);
1129         gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
1130         entry_set_int_value(entry, ts->error);
1131         entry = new_info_entry_in_frame(box, "PID");
1132         entry_set_int_value(entry, ts->pid);
1133
1134         if (ts->io_bytes[DDIR_READ])
1135                 gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
1136         if (ts->io_bytes[DDIR_WRITE])
1137                 gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
1138
1139         gfio_show_latency_buckets(vbox, ts);
1140         gfio_show_cpu_usage(vbox, ts);
1141         gfio_show_io_depths(vbox, ts);
1142
1143         gtk_widget_show_all(gc->ge->results_window);
1144         gdk_threads_leave();
1145 }
1146
1147 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
1148 {
1149         struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
1150         struct gui *ui = &main_ui;
1151         GtkTreeIter iter;
1152         struct tm *tm;
1153         time_t sec;
1154         char tmp[64], timebuf[80];
1155
1156         sec = p->log_sec;
1157         tm = localtime(&sec);
1158         strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm);
1159         sprintf(timebuf, "%s.%03ld", tmp, p->log_usec / 1000);
1160
1161         gdk_threads_enter();
1162
1163         gtk_list_store_append(ui->log_model, &iter);
1164         gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
1165         gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
1166         gtk_list_store_set(ui->log_model, &iter, 2, p->level, -1);
1167         gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
1168
1169         if (p->level == FIO_LOG_ERR)
1170                 view_log(NULL, (gpointer) ui);
1171
1172         gdk_threads_leave();
1173 }
1174
1175 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
1176 {
1177         struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
1178         struct gfio_client *gc = client->client_data;
1179         GtkWidget *box, *frame, *entry, *vbox;
1180         double util;
1181         char tmp[16];
1182
1183         gdk_threads_enter();
1184
1185         if (!gc->results_widget)
1186                 goto out;
1187
1188         if (!gc->disk_util_frame) {
1189                 gc->disk_util_frame = gtk_frame_new("Disk utilization");
1190                 gtk_box_pack_start(GTK_BOX(gc->results_widget), gc->disk_util_frame, FALSE, FALSE, 5);
1191         }
1192
1193         vbox = gtk_vbox_new(FALSE, 3);
1194         gtk_container_add(GTK_CONTAINER(gc->disk_util_frame), vbox);
1195
1196         frame = gtk_frame_new((char *) p->dus.name);
1197         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
1198
1199         box = gtk_vbox_new(FALSE, 3);
1200         gtk_container_add(GTK_CONTAINER(frame), box);
1201
1202         frame = gtk_frame_new("Read");
1203         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1204         vbox = gtk_hbox_new(TRUE, 3);
1205         gtk_container_add(GTK_CONTAINER(frame), vbox);
1206         entry = new_info_entry_in_frame(vbox, "IOs");
1207         entry_set_int_value(entry, p->dus.ios[0]);
1208         entry = new_info_entry_in_frame(vbox, "Merges");
1209         entry_set_int_value(entry, p->dus.merges[0]);
1210         entry = new_info_entry_in_frame(vbox, "Sectors");
1211         entry_set_int_value(entry, p->dus.sectors[0]);
1212         entry = new_info_entry_in_frame(vbox, "Ticks");
1213         entry_set_int_value(entry, p->dus.ticks[0]);
1214
1215         frame = gtk_frame_new("Write");
1216         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1217         vbox = gtk_hbox_new(TRUE, 3);
1218         gtk_container_add(GTK_CONTAINER(frame), vbox);
1219         entry = new_info_entry_in_frame(vbox, "IOs");
1220         entry_set_int_value(entry, p->dus.ios[1]);
1221         entry = new_info_entry_in_frame(vbox, "Merges");
1222         entry_set_int_value(entry, p->dus.merges[1]);
1223         entry = new_info_entry_in_frame(vbox, "Sectors");
1224         entry_set_int_value(entry, p->dus.sectors[1]);
1225         entry = new_info_entry_in_frame(vbox, "Ticks");
1226         entry_set_int_value(entry, p->dus.ticks[1]);
1227
1228         frame = gtk_frame_new("Shared");
1229         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1230         vbox = gtk_hbox_new(TRUE, 3);
1231         gtk_container_add(GTK_CONTAINER(frame), vbox);
1232         entry = new_info_entry_in_frame(vbox, "IO ticks");
1233         entry_set_int_value(entry, p->dus.io_ticks);
1234         entry = new_info_entry_in_frame(vbox, "Time in queue");
1235         entry_set_int_value(entry, p->dus.time_in_queue);
1236
1237         util = 0.0;
1238         if (p->dus.msec)
1239                 util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec;
1240         if (util > 100.0)
1241                 util = 100.0;
1242
1243         sprintf(tmp, "%3.2f%%", util);
1244         entry = new_info_entry_in_frame(vbox, "Disk utilization");
1245         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1246
1247         gtk_widget_show_all(gc->results_widget);
1248 out:
1249         gdk_threads_leave();
1250 }
1251
1252 extern int sum_stat_clients;
1253 extern struct thread_stat client_ts;
1254 extern struct group_run_stats client_gs;
1255
1256 static int sum_stat_nr;
1257
1258 static void gfio_thread_status_op(struct fio_client *client,
1259                                   struct fio_net_cmd *cmd)
1260 {
1261         struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
1262
1263         gfio_display_ts(client, &p->ts, &p->rs);
1264
1265         if (sum_stat_clients == 1)
1266                 return;
1267
1268         sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
1269         sum_group_stats(&client_gs, &p->rs);
1270
1271         client_ts.members++;
1272         client_ts.groupid = p->ts.groupid;
1273
1274         if (++sum_stat_nr == sum_stat_clients) {
1275                 strcpy(client_ts.name, "All clients");
1276                 gfio_display_ts(client, &client_ts, &client_gs);
1277         }
1278 }
1279
1280 static void gfio_group_stats_op(struct fio_client *client,
1281                                 struct fio_net_cmd *cmd)
1282 {
1283         /* We're ignoring group stats for now */
1284 }
1285
1286 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
1287                                    gpointer data)
1288 {
1289         struct gfio_graphs *g = data;
1290
1291         graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
1292         graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
1293         graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
1294         graph_set_position(g->bandwidth_graph, 0, 0);
1295         return TRUE;
1296 }
1297
1298 static void draw_graph(struct graph *g, cairo_t *cr)
1299 {
1300         line_graph_draw(g, cr);
1301         cairo_stroke(cr);
1302 }
1303
1304 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
1305                               gboolean keyboard_mode, GtkTooltip *tooltip,
1306                               gpointer data)
1307 {
1308         struct gfio_graphs *g = data;
1309         const char *text = NULL;
1310
1311         if (graph_contains_xy(g->iops_graph, x, y))
1312                 text = graph_find_tooltip(g->iops_graph, x, y);
1313         else if (graph_contains_xy(g->bandwidth_graph, x, y))
1314                 text = graph_find_tooltip(g->bandwidth_graph, x, y);
1315
1316         if (text) {
1317                 gtk_tooltip_set_text(tooltip, text);
1318                 return TRUE;
1319         }
1320
1321         return FALSE;
1322 }
1323
1324 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
1325 {
1326         struct gfio_graphs *g = p;
1327         cairo_t *cr;
1328
1329         cr = gdk_cairo_create(w->window);
1330
1331         if (graph_has_tooltips(g->iops_graph) ||
1332             graph_has_tooltips(g->bandwidth_graph)) {
1333                 g_object_set(w, "has-tooltip", TRUE, NULL);
1334                 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
1335         }
1336
1337         cairo_set_source_rgb(cr, 0, 0, 0);
1338         draw_graph(g->iops_graph, cr);
1339         draw_graph(g->bandwidth_graph, cr);
1340         cairo_destroy(cr);
1341
1342         return FALSE;
1343 }
1344
1345 /*
1346  * Client specific ETA
1347  */
1348 static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
1349 {
1350         struct gfio_client *gc = client->client_data;
1351         struct gui_entry *ge = gc->ge;
1352         static int eta_good;
1353         char eta_str[128];
1354         char output[256];
1355         char tmp[32];
1356         double perc = 0.0;
1357         int i2p = 0;
1358
1359         gdk_threads_enter();
1360
1361         eta_str[0] = '\0';
1362         output[0] = '\0';
1363
1364         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1365                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1366                 eta_to_str(eta_str, je->eta_sec);
1367         }
1368
1369         sprintf(tmp, "%u", je->nr_running);
1370         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
1371         sprintf(tmp, "%u", je->files_open);
1372         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
1373
1374 #if 0
1375         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1376         if (je->m_rate || je->t_rate) {
1377                 char *tr, *mr;
1378
1379                 mr = num2str(je->m_rate, 4, 0, i2p);
1380                 tr = num2str(je->t_rate, 4, 0, i2p);
1381                 gtk_entry_set_text(GTK_ENTRY(ge->eta);
1382                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1383                 free(tr);
1384                 free(mr);
1385         } else if (je->m_iops || je->t_iops)
1386                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1387
1388         gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
1389         gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
1390         gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
1391         gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
1392 #endif
1393
1394         if (je->eta_sec != INT_MAX && je->nr_running) {
1395                 char *iops_str[2];
1396                 char *rate_str[2];
1397
1398                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1399                         strcpy(output, "-.-% done");
1400                 else {
1401                         eta_good = 1;
1402                         perc *= 100.0;
1403                         sprintf(output, "%3.1f%% done", perc);
1404                 }
1405
1406                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1407                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1408
1409                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1410                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1411
1412                 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
1413                 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
1414                 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
1415                 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
1416
1417                 graph_add_xy_data(ge->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1418                 graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1419                 graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1420                 graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1421
1422                 free(rate_str[0]);
1423                 free(rate_str[1]);
1424                 free(iops_str[0]);
1425                 free(iops_str[1]);
1426         }
1427
1428         if (eta_str[0]) {
1429                 char *dst = output + strlen(output);
1430
1431                 sprintf(dst, " - %s", eta_str);
1432         }
1433                 
1434         gfio_update_thread_status(ge, output, perc);
1435         gdk_threads_leave();
1436 }
1437
1438 /*
1439  * Update ETA in main window for all clients
1440  */
1441 static void gfio_update_all_eta(struct jobs_eta *je)
1442 {
1443         struct gui *ui = &main_ui;
1444         static int eta_good;
1445         char eta_str[128];
1446         char output[256];
1447         double perc = 0.0;
1448         int i2p = 0;
1449
1450         gdk_threads_enter();
1451
1452         eta_str[0] = '\0';
1453         output[0] = '\0';
1454
1455         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1456                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1457                 eta_to_str(eta_str, je->eta_sec);
1458         }
1459
1460 #if 0
1461         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1462         if (je->m_rate || je->t_rate) {
1463                 char *tr, *mr;
1464
1465                 mr = num2str(je->m_rate, 4, 0, i2p);
1466                 tr = num2str(je->t_rate, 4, 0, i2p);
1467                 gtk_entry_set_text(GTK_ENTRY(ui->eta);
1468                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1469                 free(tr);
1470                 free(mr);
1471         } else if (je->m_iops || je->t_iops)
1472                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1473
1474         gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
1475         gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
1476         gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
1477         gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
1478 #endif
1479
1480         entry_set_int_value(ui->eta.jobs, je->nr_running);
1481
1482         if (je->eta_sec != INT_MAX && je->nr_running) {
1483                 char *iops_str[2];
1484                 char *rate_str[2];
1485
1486                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1487                         strcpy(output, "-.-% done");
1488                 else {
1489                         eta_good = 1;
1490                         perc *= 100.0;
1491                         sprintf(output, "%3.1f%% done", perc);
1492                 }
1493
1494                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1495                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1496
1497                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1498                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1499
1500                 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
1501                 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
1502                 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
1503                 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
1504
1505                 graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1506                 graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1507                 graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1508                 graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1509
1510                 free(rate_str[0]);
1511                 free(rate_str[1]);
1512                 free(iops_str[0]);
1513                 free(iops_str[1]);
1514         }
1515
1516         if (eta_str[0]) {
1517                 char *dst = output + strlen(output);
1518
1519                 sprintf(dst, " - %s", eta_str);
1520         }
1521                 
1522         gfio_update_thread_status_all(output, perc);
1523         gdk_threads_leave();
1524 }
1525
1526 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
1527 {
1528         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
1529         struct gfio_client *gc = client->client_data;
1530         struct gui_entry *ge = gc->ge;
1531         const char *os, *arch;
1532         char buf[64];
1533
1534         os = fio_get_os_string(probe->os);
1535         if (!os)
1536                 os = "unknown";
1537
1538         arch = fio_get_arch_string(probe->arch);
1539         if (!arch)
1540                 os = "unknown";
1541
1542         if (!client->name)
1543                 client->name = strdup((char *) probe->hostname);
1544
1545         gdk_threads_enter();
1546
1547         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
1548         gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
1549         gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
1550         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
1551         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
1552
1553         gfio_set_state(ge, GE_STATE_CONNECTED);
1554
1555         gdk_threads_leave();
1556 }
1557
1558 static void gfio_update_thread_status(struct gui_entry *ge,
1559                                       char *status_message, double perc)
1560 {
1561         static char message[100];
1562         const char *m = message;
1563
1564         strncpy(message, status_message, sizeof(message) - 1);
1565         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
1566         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
1567         gtk_widget_queue_draw(main_ui.window);
1568 }
1569
1570 static void gfio_update_thread_status_all(char *status_message, double perc)
1571 {
1572         struct gui *ui = &main_ui;
1573         static char message[100];
1574         const char *m = message;
1575
1576         strncpy(message, status_message, sizeof(message) - 1);
1577         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
1578         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
1579         gtk_widget_queue_draw(ui->window);
1580 }
1581
1582 static void gfio_quit_op(struct fio_client *client)
1583 {
1584         struct gfio_client *gc = client->client_data;
1585
1586         gdk_threads_enter();
1587         gfio_set_state(gc->ge, GE_STATE_NEW);
1588         gdk_threads_leave();
1589 }
1590
1591 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
1592 {
1593         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
1594         struct gfio_client *gc = client->client_data;
1595         struct thread_options *o = &gc->o;
1596         struct gui_entry *ge = gc->ge;
1597         char tmp[8];
1598
1599         convert_thread_options_to_cpu(o, &p->top);
1600
1601         gdk_threads_enter();
1602
1603         gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
1604
1605         gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
1606         gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
1607
1608         multitext_add_entry(&ge->eta.iotype, ddir_str(o->td_ddir));
1609         multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
1610
1611         sprintf(tmp, "%u", o->iodepth);
1612         multitext_add_entry(&ge->eta.iodepth, tmp);
1613
1614         multitext_set_entry(&ge->eta.iotype, 0);
1615         multitext_set_entry(&ge->eta.ioengine, 0);
1616         multitext_set_entry(&ge->eta.iodepth, 0);
1617
1618         gc->job_added++;
1619
1620         gfio_set_state(ge, GE_STATE_JOB_SENT);
1621
1622         gdk_threads_leave();
1623 }
1624
1625 static void gfio_client_timed_out(struct fio_client *client)
1626 {
1627         struct gfio_client *gc = client->client_data;
1628         char buf[256];
1629
1630         gdk_threads_enter();
1631
1632         gfio_set_state(gc->ge, GE_STATE_NEW);
1633         clear_ge_ui_info(gc->ge);
1634
1635         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
1636         show_info_dialog(gc->ge->ui, "Network timeout", buf);
1637
1638         gdk_threads_leave();
1639 }
1640
1641 static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
1642 {
1643         struct gfio_client *gc = client->client_data;
1644
1645         gdk_threads_enter();
1646
1647         gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
1648
1649         if (gc->err_entry)
1650                 entry_set_int_value(gc->err_entry, client->error);
1651
1652         gdk_threads_leave();
1653 }
1654
1655 static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
1656 {
1657         struct gfio_client *gc = client->client_data;
1658
1659         gdk_threads_enter();
1660         gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
1661         gdk_threads_leave();
1662 }
1663
1664 static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
1665 {
1666         struct gfio_client *gc = client->client_data;
1667
1668         gdk_threads_enter();
1669         gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
1670         gdk_threads_leave();
1671 }
1672
1673 struct client_ops gfio_client_ops = {
1674         .text_op                = gfio_text_op,
1675         .disk_util              = gfio_disk_util_op,
1676         .thread_status          = gfio_thread_status_op,
1677         .group_stats            = gfio_group_stats_op,
1678         .jobs_eta               = gfio_update_client_eta,
1679         .eta                    = gfio_update_all_eta,
1680         .probe                  = gfio_probe_op,
1681         .quit                   = gfio_quit_op,
1682         .add_job                = gfio_add_job_op,
1683         .timed_out              = gfio_client_timed_out,
1684         .stop                   = gfio_client_stop,
1685         .start                  = gfio_client_start,
1686         .job_start              = gfio_client_job_start,
1687         .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
1688         .stay_connected         = 1,
1689 };
1690
1691 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1692                 __attribute__((unused)) gpointer data)
1693 {
1694         gtk_main_quit();
1695 }
1696
1697 static void *job_thread(void *arg)
1698 {
1699         struct gui *ui = arg;
1700
1701         ui->handler_running = 1;
1702         fio_handle_clients(&gfio_client_ops);
1703         ui->handler_running = 0;
1704         return NULL;
1705 }
1706
1707 static int send_job_files(struct gui_entry *ge)
1708 {
1709         struct gfio_client *gc = ge->client;
1710         int i, ret = 0;
1711
1712         for (i = 0; i < ge->nr_job_files; i++) {
1713                 ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1714                 if (ret < 0) {
1715                         GError *error;
1716
1717                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1718                         report_error(error);
1719                         g_error_free(error);
1720                         break;
1721                 } else if (ret)
1722                         break;
1723
1724                 free(ge->job_files[i]);
1725                 ge->job_files[i] = NULL;
1726         }
1727         while (i < ge->nr_job_files) {
1728                 free(ge->job_files[i]);
1729                 ge->job_files[i] = NULL;
1730                 i++;
1731         }
1732
1733         ge->nr_job_files = 0;
1734         return ret;
1735 }
1736
1737 static void *server_thread(void *arg)
1738 {
1739         is_backend = 1;
1740         gfio_server_running = 1;
1741         fio_start_server(NULL);
1742         gfio_server_running = 0;
1743         return NULL;
1744 }
1745
1746 static void gfio_start_server(void)
1747 {
1748         struct gui *ui = &main_ui;
1749
1750         if (!gfio_server_running) {
1751                 gfio_server_running = 1;
1752                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1753                 pthread_detach(ui->server_t);
1754         }
1755 }
1756
1757 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1758                 gpointer data)
1759 {
1760         struct gui_entry *ge = data;
1761         struct gfio_client *gc = ge->client;
1762
1763         if (gc)
1764                 fio_start_client(gc->client);
1765 }
1766
1767 static void file_open(GtkWidget *w, gpointer data);
1768
1769 static void connect_clicked(GtkWidget *widget, gpointer data)
1770 {
1771         struct gui_entry *ge = data;
1772         struct gfio_client *gc = ge->client;
1773
1774         if (ge->state == GE_STATE_NEW) {
1775                 int ret;
1776
1777                 if (!ge->nr_job_files)
1778                         file_open(widget, ge->ui);
1779                 if (!ge->nr_job_files)
1780                         return;
1781
1782                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
1783                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1784                 ret = fio_client_connect(gc->client);
1785                 if (!ret) {
1786                         if (!ge->ui->handler_running)
1787                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
1788                         gfio_set_state(ge, GE_STATE_CONNECTED);
1789                 } else {
1790                         GError *error;
1791
1792                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
1793                         report_error(error);
1794                         g_error_free(error);
1795                 }
1796         } else {
1797                 fio_client_terminate(gc->client);
1798                 gfio_set_state(ge, GE_STATE_NEW);
1799                 clear_ge_ui_info(ge);
1800         }
1801 }
1802
1803 static void send_clicked(GtkWidget *widget, gpointer data)
1804 {
1805         struct gui_entry *ge = data;
1806
1807         if (send_job_files(ge)) {
1808                 GError *error;
1809
1810                 error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send one or more job files for client %s", ge->client->client->hostname);
1811                 report_error(error);
1812                 g_error_free(error);
1813
1814                 gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
1815         }
1816 }
1817
1818 static GtkWidget *add_button(GtkWidget *buttonbox,
1819                              struct button_spec *buttonspec, gpointer data)
1820 {
1821         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
1822
1823         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
1824         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
1825         gtk_widget_set_tooltip_text(button, buttonspec->tooltiptext);
1826         gtk_widget_set_sensitive(button, !buttonspec->start_insensitive);
1827
1828         return button;
1829 }
1830
1831 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
1832                         int nbuttons)
1833 {
1834         int i;
1835
1836         for (i = 0; i < nbuttons; i++)
1837                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
1838 }
1839
1840 static void on_info_bar_response(GtkWidget *widget, gint response,
1841                                  gpointer data)
1842 {
1843         struct gui *ui = &main_ui;
1844
1845         if (response == GTK_RESPONSE_OK) {
1846                 gtk_widget_destroy(widget);
1847                 ui->error_info_bar = NULL;
1848         }
1849 }
1850
1851 void report_error(GError *error)
1852 {
1853         struct gui *ui = &main_ui;
1854
1855         if (ui->error_info_bar == NULL) {
1856                 ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1857                                                                GTK_RESPONSE_OK,
1858                                                                NULL);
1859                 g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1860                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
1861                                               GTK_MESSAGE_ERROR);
1862                 
1863                 ui->error_label = gtk_label_new(error->message);
1864                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
1865                 gtk_container_add(GTK_CONTAINER(container), ui->error_label);
1866                 
1867                 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
1868                 gtk_widget_show_all(ui->vbox);
1869         } else {
1870                 char buffer[256];
1871                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1872                 gtk_label_set(GTK_LABEL(ui->error_label), buffer);
1873         }
1874 }
1875
1876 struct connection_widgets
1877 {
1878         GtkWidget *hentry;
1879         GtkWidget *combo;
1880         GtkWidget *button;
1881 };
1882
1883 static void hostname_cb(GtkEntry *entry, gpointer data)
1884 {
1885         struct connection_widgets *cw = data;
1886         int uses_net = 0, is_localhost = 0;
1887         const gchar *text;
1888         gchar *ctext;
1889
1890         /*
1891          * Check whether to display the 'auto start backend' box
1892          * or not. Show it if we are a localhost and using network,
1893          * or using a socket.
1894          */
1895         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1896         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1897                 uses_net = 1;
1898         g_free(ctext);
1899
1900         if (uses_net) {
1901                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1902                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1903                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1904                     !strcmp(text, "ip6-loopback"))
1905                         is_localhost = 1;
1906         }
1907
1908         if (!uses_net || is_localhost) {
1909                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1910                 gtk_widget_set_sensitive(cw->button, 1);
1911         } else {
1912                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1913                 gtk_widget_set_sensitive(cw->button, 0);
1914         }
1915 }
1916
1917 static int get_connection_details(char **host, int *port, int *type,
1918                                   int *server_start)
1919 {
1920         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1921         struct connection_widgets cw;
1922         char *typeentry;
1923
1924         dialog = gtk_dialog_new_with_buttons("Connection details",
1925                         GTK_WINDOW(main_ui.window),
1926                         GTK_DIALOG_DESTROY_WITH_PARENT,
1927                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1928                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1929
1930         frame = gtk_frame_new("Hostname / socket name");
1931         /* gtk_dialog_get_content_area() is 2.14 and newer */
1932         vbox = GTK_DIALOG(dialog)->vbox;
1933         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1934
1935         box = gtk_vbox_new(FALSE, 6);
1936         gtk_container_add(GTK_CONTAINER(frame), box);
1937
1938         hbox = gtk_hbox_new(TRUE, 10);
1939         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1940         cw.hentry = gtk_entry_new();
1941         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1942         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
1943
1944         frame = gtk_frame_new("Port");
1945         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1946         box = gtk_vbox_new(FALSE, 10);
1947         gtk_container_add(GTK_CONTAINER(frame), box);
1948
1949         hbox = gtk_hbox_new(TRUE, 4);
1950         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1951         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
1952
1953         frame = gtk_frame_new("Type");
1954         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1955         box = gtk_vbox_new(FALSE, 10);
1956         gtk_container_add(GTK_CONTAINER(frame), box);
1957
1958         hbox = gtk_hbox_new(TRUE, 4);
1959         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1960
1961         cw.combo = gtk_combo_box_new_text();
1962         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
1963         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
1964         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
1965         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
1966
1967         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
1968
1969         frame = gtk_frame_new("Options");
1970         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1971         box = gtk_vbox_new(FALSE, 10);
1972         gtk_container_add(GTK_CONTAINER(frame), box);
1973
1974         hbox = gtk_hbox_new(TRUE, 4);
1975         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1976
1977         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
1978         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
1979         gtk_widget_set_tooltip_text(cw.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.");
1980         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
1981
1982         /*
1983          * Connect edit signal, so we can show/not-show the auto start button
1984          */
1985         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
1986         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
1987
1988         gtk_widget_show_all(dialog);
1989
1990         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1991                 gtk_widget_destroy(dialog);
1992                 return 1;
1993         }
1994
1995         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
1996         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
1997
1998         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
1999         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
2000                 *type = Fio_client_ipv4;
2001         else if (!strncmp(typeentry, "IPv6", 4))
2002                 *type = Fio_client_ipv6;
2003         else
2004                 *type = Fio_client_socket;
2005         g_free(typeentry);
2006
2007         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2008
2009         gtk_widget_destroy(dialog);
2010         return 0;
2011 }
2012
2013 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
2014 {
2015         struct gfio_client *gc;
2016
2017         gc = malloc(sizeof(*gc));
2018         memset(gc, 0, sizeof(*gc));
2019         gc->ge = ge;
2020         gc->client = fio_get_client(client);
2021
2022         ge->client = gc;
2023
2024         client->client_data = gc;
2025 }
2026
2027 static GtkWidget *new_client_page(struct gui_entry *ge);
2028
2029 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2030 {
2031         struct gui_entry *ge;
2032
2033         ge = malloc(sizeof(*ge));
2034         memset(ge, 0, sizeof(*ge));
2035         ge->state = GE_STATE_NEW;
2036         INIT_FLIST_HEAD(&ge->list);
2037         flist_add_tail(&ge->list, &ui->list);
2038         ge->ui = ui;
2039         return ge;
2040 }
2041
2042 /*
2043  * FIXME: need more handling here
2044  */
2045 static void ge_destroy(GtkWidget *w, gpointer data)
2046 {
2047         struct gui_entry *ge = data;
2048         struct gfio_client *gc = ge->client;
2049
2050         if (gc && gc->client) {
2051                 if (ge->state >= GE_STATE_CONNECTED)
2052                         fio_client_terminate(gc->client);
2053
2054                 fio_put_client(gc->client);
2055         }
2056
2057         flist_del(&ge->list);
2058         free(ge);
2059 }
2060
2061 static struct gui_entry *get_new_ge_with_tab(const char *name)
2062 {
2063         struct gui_entry *ge;
2064
2065         ge = alloc_new_gui_entry(&main_ui);
2066
2067         ge->vbox = new_client_page(ge);
2068         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_destroy), ge);
2069
2070         ge->page_label = gtk_label_new(name);
2071         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
2072
2073         gtk_widget_show_all(main_ui.window);
2074         return ge;
2075 }
2076
2077 static void file_new(GtkWidget *w, gpointer data)
2078 {
2079         struct gui *ui = (struct gui *) data;
2080         struct gui_entry *ge;
2081
2082         ge = get_new_ge_with_tab("Untitled");
2083         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2084 }
2085
2086 /*
2087  * Return the 'ge' corresponding to the tab. If the active tab is the
2088  * main tab, open a new tab.
2089  */
2090 static struct gui_entry *get_ge_from_page(gint cur_page, int *created)
2091 {
2092         struct flist_head *entry;
2093         struct gui_entry *ge;
2094
2095         if (!cur_page) {
2096                 if (created)
2097                         *created = 1;
2098                 return get_new_ge_with_tab("Untitled");
2099         }
2100
2101         if (created)
2102                 *created = 0;
2103
2104         flist_for_each(entry, &main_ui.list) {
2105                 ge = flist_entry(entry, struct gui_entry, list);
2106                 if (ge->page_num == cur_page)
2107                         return ge;
2108         }
2109
2110         return NULL;
2111 }
2112
2113 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2114 {
2115         gint cur_page;
2116
2117         /*
2118          * Main tab is tab 0, so any current page other than 0 holds
2119          * a ge entry.
2120          */
2121         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2122         if (cur_page)
2123                 return get_ge_from_page(cur_page, NULL);
2124
2125         return NULL;
2126 }
2127
2128 static void file_close(GtkWidget *w, gpointer data)
2129 {
2130         struct gui *ui = (struct gui *) data;
2131         struct gui_entry *ge;
2132
2133         /*
2134          * Can't close the main tab
2135          */
2136         ge = get_ge_from_cur_tab(ui);
2137         if (ge) {
2138                 gtk_widget_destroy(ge->vbox);
2139                 return;
2140         }
2141
2142         if (!flist_empty(&ui->list)) {
2143                 show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2144                 return;
2145         }
2146
2147         gtk_main_quit();
2148 }
2149
2150 static void file_add_recent(struct gui *ui, const gchar *uri)
2151 {
2152         gtk_recent_manager_add_item(ui->recentmanager, uri);
2153 }
2154
2155 static gchar *get_filename_from_uri(const gchar *uri)
2156 {
2157         if (strncmp(uri, "file://", 7))
2158                 return strdup(uri);
2159
2160         return strdup(uri + 7);
2161 }
2162
2163 static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2164                         int type, int port)
2165 {
2166         struct fio_client *client;
2167         gchar *filename;
2168
2169         filename = get_filename_from_uri(uri);
2170
2171         ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2172         ge->job_files[ge->nr_job_files] = strdup(filename);
2173         ge->nr_job_files++;
2174
2175         client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2176         if (!client) {
2177                 GError *error;
2178
2179                 error = g_error_new(g_quark_from_string("fio"), 1,
2180                                 "Failed to add client %s", host);
2181                 report_error(error);
2182                 g_error_free(error);
2183                 return 1;
2184         }
2185
2186         gfio_client_added(ge, client);
2187         file_add_recent(ge->ui, uri);
2188         return 0;
2189 }
2190
2191 static void file_open(GtkWidget *w, gpointer data)
2192 {
2193         struct gui *ui = data;
2194         GtkWidget *dialog;
2195         GSList *filenames, *fn_glist;
2196         GtkFileFilter *filter;
2197         int port, type, server_start, ge_is_new = 0;
2198         struct gui_entry *ge;
2199         gint cur_page;
2200         char *host;
2201
2202         /*
2203          * Creates new tab if current tab is the main window, or the
2204          * current tab already has a client.
2205          */
2206         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2207         ge = get_ge_from_page(cur_page, &ge_is_new);
2208         if (ge->client) {
2209                 ge = get_new_ge_with_tab("Untitled");
2210                 ge_is_new = 1;
2211         }
2212
2213         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2214
2215         dialog = gtk_file_chooser_dialog_new("Open File",
2216                 GTK_WINDOW(ui->window),
2217                 GTK_FILE_CHOOSER_ACTION_OPEN,
2218                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2219                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2220                 NULL);
2221         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2222
2223         filter = gtk_file_filter_new();
2224         gtk_file_filter_add_pattern(filter, "*.fio");
2225         gtk_file_filter_add_pattern(filter, "*.job");
2226         gtk_file_filter_add_pattern(filter, "*.ini");
2227         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2228         gtk_file_filter_set_name(filter, "Fio job file");
2229         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2230
2231         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2232                 if (ge_is_new)
2233                         gtk_widget_destroy(ge->vbox);
2234
2235                 gtk_widget_destroy(dialog);
2236                 return;
2237         }
2238
2239         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2240
2241         gtk_widget_destroy(dialog);
2242
2243         if (get_connection_details(&host, &port, &type, &server_start)) {
2244                 if (ge_is_new)
2245                         gtk_widget_destroy(ge->vbox);
2246         
2247                 goto done;
2248         }
2249
2250         filenames = fn_glist;
2251         while (filenames != NULL) {
2252                 do_file_open(ge, filenames->data, host, type, port);
2253                 g_free(filenames->data);
2254                 filenames = g_slist_next(filenames);
2255         }
2256         free(host);
2257
2258         if (server_start)
2259                 gfio_start_server();
2260 done:
2261         g_slist_free(fn_glist);
2262
2263 }
2264
2265 static void file_save(GtkWidget *w, gpointer data)
2266 {
2267         struct gui *ui = data;
2268         GtkWidget *dialog;
2269
2270         dialog = gtk_file_chooser_dialog_new("Save File",
2271                 GTK_WINDOW(ui->window),
2272                 GTK_FILE_CHOOSER_ACTION_SAVE,
2273                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2274                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2275                 NULL);
2276
2277         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2278         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2279
2280         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2281                 char *filename;
2282
2283                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2284                 // save_job_file(filename);
2285                 g_free(filename);
2286         }
2287         gtk_widget_destroy(dialog);
2288 }
2289
2290 static void view_log_destroy(GtkWidget *w, gpointer data)
2291 {
2292         struct gui *ui = (struct gui *) data;
2293
2294         gtk_widget_ref(ui->log_tree);
2295         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2296         gtk_widget_destroy(w);
2297         ui->log_view = NULL;
2298 }
2299
2300 static void view_log(GtkWidget *w, gpointer data)
2301 {
2302         GtkWidget *win, *scroll, *vbox, *box;
2303         struct gui *ui = (struct gui *) data;
2304
2305         if (ui->log_view)
2306                 return;
2307
2308         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2309         gtk_window_set_title(GTK_WINDOW(win), "Log");
2310         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2311
2312         scroll = gtk_scrolled_window_new(NULL, NULL);
2313
2314         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2315
2316         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2317
2318         box = gtk_hbox_new(TRUE, 0);
2319         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2320         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2321         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2322
2323         vbox = gtk_vbox_new(TRUE, 5);
2324         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2325
2326         gtk_container_add(GTK_CONTAINER(win), vbox);
2327         gtk_widget_show_all(win);
2328 }
2329
2330 static void connect_job_entry(GtkWidget *w, gpointer data)
2331 {
2332         struct gui *ui = (struct gui *) data;
2333         struct gui_entry *ge;
2334         
2335         ge = get_ge_from_cur_tab(ui);
2336         if (ge)
2337                 connect_clicked(w, ge);
2338 }
2339
2340 static void send_job_entry(GtkWidget *w, gpointer data)
2341 {
2342         struct gui *ui = (struct gui *) data;
2343         struct gui_entry *ge;
2344
2345         ge = get_ge_from_cur_tab(ui);
2346         if (ge)
2347                 send_clicked(w, ge);
2348
2349 }
2350
2351 static void edit_job_entry(GtkWidget *w, gpointer data)
2352 {
2353 }
2354
2355 static void start_job_entry(GtkWidget *w, gpointer data)
2356 {
2357         struct gui *ui = (struct gui *) data;
2358         struct gui_entry *ge;
2359
2360         ge = get_ge_from_cur_tab(ui);
2361         if (ge)
2362                 start_job_clicked(w, ge);
2363 }
2364
2365 static void __update_graph_limits(struct gfio_graphs *g)
2366 {
2367         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2368         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2369 }
2370
2371 static void update_graph_limits(void)
2372 {
2373         struct flist_head *entry;
2374         struct gui_entry *ge;
2375
2376         __update_graph_limits(&main_ui.graphs);
2377
2378         flist_for_each(entry, &main_ui.list) {
2379                 ge = flist_entry(entry, struct gui_entry, list);
2380                 __update_graph_limits(&ge->graphs);
2381         }
2382 }
2383
2384 static void preferences(GtkWidget *w, gpointer data)
2385 {
2386         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2387         GtkWidget *hbox, *spin, *entry, *spin_int;
2388         int i;
2389
2390         dialog = gtk_dialog_new_with_buttons("Preferences",
2391                 GTK_WINDOW(main_ui.window),
2392                 GTK_DIALOG_DESTROY_WITH_PARENT,
2393                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2394                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2395                 NULL);
2396
2397         frame = gtk_frame_new("Graphing");
2398         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2399         vbox = gtk_vbox_new(FALSE, 6);
2400         gtk_container_add(GTK_CONTAINER(frame), vbox);
2401
2402         hbox = gtk_hbox_new(FALSE, 5);
2403         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2404         entry = gtk_label_new("Font face to use for graph labels");
2405         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2406
2407         font = gtk_font_button_new();
2408         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2409
2410         box = gtk_vbox_new(FALSE, 6);
2411         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2412
2413         hbox = gtk_hbox_new(FALSE, 5);
2414         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2415         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2416         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2417
2418         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2419
2420         box = gtk_vbox_new(FALSE, 6);
2421         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2422
2423         hbox = gtk_hbox_new(FALSE, 5);
2424         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2425         entry = gtk_label_new("Client ETA request interval (msec)");
2426         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2427
2428         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2429         frame = gtk_frame_new("Debug logging");
2430         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2431         vbox = gtk_vbox_new(FALSE, 6);
2432         gtk_container_add(GTK_CONTAINER(frame), vbox);
2433
2434         box = gtk_hbox_new(FALSE, 6);
2435         gtk_container_add(GTK_CONTAINER(vbox), box);
2436
2437         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2438
2439         for (i = 0; i < FD_DEBUG_MAX; i++) {
2440                 if (i == 7) {
2441                         box = gtk_hbox_new(FALSE, 6);
2442                         gtk_container_add(GTK_CONTAINER(vbox), box);
2443                 }
2444
2445
2446                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2447                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2448                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2449         }
2450
2451         gtk_widget_show_all(dialog);
2452
2453         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2454                 gtk_widget_destroy(dialog);
2455                 return;
2456         }
2457
2458         for (i = 0; i < FD_DEBUG_MAX; i++) {
2459                 int set;
2460
2461                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2462                 if (set)
2463                         fio_debug |= (1UL << i);
2464         }
2465
2466         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2467         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2468         update_graph_limits();
2469         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2470
2471         gtk_widget_destroy(dialog);
2472 }
2473
2474 static void about_dialog(GtkWidget *w, gpointer data)
2475 {
2476         const char *authors[] = {
2477                 "Jens Axboe <axboe@kernel.dk>",
2478                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2479                 NULL
2480         };
2481         const char *license[] = {
2482                 "Fio is free software; you can redistribute it and/or modify "
2483                 "it under the terms of the GNU General Public License as published by "
2484                 "the Free Software Foundation; either version 2 of the License, or "
2485                 "(at your option) any later version.\n",
2486                 "Fio is distributed in the hope that it will be useful, "
2487                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2488                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2489                 "GNU General Public License for more details.\n",
2490                 "You should have received a copy of the GNU General Public License "
2491                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2492                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2493         };
2494         char *license_trans;
2495
2496         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2497                                      license[2], "\n", NULL);
2498
2499         gtk_show_about_dialog(NULL,
2500                 "program-name", "gfio",
2501                 "comments", "Gtk2 UI for fio",
2502                 "license", license_trans,
2503                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2504                 "authors", authors,
2505                 "version", fio_version_string,
2506                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2507                 "logo-icon-name", "fio",
2508                 /* Must be last: */
2509                 "wrap-license", TRUE,
2510                 NULL);
2511
2512         g_free(license_trans);
2513 }
2514
2515 static GtkActionEntry menu_items[] = {
2516         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2517         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2518         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2519         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2520         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2521         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2522         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2523         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2524         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2525         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2526         { "ConnectJob", NULL, "Connect", "<Control>E", NULL, G_CALLBACK(connect_job_entry) },
2527         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2528         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2529         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2530         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2531         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2532 };
2533 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2534
2535 static const gchar *ui_string = " \
2536         <ui> \
2537                 <menubar name=\"MainMenu\"> \
2538                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2539                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2540                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2541                                 <separator name=\"Separator1\"/> \
2542                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2543                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2544                                 <separator name=\"Separator2\"/> \
2545                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2546                                 <separator name=\"Separator3\"/> \
2547                                 <placeholder name=\"FileRecentFiles\"/> \
2548                                 <separator name=\"Separator4\"/> \
2549                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2550                         </menu> \
2551                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2552                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2553                                 <separator name=\"Separator5\"/> \
2554                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2555                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2556                                 <separator name=\"Separator6\"/> \
2557                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2558                         </menu>\
2559                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2560                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2561                         </menu>\
2562                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2563                                 <menuitem name=\"About\" action=\"About\" /> \
2564                         </menu> \
2565                 </menubar> \
2566         </ui> \
2567 ";
2568
2569 static void set_job_menu_visible(struct gui *ui, int visible)
2570 {
2571         GtkWidget *job;
2572
2573         job = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu");
2574         gtk_widget_set_sensitive(job, visible);
2575 }
2576
2577 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2578                                    struct gui *ui)
2579 {
2580         GtkActionGroup *action_group = gtk_action_group_new("Menu");
2581         GError *error = 0;
2582
2583         action_group = gtk_action_group_new("Menu");
2584         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2585
2586         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2587         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2588
2589         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2590
2591         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2592 }
2593
2594 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2595                    GtkWidget *vbox, GtkUIManager *ui_manager)
2596 {
2597         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2598 }
2599
2600 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2601 {
2602         struct gui_entry *ge = (struct gui_entry *) data;
2603         gint index;
2604
2605         index = gtk_combo_box_get_active(box);
2606
2607         multitext_set_entry(&ge->eta.iotype, index);
2608         multitext_set_entry(&ge->eta.ioengine, index);
2609         multitext_set_entry(&ge->eta.iodepth, index);
2610 }
2611
2612 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2613 {
2614         struct gui_entry *ge = (struct gui_entry *) data;
2615
2616         multitext_free(&ge->eta.iotype);
2617         multitext_free(&ge->eta.ioengine);
2618         multitext_free(&ge->eta.iodepth);
2619 }
2620
2621 static GtkWidget *new_client_page(struct gui_entry *ge)
2622 {
2623         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2624         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2625         GdkColor white;
2626
2627         main_vbox = gtk_vbox_new(FALSE, 3);
2628
2629         top_align = gtk_alignment_new(0, 0, 1, 0);
2630         top_vbox = gtk_vbox_new(FALSE, 3);
2631         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2632         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2633
2634         probe = gtk_frame_new("Job");
2635         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2636         probe_frame = gtk_vbox_new(FALSE, 3);
2637         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2638
2639         probe_box = gtk_hbox_new(FALSE, 3);
2640         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2641         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2642         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2643         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2644         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2645
2646         probe_box = gtk_hbox_new(FALSE, 3);
2647         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2648
2649         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2650         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2651         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2652         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2653         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2654         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2655         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2656         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2657
2658         probe_box = gtk_hbox_new(FALSE, 3);
2659         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2660         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2661         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2662         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2663         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2664
2665         /*
2666          * Only add this if we have a commit rate
2667          */
2668 #if 0
2669         probe_box = gtk_hbox_new(FALSE, 3);
2670         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2671
2672         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2673         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2674
2675         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2676         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2677 #endif
2678
2679         /*
2680          * Set up a drawing area and IOPS and bandwidth graphs
2681          */
2682         gdk_color_parse("white", &white);
2683         ge->graphs.drawing_area = gtk_drawing_area_new();
2684         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2685                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2686         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2687         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2688                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2689         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2690                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2691         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2692         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2693                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2694         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2695                                         ge->graphs.drawing_area);
2696         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2697
2698         setup_graphs(&ge->graphs);
2699
2700         /*
2701          * Set up alignments for widgets at the bottom of ui, 
2702          * align bottom left, expand horizontally but not vertically
2703          */
2704         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2705         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2706         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2707         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2708
2709         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2710
2711         /*
2712          * Set up thread status progress bar
2713          */
2714         ge->thread_status_pb = gtk_progress_bar_new();
2715         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2716         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2717         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2718
2719
2720         return main_vbox;
2721 }
2722
2723 static GtkWidget *new_main_page(struct gui *ui)
2724 {
2725         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2726         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2727         GdkColor white;
2728
2729         main_vbox = gtk_vbox_new(FALSE, 3);
2730
2731         /*
2732          * Set up alignments for widgets at the top of ui,
2733          * align top left, expand horizontally but not vertically
2734          */
2735         top_align = gtk_alignment_new(0, 0, 1, 0);
2736         top_vbox = gtk_vbox_new(FALSE, 0);
2737         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2738         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2739
2740         probe = gtk_frame_new("Run statistics");
2741         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2742         probe_frame = gtk_vbox_new(FALSE, 3);
2743         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2744
2745         probe_box = gtk_hbox_new(FALSE, 3);
2746         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2747         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2748         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2749         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2750         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2751         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2752
2753         /*
2754          * Only add this if we have a commit rate
2755          */
2756 #if 0
2757         probe_box = gtk_hbox_new(FALSE, 3);
2758         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2759
2760         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2761         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2762
2763         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2764         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2765 #endif
2766
2767         /*
2768          * Set up a drawing area and IOPS and bandwidth graphs
2769          */
2770         gdk_color_parse("white", &white);
2771         ui->graphs.drawing_area = gtk_drawing_area_new();
2772         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2773                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2774         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2775         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2776                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2777         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2778                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2779         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2780         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2781                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2782         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2783                                         ui->graphs.drawing_area);
2784         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2785                         TRUE, TRUE, 0);
2786
2787         setup_graphs(&ui->graphs);
2788
2789         /*
2790          * Set up alignments for widgets at the bottom of ui, 
2791          * align bottom left, expand horizontally but not vertically
2792          */
2793         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2794         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2795         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2796         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2797
2798         /*
2799          * Set up thread status progress bar
2800          */
2801         ui->thread_status_pb = gtk_progress_bar_new();
2802         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2803         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2804         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2805
2806         return main_vbox;
2807 }
2808
2809 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2810                                      guint page, gpointer data)
2811
2812 {
2813         struct gui *ui = (struct gui *) data;
2814         struct gui_entry *ge;
2815
2816         if (!page) {
2817                 set_job_menu_visible(ui, 0);
2818                 return TRUE;
2819         }
2820
2821         set_job_menu_visible(ui, 1);
2822         ge = get_ge_from_page(page, NULL);
2823         if (ge)
2824                 update_button_states(ui, ge);
2825
2826         return TRUE;
2827 }
2828
2829 static void recent_open(GtkAction *action, gpointer data)
2830 {
2831         struct gui *ui = (struct gui *) data;
2832         int port, type, server_start;
2833         struct gui_entry *ge;
2834         GtkRecentInfo *info;
2835         const gchar *uri;
2836         gint cur_page;
2837         char *host;
2838         int ret, ge_is_new = 0;
2839
2840         /*
2841          * Creates new tab if current tab is the main window, or the
2842          * current tab already has a client.
2843          */
2844         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2845         ge = get_ge_from_page(cur_page, &ge_is_new);
2846         if (ge->client) {
2847                 ge = get_new_ge_with_tab("Untitled");
2848                 ge_is_new = 1;
2849         }
2850
2851         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2852
2853         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2854         uri = gtk_recent_info_get_uri(info);
2855
2856         if (get_connection_details(&host, &port, &type, &server_start)) {
2857                 if (ge_is_new)
2858                         gtk_widget_destroy(ge->vbox);
2859                         
2860                 return;
2861         }
2862
2863         ret = do_file_open(ge, uri, host, type, port);
2864
2865         free(host);
2866
2867         if (!ret) {
2868                 if (server_start)
2869                         gfio_start_server();
2870         } else {
2871                 if (ge_is_new)
2872                         gtk_widget_destroy(ge->vbox);
2873         }
2874 }
2875
2876 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2877 {
2878         time_t time_a = gtk_recent_info_get_visited(a);
2879         time_t time_b = gtk_recent_info_get_visited(b);
2880
2881         return time_b - time_a;
2882 }
2883
2884 static void add_recent_file_items(struct gui *ui)
2885 {
2886         const gchar *gfio = g_get_application_name();
2887         GList *items, *item;
2888         int i = 0;
2889
2890         if (ui->recent_ui_id) {
2891                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2892                 gtk_ui_manager_ensure_update(ui->uimanager);
2893         }
2894         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2895
2896         if (ui->actiongroup) {
2897                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2898                 g_object_unref(ui->actiongroup);
2899         }
2900         ui->actiongroup = gtk_action_group_new("RecentFileActions");
2901
2902         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2903
2904         items = gtk_recent_manager_get_items(ui->recentmanager);
2905         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2906
2907         for (item = items; item && item->data; item = g_list_next(item)) {
2908                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2909                 gchar *action_name;
2910                 const gchar *label;
2911                 GtkAction *action;
2912
2913                 if (!gtk_recent_info_has_application(info, gfio))
2914                         continue;
2915
2916                 /*
2917                  * We only support local files for now
2918                  */
2919                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2920                         continue;
2921
2922                 action_name = g_strdup_printf("RecentFile%u", i++);
2923                 label = gtk_recent_info_get_display_name(info);
2924
2925                 action = g_object_new(GTK_TYPE_ACTION,
2926                                         "name", action_name,
2927                                         "label", label, NULL);
2928
2929                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2930                                         gtk_recent_info_ref(info),
2931                                         (GDestroyNotify) gtk_recent_info_unref);
2932
2933
2934                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2935
2936                 gtk_action_group_add_action(ui->actiongroup, action);
2937                 g_object_unref(action);
2938
2939                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2940                                         "/MainMenu/FileMenu/FileRecentFiles",
2941                                         label, action_name,
2942                                         GTK_UI_MANAGER_MENUITEM, FALSE);
2943
2944                 g_free(action_name);
2945
2946                 if (i == 8)
2947                         break;
2948         }
2949
2950         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2951         g_list_free(items);
2952 }
2953
2954 static void init_ui(int *argc, char **argv[], struct gui *ui)
2955 {
2956         GtkSettings *settings;
2957         GtkWidget *vbox;
2958
2959         /* Magical g*thread incantation, you just need this thread stuff.
2960          * Without it, the update that happens in gfio_update_thread_status
2961          * doesn't really happen in a timely fashion, you need expose events
2962          */
2963         if (!g_thread_supported())
2964                 g_thread_init(NULL);
2965         gdk_threads_init();
2966
2967         gtk_init(argc, argv);
2968         settings = gtk_settings_get_default();
2969         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
2970         g_type_init();
2971         
2972         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2973         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2974         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
2975
2976         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
2977         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
2978
2979         ui->vbox = gtk_vbox_new(FALSE, 0);
2980         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
2981
2982         ui->uimanager = gtk_ui_manager_new();
2983         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
2984         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
2985
2986         ui->recentmanager = gtk_recent_manager_get_default();
2987         add_recent_file_items(ui);
2988
2989         ui->notebook = gtk_notebook_new();
2990         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
2991         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
2992         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
2993         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
2994
2995         vbox = new_main_page(ui);
2996
2997         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
2998
2999         gfio_ui_setup_log(ui);
3000
3001         gtk_widget_show_all(ui->window);
3002 }
3003
3004 int main(int argc, char *argv[], char *envp[])
3005 {
3006         if (initialize_fio(envp))
3007                 return 1;
3008         if (fio_init_options())
3009                 return 1;
3010
3011         memset(&main_ui, 0, sizeof(main_ui));
3012         INIT_FLIST_HEAD(&main_ui.list);
3013
3014         init_ui(&argc, &argv, &main_ui);
3015
3016         gdk_threads_enter();
3017         gtk_main();
3018         gdk_threads_leave();
3019         return 0;
3020 }