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