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