a67accf4b885189f493a12c1a769cbf0a14304b5
[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         printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples);
1693         free(pdu);
1694 }
1695
1696 struct client_ops gfio_client_ops = {
1697         .text                   = gfio_text_op,
1698         .disk_util              = gfio_disk_util_op,
1699         .thread_status          = gfio_thread_status_op,
1700         .group_stats            = gfio_group_stats_op,
1701         .jobs_eta               = gfio_update_client_eta,
1702         .eta                    = gfio_update_all_eta,
1703         .probe                  = gfio_probe_op,
1704         .quit                   = gfio_quit_op,
1705         .add_job                = gfio_add_job_op,
1706         .timed_out              = gfio_client_timed_out,
1707         .stop                   = gfio_client_stop,
1708         .start                  = gfio_client_start,
1709         .job_start              = gfio_client_job_start,
1710         .iolog                  = gfio_client_iolog,
1711         .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
1712         .stay_connected         = 1,
1713         .client_type            = FIO_CLIENT_TYPE_GUI,
1714 };
1715
1716 /*
1717  * FIXME: need more handling here
1718  */
1719 static void ge_destroy(struct gui_entry *ge)
1720 {
1721         struct gfio_client *gc = ge->client;
1722
1723         if (gc && gc->client) {
1724                 if (ge->state >= GE_STATE_CONNECTED)
1725                         fio_client_terminate(gc->client);
1726
1727                 fio_put_client(gc->client);
1728         }
1729
1730         flist_del(&ge->list);
1731         free(ge);
1732 }
1733
1734 static void ge_widget_destroy(GtkWidget *w, gpointer data)
1735 {
1736 }
1737
1738 static void gfio_quit(struct gui *ui)
1739 {
1740         struct gui_entry *ge;
1741
1742         while (!flist_empty(&ui->list)) {
1743                 ge = flist_entry(ui->list.next, struct gui_entry, list);
1744                 ge_destroy(ge);
1745         }
1746
1747         gtk_main_quit();
1748 }
1749
1750 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1751                 __attribute__((unused)) gpointer data)
1752 {
1753         gfio_quit(data);
1754 }
1755
1756 static void *job_thread(void *arg)
1757 {
1758         struct gui *ui = arg;
1759
1760         ui->handler_running = 1;
1761         fio_handle_clients(&gfio_client_ops);
1762         ui->handler_running = 0;
1763         return NULL;
1764 }
1765
1766 static int send_job_files(struct gui_entry *ge)
1767 {
1768         struct gfio_client *gc = ge->client;
1769         int i, ret = 0;
1770
1771         for (i = 0; i < ge->nr_job_files; i++) {
1772                 ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1773                 if (ret < 0) {
1774                         GError *error;
1775
1776                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1777                         report_error(error);
1778                         g_error_free(error);
1779                         break;
1780                 } else if (ret)
1781                         break;
1782
1783                 free(ge->job_files[i]);
1784                 ge->job_files[i] = NULL;
1785         }
1786         while (i < ge->nr_job_files) {
1787                 free(ge->job_files[i]);
1788                 ge->job_files[i] = NULL;
1789                 i++;
1790         }
1791
1792         free(ge->job_files);
1793         ge->job_files = NULL;
1794         ge->nr_job_files = 0;
1795         return ret;
1796 }
1797
1798 static void *server_thread(void *arg)
1799 {
1800         is_backend = 1;
1801         gfio_server_running = 1;
1802         fio_start_server(NULL);
1803         gfio_server_running = 0;
1804         return NULL;
1805 }
1806
1807 static void gfio_start_server(void)
1808 {
1809         struct gui *ui = &main_ui;
1810
1811         if (!gfio_server_running) {
1812                 gfio_server_running = 1;
1813                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1814                 pthread_detach(ui->server_t);
1815         }
1816 }
1817
1818 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1819                 gpointer data)
1820 {
1821         struct gui_entry *ge = data;
1822         struct gfio_client *gc = ge->client;
1823
1824         if (gc)
1825                 fio_start_client(gc->client);
1826 }
1827
1828 static void file_open(GtkWidget *w, gpointer data);
1829
1830 static void connect_clicked(GtkWidget *widget, gpointer data)
1831 {
1832         struct gui_entry *ge = data;
1833         struct gfio_client *gc = ge->client;
1834
1835         if (ge->state == GE_STATE_NEW) {
1836                 int ret;
1837
1838                 if (!ge->nr_job_files)
1839                         file_open(widget, ge->ui);
1840                 if (!ge->nr_job_files)
1841                         return;
1842
1843                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
1844                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1845                 ret = fio_client_connect(gc->client);
1846                 if (!ret) {
1847                         if (!ge->ui->handler_running)
1848                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
1849                         gfio_set_state(ge, GE_STATE_CONNECTED);
1850                 } else {
1851                         GError *error;
1852
1853                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
1854                         report_error(error);
1855                         g_error_free(error);
1856                 }
1857         } else {
1858                 fio_client_terminate(gc->client);
1859                 gfio_set_state(ge, GE_STATE_NEW);
1860                 clear_ge_ui_info(ge);
1861         }
1862 }
1863
1864 static void send_clicked(GtkWidget *widget, gpointer data)
1865 {
1866         struct gui_entry *ge = data;
1867
1868         if (send_job_files(ge)) {
1869                 GError *error;
1870
1871                 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);
1872                 report_error(error);
1873                 g_error_free(error);
1874
1875                 gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
1876         }
1877 }
1878
1879 static GtkWidget *add_button(GtkWidget *buttonbox,
1880                              struct button_spec *buttonspec, gpointer data)
1881 {
1882         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
1883
1884         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
1885         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
1886         gtk_widget_set_tooltip_text(button, buttonspec->tooltiptext);
1887         gtk_widget_set_sensitive(button, !buttonspec->start_insensitive);
1888
1889         return button;
1890 }
1891
1892 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
1893                         int nbuttons)
1894 {
1895         int i;
1896
1897         for (i = 0; i < nbuttons; i++)
1898                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
1899 }
1900
1901 static void on_info_bar_response(GtkWidget *widget, gint response,
1902                                  gpointer data)
1903 {
1904         struct gui *ui = &main_ui;
1905
1906         if (response == GTK_RESPONSE_OK) {
1907                 gtk_widget_destroy(widget);
1908                 ui->error_info_bar = NULL;
1909         }
1910 }
1911
1912 void report_error(GError *error)
1913 {
1914         struct gui *ui = &main_ui;
1915
1916         if (ui->error_info_bar == NULL) {
1917                 ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1918                                                                GTK_RESPONSE_OK,
1919                                                                NULL);
1920                 g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1921                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
1922                                               GTK_MESSAGE_ERROR);
1923                 
1924                 ui->error_label = gtk_label_new(error->message);
1925                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
1926                 gtk_container_add(GTK_CONTAINER(container), ui->error_label);
1927                 
1928                 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
1929                 gtk_widget_show_all(ui->vbox);
1930         } else {
1931                 char buffer[256];
1932                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1933                 gtk_label_set(GTK_LABEL(ui->error_label), buffer);
1934         }
1935 }
1936
1937 struct connection_widgets
1938 {
1939         GtkWidget *hentry;
1940         GtkWidget *combo;
1941         GtkWidget *button;
1942 };
1943
1944 static void hostname_cb(GtkEntry *entry, gpointer data)
1945 {
1946         struct connection_widgets *cw = data;
1947         int uses_net = 0, is_localhost = 0;
1948         const gchar *text;
1949         gchar *ctext;
1950
1951         /*
1952          * Check whether to display the 'auto start backend' box
1953          * or not. Show it if we are a localhost and using network,
1954          * or using a socket.
1955          */
1956         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1957         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1958                 uses_net = 1;
1959         g_free(ctext);
1960
1961         if (uses_net) {
1962                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1963                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1964                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1965                     !strcmp(text, "ip6-loopback"))
1966                         is_localhost = 1;
1967         }
1968
1969         if (!uses_net || is_localhost) {
1970                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1971                 gtk_widget_set_sensitive(cw->button, 1);
1972         } else {
1973                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1974                 gtk_widget_set_sensitive(cw->button, 0);
1975         }
1976 }
1977
1978 static int get_connection_details(char **host, int *port, int *type,
1979                                   int *server_start)
1980 {
1981         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1982         struct connection_widgets cw;
1983         char *typeentry;
1984
1985         dialog = gtk_dialog_new_with_buttons("Connection details",
1986                         GTK_WINDOW(main_ui.window),
1987                         GTK_DIALOG_DESTROY_WITH_PARENT,
1988                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1989                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1990
1991         frame = gtk_frame_new("Hostname / socket name");
1992         /* gtk_dialog_get_content_area() is 2.14 and newer */
1993         vbox = GTK_DIALOG(dialog)->vbox;
1994         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1995
1996         box = gtk_vbox_new(FALSE, 6);
1997         gtk_container_add(GTK_CONTAINER(frame), box);
1998
1999         hbox = gtk_hbox_new(TRUE, 10);
2000         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2001         cw.hentry = gtk_entry_new();
2002         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
2003         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
2004
2005         frame = gtk_frame_new("Port");
2006         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2007         box = gtk_vbox_new(FALSE, 10);
2008         gtk_container_add(GTK_CONTAINER(frame), box);
2009
2010         hbox = gtk_hbox_new(TRUE, 4);
2011         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2012         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
2013
2014         frame = gtk_frame_new("Type");
2015         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2016         box = gtk_vbox_new(FALSE, 10);
2017         gtk_container_add(GTK_CONTAINER(frame), box);
2018
2019         hbox = gtk_hbox_new(TRUE, 4);
2020         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2021
2022         cw.combo = gtk_combo_box_new_text();
2023         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
2024         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
2025         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
2026         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
2027
2028         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
2029
2030         frame = gtk_frame_new("Options");
2031         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2032         box = gtk_vbox_new(FALSE, 10);
2033         gtk_container_add(GTK_CONTAINER(frame), box);
2034
2035         hbox = gtk_hbox_new(TRUE, 4);
2036         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2037
2038         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
2039         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
2040         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.");
2041         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2042
2043         /*
2044          * Connect edit signal, so we can show/not-show the auto start button
2045          */
2046         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
2047         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2048
2049         gtk_widget_show_all(dialog);
2050
2051         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2052                 gtk_widget_destroy(dialog);
2053                 return 1;
2054         }
2055
2056         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
2057         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2058
2059         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
2060         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
2061                 *type = Fio_client_ipv4;
2062         else if (!strncmp(typeentry, "IPv6", 4))
2063                 *type = Fio_client_ipv6;
2064         else
2065                 *type = Fio_client_socket;
2066         g_free(typeentry);
2067
2068         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2069
2070         gtk_widget_destroy(dialog);
2071         return 0;
2072 }
2073
2074 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
2075 {
2076         struct gfio_client *gc;
2077
2078         gc = malloc(sizeof(*gc));
2079         memset(gc, 0, sizeof(*gc));
2080         gc->ge = ge;
2081         gc->client = fio_get_client(client);
2082
2083         ge->client = gc;
2084
2085         client->client_data = gc;
2086 }
2087
2088 static GtkWidget *new_client_page(struct gui_entry *ge);
2089
2090 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2091 {
2092         struct gui_entry *ge;
2093
2094         ge = malloc(sizeof(*ge));
2095         memset(ge, 0, sizeof(*ge));
2096         ge->state = GE_STATE_NEW;
2097         INIT_FLIST_HEAD(&ge->list);
2098         flist_add_tail(&ge->list, &ui->list);
2099         ge->ui = ui;
2100         return ge;
2101 }
2102
2103 static struct gui_entry *get_new_ge_with_tab(const char *name)
2104 {
2105         struct gui_entry *ge;
2106
2107         ge = alloc_new_gui_entry(&main_ui);
2108
2109         ge->vbox = new_client_page(ge);
2110         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
2111
2112         ge->page_label = gtk_label_new(name);
2113         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
2114
2115         gtk_widget_show_all(main_ui.window);
2116         return ge;
2117 }
2118
2119 static void file_new(GtkWidget *w, gpointer data)
2120 {
2121         struct gui *ui = (struct gui *) data;
2122         struct gui_entry *ge;
2123
2124         ge = get_new_ge_with_tab("Untitled");
2125         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2126 }
2127
2128 /*
2129  * Return the 'ge' corresponding to the tab. If the active tab is the
2130  * main tab, open a new tab.
2131  */
2132 static struct gui_entry *get_ge_from_page(gint cur_page, int *created)
2133 {
2134         struct flist_head *entry;
2135         struct gui_entry *ge;
2136
2137         if (!cur_page) {
2138                 if (created)
2139                         *created = 1;
2140                 return get_new_ge_with_tab("Untitled");
2141         }
2142
2143         if (created)
2144                 *created = 0;
2145
2146         flist_for_each(entry, &main_ui.list) {
2147                 ge = flist_entry(entry, struct gui_entry, list);
2148                 if (ge->page_num == cur_page)
2149                         return ge;
2150         }
2151
2152         return NULL;
2153 }
2154
2155 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2156 {
2157         gint cur_page;
2158
2159         /*
2160          * Main tab is tab 0, so any current page other than 0 holds
2161          * a ge entry.
2162          */
2163         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2164         if (cur_page)
2165                 return get_ge_from_page(cur_page, NULL);
2166
2167         return NULL;
2168 }
2169
2170 static void file_close(GtkWidget *w, gpointer data)
2171 {
2172         struct gui *ui = (struct gui *) data;
2173         struct gui_entry *ge;
2174
2175         /*
2176          * Can't close the main tab
2177          */
2178         ge = get_ge_from_cur_tab(ui);
2179         if (ge) {
2180                 gtk_widget_destroy(ge->vbox);
2181                 return;
2182         }
2183
2184         if (!flist_empty(&ui->list)) {
2185                 show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2186                 return;
2187         }
2188
2189         gfio_quit(ui);
2190 }
2191
2192 static void file_add_recent(struct gui *ui, const gchar *uri)
2193 {
2194         GtkRecentData grd;
2195
2196         memset(&grd, 0, sizeof(grd));
2197         grd.display_name = strdup("gfio");
2198         grd.description = strdup("Fio job file");
2199         grd.mime_type = strdup(GFIO_MIME);
2200         grd.app_name = strdup(g_get_application_name());
2201         grd.app_exec = strdup("gfio %f/%u");
2202
2203         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2204 }
2205
2206 static gchar *get_filename_from_uri(const gchar *uri)
2207 {
2208         if (strncmp(uri, "file://", 7))
2209                 return strdup(uri);
2210
2211         return strdup(uri + 7);
2212 }
2213
2214 static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2215                         int type, int port)
2216 {
2217         struct fio_client *client;
2218         gchar *filename;
2219
2220         filename = get_filename_from_uri(uri);
2221
2222         ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2223         ge->job_files[ge->nr_job_files] = strdup(filename);
2224         ge->nr_job_files++;
2225
2226         client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2227         if (!client) {
2228                 GError *error;
2229
2230                 error = g_error_new(g_quark_from_string("fio"), 1,
2231                                 "Failed to add client %s", host);
2232                 report_error(error);
2233                 g_error_free(error);
2234                 return 1;
2235         }
2236
2237         gfio_client_added(ge, client);
2238         file_add_recent(ge->ui, uri);
2239         return 0;
2240 }
2241
2242 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2243 {
2244         int port, type, server_start;
2245         struct gui_entry *ge;
2246         gint cur_page;
2247         char *host;
2248         int ret, ge_is_new = 0;
2249
2250         /*
2251          * Creates new tab if current tab is the main window, or the
2252          * current tab already has a client.
2253          */
2254         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2255         ge = get_ge_from_page(cur_page, &ge_is_new);
2256         if (ge->client) {
2257                 ge = get_new_ge_with_tab("Untitled");
2258                 ge_is_new = 1;
2259         }
2260
2261         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2262
2263         if (get_connection_details(&host, &port, &type, &server_start)) {
2264                 if (ge_is_new)
2265                         gtk_widget_destroy(ge->vbox);
2266                         
2267                 return 1;
2268         }
2269
2270         ret = do_file_open(ge, uri, host, type, port);
2271
2272         free(host);
2273
2274         if (!ret) {
2275                 if (server_start)
2276                         gfio_start_server();
2277         } else {
2278                 if (ge_is_new)
2279                         gtk_widget_destroy(ge->vbox);
2280         }
2281
2282         return ret;
2283 }
2284
2285 static void recent_open(GtkAction *action, gpointer data)
2286 {
2287         struct gui *ui = (struct gui *) data;
2288         GtkRecentInfo *info;
2289         const gchar *uri;
2290
2291         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2292         uri = gtk_recent_info_get_uri(info);
2293
2294         do_file_open_with_tab(ui, uri);
2295 }
2296
2297 static void file_open(GtkWidget *w, gpointer data)
2298 {
2299         struct gui *ui = data;
2300         GtkWidget *dialog;
2301         GSList *filenames, *fn_glist;
2302         GtkFileFilter *filter;
2303
2304         dialog = gtk_file_chooser_dialog_new("Open File",
2305                 GTK_WINDOW(ui->window),
2306                 GTK_FILE_CHOOSER_ACTION_OPEN,
2307                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2308                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2309                 NULL);
2310         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2311
2312         filter = gtk_file_filter_new();
2313         gtk_file_filter_add_pattern(filter, "*.fio");
2314         gtk_file_filter_add_pattern(filter, "*.job");
2315         gtk_file_filter_add_pattern(filter, "*.ini");
2316         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2317         gtk_file_filter_set_name(filter, "Fio job file");
2318         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2319
2320         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2321                 gtk_widget_destroy(dialog);
2322                 return;
2323         }
2324
2325         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2326
2327         gtk_widget_destroy(dialog);
2328
2329         filenames = fn_glist;
2330         while (filenames != NULL) {
2331                 if (do_file_open_with_tab(ui, filenames->data))
2332                         break;
2333                 filenames = g_slist_next(filenames);
2334         }
2335
2336         g_slist_free(fn_glist);
2337 }
2338
2339 static void file_save(GtkWidget *w, gpointer data)
2340 {
2341         struct gui *ui = data;
2342         GtkWidget *dialog;
2343
2344         dialog = gtk_file_chooser_dialog_new("Save File",
2345                 GTK_WINDOW(ui->window),
2346                 GTK_FILE_CHOOSER_ACTION_SAVE,
2347                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2348                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2349                 NULL);
2350
2351         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2352         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2353
2354         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2355                 char *filename;
2356
2357                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2358                 // save_job_file(filename);
2359                 g_free(filename);
2360         }
2361         gtk_widget_destroy(dialog);
2362 }
2363
2364 static void view_log_destroy(GtkWidget *w, gpointer data)
2365 {
2366         struct gui *ui = (struct gui *) data;
2367
2368         gtk_widget_ref(ui->log_tree);
2369         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2370         gtk_widget_destroy(w);
2371         ui->log_view = NULL;
2372 }
2373
2374 static void view_log(GtkWidget *w, gpointer data)
2375 {
2376         GtkWidget *win, *scroll, *vbox, *box;
2377         struct gui *ui = (struct gui *) data;
2378
2379         if (ui->log_view)
2380                 return;
2381
2382         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2383         gtk_window_set_title(GTK_WINDOW(win), "Log");
2384         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2385
2386         scroll = gtk_scrolled_window_new(NULL, NULL);
2387
2388         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2389
2390         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2391
2392         box = gtk_hbox_new(TRUE, 0);
2393         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2394         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2395         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2396
2397         vbox = gtk_vbox_new(TRUE, 5);
2398         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2399
2400         gtk_container_add(GTK_CONTAINER(win), vbox);
2401         gtk_widget_show_all(win);
2402 }
2403
2404 static void connect_job_entry(GtkWidget *w, gpointer data)
2405 {
2406         struct gui *ui = (struct gui *) data;
2407         struct gui_entry *ge;
2408         
2409         ge = get_ge_from_cur_tab(ui);
2410         if (ge)
2411                 connect_clicked(w, ge);
2412 }
2413
2414 static void send_job_entry(GtkWidget *w, gpointer data)
2415 {
2416         struct gui *ui = (struct gui *) data;
2417         struct gui_entry *ge;
2418
2419         ge = get_ge_from_cur_tab(ui);
2420         if (ge)
2421                 send_clicked(w, ge);
2422
2423 }
2424
2425 static void edit_job_entry(GtkWidget *w, gpointer data)
2426 {
2427 }
2428
2429 static void start_job_entry(GtkWidget *w, gpointer data)
2430 {
2431         struct gui *ui = (struct gui *) data;
2432         struct gui_entry *ge;
2433
2434         ge = get_ge_from_cur_tab(ui);
2435         if (ge)
2436                 start_job_clicked(w, ge);
2437 }
2438
2439 static void __update_graph_limits(struct gfio_graphs *g)
2440 {
2441         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2442         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2443 }
2444
2445 static void update_graph_limits(void)
2446 {
2447         struct flist_head *entry;
2448         struct gui_entry *ge;
2449
2450         __update_graph_limits(&main_ui.graphs);
2451
2452         flist_for_each(entry, &main_ui.list) {
2453                 ge = flist_entry(entry, struct gui_entry, list);
2454                 __update_graph_limits(&ge->graphs);
2455         }
2456 }
2457
2458 static void preferences(GtkWidget *w, gpointer data)
2459 {
2460         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2461         GtkWidget *hbox, *spin, *entry, *spin_int;
2462         int i;
2463
2464         dialog = gtk_dialog_new_with_buttons("Preferences",
2465                 GTK_WINDOW(main_ui.window),
2466                 GTK_DIALOG_DESTROY_WITH_PARENT,
2467                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2468                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2469                 NULL);
2470
2471         frame = gtk_frame_new("Graphing");
2472         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2473         vbox = gtk_vbox_new(FALSE, 6);
2474         gtk_container_add(GTK_CONTAINER(frame), vbox);
2475
2476         hbox = gtk_hbox_new(FALSE, 5);
2477         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2478         entry = gtk_label_new("Font face to use for graph labels");
2479         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2480
2481         font = gtk_font_button_new();
2482         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2483
2484         box = gtk_vbox_new(FALSE, 6);
2485         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2486
2487         hbox = gtk_hbox_new(FALSE, 5);
2488         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2489         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2490         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2491
2492         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2493
2494         box = gtk_vbox_new(FALSE, 6);
2495         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2496
2497         hbox = gtk_hbox_new(FALSE, 5);
2498         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2499         entry = gtk_label_new("Client ETA request interval (msec)");
2500         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2501
2502         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2503         frame = gtk_frame_new("Debug logging");
2504         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2505         vbox = gtk_vbox_new(FALSE, 6);
2506         gtk_container_add(GTK_CONTAINER(frame), vbox);
2507
2508         box = gtk_hbox_new(FALSE, 6);
2509         gtk_container_add(GTK_CONTAINER(vbox), box);
2510
2511         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2512
2513         for (i = 0; i < FD_DEBUG_MAX; i++) {
2514                 if (i == 7) {
2515                         box = gtk_hbox_new(FALSE, 6);
2516                         gtk_container_add(GTK_CONTAINER(vbox), box);
2517                 }
2518
2519
2520                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2521                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2522                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2523         }
2524
2525         gtk_widget_show_all(dialog);
2526
2527         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2528                 gtk_widget_destroy(dialog);
2529                 return;
2530         }
2531
2532         for (i = 0; i < FD_DEBUG_MAX; i++) {
2533                 int set;
2534
2535                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2536                 if (set)
2537                         fio_debug |= (1UL << i);
2538         }
2539
2540         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2541         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2542         update_graph_limits();
2543         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2544
2545         gtk_widget_destroy(dialog);
2546 }
2547
2548 static void about_dialog(GtkWidget *w, gpointer data)
2549 {
2550         const char *authors[] = {
2551                 "Jens Axboe <axboe@kernel.dk>",
2552                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2553                 NULL
2554         };
2555         const char *license[] = {
2556                 "Fio is free software; you can redistribute it and/or modify "
2557                 "it under the terms of the GNU General Public License as published by "
2558                 "the Free Software Foundation; either version 2 of the License, or "
2559                 "(at your option) any later version.\n",
2560                 "Fio is distributed in the hope that it will be useful, "
2561                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2562                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2563                 "GNU General Public License for more details.\n",
2564                 "You should have received a copy of the GNU General Public License "
2565                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2566                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2567         };
2568         char *license_trans;
2569
2570         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2571                                      license[2], "\n", NULL);
2572
2573         gtk_show_about_dialog(NULL,
2574                 "program-name", "gfio",
2575                 "comments", "Gtk2 UI for fio",
2576                 "license", license_trans,
2577                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2578                 "authors", authors,
2579                 "version", fio_version_string,
2580                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2581                 "logo-icon-name", "fio",
2582                 /* Must be last: */
2583                 "wrap-license", TRUE,
2584                 NULL);
2585
2586         g_free(license_trans);
2587 }
2588
2589 static GtkActionEntry menu_items[] = {
2590         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2591         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2592         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2593         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2594         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2595         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2596         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2597         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2598         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2599         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2600         { "ConnectJob", NULL, "Connect", "<Control>E", NULL, G_CALLBACK(connect_job_entry) },
2601         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2602         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2603         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2604         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2605         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2606 };
2607 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2608
2609 static const gchar *ui_string = " \
2610         <ui> \
2611                 <menubar name=\"MainMenu\"> \
2612                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2613                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2614                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2615                                 <separator name=\"Separator1\"/> \
2616                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2617                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2618                                 <separator name=\"Separator2\"/> \
2619                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2620                                 <separator name=\"Separator3\"/> \
2621                                 <placeholder name=\"FileRecentFiles\"/> \
2622                                 <separator name=\"Separator4\"/> \
2623                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2624                         </menu> \
2625                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2626                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2627                                 <separator name=\"Separator5\"/> \
2628                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2629                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2630                                 <separator name=\"Separator6\"/> \
2631                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2632                         </menu>\
2633                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2634                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2635                         </menu>\
2636                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2637                                 <menuitem name=\"About\" action=\"About\" /> \
2638                         </menu> \
2639                 </menubar> \
2640         </ui> \
2641 ";
2642
2643 static void set_job_menu_visible(struct gui *ui, int visible)
2644 {
2645         GtkWidget *job;
2646
2647         job = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu");
2648         gtk_widget_set_sensitive(job, visible);
2649 }
2650
2651 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2652                                    struct gui *ui)
2653 {
2654         GtkActionGroup *action_group;
2655         GError *error = 0;
2656
2657         action_group = gtk_action_group_new("Menu");
2658         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2659
2660         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2661         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2662
2663         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2664
2665         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2666 }
2667
2668 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2669                    GtkWidget *vbox, GtkUIManager *ui_manager)
2670 {
2671         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2672 }
2673
2674 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2675 {
2676         struct gui_entry *ge = (struct gui_entry *) data;
2677         gint index;
2678
2679         index = gtk_combo_box_get_active(box);
2680
2681         multitext_set_entry(&ge->eta.iotype, index);
2682         multitext_set_entry(&ge->eta.ioengine, index);
2683         multitext_set_entry(&ge->eta.iodepth, index);
2684 }
2685
2686 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2687 {
2688         struct gui_entry *ge = (struct gui_entry *) data;
2689
2690         multitext_free(&ge->eta.iotype);
2691         multitext_free(&ge->eta.ioengine);
2692         multitext_free(&ge->eta.iodepth);
2693 }
2694
2695 static GtkWidget *new_client_page(struct gui_entry *ge)
2696 {
2697         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2698         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2699         GdkColor white;
2700
2701         main_vbox = gtk_vbox_new(FALSE, 3);
2702
2703         top_align = gtk_alignment_new(0, 0, 1, 0);
2704         top_vbox = gtk_vbox_new(FALSE, 3);
2705         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2706         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2707
2708         probe = gtk_frame_new("Job");
2709         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2710         probe_frame = gtk_vbox_new(FALSE, 3);
2711         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2712
2713         probe_box = gtk_hbox_new(FALSE, 3);
2714         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2715         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2716         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2717         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2718         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2719
2720         probe_box = gtk_hbox_new(FALSE, 3);
2721         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2722
2723         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2724         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2725         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2726         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2727         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2728         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2729         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2730         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2731
2732         probe_box = gtk_hbox_new(FALSE, 3);
2733         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2734         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2735         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2736         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2737         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2738
2739         /*
2740          * Only add this if we have a commit rate
2741          */
2742 #if 0
2743         probe_box = gtk_hbox_new(FALSE, 3);
2744         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2745
2746         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2747         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2748
2749         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2750         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2751 #endif
2752
2753         /*
2754          * Set up a drawing area and IOPS and bandwidth graphs
2755          */
2756         gdk_color_parse("white", &white);
2757         ge->graphs.drawing_area = gtk_drawing_area_new();
2758         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2759                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2760         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2761         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2762                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2763         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2764                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2765         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2766         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2767                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2768         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2769                                         ge->graphs.drawing_area);
2770         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2771
2772         setup_graphs(&ge->graphs);
2773
2774         /*
2775          * Set up alignments for widgets at the bottom of ui, 
2776          * align bottom left, expand horizontally but not vertically
2777          */
2778         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2779         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2780         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2781         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2782
2783         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2784
2785         /*
2786          * Set up thread status progress bar
2787          */
2788         ge->thread_status_pb = gtk_progress_bar_new();
2789         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2790         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2791         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2792
2793
2794         return main_vbox;
2795 }
2796
2797 static GtkWidget *new_main_page(struct gui *ui)
2798 {
2799         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2800         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2801         GdkColor white;
2802
2803         main_vbox = gtk_vbox_new(FALSE, 3);
2804
2805         /*
2806          * Set up alignments for widgets at the top of ui,
2807          * align top left, expand horizontally but not vertically
2808          */
2809         top_align = gtk_alignment_new(0, 0, 1, 0);
2810         top_vbox = gtk_vbox_new(FALSE, 0);
2811         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2812         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2813
2814         probe = gtk_frame_new("Run statistics");
2815         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2816         probe_frame = gtk_vbox_new(FALSE, 3);
2817         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2818
2819         probe_box = gtk_hbox_new(FALSE, 3);
2820         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2821         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2822         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2823         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2824         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2825         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2826
2827         /*
2828          * Only add this if we have a commit rate
2829          */
2830 #if 0
2831         probe_box = gtk_hbox_new(FALSE, 3);
2832         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2833
2834         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2835         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2836
2837         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2838         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2839 #endif
2840
2841         /*
2842          * Set up a drawing area and IOPS and bandwidth graphs
2843          */
2844         gdk_color_parse("white", &white);
2845         ui->graphs.drawing_area = gtk_drawing_area_new();
2846         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2847                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2848         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2849         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2850                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2851         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2852                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2853         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2854         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2855                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2856         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2857                                         ui->graphs.drawing_area);
2858         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2859                         TRUE, TRUE, 0);
2860
2861         setup_graphs(&ui->graphs);
2862
2863         /*
2864          * Set up alignments for widgets at the bottom of ui, 
2865          * align bottom left, expand horizontally but not vertically
2866          */
2867         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2868         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2869         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2870         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2871
2872         /*
2873          * Set up thread status progress bar
2874          */
2875         ui->thread_status_pb = gtk_progress_bar_new();
2876         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2877         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2878         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2879
2880         return main_vbox;
2881 }
2882
2883 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2884                                      guint page, gpointer data)
2885
2886 {
2887         struct gui *ui = (struct gui *) data;
2888         struct gui_entry *ge;
2889
2890         if (!page) {
2891                 set_job_menu_visible(ui, 0);
2892                 return TRUE;
2893         }
2894
2895         set_job_menu_visible(ui, 1);
2896         ge = get_ge_from_page(page, NULL);
2897         if (ge)
2898                 update_button_states(ui, ge);
2899
2900         return TRUE;
2901 }
2902
2903 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2904 {
2905         time_t time_a = gtk_recent_info_get_visited(a);
2906         time_t time_b = gtk_recent_info_get_visited(b);
2907
2908         return time_b - time_a;
2909 }
2910
2911 static void add_recent_file_items(struct gui *ui)
2912 {
2913         const gchar *gfio = g_get_application_name();
2914         GList *items, *item;
2915         int i = 0;
2916
2917         if (ui->recent_ui_id) {
2918                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2919                 gtk_ui_manager_ensure_update(ui->uimanager);
2920         }
2921         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2922
2923         if (ui->actiongroup) {
2924                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2925                 g_object_unref(ui->actiongroup);
2926         }
2927         ui->actiongroup = gtk_action_group_new("RecentFileActions");
2928
2929         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2930
2931         items = gtk_recent_manager_get_items(ui->recentmanager);
2932         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2933
2934         for (item = items; item && item->data; item = g_list_next(item)) {
2935                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2936                 gchar *action_name;
2937                 const gchar *label;
2938                 GtkAction *action;
2939
2940                 if (!gtk_recent_info_has_application(info, gfio))
2941                         continue;
2942
2943                 /*
2944                  * We only support local files for now
2945                  */
2946                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2947                         continue;
2948
2949                 action_name = g_strdup_printf("RecentFile%u", i++);
2950                 label = gtk_recent_info_get_display_name(info);
2951
2952                 action = g_object_new(GTK_TYPE_ACTION,
2953                                         "name", action_name,
2954                                         "label", label, NULL);
2955
2956                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2957                                         gtk_recent_info_ref(info),
2958                                         (GDestroyNotify) gtk_recent_info_unref);
2959
2960
2961                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2962
2963                 gtk_action_group_add_action(ui->actiongroup, action);
2964                 g_object_unref(action);
2965
2966                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2967                                         "/MainMenu/FileMenu/FileRecentFiles",
2968                                         label, action_name,
2969                                         GTK_UI_MANAGER_MENUITEM, FALSE);
2970
2971                 g_free(action_name);
2972
2973                 if (i == 8)
2974                         break;
2975         }
2976
2977         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2978         g_list_free(items);
2979 }
2980
2981 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2982                                    gint x, gint y, GtkSelectionData *data,
2983                                    guint info, guint time)
2984 {
2985         struct gui *ui = &main_ui;
2986         gchar **uris;
2987         GtkWidget *source;
2988         int i;
2989
2990         source = gtk_drag_get_source_widget(ctx);
2991         if (source && widget == gtk_widget_get_toplevel(source)) {
2992                 gtk_drag_finish(ctx, FALSE, FALSE, time);
2993                 return;
2994         }
2995
2996         uris = gtk_selection_data_get_uris(data);
2997         if (!uris) {
2998                 gtk_drag_finish(ctx, FALSE, FALSE, time);
2999                 return;
3000         }
3001
3002         i = 0;
3003         while (uris[i]) {
3004                 if (do_file_open_with_tab(ui, uris[i]))
3005                         break;
3006                 i++;
3007         }
3008
3009         gtk_drag_finish(ctx, TRUE, FALSE, time);
3010         g_strfreev(uris);
3011 }
3012
3013 static void init_ui(int *argc, char **argv[], struct gui *ui)
3014 {
3015         GtkSettings *settings;
3016         GtkWidget *vbox;
3017
3018         /* Magical g*thread incantation, you just need this thread stuff.
3019          * Without it, the update that happens in gfio_update_thread_status
3020          * doesn't really happen in a timely fashion, you need expose events
3021          */
3022         if (!g_thread_supported())
3023                 g_thread_init(NULL);
3024         gdk_threads_init();
3025
3026         gtk_init(argc, argv);
3027         settings = gtk_settings_get_default();
3028         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
3029         g_type_init();
3030         
3031         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3032         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
3033         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
3034
3035         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
3036         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
3037
3038         ui->vbox = gtk_vbox_new(FALSE, 0);
3039         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3040
3041         ui->uimanager = gtk_ui_manager_new();
3042         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3043         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3044
3045         ui->recentmanager = gtk_recent_manager_get_default();
3046         add_recent_file_items(ui);
3047
3048         ui->notebook = gtk_notebook_new();
3049         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3050         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3051         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3052         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3053
3054         vbox = new_main_page(ui);
3055         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3056         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3057         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3058
3059         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3060
3061         gfio_ui_setup_log(ui);
3062
3063         gtk_widget_show_all(ui->window);
3064 }
3065
3066 int main(int argc, char *argv[], char *envp[])
3067 {
3068         if (initialize_fio(envp))
3069                 return 1;
3070         if (fio_init_options())
3071                 return 1;
3072
3073         memset(&main_ui, 0, sizeof(main_ui));
3074         INIT_FLIST_HEAD(&main_ui.list);
3075
3076         init_ui(&argc, &argv, &main_ui);
3077
3078         gdk_threads_enter();
3079         gtk_main();
3080         gdk_threads_leave();
3081         return 0;
3082 }