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