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