d200ade05168c6ae83d50f92757b1945ee07e660
[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 "gfio.h"
34 #include "ghelpers.h"
35 #include "goptions.h"
36 #include "gerror.h"
37 #include "gclient.h"
38 #include "graph.h"
39
40 static int gfio_server_running;
41 static unsigned int gfio_graph_limit = 100;
42
43 GdkColor gfio_color_white;
44 const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
45
46 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
47
48 static void connect_clicked(GtkWidget *widget, gpointer data);
49 static void start_job_clicked(GtkWidget *widget, gpointer data);
50 static void send_clicked(GtkWidget *widget, gpointer data);
51
52 static struct button_spec {
53         const char *buttontext;
54         clickfunction f;
55         const char *tooltiptext[2];
56         const int start_sensitive;
57 } buttonspeclist[] = {
58         {
59           .buttontext           = "Connect",
60           .f                    = connect_clicked,
61           .tooltiptext          = { "Disconnect from host", "Connect to host" },
62           .start_sensitive      = 1,
63         },
64         {
65           .buttontext           = "Send",
66           .f                    = send_clicked,
67           .tooltiptext          = { "Send job description to host", NULL },
68           .start_sensitive      = 0,
69         },
70         {
71           .buttontext           = "Start Job",
72           .f                    = start_job_clicked,
73           .tooltiptext          = { "Start the current job on the server", NULL },
74           .start_sensitive      = 0,
75         },
76 };
77
78 static void setup_iops_graph(struct gfio_graphs *gg)
79 {
80         struct graph *g;
81
82         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
83         graph_title(g, "IOPS (IOs/sec)");
84         graph_x_title(g, "Time (secs)");
85         gg->read_iops = graph_add_label(g, "Read IOPS");
86         gg->write_iops = graph_add_label(g, "Write IOPS");
87         graph_set_color(g, gg->read_iops, 0.13, 0.54, 0.13);
88         graph_set_color(g, gg->write_iops, 1.0, 0.0, 0.0);
89         line_graph_set_data_count_limit(g, gfio_graph_limit);
90         graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
91         graph_set_graph_all_zeroes(g, 0);
92         gg->iops_graph = g;
93 }
94
95 static void setup_bandwidth_graph(struct gfio_graphs *gg)
96 {
97         struct graph *g;
98
99         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
100         graph_title(g, "Bandwidth (bytes/sec)");
101         graph_x_title(g, "Time (secs)");
102         gg->read_bw = graph_add_label(g, "Read Bandwidth");
103         gg->write_bw = graph_add_label(g, "Write Bandwidth");
104         graph_set_color(g, gg->read_bw, 0.13, 0.54, 0.13);
105         graph_set_color(g, gg->write_bw, 1.0, 0.0, 0.0);
106         graph_set_base_offset(g, 1);
107         line_graph_set_data_count_limit(g, 100);
108         graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
109         graph_set_graph_all_zeroes(g, 0);
110         gg->bandwidth_graph = g;
111 }
112
113 static void setup_graphs(struct gfio_graphs *g)
114 {
115         setup_iops_graph(g);
116         setup_bandwidth_graph(g);
117 }
118
119 void clear_ge_ui_info(struct gui_entry *ge)
120 {
121         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
122         gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
123         gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
124         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
125 #if 0
126         /* should we empty it... */
127         gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
128 #endif
129         multitext_update_entry(&ge->eta.iotype, 0, "");
130         multitext_update_entry(&ge->eta.bs, 0, "");
131         multitext_update_entry(&ge->eta.ioengine, 0, "");
132         multitext_update_entry(&ge->eta.iodepth, 0, "");
133         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
134         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
135         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
136         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
137         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
138         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
139 }
140
141 static void set_menu_entry_text(struct gui *ui, const char *path,
142                                 const char *text)
143 {
144         GtkWidget *w;
145
146         w = gtk_ui_manager_get_widget(ui->uimanager, path);
147         if (w)
148                 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
149         else
150                 fprintf(stderr, "gfio: can't find path %s\n", path);
151 }
152
153
154 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
155 {
156         GtkWidget *w;
157
158         w = gtk_ui_manager_get_widget(ui->uimanager, path);
159         if (w)
160                 gtk_widget_set_sensitive(w, show);
161         else
162                 fprintf(stderr, "gfio: can't find path %s\n", path);
163 }
164
165 static void set_job_menu_visible(struct gui *ui, int visible)
166 {
167         set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
168 }
169
170 static void set_view_results_visible(struct gui *ui, int visible)
171 {
172         set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
173 }
174
175 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
176 {
177         if (s->tooltiptext[sensitive])
178                 return s->tooltiptext[sensitive];
179
180         return s->tooltiptext[0];
181 }
182
183 static GtkWidget *add_button(GtkWidget *buttonbox,
184                              struct button_spec *buttonspec, gpointer data)
185 {
186         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
187         gboolean sens = buttonspec->start_sensitive;
188
189         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
190         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
191
192         sens = buttonspec->start_sensitive;
193         gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
194         gtk_widget_set_sensitive(button, sens);
195
196         return button;
197 }
198
199 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
200                         int nbuttons)
201 {
202         int i;
203
204         for (i = 0; i < nbuttons; i++)
205                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
206 }
207
208 /*
209  * Update sensitivity of job buttons and job menu items, based on the
210  * state of the client.
211  */
212 static void update_button_states(struct gui *ui, struct gui_entry *ge)
213 {
214         unsigned int connect_state, send_state, start_state, edit_state;
215         const char *connect_str = NULL;
216
217         switch (ge->state) {
218         default:
219                 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
220                 /* fall through to new state */
221         case GE_STATE_NEW:
222                 connect_state = 1;
223                 edit_state = 1;
224                 connect_str = "Connect";
225                 send_state = 0;
226                 start_state = 0;
227                 break;
228         case GE_STATE_CONNECTED:
229                 connect_state = 1;
230                 edit_state = 1;
231                 connect_str = "Disconnect";
232                 send_state = 1;
233                 start_state = 0;
234                 break;
235         case GE_STATE_JOB_SENT:
236                 connect_state = 1;
237                 edit_state = 1;
238                 connect_str = "Disconnect";
239                 send_state = 0;
240                 start_state = 1;
241                 break;
242         case GE_STATE_JOB_STARTED:
243                 connect_state = 1;
244                 edit_state = 1;
245                 connect_str = "Disconnect";
246                 send_state = 0;
247                 start_state = 1;
248                 break;
249         case GE_STATE_JOB_RUNNING:
250                 connect_state = 1;
251                 edit_state = 0;
252                 connect_str = "Disconnect";
253                 send_state = 0;
254                 start_state = 0;
255                 break;
256         case GE_STATE_JOB_DONE:
257                 connect_state = 1;
258                 edit_state = 0;
259                 connect_str = "Connect";
260                 send_state = 0;
261                 start_state = 0;
262                 break;
263         }
264
265         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
266         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
267         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
268         gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
269         gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
270
271         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
272         set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
273
274         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
275         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
276         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
277
278         if (ge->client && ge->client->nr_results)
279                 set_view_results_visible(ui, 1);
280         else
281                 set_view_results_visible(ui, 0);
282 }
283
284 void gfio_set_state(struct gui_entry *ge, unsigned int state)
285 {
286         ge->state = state;
287         update_button_states(ge->ui, ge);
288 }
289
290 static void gfio_ui_setup_log(struct gui *ui)
291 {
292         GtkTreeSelection *selection;
293         GtkListStore *model;
294         GtkWidget *tree_view;
295
296         model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
297
298         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
299         gtk_widget_set_can_focus(tree_view, FALSE);
300
301         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
302         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
303         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
304                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
305
306         tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
307         tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
308         tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
309         tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
310
311         ui->log_model = model;
312         ui->log_tree = tree_view;
313 }
314
315 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
316                                    gpointer data)
317 {
318         struct gfio_graphs *g = data;
319
320         graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
321         graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
322         graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
323         graph_set_position(g->bandwidth_graph, 0, 0);
324         return TRUE;
325 }
326
327 static void draw_graph(struct graph *g, cairo_t *cr)
328 {
329         line_graph_draw(g, cr);
330         cairo_stroke(cr);
331 }
332
333 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
334                               gboolean keyboard_mode, GtkTooltip *tooltip,
335                               gpointer data)
336 {
337         struct gfio_graphs *g = data;
338         const char *text = NULL;
339
340         if (graph_contains_xy(g->iops_graph, x, y))
341                 text = graph_find_tooltip(g->iops_graph, x, y);
342         else if (graph_contains_xy(g->bandwidth_graph, x, y))
343                 text = graph_find_tooltip(g->bandwidth_graph, x, y);
344
345         if (text) {
346                 gtk_tooltip_set_text(tooltip, text);
347                 return TRUE;
348         }
349
350         return FALSE;
351 }
352
353 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
354 {
355         struct gfio_graphs *g = p;
356         cairo_t *cr;
357
358         cr = gdk_cairo_create(w->window);
359
360         if (graph_has_tooltips(g->iops_graph) ||
361             graph_has_tooltips(g->bandwidth_graph)) {
362                 g_object_set(w, "has-tooltip", TRUE, NULL);
363                 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
364         }
365
366         cairo_set_source_rgb(cr, 0, 0, 0);
367         draw_graph(g->iops_graph, cr);
368         draw_graph(g->bandwidth_graph, cr);
369         cairo_destroy(cr);
370
371         return FALSE;
372 }
373
374 /*
375  * FIXME: need more handling here
376  */
377 static void ge_destroy(struct gui_entry *ge)
378 {
379         struct gfio_client *gc = ge->client;
380
381         if (gc) {
382                 if (gc->client) {
383                         if (ge->state >= GE_STATE_CONNECTED)
384                                 fio_client_terminate(gc->client);
385
386                         fio_put_client(gc->client);
387                 }
388                 free(gc);
389         }
390
391         g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
392
393         free(ge->job_file);
394         free(ge->host);
395         free(ge);
396 }
397
398 static void ge_widget_destroy(GtkWidget *w, gpointer data)
399 {
400         struct gui_entry *ge = (struct gui_entry *) data;
401
402         ge_destroy(ge);
403 }
404
405 static void gfio_quit(struct gui *ui)
406 {
407         gtk_main_quit();
408 }
409
410 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
411                 __attribute__((unused)) gpointer data)
412 {
413         struct gui *ui = (struct gui *) data;
414
415         gfio_quit(ui);
416 }
417
418 static void *job_thread(void *arg)
419 {
420         struct gui *ui = arg;
421
422         ui->handler_running = 1;
423         fio_handle_clients(&gfio_client_ops);
424         ui->handler_running = 0;
425         return NULL;
426 }
427
428 static int send_job_file(struct gui_entry *ge)
429 {
430         struct gfio_client *gc = ge->client;
431         int ret = 0;
432
433         ret = fio_client_send_ini(gc->client, ge->job_file);
434         if (!ret)
435                 return 0;
436
437         gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
438         return 1;
439 }
440
441 static void *server_thread(void *arg)
442 {
443         is_backend = 1;
444         gfio_server_running = 1;
445         fio_start_server(NULL);
446         gfio_server_running = 0;
447         return NULL;
448 }
449
450 static void gfio_start_server(struct gui *ui)
451 {
452         if (!gfio_server_running) {
453                 gfio_server_running = 1;
454                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
455                 pthread_detach(ui->server_t);
456         }
457 }
458
459 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
460                 gpointer data)
461 {
462         struct gui_entry *ge = data;
463         struct gfio_client *gc = ge->client;
464
465         if (gc)
466                 fio_start_client(gc->client);
467 }
468
469 static void file_open(GtkWidget *w, gpointer data);
470
471 struct connection_widgets
472 {
473         GtkWidget *hentry;
474         GtkWidget *combo;
475         GtkWidget *button;
476 };
477
478 static void hostname_cb(GtkEntry *entry, gpointer data)
479 {
480         struct connection_widgets *cw = data;
481         int uses_net = 0, is_localhost = 0;
482         const gchar *text;
483         gchar *ctext;
484
485         /*
486          * Check whether to display the 'auto start backend' box
487          * or not. Show it if we are a localhost and using network,
488          * or using a socket.
489          */
490         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
491         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
492                 uses_net = 1;
493         g_free(ctext);
494
495         if (uses_net) {
496                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
497                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
498                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
499                     !strcmp(text, "ip6-loopback"))
500                         is_localhost = 1;
501         }
502
503         if (!uses_net || is_localhost) {
504                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
505                 gtk_widget_set_sensitive(cw->button, 1);
506         } else {
507                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
508                 gtk_widget_set_sensitive(cw->button, 0);
509         }
510 }
511
512 static int get_connection_details(struct gui_entry *ge)
513 {
514         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
515         struct connection_widgets cw;
516         struct gui *ui = ge->ui;
517         char *typeentry;
518
519         if (ge->host)
520                 return 0;
521
522         dialog = gtk_dialog_new_with_buttons("Connection details",
523                         GTK_WINDOW(ui->window),
524                         GTK_DIALOG_DESTROY_WITH_PARENT,
525                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
526                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
527
528         frame = gtk_frame_new("Hostname / socket name");
529         /* gtk_dialog_get_content_area() is 2.14 and newer */
530         vbox = GTK_DIALOG(dialog)->vbox;
531         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
532
533         box = gtk_vbox_new(FALSE, 6);
534         gtk_container_add(GTK_CONTAINER(frame), box);
535
536         hbox = gtk_hbox_new(TRUE, 10);
537         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
538         cw.hentry = gtk_entry_new();
539         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
540         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
541
542         frame = gtk_frame_new("Port");
543         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
544         box = gtk_vbox_new(FALSE, 10);
545         gtk_container_add(GTK_CONTAINER(frame), box);
546
547         hbox = gtk_hbox_new(TRUE, 4);
548         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
549         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
550
551         frame = gtk_frame_new("Type");
552         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
553         box = gtk_vbox_new(FALSE, 10);
554         gtk_container_add(GTK_CONTAINER(frame), box);
555
556         hbox = gtk_hbox_new(TRUE, 4);
557         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
558
559         cw.combo = gtk_combo_box_new_text();
560         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
561         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
562         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
563         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
564
565         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
566
567         frame = gtk_frame_new("Options");
568         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
569         box = gtk_vbox_new(FALSE, 10);
570         gtk_container_add(GTK_CONTAINER(frame), box);
571
572         hbox = gtk_hbox_new(TRUE, 4);
573         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
574
575         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
576         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
577         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.");
578         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
579
580         /*
581          * Connect edit signal, so we can show/not-show the auto start button
582          */
583         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
584         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
585
586         gtk_widget_show_all(dialog);
587
588         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
589                 gtk_widget_destroy(dialog);
590                 return 1;
591         }
592
593         ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
594         ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
595
596         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
597         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
598                 ge->type = Fio_client_ipv4;
599         else if (!strncmp(typeentry, "IPv6", 4))
600                 ge->type = Fio_client_ipv6;
601         else
602                 ge->type = Fio_client_socket;
603         g_free(typeentry);
604
605         ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
606
607         gtk_widget_destroy(dialog);
608         return 0;
609 }
610
611 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
612 {
613         gc->client = fio_get_client(client);
614         client->client_data = gc;
615 }
616
617 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
618 {
619         struct gfio_client_options *gco;
620         struct gfio_client *gc;
621
622         gc = calloc(1, sizeof(*gc));
623         INIT_FLIST_HEAD(&gc->o_list);
624         gc->ge = ge;
625         ge->client = gc;
626         gfio_set_client(gc, client);
627
628         /*
629          * Just add a default set of options, need to consider how best
630          * to handle this
631          */
632         gco = calloc(1, sizeof(*gco));
633         options_default_fill(&gco->o);
634         flist_add_tail(&gco->list, &gc->o_list);
635 }
636
637 static void connect_clicked(GtkWidget *widget, gpointer data)
638 {
639         struct gui_entry *ge = data;
640         struct gfio_client *gc = ge->client;
641
642         if (ge->state == GE_STATE_NEW) {
643                 int ret;
644
645                 if (!ge->job_file)
646                         file_open(widget, ge->ui);
647                 if (!ge->job_file)
648                         return;
649
650                 gc = ge->client;
651
652                 if (!gc->client) {
653                         struct fio_client *client;
654
655                         if (get_connection_details(ge)) {
656                                 gfio_report_error(ge, "Failed to get connection details\n");
657                                 return;
658                         }
659
660                         client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
661                         if (!client) {
662                                 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
663                                 free(ge->host);
664                                 ge->host = NULL;
665                                 return;
666                         }
667                         gfio_set_client(gc, client);
668                 }
669
670                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
671                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
672                 ret = fio_client_connect(gc->client);
673                 if (!ret) {
674                         if (!ge->ui->handler_running)
675                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
676                         gfio_set_state(ge, GE_STATE_CONNECTED);
677                 } else {
678                         gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
679                 }
680         } else {
681                 fio_client_terminate(gc->client);
682                 gfio_set_state(ge, GE_STATE_NEW);
683                 clear_ge_ui_info(ge);
684         }
685 }
686
687 static void send_clicked(GtkWidget *widget, gpointer data)
688 {
689         struct gui_entry *ge = data;
690
691         if (send_job_file(ge))
692                 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
693 }
694
695 static GtkWidget *new_client_page(struct gui_entry *ge);
696
697 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
698 {
699         struct gui_entry *ge;
700
701         ge = malloc(sizeof(*ge));
702         memset(ge, 0, sizeof(*ge));
703         ge->state = GE_STATE_NEW;
704         ge->ui = ui;
705         return ge;
706 }
707
708 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
709 {
710         struct gui_entry *ge;
711
712         ge = alloc_new_gui_entry(ui);
713
714         ge->vbox = new_client_page(ge);
715         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
716
717         ge->page_label = gtk_label_new(name);
718         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
719
720         g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
721
722         gtk_widget_show_all(ui->window);
723         return ge;
724 }
725
726 static void file_new(GtkWidget *w, gpointer data)
727 {
728         struct gui *ui = (struct gui *) data;
729         struct gui_entry *ge;
730
731         ge = get_new_ge_with_tab(ui, "Untitled");
732         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
733 }
734
735 /*
736  * Return the 'ge' corresponding to the tab. If the active tab is the
737  * main tab, open a new tab.
738  */
739 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
740                                           int *created)
741 {
742         if (!cur_page) {
743                 if (created)
744                         *created = 1;
745                 return get_new_ge_with_tab(ui, "Untitled");
746         }
747
748         if (created)
749                 *created = 0;
750
751         return g_hash_table_lookup(ui->ge_hash, &cur_page);
752 }
753
754 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
755 {
756         gint cur_page;
757
758         /*
759          * Main tab is tab 0, so any current page other than 0 holds
760          * a ge entry.
761          */
762         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
763         if (cur_page)
764                 return get_ge_from_page(ui, cur_page, NULL);
765
766         return NULL;
767 }
768
769 static void file_close(GtkWidget *w, gpointer data)
770 {
771         struct gui *ui = (struct gui *) data;
772         struct gui_entry *ge;
773
774         /*
775          * Can't close the main tab
776          */
777         ge = get_ge_from_cur_tab(ui);
778         if (ge) {
779                 gtk_widget_destroy(ge->vbox);
780                 return;
781         }
782
783         if (g_hash_table_size(ui->ge_hash)) {
784                 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
785                 return;
786         }
787
788         gfio_quit(ui);
789 }
790
791 static void file_add_recent(struct gui *ui, const gchar *uri)
792 {
793         GtkRecentData grd;
794
795         memset(&grd, 0, sizeof(grd));
796         grd.display_name = strdup("gfio");
797         grd.description = strdup("Fio job file");
798         grd.mime_type = strdup(GFIO_MIME);
799         grd.app_name = strdup(g_get_application_name());
800         grd.app_exec = strdup("gfio %f/%u");
801
802         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
803 }
804
805 static gchar *get_filename_from_uri(const gchar *uri)
806 {
807         if (strncmp(uri, "file://", 7))
808                 return strdup(uri);
809
810         return strdup(uri + 7);
811 }
812
813 static int do_file_open(struct gui_entry *ge, const gchar *uri)
814 {
815         struct fio_client *client;
816
817         assert(!ge->job_file);
818
819         ge->job_file = get_filename_from_uri(uri);
820
821         client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
822         if (client) {
823                 char *label = strdup(uri);
824
825                 basename(label);
826                 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
827                 free(label);
828
829                 gfio_client_added(ge, client);
830                 file_add_recent(ge->ui, uri);
831                 return 0;
832         }
833
834         gfio_report_error(ge, "Failed to add client %s\n", ge->host);
835         free(ge->host);
836         ge->host = NULL;
837         free(ge->job_file);
838         ge->job_file = NULL;
839         return 1;
840 }
841
842 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
843 {
844         struct gui_entry *ge;
845         gint cur_page;
846         int ret, ge_is_new = 0;
847
848         /*
849          * Creates new tab if current tab is the main window, or the
850          * current tab already has a client.
851          */
852         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
853         ge = get_ge_from_page(ui, cur_page, &ge_is_new);
854         if (ge->client) {
855                 ge = get_new_ge_with_tab(ui, "Untitled");
856                 ge_is_new = 1;
857         }
858
859         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
860
861         if (get_connection_details(ge)) {
862                 if (ge_is_new)
863                         gtk_widget_destroy(ge->vbox);
864                         
865                 return 1;
866         }
867
868         ret = do_file_open(ge, uri);
869
870         if (!ret) {
871                 if (ge->server_start)
872                         gfio_start_server(ui);
873         } else {
874                 if (ge_is_new)
875                         gtk_widget_destroy(ge->vbox);
876         }
877
878         return ret;
879 }
880
881 static void recent_open(GtkAction *action, gpointer data)
882 {
883         struct gui *ui = (struct gui *) data;
884         GtkRecentInfo *info;
885         const gchar *uri;
886
887         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
888         uri = gtk_recent_info_get_uri(info);
889
890         do_file_open_with_tab(ui, uri);
891 }
892
893 static void file_open(GtkWidget *w, gpointer data)
894 {
895         struct gui *ui = data;
896         GtkWidget *dialog;
897         GtkFileFilter *filter;
898         gchar *filename;
899
900         dialog = gtk_file_chooser_dialog_new("Open File",
901                 GTK_WINDOW(ui->window),
902                 GTK_FILE_CHOOSER_ACTION_OPEN,
903                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
904                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
905                 NULL);
906         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
907
908         filter = gtk_file_filter_new();
909         gtk_file_filter_add_pattern(filter, "*.fio");
910         gtk_file_filter_add_pattern(filter, "*.job");
911         gtk_file_filter_add_pattern(filter, "*.ini");
912         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
913         gtk_file_filter_set_name(filter, "Fio job file");
914         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
915
916         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
917                 gtk_widget_destroy(dialog);
918                 return;
919         }
920
921         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
922
923         gtk_widget_destroy(dialog);
924
925         do_file_open_with_tab(ui, filename);
926         g_free(filename);
927 }
928
929 static void file_save(GtkWidget *w, gpointer data)
930 {
931         struct gui *ui = data;
932         GtkWidget *dialog;
933
934         dialog = gtk_file_chooser_dialog_new("Save File",
935                 GTK_WINDOW(ui->window),
936                 GTK_FILE_CHOOSER_ACTION_SAVE,
937                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
938                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
939                 NULL);
940
941         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
942         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
943
944         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
945                 char *filename;
946
947                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
948                 // save_job_file(filename);
949                 g_free(filename);
950         }
951         gtk_widget_destroy(dialog);
952 }
953
954 static void view_log_destroy(GtkWidget *w, gpointer data)
955 {
956         struct gui *ui = (struct gui *) data;
957
958         gtk_widget_ref(ui->log_tree);
959         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
960         gtk_widget_destroy(w);
961         ui->log_view = NULL;
962 }
963
964 void gfio_view_log(struct gui *ui)
965 {
966         GtkWidget *win, *scroll, *vbox, *box;
967
968         if (ui->log_view)
969                 return;
970
971         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
972         gtk_window_set_title(GTK_WINDOW(win), "Log");
973         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
974
975         scroll = gtk_scrolled_window_new(NULL, NULL);
976
977         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
978
979         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
980
981         box = gtk_hbox_new(TRUE, 0);
982         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
983         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
984         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
985
986         vbox = gtk_vbox_new(TRUE, 5);
987         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
988
989         gtk_container_add(GTK_CONTAINER(win), vbox);
990         gtk_widget_show_all(win);
991 }
992
993 static void view_log(GtkWidget *w, gpointer data)
994 {
995         struct gui *ui = (struct gui *) data;
996
997         gfio_view_log(ui);
998 }
999
1000 static void connect_job_entry(GtkWidget *w, gpointer data)
1001 {
1002         struct gui *ui = (struct gui *) data;
1003         struct gui_entry *ge;
1004         
1005         ge = get_ge_from_cur_tab(ui);
1006         if (ge)
1007                 connect_clicked(w, ge);
1008 }
1009
1010 static void send_job_entry(GtkWidget *w, gpointer data)
1011 {
1012         struct gui *ui = (struct gui *) data;
1013         struct gui_entry *ge;
1014
1015         ge = get_ge_from_cur_tab(ui);
1016         if (ge)
1017                 send_clicked(w, ge);
1018 }
1019
1020 static void edit_job_entry(GtkWidget *w, gpointer data)
1021 {
1022         struct gui *ui = (struct gui *) data;
1023         struct gui_entry *ge;
1024
1025         ge = get_ge_from_cur_tab(ui);
1026         if (ge && ge->client)
1027                 gopt_get_options_window(ui->window, ge->client);
1028 }
1029
1030 static void start_job_entry(GtkWidget *w, gpointer data)
1031 {
1032         struct gui *ui = (struct gui *) data;
1033         struct gui_entry *ge;
1034
1035         ge = get_ge_from_cur_tab(ui);
1036         if (ge)
1037                 start_job_clicked(w, ge);
1038 }
1039
1040 static void view_results(GtkWidget *w, gpointer data)
1041 {
1042         struct gui *ui = (struct gui *) data;
1043         struct gfio_client *gc;
1044         struct gui_entry *ge;
1045
1046         ge = get_ge_from_cur_tab(ui);
1047         if (!ge)
1048                 return;
1049
1050         if (ge->results_window)
1051                 return;
1052
1053         gc = ge->client;
1054         if (gc && gc->nr_results)
1055                 gfio_display_end_results(gc);
1056 }
1057
1058 static void __update_graph_settings(struct gfio_graphs *g)
1059 {
1060         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1061         graph_set_font(g->iops_graph, gfio_graph_font);
1062         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1063         graph_set_font(g->bandwidth_graph, gfio_graph_font);
1064 }
1065
1066 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1067 {
1068         struct gui_entry *ge = (struct gui_entry *) value;
1069         GdkEvent *ev;
1070
1071         __update_graph_settings(&ge->graphs);
1072
1073         ev = gdk_event_new(GDK_EXPOSE);
1074         g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), "expose_event", GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1075         gdk_event_free(ev);
1076 }
1077
1078 static void update_graph_limits(void)
1079 {
1080         struct gui *ui = &main_ui;
1081         GdkEvent *ev;
1082
1083         __update_graph_settings(&ui->graphs);
1084
1085         ev = gdk_event_new(GDK_EXPOSE);
1086         g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), "expose_event", GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1087         gdk_event_free(ev);
1088
1089         g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1090 }
1091
1092 static void preferences(GtkWidget *w, gpointer data)
1093 {
1094         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1095         GtkWidget *hbox, *spin, *entry, *spin_int;
1096         struct gui *ui = (struct gui *) data;
1097         int i;
1098
1099         dialog = gtk_dialog_new_with_buttons("Preferences",
1100                 GTK_WINDOW(ui->window),
1101                 GTK_DIALOG_DESTROY_WITH_PARENT,
1102                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1103                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1104                 NULL);
1105
1106         frame = gtk_frame_new("Graphing");
1107         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1108         vbox = gtk_vbox_new(FALSE, 6);
1109         gtk_container_add(GTK_CONTAINER(frame), vbox);
1110
1111         hbox = gtk_hbox_new(FALSE, 5);
1112         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1113         entry = gtk_label_new("Font face to use for graph labels");
1114         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1115
1116         font = gtk_font_button_new_with_font(gfio_graph_font);
1117         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1118
1119         box = gtk_vbox_new(FALSE, 6);
1120         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1121
1122         hbox = gtk_hbox_new(FALSE, 5);
1123         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1124         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1125         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1126
1127         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1128
1129         box = gtk_vbox_new(FALSE, 6);
1130         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1131
1132         hbox = gtk_hbox_new(FALSE, 5);
1133         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1134         entry = gtk_label_new("Client ETA request interval (msec)");
1135         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1136
1137         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1138         frame = gtk_frame_new("Debug logging");
1139         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1140         vbox = gtk_vbox_new(FALSE, 6);
1141         gtk_container_add(GTK_CONTAINER(frame), vbox);
1142
1143         box = gtk_hbox_new(FALSE, 6);
1144         gtk_container_add(GTK_CONTAINER(vbox), box);
1145
1146         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1147
1148         for (i = 0; i < FD_DEBUG_MAX; i++) {
1149                 if (i == 7) {
1150                         box = gtk_hbox_new(FALSE, 6);
1151                         gtk_container_add(GTK_CONTAINER(vbox), box);
1152                 }
1153
1154
1155                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1156                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1157                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1158         }
1159
1160         gtk_widget_show_all(dialog);
1161
1162         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1163                 gtk_widget_destroy(dialog);
1164                 return;
1165         }
1166
1167         for (i = 0; i < FD_DEBUG_MAX; i++) {
1168                 int set;
1169
1170                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1171                 if (set)
1172                         fio_debug |= (1UL << i);
1173         }
1174
1175         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1176         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1177         update_graph_limits();
1178         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1179
1180         gtk_widget_destroy(dialog);
1181 }
1182
1183 static void about_dialog(GtkWidget *w, gpointer data)
1184 {
1185         const char *authors[] = {
1186                 "Jens Axboe <axboe@kernel.dk>",
1187                 "Stephen Carmeron <stephenmcameron@gmail.com>",
1188                 NULL
1189         };
1190         const char *license[] = {
1191                 "Fio is free software; you can redistribute it and/or modify "
1192                 "it under the terms of the GNU General Public License as published by "
1193                 "the Free Software Foundation; either version 2 of the License, or "
1194                 "(at your option) any later version.\n",
1195                 "Fio is distributed in the hope that it will be useful, "
1196                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1197                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
1198                 "GNU General Public License for more details.\n",
1199                 "You should have received a copy of the GNU General Public License "
1200                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1201                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
1202         };
1203         char *license_trans;
1204
1205         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1206                                      license[2], "\n", NULL);
1207
1208         gtk_show_about_dialog(NULL,
1209                 "program-name", "gfio",
1210                 "comments", "Gtk2 UI for fio",
1211                 "license", license_trans,
1212                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1213                 "authors", authors,
1214                 "version", fio_version_string,
1215                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1216                 "logo-icon-name", "fio",
1217                 /* Must be last: */
1218                 "wrap-license", TRUE,
1219                 NULL);
1220
1221         g_free(license_trans);
1222 }
1223
1224 static GtkActionEntry menu_items[] = {
1225         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1226         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1227         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1228         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1229         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1230         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1231         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1232         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1233         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1234         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1235         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1236         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1237         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1238         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1239         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1240         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1241         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1242 };
1243 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1244
1245 static const gchar *ui_string = " \
1246         <ui> \
1247                 <menubar name=\"MainMenu\"> \
1248                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1249                                 <menuitem name=\"New\" action=\"NewFile\" /> \
1250                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1251                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1252                                 <separator name=\"Separator1\"/> \
1253                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1254                                 <separator name=\"Separator2\"/> \
1255                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1256                                 <separator name=\"Separator3\"/> \
1257                                 <placeholder name=\"FileRecentFiles\"/> \
1258                                 <separator name=\"Separator4\"/> \
1259                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1260                         </menu> \
1261                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1262                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1263                                 <separator name=\"Separator5\"/> \
1264                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1265                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1266                                 <separator name=\"Separator6\"/> \
1267                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1268                         </menu>\
1269                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1270                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1271                                 <separator name=\"Separator7\"/> \
1272                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1273                         </menu>\
1274                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1275                                 <menuitem name=\"About\" action=\"About\" /> \
1276                         </menu> \
1277                 </menubar> \
1278         </ui> \
1279 ";
1280
1281 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1282                                    struct gui *ui)
1283 {
1284         GtkActionGroup *action_group;
1285         GError *error = 0;
1286
1287         action_group = gtk_action_group_new("Menu");
1288         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1289
1290         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1291         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1292
1293         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1294
1295         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1296 }
1297
1298 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1299                    GtkWidget *vbox, GtkUIManager *ui_manager)
1300 {
1301         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1302 }
1303
1304 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1305 {
1306         struct gui_entry *ge = (struct gui_entry *) data;
1307         gint index;
1308
1309         index = gtk_combo_box_get_active(box);
1310
1311         multitext_set_entry(&ge->eta.iotype, index);
1312         multitext_set_entry(&ge->eta.bs, index);
1313         multitext_set_entry(&ge->eta.ioengine, index);
1314         multitext_set_entry(&ge->eta.iodepth, index);
1315 }
1316
1317 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1318 {
1319         struct gui_entry *ge = (struct gui_entry *) data;
1320
1321         multitext_free(&ge->eta.iotype);
1322         multitext_free(&ge->eta.bs);
1323         multitext_free(&ge->eta.ioengine);
1324         multitext_free(&ge->eta.iodepth);
1325 }
1326
1327 static GtkWidget *new_client_page(struct gui_entry *ge)
1328 {
1329         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1330         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1331
1332         main_vbox = gtk_vbox_new(FALSE, 3);
1333
1334         top_align = gtk_alignment_new(0, 0, 1, 0);
1335         top_vbox = gtk_vbox_new(FALSE, 3);
1336         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1337         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1338
1339         probe = gtk_frame_new("Job");
1340         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1341         probe_frame = gtk_vbox_new(FALSE, 3);
1342         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1343
1344         probe_box = gtk_hbox_new(FALSE, 3);
1345         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1346         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1347         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1348         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1349         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1350
1351         probe_box = gtk_hbox_new(FALSE, 3);
1352         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1353
1354         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1355         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1356         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1357         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1358         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1359         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1360         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1361         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1362         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1363
1364         probe_box = gtk_hbox_new(FALSE, 3);
1365         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1366         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1367         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1368         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1369         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1370
1371         /*
1372          * Only add this if we have a commit rate
1373          */
1374 #if 0
1375         probe_box = gtk_hbox_new(FALSE, 3);
1376         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1377
1378         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1379         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1380
1381         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1382         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1383 #endif
1384
1385         /*
1386          * Set up a drawing area and IOPS and bandwidth graphs
1387          */
1388         ge->graphs.drawing_area = gtk_drawing_area_new();
1389         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1390                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1391         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1392         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1393                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1394         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1395                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1396         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1397         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1398                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1399         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1400                                         ge->graphs.drawing_area);
1401         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1402
1403         setup_graphs(&ge->graphs);
1404
1405         /*
1406          * Set up alignments for widgets at the bottom of ui, 
1407          * align bottom left, expand horizontally but not vertically
1408          */
1409         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1410         ge->buttonbox = gtk_hbox_new(FALSE, 0);
1411         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1412         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1413
1414         add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1415
1416         /*
1417          * Set up thread status progress bar
1418          */
1419         ge->thread_status_pb = gtk_progress_bar_new();
1420         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1421         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1422         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1423
1424
1425         return main_vbox;
1426 }
1427
1428 static GtkWidget *new_main_page(struct gui *ui)
1429 {
1430         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1431         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1432
1433         main_vbox = gtk_vbox_new(FALSE, 3);
1434
1435         /*
1436          * Set up alignments for widgets at the top of ui,
1437          * align top left, expand horizontally but not vertically
1438          */
1439         top_align = gtk_alignment_new(0, 0, 1, 0);
1440         top_vbox = gtk_vbox_new(FALSE, 0);
1441         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1442         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1443
1444         probe = gtk_frame_new("Run statistics");
1445         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1446         probe_frame = gtk_vbox_new(FALSE, 3);
1447         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1448
1449         probe_box = gtk_hbox_new(FALSE, 3);
1450         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1451         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1452         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1453         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1454         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1455         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1456
1457         /*
1458          * Only add this if we have a commit rate
1459          */
1460 #if 0
1461         probe_box = gtk_hbox_new(FALSE, 3);
1462         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1463
1464         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1465         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1466
1467         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1468         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1469 #endif
1470
1471         /*
1472          * Set up a drawing area and IOPS and bandwidth graphs
1473          */
1474         ui->graphs.drawing_area = gtk_drawing_area_new();
1475         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1476                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1477         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1478         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1479                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1480         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1481                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
1482         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1483         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1484                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1485         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1486                                         ui->graphs.drawing_area);
1487         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1488                         TRUE, TRUE, 0);
1489
1490         setup_graphs(&ui->graphs);
1491
1492         /*
1493          * Set up alignments for widgets at the bottom of ui, 
1494          * align bottom left, expand horizontally but not vertically
1495          */
1496         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1497         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1498         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1499         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1500
1501         /*
1502          * Set up thread status progress bar
1503          */
1504         ui->thread_status_pb = gtk_progress_bar_new();
1505         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1506         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1507         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1508
1509         return main_vbox;
1510 }
1511
1512 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1513                                      guint page, gpointer data)
1514
1515 {
1516         struct gui *ui = (struct gui *) data;
1517         struct gui_entry *ge;
1518
1519         if (!page) {
1520                 set_job_menu_visible(ui, 0);
1521                 set_view_results_visible(ui, 0);
1522                 return TRUE;
1523         }
1524
1525         set_job_menu_visible(ui, 1);
1526         ge = get_ge_from_page(ui, page, NULL);
1527         if (ge)
1528                 update_button_states(ui, ge);
1529
1530         return TRUE;
1531 }
1532
1533 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1534 {
1535         time_t time_a = gtk_recent_info_get_visited(a);
1536         time_t time_b = gtk_recent_info_get_visited(b);
1537
1538         return time_b - time_a;
1539 }
1540
1541 static void add_recent_file_items(struct gui *ui)
1542 {
1543         const gchar *gfio = g_get_application_name();
1544         GList *items, *item;
1545         int i = 0;
1546
1547         if (ui->recent_ui_id) {
1548                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1549                 gtk_ui_manager_ensure_update(ui->uimanager);
1550         }
1551         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1552
1553         if (ui->actiongroup) {
1554                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1555                 g_object_unref(ui->actiongroup);
1556         }
1557         ui->actiongroup = gtk_action_group_new("RecentFileActions");
1558
1559         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1560
1561         items = gtk_recent_manager_get_items(ui->recentmanager);
1562         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1563
1564         for (item = items; item && item->data; item = g_list_next(item)) {
1565                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1566                 gchar *action_name;
1567                 const gchar *label;
1568                 GtkAction *action;
1569
1570                 if (!gtk_recent_info_has_application(info, gfio))
1571                         continue;
1572
1573                 /*
1574                  * We only support local files for now
1575                  */
1576                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1577                         continue;
1578
1579                 action_name = g_strdup_printf("RecentFile%u", i++);
1580                 label = gtk_recent_info_get_display_name(info);
1581
1582                 action = g_object_new(GTK_TYPE_ACTION,
1583                                         "name", action_name,
1584                                         "label", label, NULL);
1585
1586                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1587                                         gtk_recent_info_ref(info),
1588                                         (GDestroyNotify) gtk_recent_info_unref);
1589
1590
1591                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1592
1593                 gtk_action_group_add_action(ui->actiongroup, action);
1594                 g_object_unref(action);
1595
1596                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1597                                         "/MainMenu/FileMenu/FileRecentFiles",
1598                                         label, action_name,
1599                                         GTK_UI_MANAGER_MENUITEM, FALSE);
1600
1601                 g_free(action_name);
1602
1603                 if (i == 8)
1604                         break;
1605         }
1606
1607         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1608         g_list_free(items);
1609 }
1610
1611 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1612                                    gint x, gint y, GtkSelectionData *seldata,
1613                                    guint info, guint time, gpointer *data)
1614 {
1615         struct gui *ui = (struct gui *) data;
1616         gchar **uris;
1617         GtkWidget *source;
1618
1619         source = gtk_drag_get_source_widget(ctx);
1620         if (source && widget == gtk_widget_get_toplevel(source)) {
1621                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1622                 return;
1623         }
1624
1625         uris = gtk_selection_data_get_uris(seldata);
1626         if (!uris) {
1627                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1628                 return;
1629         }
1630
1631         if (uris[0])
1632                 do_file_open_with_tab(ui, uris[0]);
1633
1634         gtk_drag_finish(ctx, TRUE, FALSE, time);
1635         g_strfreev(uris);
1636 }
1637
1638 static void init_ui(int *argc, char **argv[], struct gui *ui)
1639 {
1640         GtkSettings *settings;
1641         GtkWidget *vbox;
1642
1643         /* Magical g*thread incantation, you just need this thread stuff.
1644          * Without it, the update that happens in gfio_update_thread_status
1645          * doesn't really happen in a timely fashion, you need expose events
1646          */
1647         if (!g_thread_supported())
1648                 g_thread_init(NULL);
1649         gdk_threads_init();
1650
1651         gtk_init(argc, argv);
1652         settings = gtk_settings_get_default();
1653         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1654         g_type_init();
1655         gdk_color_parse("white", &gfio_color_white);
1656         
1657         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1658         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1659         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1660
1661         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1662         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1663
1664         ui->vbox = gtk_vbox_new(FALSE, 0);
1665         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1666
1667         ui->uimanager = gtk_ui_manager_new();
1668         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1669         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1670
1671         ui->recentmanager = gtk_recent_manager_get_default();
1672         add_recent_file_items(ui);
1673
1674         ui->notebook = gtk_notebook_new();
1675         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1676         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1677         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1678         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1679
1680         vbox = new_main_page(ui);
1681         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1682         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1683         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1684
1685         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1686
1687         gfio_ui_setup_log(ui);
1688
1689         gtk_widget_show_all(ui->window);
1690 }
1691
1692 int main(int argc, char *argv[], char *envp[])
1693 {
1694         if (initialize_fio(envp))
1695                 return 1;
1696         if (fio_init_options())
1697                 return 1;
1698
1699         memset(&main_ui, 0, sizeof(main_ui));
1700         main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1701
1702         init_ui(&argc, &argv, &main_ui);
1703
1704         gdk_threads_enter();
1705         gtk_main();
1706         gdk_threads_leave();
1707
1708         g_hash_table_destroy(main_ui.ge_hash);
1709         return 0;
1710 }