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