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