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