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