graph: move graphs a bit to the left, now we don't use Y axis labels
[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)
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 struct client_ops gfio_client_ops = {
1640         .text_op                = gfio_text_op,
1641         .disk_util              = gfio_disk_util_op,
1642         .thread_status          = gfio_thread_status_op,
1643         .group_stats            = gfio_group_stats_op,
1644         .jobs_eta               = gfio_update_client_eta,
1645         .eta                    = gfio_update_all_eta,
1646         .probe                  = gfio_probe_op,
1647         .quit                   = gfio_quit_op,
1648         .add_job                = gfio_add_job_op,
1649         .timed_out              = gfio_client_timed_out,
1650         .stop                   = gfio_client_stop,
1651         .start                  = gfio_client_start,
1652         .job_start              = gfio_client_job_start,
1653         .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
1654         .stay_connected         = 1,
1655 };
1656
1657 /*
1658  * FIXME: need more handling here
1659  */
1660 static void ge_destroy(struct gui_entry *ge)
1661 {
1662         struct gfio_client *gc = ge->client;
1663
1664         if (gc && gc->client) {
1665                 if (ge->state >= GE_STATE_CONNECTED)
1666                         fio_client_terminate(gc->client);
1667
1668                 fio_put_client(gc->client);
1669         }
1670
1671         flist_del(&ge->list);
1672         free(ge);
1673 }
1674
1675 static void ge_widget_destroy(GtkWidget *w, gpointer data)
1676 {
1677         struct gui_entry *ge = data;
1678
1679         ge_destroy(ge);
1680 }
1681
1682 static void gfio_quit(struct gui *ui)
1683 {
1684         struct gui_entry *ge;
1685
1686         while (!flist_empty(&ui->list)) {
1687                 ge = flist_entry(ui->list.next, struct gui_entry, list);
1688                 ge_destroy(ge);
1689         }
1690
1691         gtk_main_quit();
1692 }
1693
1694 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1695                 __attribute__((unused)) gpointer data)
1696 {
1697         gfio_quit(data);
1698 }
1699
1700 static void *job_thread(void *arg)
1701 {
1702         struct gui *ui = arg;
1703
1704         ui->handler_running = 1;
1705         fio_handle_clients(&gfio_client_ops);
1706         ui->handler_running = 0;
1707         return NULL;
1708 }
1709
1710 static int send_job_files(struct gui_entry *ge)
1711 {
1712         struct gfio_client *gc = ge->client;
1713         int i, ret = 0;
1714
1715         for (i = 0; i < ge->nr_job_files; i++) {
1716                 ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1717                 if (ret < 0) {
1718                         GError *error;
1719
1720                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1721                         report_error(error);
1722                         g_error_free(error);
1723                         break;
1724                 } else if (ret)
1725                         break;
1726
1727                 free(ge->job_files[i]);
1728                 ge->job_files[i] = NULL;
1729         }
1730         while (i < ge->nr_job_files) {
1731                 free(ge->job_files[i]);
1732                 ge->job_files[i] = NULL;
1733                 i++;
1734         }
1735
1736         free(ge->job_files);
1737         ge->job_files = NULL;
1738         ge->nr_job_files = 0;
1739         return ret;
1740 }
1741
1742 static void *server_thread(void *arg)
1743 {
1744         is_backend = 1;
1745         gfio_server_running = 1;
1746         fio_start_server(NULL);
1747         gfio_server_running = 0;
1748         return NULL;
1749 }
1750
1751 static void gfio_start_server(void)
1752 {
1753         struct gui *ui = &main_ui;
1754
1755         if (!gfio_server_running) {
1756                 gfio_server_running = 1;
1757                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1758                 pthread_detach(ui->server_t);
1759         }
1760 }
1761
1762 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1763                 gpointer data)
1764 {
1765         struct gui_entry *ge = data;
1766         struct gfio_client *gc = ge->client;
1767
1768         if (gc)
1769                 fio_start_client(gc->client);
1770 }
1771
1772 static void file_open(GtkWidget *w, gpointer data);
1773
1774 static void connect_clicked(GtkWidget *widget, gpointer data)
1775 {
1776         struct gui_entry *ge = data;
1777         struct gfio_client *gc = ge->client;
1778
1779         if (ge->state == GE_STATE_NEW) {
1780                 int ret;
1781
1782                 if (!ge->nr_job_files)
1783                         file_open(widget, ge->ui);
1784                 if (!ge->nr_job_files)
1785                         return;
1786
1787                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
1788                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1789                 ret = fio_client_connect(gc->client);
1790                 if (!ret) {
1791                         if (!ge->ui->handler_running)
1792                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
1793                         gfio_set_state(ge, GE_STATE_CONNECTED);
1794                 } else {
1795                         GError *error;
1796
1797                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
1798                         report_error(error);
1799                         g_error_free(error);
1800                 }
1801         } else {
1802                 fio_client_terminate(gc->client);
1803                 gfio_set_state(ge, GE_STATE_NEW);
1804                 clear_ge_ui_info(ge);
1805         }
1806 }
1807
1808 static void send_clicked(GtkWidget *widget, gpointer data)
1809 {
1810         struct gui_entry *ge = data;
1811
1812         if (send_job_files(ge)) {
1813                 GError *error;
1814
1815                 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);
1816                 report_error(error);
1817                 g_error_free(error);
1818
1819                 gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
1820         }
1821 }
1822
1823 static GtkWidget *add_button(GtkWidget *buttonbox,
1824                              struct button_spec *buttonspec, gpointer data)
1825 {
1826         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
1827
1828         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
1829         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
1830         gtk_widget_set_tooltip_text(button, buttonspec->tooltiptext);
1831         gtk_widget_set_sensitive(button, !buttonspec->start_insensitive);
1832
1833         return button;
1834 }
1835
1836 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
1837                         int nbuttons)
1838 {
1839         int i;
1840
1841         for (i = 0; i < nbuttons; i++)
1842                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
1843 }
1844
1845 static void on_info_bar_response(GtkWidget *widget, gint response,
1846                                  gpointer data)
1847 {
1848         struct gui *ui = &main_ui;
1849
1850         if (response == GTK_RESPONSE_OK) {
1851                 gtk_widget_destroy(widget);
1852                 ui->error_info_bar = NULL;
1853         }
1854 }
1855
1856 void report_error(GError *error)
1857 {
1858         struct gui *ui = &main_ui;
1859
1860         if (ui->error_info_bar == NULL) {
1861                 ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1862                                                                GTK_RESPONSE_OK,
1863                                                                NULL);
1864                 g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1865                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
1866                                               GTK_MESSAGE_ERROR);
1867                 
1868                 ui->error_label = gtk_label_new(error->message);
1869                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
1870                 gtk_container_add(GTK_CONTAINER(container), ui->error_label);
1871                 
1872                 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
1873                 gtk_widget_show_all(ui->vbox);
1874         } else {
1875                 char buffer[256];
1876                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1877                 gtk_label_set(GTK_LABEL(ui->error_label), buffer);
1878         }
1879 }
1880
1881 struct connection_widgets
1882 {
1883         GtkWidget *hentry;
1884         GtkWidget *combo;
1885         GtkWidget *button;
1886 };
1887
1888 static void hostname_cb(GtkEntry *entry, gpointer data)
1889 {
1890         struct connection_widgets *cw = data;
1891         int uses_net = 0, is_localhost = 0;
1892         const gchar *text;
1893         gchar *ctext;
1894
1895         /*
1896          * Check whether to display the 'auto start backend' box
1897          * or not. Show it if we are a localhost and using network,
1898          * or using a socket.
1899          */
1900         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1901         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1902                 uses_net = 1;
1903         g_free(ctext);
1904
1905         if (uses_net) {
1906                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1907                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1908                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1909                     !strcmp(text, "ip6-loopback"))
1910                         is_localhost = 1;
1911         }
1912
1913         if (!uses_net || is_localhost) {
1914                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1915                 gtk_widget_set_sensitive(cw->button, 1);
1916         } else {
1917                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1918                 gtk_widget_set_sensitive(cw->button, 0);
1919         }
1920 }
1921
1922 static int get_connection_details(char **host, int *port, int *type,
1923                                   int *server_start)
1924 {
1925         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1926         struct connection_widgets cw;
1927         char *typeentry;
1928
1929         dialog = gtk_dialog_new_with_buttons("Connection details",
1930                         GTK_WINDOW(main_ui.window),
1931                         GTK_DIALOG_DESTROY_WITH_PARENT,
1932                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1933                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1934
1935         frame = gtk_frame_new("Hostname / socket name");
1936         /* gtk_dialog_get_content_area() is 2.14 and newer */
1937         vbox = GTK_DIALOG(dialog)->vbox;
1938         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1939
1940         box = gtk_vbox_new(FALSE, 6);
1941         gtk_container_add(GTK_CONTAINER(frame), box);
1942
1943         hbox = gtk_hbox_new(TRUE, 10);
1944         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1945         cw.hentry = gtk_entry_new();
1946         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1947         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
1948
1949         frame = gtk_frame_new("Port");
1950         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1951         box = gtk_vbox_new(FALSE, 10);
1952         gtk_container_add(GTK_CONTAINER(frame), box);
1953
1954         hbox = gtk_hbox_new(TRUE, 4);
1955         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1956         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
1957
1958         frame = gtk_frame_new("Type");
1959         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1960         box = gtk_vbox_new(FALSE, 10);
1961         gtk_container_add(GTK_CONTAINER(frame), box);
1962
1963         hbox = gtk_hbox_new(TRUE, 4);
1964         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1965
1966         cw.combo = gtk_combo_box_new_text();
1967         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
1968         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
1969         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
1970         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
1971
1972         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
1973
1974         frame = gtk_frame_new("Options");
1975         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1976         box = gtk_vbox_new(FALSE, 10);
1977         gtk_container_add(GTK_CONTAINER(frame), box);
1978
1979         hbox = gtk_hbox_new(TRUE, 4);
1980         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1981
1982         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
1983         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
1984         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.");
1985         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
1986
1987         /*
1988          * Connect edit signal, so we can show/not-show the auto start button
1989          */
1990         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
1991         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
1992
1993         gtk_widget_show_all(dialog);
1994
1995         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1996                 gtk_widget_destroy(dialog);
1997                 return 1;
1998         }
1999
2000         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
2001         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2002
2003         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
2004         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
2005                 *type = Fio_client_ipv4;
2006         else if (!strncmp(typeentry, "IPv6", 4))
2007                 *type = Fio_client_ipv6;
2008         else
2009                 *type = Fio_client_socket;
2010         g_free(typeentry);
2011
2012         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2013
2014         gtk_widget_destroy(dialog);
2015         return 0;
2016 }
2017
2018 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
2019 {
2020         struct gfio_client *gc;
2021
2022         gc = malloc(sizeof(*gc));
2023         memset(gc, 0, sizeof(*gc));
2024         gc->ge = ge;
2025         gc->client = fio_get_client(client);
2026
2027         ge->client = gc;
2028
2029         client->client_data = gc;
2030 }
2031
2032 static GtkWidget *new_client_page(struct gui_entry *ge);
2033
2034 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2035 {
2036         struct gui_entry *ge;
2037
2038         ge = malloc(sizeof(*ge));
2039         memset(ge, 0, sizeof(*ge));
2040         ge->state = GE_STATE_NEW;
2041         INIT_FLIST_HEAD(&ge->list);
2042         flist_add_tail(&ge->list, &ui->list);
2043         ge->ui = ui;
2044         return ge;
2045 }
2046
2047 static struct gui_entry *get_new_ge_with_tab(const char *name)
2048 {
2049         struct gui_entry *ge;
2050
2051         ge = alloc_new_gui_entry(&main_ui);
2052
2053         ge->vbox = new_client_page(ge);
2054         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
2055
2056         ge->page_label = gtk_label_new(name);
2057         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
2058
2059         gtk_widget_show_all(main_ui.window);
2060         return ge;
2061 }
2062
2063 static void file_new(GtkWidget *w, gpointer data)
2064 {
2065         struct gui *ui = (struct gui *) data;
2066         struct gui_entry *ge;
2067
2068         ge = get_new_ge_with_tab("Untitled");
2069         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2070 }
2071
2072 /*
2073  * Return the 'ge' corresponding to the tab. If the active tab is the
2074  * main tab, open a new tab.
2075  */
2076 static struct gui_entry *get_ge_from_page(gint cur_page, int *created)
2077 {
2078         struct flist_head *entry;
2079         struct gui_entry *ge;
2080
2081         if (!cur_page) {
2082                 if (created)
2083                         *created = 1;
2084                 return get_new_ge_with_tab("Untitled");
2085         }
2086
2087         if (created)
2088                 *created = 0;
2089
2090         flist_for_each(entry, &main_ui.list) {
2091                 ge = flist_entry(entry, struct gui_entry, list);
2092                 if (ge->page_num == cur_page)
2093                         return ge;
2094         }
2095
2096         return NULL;
2097 }
2098
2099 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2100 {
2101         gint cur_page;
2102
2103         /*
2104          * Main tab is tab 0, so any current page other than 0 holds
2105          * a ge entry.
2106          */
2107         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2108         if (cur_page)
2109                 return get_ge_from_page(cur_page, NULL);
2110
2111         return NULL;
2112 }
2113
2114 static void file_close(GtkWidget *w, gpointer data)
2115 {
2116         struct gui *ui = (struct gui *) data;
2117         struct gui_entry *ge;
2118
2119         /*
2120          * Can't close the main tab
2121          */
2122         ge = get_ge_from_cur_tab(ui);
2123         if (ge) {
2124                 gtk_widget_destroy(ge->vbox);
2125                 return;
2126         }
2127
2128         if (!flist_empty(&ui->list)) {
2129                 show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2130                 return;
2131         }
2132
2133         gfio_quit(ui);
2134 }
2135
2136 static void file_add_recent(struct gui *ui, const gchar *uri)
2137 {
2138         GtkRecentData grd;
2139
2140         memset(&grd, 0, sizeof(grd));
2141         grd.display_name = strdup("gfio");
2142         grd.description = strdup("Fio job file");
2143         grd.mime_type = strdup(GFIO_MIME);
2144         grd.app_name = strdup(g_get_application_name());
2145         grd.app_exec = strdup("gfio %f/%u");
2146
2147         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2148 }
2149
2150 static gchar *get_filename_from_uri(const gchar *uri)
2151 {
2152         if (strncmp(uri, "file://", 7))
2153                 return strdup(uri);
2154
2155         return strdup(uri + 7);
2156 }
2157
2158 static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2159                         int type, int port)
2160 {
2161         struct fio_client *client;
2162         gchar *filename;
2163
2164         filename = get_filename_from_uri(uri);
2165
2166         ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2167         ge->job_files[ge->nr_job_files] = strdup(filename);
2168         ge->nr_job_files++;
2169
2170         client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2171         if (!client) {
2172                 GError *error;
2173
2174                 error = g_error_new(g_quark_from_string("fio"), 1,
2175                                 "Failed to add client %s", host);
2176                 report_error(error);
2177                 g_error_free(error);
2178                 return 1;
2179         }
2180
2181         gfio_client_added(ge, client);
2182         file_add_recent(ge->ui, uri);
2183         return 0;
2184 }
2185
2186 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2187 {
2188         int port, type, server_start;
2189         struct gui_entry *ge;
2190         gint cur_page;
2191         char *host;
2192         int ret, ge_is_new = 0;
2193
2194         /*
2195          * Creates new tab if current tab is the main window, or the
2196          * current tab already has a client.
2197          */
2198         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2199         ge = get_ge_from_page(cur_page, &ge_is_new);
2200         if (ge->client) {
2201                 ge = get_new_ge_with_tab("Untitled");
2202                 ge_is_new = 1;
2203         }
2204
2205         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2206
2207         if (get_connection_details(&host, &port, &type, &server_start)) {
2208                 if (ge_is_new)
2209                         gtk_widget_destroy(ge->vbox);
2210                         
2211                 return 1;
2212         }
2213
2214         ret = do_file_open(ge, uri, host, type, port);
2215
2216         free(host);
2217
2218         if (!ret) {
2219                 if (server_start)
2220                         gfio_start_server();
2221         } else {
2222                 if (ge_is_new)
2223                         gtk_widget_destroy(ge->vbox);
2224         }
2225
2226         return ret;
2227 }
2228
2229 static void recent_open(GtkAction *action, gpointer data)
2230 {
2231         struct gui *ui = (struct gui *) data;
2232         GtkRecentInfo *info;
2233         const gchar *uri;
2234
2235         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2236         uri = gtk_recent_info_get_uri(info);
2237
2238         do_file_open_with_tab(ui, uri);
2239 }
2240
2241 static void file_open(GtkWidget *w, gpointer data)
2242 {
2243         struct gui *ui = data;
2244         GtkWidget *dialog;
2245         GSList *filenames, *fn_glist;
2246         GtkFileFilter *filter;
2247
2248         dialog = gtk_file_chooser_dialog_new("Open File",
2249                 GTK_WINDOW(ui->window),
2250                 GTK_FILE_CHOOSER_ACTION_OPEN,
2251                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2252                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2253                 NULL);
2254         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2255
2256         filter = gtk_file_filter_new();
2257         gtk_file_filter_add_pattern(filter, "*.fio");
2258         gtk_file_filter_add_pattern(filter, "*.job");
2259         gtk_file_filter_add_pattern(filter, "*.ini");
2260         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2261         gtk_file_filter_set_name(filter, "Fio job file");
2262         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2263
2264         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2265                 gtk_widget_destroy(dialog);
2266                 return;
2267         }
2268
2269         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2270
2271         gtk_widget_destroy(dialog);
2272
2273         filenames = fn_glist;
2274         while (filenames != NULL) {
2275                 if (do_file_open_with_tab(ui, filenames->data))
2276                         break;
2277                 filenames = g_slist_next(filenames);
2278         }
2279
2280         g_slist_free(fn_glist);
2281 }
2282
2283 static void file_save(GtkWidget *w, gpointer data)
2284 {
2285         struct gui *ui = data;
2286         GtkWidget *dialog;
2287
2288         dialog = gtk_file_chooser_dialog_new("Save File",
2289                 GTK_WINDOW(ui->window),
2290                 GTK_FILE_CHOOSER_ACTION_SAVE,
2291                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2292                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2293                 NULL);
2294
2295         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2296         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2297
2298         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2299                 char *filename;
2300
2301                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2302                 // save_job_file(filename);
2303                 g_free(filename);
2304         }
2305         gtk_widget_destroy(dialog);
2306 }
2307
2308 static void view_log_destroy(GtkWidget *w, gpointer data)
2309 {
2310         struct gui *ui = (struct gui *) data;
2311
2312         gtk_widget_ref(ui->log_tree);
2313         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2314         gtk_widget_destroy(w);
2315         ui->log_view = NULL;
2316 }
2317
2318 static void view_log(GtkWidget *w, gpointer data)
2319 {
2320         GtkWidget *win, *scroll, *vbox, *box;
2321         struct gui *ui = (struct gui *) data;
2322
2323         if (ui->log_view)
2324                 return;
2325
2326         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2327         gtk_window_set_title(GTK_WINDOW(win), "Log");
2328         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2329
2330         scroll = gtk_scrolled_window_new(NULL, NULL);
2331
2332         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2333
2334         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2335
2336         box = gtk_hbox_new(TRUE, 0);
2337         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2338         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2339         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2340
2341         vbox = gtk_vbox_new(TRUE, 5);
2342         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2343
2344         gtk_container_add(GTK_CONTAINER(win), vbox);
2345         gtk_widget_show_all(win);
2346 }
2347
2348 static void connect_job_entry(GtkWidget *w, gpointer data)
2349 {
2350         struct gui *ui = (struct gui *) data;
2351         struct gui_entry *ge;
2352         
2353         ge = get_ge_from_cur_tab(ui);
2354         if (ge)
2355                 connect_clicked(w, ge);
2356 }
2357
2358 static void send_job_entry(GtkWidget *w, gpointer data)
2359 {
2360         struct gui *ui = (struct gui *) data;
2361         struct gui_entry *ge;
2362
2363         ge = get_ge_from_cur_tab(ui);
2364         if (ge)
2365                 send_clicked(w, ge);
2366
2367 }
2368
2369 static void edit_job_entry(GtkWidget *w, gpointer data)
2370 {
2371 }
2372
2373 static void start_job_entry(GtkWidget *w, gpointer data)
2374 {
2375         struct gui *ui = (struct gui *) data;
2376         struct gui_entry *ge;
2377
2378         ge = get_ge_from_cur_tab(ui);
2379         if (ge)
2380                 start_job_clicked(w, ge);
2381 }
2382
2383 static void __update_graph_limits(struct gfio_graphs *g)
2384 {
2385         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2386         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2387 }
2388
2389 static void update_graph_limits(void)
2390 {
2391         struct flist_head *entry;
2392         struct gui_entry *ge;
2393
2394         __update_graph_limits(&main_ui.graphs);
2395
2396         flist_for_each(entry, &main_ui.list) {
2397                 ge = flist_entry(entry, struct gui_entry, list);
2398                 __update_graph_limits(&ge->graphs);
2399         }
2400 }
2401
2402 static void preferences(GtkWidget *w, gpointer data)
2403 {
2404         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2405         GtkWidget *hbox, *spin, *entry, *spin_int;
2406         int i;
2407
2408         dialog = gtk_dialog_new_with_buttons("Preferences",
2409                 GTK_WINDOW(main_ui.window),
2410                 GTK_DIALOG_DESTROY_WITH_PARENT,
2411                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2412                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2413                 NULL);
2414
2415         frame = gtk_frame_new("Graphing");
2416         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2417         vbox = gtk_vbox_new(FALSE, 6);
2418         gtk_container_add(GTK_CONTAINER(frame), vbox);
2419
2420         hbox = gtk_hbox_new(FALSE, 5);
2421         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2422         entry = gtk_label_new("Font face to use for graph labels");
2423         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2424
2425         font = gtk_font_button_new();
2426         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2427
2428         box = gtk_vbox_new(FALSE, 6);
2429         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2430
2431         hbox = gtk_hbox_new(FALSE, 5);
2432         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2433         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2434         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2435
2436         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2437
2438         box = gtk_vbox_new(FALSE, 6);
2439         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2440
2441         hbox = gtk_hbox_new(FALSE, 5);
2442         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2443         entry = gtk_label_new("Client ETA request interval (msec)");
2444         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2445
2446         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2447         frame = gtk_frame_new("Debug logging");
2448         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2449         vbox = gtk_vbox_new(FALSE, 6);
2450         gtk_container_add(GTK_CONTAINER(frame), vbox);
2451
2452         box = gtk_hbox_new(FALSE, 6);
2453         gtk_container_add(GTK_CONTAINER(vbox), box);
2454
2455         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2456
2457         for (i = 0; i < FD_DEBUG_MAX; i++) {
2458                 if (i == 7) {
2459                         box = gtk_hbox_new(FALSE, 6);
2460                         gtk_container_add(GTK_CONTAINER(vbox), box);
2461                 }
2462
2463
2464                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2465                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2466                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2467         }
2468
2469         gtk_widget_show_all(dialog);
2470
2471         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2472                 gtk_widget_destroy(dialog);
2473                 return;
2474         }
2475
2476         for (i = 0; i < FD_DEBUG_MAX; i++) {
2477                 int set;
2478
2479                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2480                 if (set)
2481                         fio_debug |= (1UL << i);
2482         }
2483
2484         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2485         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2486         update_graph_limits();
2487         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2488
2489         gtk_widget_destroy(dialog);
2490 }
2491
2492 static void about_dialog(GtkWidget *w, gpointer data)
2493 {
2494         const char *authors[] = {
2495                 "Jens Axboe <axboe@kernel.dk>",
2496                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2497                 NULL
2498         };
2499         const char *license[] = {
2500                 "Fio is free software; you can redistribute it and/or modify "
2501                 "it under the terms of the GNU General Public License as published by "
2502                 "the Free Software Foundation; either version 2 of the License, or "
2503                 "(at your option) any later version.\n",
2504                 "Fio is distributed in the hope that it will be useful, "
2505                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2506                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2507                 "GNU General Public License for more details.\n",
2508                 "You should have received a copy of the GNU General Public License "
2509                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2510                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2511         };
2512         char *license_trans;
2513
2514         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2515                                      license[2], "\n", NULL);
2516
2517         gtk_show_about_dialog(NULL,
2518                 "program-name", "gfio",
2519                 "comments", "Gtk2 UI for fio",
2520                 "license", license_trans,
2521                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2522                 "authors", authors,
2523                 "version", fio_version_string,
2524                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2525                 "logo-icon-name", "fio",
2526                 /* Must be last: */
2527                 "wrap-license", TRUE,
2528                 NULL);
2529
2530         g_free(license_trans);
2531 }
2532
2533 static GtkActionEntry menu_items[] = {
2534         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2535         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2536         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2537         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2538         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2539         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2540         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2541         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2542         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2543         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2544         { "ConnectJob", NULL, "Connect", "<Control>E", NULL, G_CALLBACK(connect_job_entry) },
2545         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2546         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2547         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2548         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2549         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2550 };
2551 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2552
2553 static const gchar *ui_string = " \
2554         <ui> \
2555                 <menubar name=\"MainMenu\"> \
2556                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2557                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2558                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2559                                 <separator name=\"Separator1\"/> \
2560                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2561                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2562                                 <separator name=\"Separator2\"/> \
2563                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2564                                 <separator name=\"Separator3\"/> \
2565                                 <placeholder name=\"FileRecentFiles\"/> \
2566                                 <separator name=\"Separator4\"/> \
2567                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2568                         </menu> \
2569                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2570                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2571                                 <separator name=\"Separator5\"/> \
2572                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2573                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2574                                 <separator name=\"Separator6\"/> \
2575                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2576                         </menu>\
2577                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2578                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2579                         </menu>\
2580                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2581                                 <menuitem name=\"About\" action=\"About\" /> \
2582                         </menu> \
2583                 </menubar> \
2584         </ui> \
2585 ";
2586
2587 static void set_job_menu_visible(struct gui *ui, int visible)
2588 {
2589         GtkWidget *job;
2590
2591         job = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu");
2592         gtk_widget_set_sensitive(job, visible);
2593 }
2594
2595 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2596                                    struct gui *ui)
2597 {
2598         GtkActionGroup *action_group = gtk_action_group_new("Menu");
2599         GError *error = 0;
2600
2601         action_group = gtk_action_group_new("Menu");
2602         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2603
2604         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2605         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2606
2607         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2608
2609         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2610 }
2611
2612 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2613                    GtkWidget *vbox, GtkUIManager *ui_manager)
2614 {
2615         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2616 }
2617
2618 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2619 {
2620         struct gui_entry *ge = (struct gui_entry *) data;
2621         gint index;
2622
2623         index = gtk_combo_box_get_active(box);
2624
2625         multitext_set_entry(&ge->eta.iotype, index);
2626         multitext_set_entry(&ge->eta.ioengine, index);
2627         multitext_set_entry(&ge->eta.iodepth, index);
2628 }
2629
2630 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2631 {
2632         struct gui_entry *ge = (struct gui_entry *) data;
2633
2634         multitext_free(&ge->eta.iotype);
2635         multitext_free(&ge->eta.ioengine);
2636         multitext_free(&ge->eta.iodepth);
2637 }
2638
2639 static GtkWidget *new_client_page(struct gui_entry *ge)
2640 {
2641         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2642         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2643         GdkColor white;
2644
2645         main_vbox = gtk_vbox_new(FALSE, 3);
2646
2647         top_align = gtk_alignment_new(0, 0, 1, 0);
2648         top_vbox = gtk_vbox_new(FALSE, 3);
2649         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2650         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2651
2652         probe = gtk_frame_new("Job");
2653         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2654         probe_frame = gtk_vbox_new(FALSE, 3);
2655         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2656
2657         probe_box = gtk_hbox_new(FALSE, 3);
2658         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2659         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2660         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2661         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2662         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2663
2664         probe_box = gtk_hbox_new(FALSE, 3);
2665         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2666
2667         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2668         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2669         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2670         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2671         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2672         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2673         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2674         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2675
2676         probe_box = gtk_hbox_new(FALSE, 3);
2677         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2678         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2679         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2680         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2681         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2682
2683         /*
2684          * Only add this if we have a commit rate
2685          */
2686 #if 0
2687         probe_box = gtk_hbox_new(FALSE, 3);
2688         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2689
2690         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2691         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2692
2693         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2694         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2695 #endif
2696
2697         /*
2698          * Set up a drawing area and IOPS and bandwidth graphs
2699          */
2700         gdk_color_parse("white", &white);
2701         ge->graphs.drawing_area = gtk_drawing_area_new();
2702         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2703                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2704         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2705         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2706                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2707         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2708                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2709         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2710         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2711                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2712         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2713                                         ge->graphs.drawing_area);
2714         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2715
2716         setup_graphs(&ge->graphs);
2717
2718         /*
2719          * Set up alignments for widgets at the bottom of ui, 
2720          * align bottom left, expand horizontally but not vertically
2721          */
2722         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2723         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2724         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2725         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2726
2727         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2728
2729         /*
2730          * Set up thread status progress bar
2731          */
2732         ge->thread_status_pb = gtk_progress_bar_new();
2733         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2734         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2735         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2736
2737
2738         return main_vbox;
2739 }
2740
2741 static GtkWidget *new_main_page(struct gui *ui)
2742 {
2743         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2744         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2745         GdkColor white;
2746
2747         main_vbox = gtk_vbox_new(FALSE, 3);
2748
2749         /*
2750          * Set up alignments for widgets at the top of ui,
2751          * align top left, expand horizontally but not vertically
2752          */
2753         top_align = gtk_alignment_new(0, 0, 1, 0);
2754         top_vbox = gtk_vbox_new(FALSE, 0);
2755         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2756         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2757
2758         probe = gtk_frame_new("Run statistics");
2759         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2760         probe_frame = gtk_vbox_new(FALSE, 3);
2761         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2762
2763         probe_box = gtk_hbox_new(FALSE, 3);
2764         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2765         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2766         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2767         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2768         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2769         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2770
2771         /*
2772          * Only add this if we have a commit rate
2773          */
2774 #if 0
2775         probe_box = gtk_hbox_new(FALSE, 3);
2776         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2777
2778         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2779         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2780
2781         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2782         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2783 #endif
2784
2785         /*
2786          * Set up a drawing area and IOPS and bandwidth graphs
2787          */
2788         gdk_color_parse("white", &white);
2789         ui->graphs.drawing_area = gtk_drawing_area_new();
2790         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2791                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2792         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2793         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2794                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2795         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2796                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2797         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2798         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2799                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2800         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2801                                         ui->graphs.drawing_area);
2802         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2803                         TRUE, TRUE, 0);
2804
2805         setup_graphs(&ui->graphs);
2806
2807         /*
2808          * Set up alignments for widgets at the bottom of ui, 
2809          * align bottom left, expand horizontally but not vertically
2810          */
2811         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2812         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2813         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2814         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2815
2816         /*
2817          * Set up thread status progress bar
2818          */
2819         ui->thread_status_pb = gtk_progress_bar_new();
2820         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2821         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2822         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2823
2824         return main_vbox;
2825 }
2826
2827 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2828                                      guint page, gpointer data)
2829
2830 {
2831         struct gui *ui = (struct gui *) data;
2832         struct gui_entry *ge;
2833
2834         if (!page) {
2835                 set_job_menu_visible(ui, 0);
2836                 return TRUE;
2837         }
2838
2839         set_job_menu_visible(ui, 1);
2840         ge = get_ge_from_page(page, NULL);
2841         if (ge)
2842                 update_button_states(ui, ge);
2843
2844         return TRUE;
2845 }
2846
2847 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2848 {
2849         time_t time_a = gtk_recent_info_get_visited(a);
2850         time_t time_b = gtk_recent_info_get_visited(b);
2851
2852         return time_b - time_a;
2853 }
2854
2855 static void add_recent_file_items(struct gui *ui)
2856 {
2857         const gchar *gfio = g_get_application_name();
2858         GList *items, *item;
2859         int i = 0;
2860
2861         if (ui->recent_ui_id) {
2862                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2863                 gtk_ui_manager_ensure_update(ui->uimanager);
2864         }
2865         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2866
2867         if (ui->actiongroup) {
2868                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2869                 g_object_unref(ui->actiongroup);
2870         }
2871         ui->actiongroup = gtk_action_group_new("RecentFileActions");
2872
2873         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2874
2875         items = gtk_recent_manager_get_items(ui->recentmanager);
2876         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2877
2878         for (item = items; item && item->data; item = g_list_next(item)) {
2879                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2880                 gchar *action_name;
2881                 const gchar *label;
2882                 GtkAction *action;
2883
2884                 if (!gtk_recent_info_has_application(info, gfio))
2885                         continue;
2886
2887                 /*
2888                  * We only support local files for now
2889                  */
2890                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2891                         continue;
2892
2893                 action_name = g_strdup_printf("RecentFile%u", i++);
2894                 label = gtk_recent_info_get_display_name(info);
2895
2896                 action = g_object_new(GTK_TYPE_ACTION,
2897                                         "name", action_name,
2898                                         "label", label, NULL);
2899
2900                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2901                                         gtk_recent_info_ref(info),
2902                                         (GDestroyNotify) gtk_recent_info_unref);
2903
2904
2905                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2906
2907                 gtk_action_group_add_action(ui->actiongroup, action);
2908                 g_object_unref(action);
2909
2910                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2911                                         "/MainMenu/FileMenu/FileRecentFiles",
2912                                         label, action_name,
2913                                         GTK_UI_MANAGER_MENUITEM, FALSE);
2914
2915                 g_free(action_name);
2916
2917                 if (i == 8)
2918                         break;
2919         }
2920
2921         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2922         g_list_free(items);
2923 }
2924
2925 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2926                                    gint x, gint y, GtkSelectionData *data,
2927                                    guint info, guint time)
2928 {
2929         struct gui *ui = &main_ui;
2930         gchar **uris;
2931         GtkWidget *source;
2932         int i;
2933
2934         source = gtk_drag_get_source_widget(ctx);
2935         if (source && widget == gtk_widget_get_toplevel(source)) {
2936                 gtk_drag_finish(ctx, FALSE, FALSE, time);
2937                 return;
2938         }
2939
2940         uris = gtk_selection_data_get_uris(data);
2941         if (!uris) {
2942                 gtk_drag_finish(ctx, FALSE, FALSE, time);
2943                 return;
2944         }
2945
2946         i = 0;
2947         while (uris[i]) {
2948                 if (do_file_open_with_tab(ui, uris[i]))
2949                         break;
2950                 i++;
2951         }
2952
2953         gtk_drag_finish(ctx, TRUE, FALSE, time);
2954         g_strfreev(uris);
2955 }
2956
2957 static void init_ui(int *argc, char **argv[], struct gui *ui)
2958 {
2959         GtkSettings *settings;
2960         GtkWidget *vbox;
2961
2962         /* Magical g*thread incantation, you just need this thread stuff.
2963          * Without it, the update that happens in gfio_update_thread_status
2964          * doesn't really happen in a timely fashion, you need expose events
2965          */
2966         if (!g_thread_supported())
2967                 g_thread_init(NULL);
2968         gdk_threads_init();
2969
2970         gtk_init(argc, argv);
2971         settings = gtk_settings_get_default();
2972         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
2973         g_type_init();
2974         
2975         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2976         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2977         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
2978
2979         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
2980         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
2981
2982         ui->vbox = gtk_vbox_new(FALSE, 0);
2983         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
2984
2985         ui->uimanager = gtk_ui_manager_new();
2986         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
2987         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
2988
2989         ui->recentmanager = gtk_recent_manager_get_default();
2990         add_recent_file_items(ui);
2991
2992         ui->notebook = gtk_notebook_new();
2993         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
2994         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
2995         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
2996         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
2997
2998         vbox = new_main_page(ui);
2999         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3000         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3001         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3002
3003         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3004
3005         gfio_ui_setup_log(ui);
3006
3007         gtk_widget_show_all(ui->window);
3008 }
3009
3010 int main(int argc, char *argv[], char *envp[])
3011 {
3012         if (initialize_fio(envp))
3013                 return 1;
3014         if (fio_init_options())
3015                 return 1;
3016
3017         memset(&main_ui, 0, sizeof(main_ui));
3018         INIT_FLIST_HEAD(&main_ui.list);
3019
3020         init_ui(&argc, &argv, &main_ui);
3021
3022         gdk_threads_enter();
3023         gtk_main();
3024         gdk_threads_leave();
3025         return 0;
3026 }