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