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