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