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