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