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