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