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