2 * gfio - gui front end for fio - the flexible io tester
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
5 * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
7 * The license below covers all files distributed with fio unless otherwise
8 * noted in the file itself.
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
43 static bool gfio_server_running;
44 static unsigned int gfio_graph_limit = 100;
46 GdkColor gfio_color_white;
47 GdkColor gfio_color_lightyellow;
48 const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
50 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
52 static void connect_clicked(GtkWidget *widget, gpointer data);
53 static void start_job_clicked(GtkWidget *widget, gpointer data);
54 static void send_clicked(GtkWidget *widget, gpointer data);
56 static struct button_spec {
57 const char *buttontext;
59 const char *tooltiptext[2];
60 const int start_sensitive;
61 } buttonspeclist[] = {
63 .buttontext = "Connect",
65 .tooltiptext = { "Disconnect from host", "Connect to host" },
71 .tooltiptext = { "Send job description to host", NULL },
75 .buttontext = "Start Job",
76 .f = start_job_clicked,
77 .tooltiptext = { "Start the current job on the server", NULL },
82 static void setup_iops_graph(struct gfio_graphs *gg)
86 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
87 graph_title(g, "IOPS (IOs/sec)");
88 graph_x_title(g, "Time (secs)");
89 gg->read_iops = graph_add_label(g, "Read IOPS");
90 gg->write_iops = graph_add_label(g, "Write IOPS");
91 gg->trim_iops = graph_add_label(g, "Trim IOPS");
92 graph_set_color(g, gg->read_iops, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
93 graph_set_color(g, gg->write_iops, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
94 graph_set_color(g, gg->trim_iops, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
95 line_graph_set_data_count_limit(g, gfio_graph_limit);
96 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
97 graph_set_graph_all_zeroes(g, 0);
101 static void setup_bandwidth_graph(struct gfio_graphs *gg)
105 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
106 graph_title(g, "Bandwidth (bytes/sec)");
107 graph_x_title(g, "Time (secs)");
108 gg->read_bw = graph_add_label(g, "Read Bandwidth");
109 gg->write_bw = graph_add_label(g, "Write Bandwidth");
110 gg->trim_bw = graph_add_label(g, "Trim Bandwidth");
111 graph_set_color(g, gg->read_bw, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
112 graph_set_color(g, gg->write_bw, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
113 graph_set_color(g, gg->trim_bw, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
114 graph_set_base_offset(g, 1);
115 line_graph_set_data_count_limit(g, 100);
116 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
117 graph_set_graph_all_zeroes(g, 0);
118 gg->bandwidth_graph = g;
121 static void setup_graphs(struct gfio_graphs *g)
124 setup_bandwidth_graph(g);
127 void clear_ge_ui_info(struct gui_entry *ge)
129 gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
130 gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
131 gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
132 gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
134 /* should we empty it... */
135 gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
137 multitext_update_entry(&ge->eta.iotype, 0, "");
138 multitext_update_entry(&ge->eta.bs, 0, "");
139 multitext_update_entry(&ge->eta.ioengine, 0, "");
140 multitext_update_entry(&ge->eta.iodepth, 0, "");
141 gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
142 gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
143 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
144 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
145 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
146 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
149 static void set_menu_entry_text(struct gui *ui, const char *path,
154 w = gtk_ui_manager_get_widget(ui->uimanager, path);
156 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
158 fprintf(stderr, "gfio: can't find path %s\n", path);
162 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
166 w = gtk_ui_manager_get_widget(ui->uimanager, path);
168 gtk_widget_set_sensitive(w, show);
170 fprintf(stderr, "gfio: can't find path %s\n", path);
173 static void set_job_menu_visible(struct gui *ui, int visible)
175 set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
178 static void set_view_results_visible(struct gui *ui, int visible)
180 set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
183 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
185 if (s->tooltiptext[sensitive])
186 return s->tooltiptext[sensitive];
188 return s->tooltiptext[0];
191 static GtkWidget *add_button(GtkWidget *buttonbox,
192 struct button_spec *buttonspec, gpointer data)
194 GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
195 gboolean sens = buttonspec->start_sensitive;
197 g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
198 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
200 sens = buttonspec->start_sensitive;
201 gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
202 gtk_widget_set_sensitive(button, sens);
207 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
212 for (i = 0; i < nbuttons; i++)
213 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
217 * Update sensitivity of job buttons and job menu items, based on the
218 * state of the client.
220 static void update_button_states(struct gui *ui, struct gui_entry *ge)
222 unsigned int connect_state, send_state, start_state, edit_state;
223 const char *connect_str = NULL;
227 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
232 connect_str = "Connect";
236 case GE_STATE_CONNECTED:
239 connect_str = "Disconnect";
243 case GE_STATE_JOB_SENT:
246 connect_str = "Disconnect";
250 case GE_STATE_JOB_STARTED:
253 connect_str = "Disconnect";
257 case GE_STATE_JOB_RUNNING:
260 connect_str = "Disconnect";
264 case GE_STATE_JOB_DONE:
267 connect_str = "Connect";
273 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
274 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
275 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
276 gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
277 gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
279 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
280 set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
282 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
283 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
284 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
286 if (ge->client && ge->client->nr_results)
287 set_view_results_visible(ui, 1);
289 set_view_results_visible(ui, 0);
292 void gfio_set_state(struct gui_entry *ge, unsigned int state)
295 update_button_states(ge->ui, ge);
298 static void gfio_ui_setup_log(struct gui *ui)
300 GtkTreeSelection *selection;
302 GtkWidget *tree_view;
304 model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
306 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
307 gtk_widget_set_can_focus(tree_view, FALSE);
309 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
310 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
311 g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
312 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
314 tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
315 tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
316 tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
317 tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
319 ui->log_model = model;
320 ui->log_tree = tree_view;
323 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
326 guint width = gtk_widget_get_allocated_width(w);
327 guint height = gtk_widget_get_allocated_height(w);
328 struct gfio_graphs *g = data;
330 graph_set_size(g->iops_graph, width / 2.0, height);
331 graph_set_position(g->iops_graph, width / 2.0, 0.0);
332 graph_set_size(g->bandwidth_graph, width / 2.0, height);
333 graph_set_position(g->bandwidth_graph, 0, 0);
337 static void draw_graph(struct graph *g, cairo_t *cr)
339 line_graph_draw(g, cr);
343 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
344 gboolean keyboard_mode, GtkTooltip *tooltip,
347 struct gfio_graphs *g = data;
348 const char *text = NULL;
350 if (graph_contains_xy(g->iops_graph, x, y))
351 text = graph_find_tooltip(g->iops_graph, x, y);
352 else if (graph_contains_xy(g->bandwidth_graph, x, y))
353 text = graph_find_tooltip(g->bandwidth_graph, x, y);
356 gtk_tooltip_set_text(tooltip, text);
363 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
365 struct gfio_graphs *g = p;
368 cr = gdk_cairo_create(gtk_widget_get_window(w));
370 if (graph_has_tooltips(g->iops_graph) ||
371 graph_has_tooltips(g->bandwidth_graph)) {
372 g_object_set(w, "has-tooltip", TRUE, NULL);
373 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
376 cairo_set_source_rgb(cr, 0, 0, 0);
377 draw_graph(g->iops_graph, cr);
378 draw_graph(g->bandwidth_graph, cr);
385 * FIXME: need more handling here
387 static void ge_destroy(struct gui_entry *ge)
389 struct gfio_client *gc = ge->client;
393 if (ge->state >= GE_STATE_CONNECTED)
394 fio_client_terminate(gc->client);
396 fio_put_client(gc->client);
401 g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
408 static void ge_widget_destroy(GtkWidget *w, gpointer data)
410 struct gui_entry *ge = (struct gui_entry *) data;
415 static void gfio_quit(struct gui *ui)
420 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
423 struct gui *ui = (struct gui *) data;
428 static void *job_thread(void *arg)
430 struct gui *ui = arg;
432 ui->handler_running = 1;
433 fio_handle_clients(&gfio_client_ops);
434 ui->handler_running = 0;
438 static int send_job_file(struct gui_entry *ge)
440 struct gfio_client *gc = ge->client;
444 * Prune old options, we are expecting the return options
445 * when the job file is parsed remotely and returned to us.
447 while (!flist_empty(&gc->o_list)) {
448 struct gfio_client_options *gco;
450 gco = flist_first_entry(&gc->o_list, struct gfio_client_options, list);
451 flist_del(&gco->list);
455 ret = fio_client_send_ini(gc->client, ge->job_file, false);
459 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
463 static void *server_thread(void *arg)
465 fio_server_create_sk_key();
467 gfio_server_running = true;
468 fio_start_server(NULL);
469 gfio_server_running = false;
470 fio_server_destroy_sk_key();
474 static void gfio_start_server(struct gui *ui)
476 if (!gfio_server_running) {
477 gfio_server_running = true;
478 pthread_create(&ui->server_t, NULL, server_thread, NULL);
479 pthread_detach(ui->server_t);
483 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
486 struct gui_entry *ge = data;
487 struct gfio_client *gc = ge->client;
490 fio_start_client(gc->client);
493 static void file_open(GtkWidget *w, gpointer data);
495 struct connection_widgets
502 static void hostname_cb(GtkEntry *entry, gpointer data)
504 struct connection_widgets *cw = data;
505 int uses_net = 0, is_localhost = 0;
510 * Check whether to display the 'auto start backend' box
511 * or not. Show it if we are a localhost and using network,
514 ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
515 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
520 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
521 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
522 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
523 !strcmp(text, "ip6-loopback"))
527 if (!uses_net || is_localhost) {
528 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
529 gtk_widget_set_sensitive(cw->button, 1);
531 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
532 gtk_widget_set_sensitive(cw->button, 0);
536 static int get_connection_details(struct gui_entry *ge)
538 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
539 struct connection_widgets cw;
540 struct gui *ui = ge->ui;
546 dialog = gtk_dialog_new_with_buttons("Connection details",
547 GTK_WINDOW(ui->window),
548 GTK_DIALOG_DESTROY_WITH_PARENT,
549 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
550 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
552 frame = gtk_frame_new("Hostname / socket name");
553 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
554 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
556 box = gtk_vbox_new(FALSE, 6);
557 gtk_container_add(GTK_CONTAINER(frame), box);
559 hbox = gtk_hbox_new(TRUE, 10);
560 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
561 cw.hentry = gtk_entry_new();
562 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
563 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
565 frame = gtk_frame_new("Port");
566 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
567 box = gtk_vbox_new(FALSE, 10);
568 gtk_container_add(GTK_CONTAINER(frame), box);
570 hbox = gtk_hbox_new(TRUE, 4);
571 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
572 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
574 frame = gtk_frame_new("Type");
575 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
576 box = gtk_vbox_new(FALSE, 10);
577 gtk_container_add(GTK_CONTAINER(frame), box);
579 hbox = gtk_hbox_new(TRUE, 4);
580 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
582 cw.combo = gtk_combo_box_text_new();
583 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
584 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
585 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
586 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
588 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
590 frame = gtk_frame_new("Options");
591 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
592 box = gtk_vbox_new(FALSE, 10);
593 gtk_container_add(GTK_CONTAINER(frame), box);
595 hbox = gtk_hbox_new(TRUE, 4);
596 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
598 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
599 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
600 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.");
601 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
604 * Connect edit signal, so we can show/not-show the auto start button
606 g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
607 g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
609 gtk_widget_show_all(dialog);
611 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
612 gtk_widget_destroy(dialog);
616 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
617 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
619 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
620 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
621 ge->type = Fio_client_ipv4;
622 else if (!strncmp(typeentry, "IPv6", 4))
623 ge->type = Fio_client_ipv6;
625 ge->type = Fio_client_socket;
628 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
630 gtk_widget_destroy(dialog);
634 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
636 gc->client = fio_get_client(client);
637 client->client_data = gc;
640 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
642 struct gfio_client_options *gco;
643 struct gfio_client *gc;
645 gc = calloc(1, sizeof(*gc));
646 INIT_FLIST_HEAD(&gc->o_list);
649 gfio_set_client(gc, client);
652 * Just add a default set of options, need to consider how best
655 gco = calloc(1, sizeof(*gco));
656 INIT_FLIST_HEAD(&gco->list);
657 options_default_fill(&gco->o);
658 flist_add_tail(&gco->list, &gc->o_list);
662 static void gfio_clear_graph_data(struct gfio_graphs *g)
664 graph_clear_values(g->iops_graph);
665 graph_clear_values(g->bandwidth_graph);
668 static void connect_clicked(GtkWidget *widget, gpointer data)
670 struct gui_entry *ge = data;
671 struct gfio_client *gc = ge->client;
673 if (ge->state == GE_STATE_NEW) {
677 file_open(widget, ge->ui);
684 struct fio_client *client;
686 if (get_connection_details(ge)) {
687 gfio_report_error(ge, "Failed to get connection details\n");
691 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
693 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
698 gfio_set_client(gc, client);
701 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
702 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
703 ret = fio_client_connect(gc->client);
705 if (!ge->ui->handler_running)
706 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
707 gfio_set_state(ge, GE_STATE_CONNECTED);
708 gfio_clear_graph_data(&ge->graphs);
710 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
713 fio_client_terminate(gc->client);
714 gfio_set_state(ge, GE_STATE_NEW);
715 clear_ge_ui_info(ge);
719 static void send_clicked(GtkWidget *widget, gpointer data)
721 struct gui_entry *ge = data;
723 if (send_job_file(ge))
724 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
727 static GtkWidget *new_client_page(struct gui_entry *ge);
729 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
731 struct gui_entry *ge;
733 ge = malloc(sizeof(*ge));
734 memset(ge, 0, sizeof(*ge));
735 ge->state = GE_STATE_NEW;
740 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
742 struct gui_entry *ge;
744 ge = alloc_new_gui_entry(ui);
746 ge->vbox = new_client_page(ge);
747 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
749 ge->page_label = gtk_label_new(name);
750 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
752 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
754 gtk_widget_show_all(ui->window);
758 static void file_new(GtkWidget *w, gpointer data)
760 struct gui *ui = (struct gui *) data;
761 struct gui_entry *ge;
763 ge = get_new_ge_with_tab(ui, "Untitled");
764 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
768 * Return the 'ge' corresponding to the tab. If the active tab is the
769 * main tab, open a new tab.
771 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
777 return get_new_ge_with_tab(ui, "Untitled");
783 return g_hash_table_lookup(ui->ge_hash, &cur_page);
786 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
791 * Main tab is tab 0, so any current page other than 0 holds
794 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
796 return get_ge_from_page(ui, cur_page, NULL);
801 static void file_close(GtkWidget *w, gpointer data)
803 struct gui *ui = (struct gui *) data;
804 struct gui_entry *ge;
807 * Can't close the main tab
809 ge = get_ge_from_cur_tab(ui);
811 gtk_widget_destroy(ge->vbox);
815 if (g_hash_table_size(ui->ge_hash)) {
816 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
823 static void file_add_recent(struct gui *ui, const gchar *uri)
827 memset(&grd, 0, sizeof(grd));
828 grd.display_name = strdup("gfio");
829 grd.description = strdup("Fio job file");
830 grd.mime_type = strdup(GFIO_MIME);
831 grd.app_name = strdup(g_get_application_name());
832 grd.app_exec = strdup("gfio %f/%u");
834 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
837 static gchar *get_filename_from_uri(const gchar *uri)
839 if (strncmp(uri, "file://", 7))
842 return strdup(uri + 7);
845 static int do_file_open(struct gui_entry *ge, const gchar *uri)
847 struct fio_client *client;
849 assert(!ge->job_file);
851 ge->job_file = get_filename_from_uri(uri);
853 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
855 char *label = strdup(uri);
858 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
861 gfio_client_added(ge, client);
862 file_add_recent(ge->ui, uri);
866 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
874 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
876 struct gui_entry *ge;
878 int ret, ge_is_new = 0;
881 * Creates new tab if current tab is the main window, or the
882 * current tab already has a client.
884 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
885 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
887 ge = get_new_ge_with_tab(ui, "Untitled");
891 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
893 if (get_connection_details(ge)) {
895 gtk_widget_destroy(ge->vbox);
900 ret = do_file_open(ge, uri);
903 if (ge->server_start)
904 gfio_start_server(ui);
907 gtk_widget_destroy(ge->vbox);
913 static void recent_open(GtkAction *action, gpointer data)
915 struct gui *ui = (struct gui *) data;
919 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
920 uri = gtk_recent_info_get_uri(info);
922 do_file_open_with_tab(ui, uri);
925 static void file_open(GtkWidget *w, gpointer data)
927 struct gui *ui = data;
929 GtkFileFilter *filter;
932 dialog = gtk_file_chooser_dialog_new("Open File",
933 GTK_WINDOW(ui->window),
934 GTK_FILE_CHOOSER_ACTION_OPEN,
935 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
936 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
938 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
940 filter = gtk_file_filter_new();
941 gtk_file_filter_add_pattern(filter, "*.fio");
942 gtk_file_filter_add_pattern(filter, "*.job");
943 gtk_file_filter_add_pattern(filter, "*.ini");
944 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
945 gtk_file_filter_set_name(filter, "Fio job file");
946 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
948 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
949 gtk_widget_destroy(dialog);
953 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
955 gtk_widget_destroy(dialog);
957 do_file_open_with_tab(ui, filename);
961 static void file_save(GtkWidget *w, gpointer data)
963 struct gui *ui = data;
966 dialog = gtk_file_chooser_dialog_new("Save File",
967 GTK_WINDOW(ui->window),
968 GTK_FILE_CHOOSER_ACTION_SAVE,
969 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
970 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
973 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
974 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
976 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
979 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
980 // save_job_file(filename);
983 gtk_widget_destroy(dialog);
986 static void view_log_destroy(GtkWidget *w, gpointer data)
988 struct gui *ui = (struct gui *) data;
990 g_object_ref(G_OBJECT(ui->log_tree));
991 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
992 gtk_widget_destroy(w);
996 void gfio_view_log(struct gui *ui)
998 GtkWidget *win, *scroll, *vbox, *box;
1003 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1004 gtk_window_set_title(GTK_WINDOW(win), "Log");
1005 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
1007 scroll = gtk_scrolled_window_new(NULL, NULL);
1009 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1011 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1013 box = gtk_hbox_new(TRUE, 0);
1014 gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
1015 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
1016 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
1018 vbox = gtk_vbox_new(TRUE, 5);
1019 gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
1021 gtk_container_add(GTK_CONTAINER(win), vbox);
1022 gtk_widget_show_all(win);
1025 static void view_log(GtkWidget *w, gpointer data)
1027 struct gui *ui = (struct gui *) data;
1032 static void connect_job_entry(GtkWidget *w, gpointer data)
1034 struct gui *ui = (struct gui *) data;
1035 struct gui_entry *ge;
1037 ge = get_ge_from_cur_tab(ui);
1039 connect_clicked(w, ge);
1042 static void send_job_entry(GtkWidget *w, gpointer data)
1044 struct gui *ui = (struct gui *) data;
1045 struct gui_entry *ge;
1047 ge = get_ge_from_cur_tab(ui);
1049 send_clicked(w, ge);
1052 static void edit_job_entry(GtkWidget *w, gpointer data)
1054 struct gui *ui = (struct gui *) data;
1055 struct gui_entry *ge;
1057 ge = get_ge_from_cur_tab(ui);
1058 if (ge && ge->client)
1059 gopt_get_options_window(ui->window, ge->client);
1062 static void start_job_entry(GtkWidget *w, gpointer data)
1064 struct gui *ui = (struct gui *) data;
1065 struct gui_entry *ge;
1067 ge = get_ge_from_cur_tab(ui);
1069 start_job_clicked(w, ge);
1072 static void view_results(GtkWidget *w, gpointer data)
1074 struct gui *ui = (struct gui *) data;
1075 struct gfio_client *gc;
1076 struct gui_entry *ge;
1078 ge = get_ge_from_cur_tab(ui);
1082 if (ge->results_window)
1086 if (gc && gc->nr_results)
1087 gfio_display_end_results(gc);
1090 static void __update_graph_settings(struct gfio_graphs *g)
1092 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1093 graph_set_font(g->iops_graph, gfio_graph_font);
1094 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1095 graph_set_font(g->bandwidth_graph, gfio_graph_font);
1098 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1100 struct gui_entry *ge = (struct gui_entry *) value;
1103 __update_graph_settings(&ge->graphs);
1105 ev = gdk_event_new(GDK_EXPOSE);
1106 g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1110 static void update_graph_limits(void)
1112 struct gui *ui = &main_ui;
1115 __update_graph_settings(&ui->graphs);
1117 ev = gdk_event_new(GDK_EXPOSE);
1118 g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1121 g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1124 static void preferences(GtkWidget *w, gpointer data)
1126 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1127 GtkWidget *hbox, *spin, *entry, *spin_int;
1128 struct gui *ui = (struct gui *) data;
1131 dialog = gtk_dialog_new_with_buttons("Preferences",
1132 GTK_WINDOW(ui->window),
1133 GTK_DIALOG_DESTROY_WITH_PARENT,
1134 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1135 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1138 frame = gtk_frame_new("Graphing");
1139 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1140 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1141 vbox = gtk_vbox_new(FALSE, 6);
1142 gtk_container_add(GTK_CONTAINER(frame), vbox);
1144 hbox = gtk_hbox_new(FALSE, 5);
1145 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1146 entry = gtk_label_new("Font face to use for graph labels");
1147 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1149 font = gtk_font_button_new_with_font(gfio_graph_font);
1150 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1152 box = gtk_vbox_new(FALSE, 6);
1153 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1155 hbox = gtk_hbox_new(FALSE, 5);
1156 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1157 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1158 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1160 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1162 box = gtk_vbox_new(FALSE, 6);
1163 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1165 hbox = gtk_hbox_new(FALSE, 5);
1166 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1167 entry = gtk_label_new("Client ETA request interval (msec)");
1168 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1170 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1171 frame = gtk_frame_new("Debug logging");
1172 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1173 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1174 vbox = gtk_vbox_new(FALSE, 6);
1175 gtk_container_add(GTK_CONTAINER(frame), vbox);
1177 box = gtk_hbox_new(FALSE, 6);
1178 gtk_container_add(GTK_CONTAINER(vbox), box);
1180 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1182 for (i = 0; i < FD_DEBUG_MAX; i++) {
1184 box = gtk_hbox_new(FALSE, 6);
1185 gtk_container_add(GTK_CONTAINER(vbox), box);
1189 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1190 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1191 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1194 gtk_widget_show_all(dialog);
1196 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1197 gtk_widget_destroy(dialog);
1201 for (i = 0; i < FD_DEBUG_MAX; i++) {
1204 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1206 fio_debug |= (1UL << i);
1209 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1210 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1211 update_graph_limits();
1212 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1214 gtk_widget_destroy(dialog);
1217 static void about_dialog(GtkWidget *w, gpointer data)
1219 const char *authors[] = {
1220 "Jens Axboe <axboe@kernel.dk>",
1221 "Stephen Cameron <stephenmcameron@gmail.com>",
1224 const char *license[] = {
1225 "Fio is free software; you can redistribute it and/or modify "
1226 "it under the terms of the GNU General Public License as published by "
1227 "the Free Software Foundation; either version 2 of the License, or "
1228 "(at your option) any later version.\n",
1229 "Fio is distributed in the hope that it will be useful, "
1230 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1231 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1232 "GNU General Public License for more details.\n",
1233 "You should have received a copy of the GNU General Public License "
1234 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1235 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1237 char *license_trans;
1239 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1240 license[2], "\n", NULL);
1242 gtk_show_about_dialog(NULL,
1243 "program-name", "gfio",
1244 "comments", "Gtk2 UI for fio",
1245 "license", license_trans,
1246 "website", "http://git.kernel.dk/cgit/fio/",
1248 "version", fio_version_string,
1249 "copyright", "© 2012-2017 Jens Axboe <axboe@kernel.dk>",
1250 "logo-icon-name", "fio",
1252 "wrap-license", TRUE,
1255 g_free(license_trans);
1258 static GtkActionEntry menu_items[] = {
1259 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1260 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1261 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1262 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1263 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1264 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1265 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1266 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1267 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1268 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1269 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1270 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1271 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1272 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1273 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1274 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1275 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1277 static gint nmenu_items = FIO_ARRAY_SIZE(menu_items);
1279 static const gchar *ui_string = " \
1281 <menubar name=\"MainMenu\"> \
1282 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1283 <menuitem name=\"New\" action=\"NewFile\" /> \
1284 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1285 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1286 <separator name=\"Separator1\"/> \
1287 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1288 <separator name=\"Separator2\"/> \
1289 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1290 <separator name=\"Separator3\"/> \
1291 <placeholder name=\"FileRecentFiles\"/> \
1292 <separator name=\"Separator4\"/> \
1293 <menuitem name=\"Quit\" action=\"Quit\" /> \
1295 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1296 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1297 <separator name=\"Separator5\"/> \
1298 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1299 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1300 <separator name=\"Separator6\"/> \
1301 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1303 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1304 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1305 <separator name=\"Separator7\"/> \
1306 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1308 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1309 <menuitem name=\"About\" action=\"About\" /> \
1315 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1318 GtkActionGroup *action_group;
1321 action_group = gtk_action_group_new("Menu");
1322 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1324 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1325 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1327 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1329 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1332 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1333 GtkWidget *vbox, GtkUIManager *ui_manager)
1335 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1338 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1340 struct gui_entry *ge = (struct gui_entry *) data;
1343 index = gtk_combo_box_get_active(box);
1345 multitext_set_entry(&ge->eta.iotype, index);
1346 multitext_set_entry(&ge->eta.bs, index);
1347 multitext_set_entry(&ge->eta.ioengine, index);
1348 multitext_set_entry(&ge->eta.iodepth, index);
1351 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1353 struct gui_entry *ge = (struct gui_entry *) data;
1355 multitext_free(&ge->eta.iotype);
1356 multitext_free(&ge->eta.bs);
1357 multitext_free(&ge->eta.ioengine);
1358 multitext_free(&ge->eta.iodepth);
1361 static GtkWidget *new_client_page(struct gui_entry *ge)
1363 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1364 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1366 main_vbox = gtk_vbox_new(FALSE, 3);
1368 top_align = gtk_alignment_new(0, 0, 1, 0);
1369 top_vbox = gtk_vbox_new(FALSE, 3);
1370 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1371 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1373 probe = gtk_frame_new("Job");
1374 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1375 probe_frame = gtk_vbox_new(FALSE, 3);
1376 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1378 probe_box = gtk_hbox_new(FALSE, 3);
1379 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1380 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1381 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1382 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1383 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1385 probe_box = gtk_hbox_new(FALSE, 3);
1386 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1388 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1389 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1390 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1391 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1392 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write/Trim)");
1393 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1394 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1395 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1396 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1398 probe_box = gtk_hbox_new(FALSE, 3);
1399 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1400 ge->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1401 ge->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "Read IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1402 ge->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1403 ge->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "Write IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1404 ge->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
1405 ge->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "Trim IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
1408 * Only add this if we have a commit rate
1411 probe_box = gtk_hbox_new(FALSE, 3);
1412 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1414 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1415 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1417 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1418 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1422 * Set up a drawing area and IOPS and bandwidth graphs
1424 ge->graphs.drawing_area = gtk_drawing_area_new();
1425 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1426 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1427 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
1428 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
1429 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1430 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1431 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1432 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1433 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1434 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1435 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1436 ge->graphs.drawing_area);
1437 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1439 setup_graphs(&ge->graphs);
1442 * Set up alignments for widgets at the bottom of ui,
1443 * align bottom left, expand horizontally but not vertically
1445 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1446 ge->buttonbox = gtk_hbox_new(FALSE, 0);
1447 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1448 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1450 add_buttons(ge, buttonspeclist, FIO_ARRAY_SIZE(buttonspeclist));
1453 * Set up thread status progress bar
1455 ge->thread_status_pb = gtk_progress_bar_new();
1456 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1457 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1458 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1464 static GtkWidget *new_main_page(struct gui *ui)
1466 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1467 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1469 main_vbox = gtk_vbox_new(FALSE, 3);
1472 * Set up alignments for widgets at the top of ui,
1473 * align top left, expand horizontally but not vertically
1475 top_align = gtk_alignment_new(0, 0, 1, 0);
1476 top_vbox = gtk_vbox_new(FALSE, 0);
1477 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1478 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1480 probe = gtk_frame_new("Run statistics");
1481 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1482 probe_frame = gtk_vbox_new(FALSE, 3);
1483 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1485 probe_box = gtk_hbox_new(FALSE, 3);
1486 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1487 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1488 ui->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1489 ui->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1490 ui->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1491 ui->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1492 ui->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
1493 ui->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
1496 * Only add this if we have a commit rate
1499 probe_box = gtk_hbox_new(FALSE, 3);
1500 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1502 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1503 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1505 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1506 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1510 * Set up a drawing area and IOPS and bandwidth graphs
1512 ui->graphs.drawing_area = gtk_drawing_area_new();
1513 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1514 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1515 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
1516 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
1517 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1518 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1519 G_CALLBACK(on_config_drawing_area), &ui->graphs);
1520 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1521 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1522 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1523 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1524 ui->graphs.drawing_area);
1525 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1528 setup_graphs(&ui->graphs);
1531 * Set up alignments for widgets at the bottom of ui,
1532 * align bottom left, expand horizontally but not vertically
1534 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1535 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1536 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1537 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1540 * Set up thread status progress bar
1542 ui->thread_status_pb = gtk_progress_bar_new();
1543 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1544 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1545 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1550 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1551 guint page, gpointer data)
1554 struct gui *ui = (struct gui *) data;
1555 struct gui_entry *ge;
1558 set_job_menu_visible(ui, 0);
1559 set_view_results_visible(ui, 0);
1563 set_job_menu_visible(ui, 1);
1564 ge = get_ge_from_page(ui, page, NULL);
1566 update_button_states(ui, ge);
1571 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1573 time_t time_a = gtk_recent_info_get_visited(a);
1574 time_t time_b = gtk_recent_info_get_visited(b);
1576 return time_b - time_a;
1579 static void add_recent_file_items(struct gui *ui)
1581 const gchar *gfio = g_get_application_name();
1582 GList *items, *item;
1585 if (ui->recent_ui_id) {
1586 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1587 gtk_ui_manager_ensure_update(ui->uimanager);
1589 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1591 if (ui->actiongroup) {
1592 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1593 g_object_unref(ui->actiongroup);
1595 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1597 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1599 items = gtk_recent_manager_get_items(ui->recentmanager);
1600 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1602 for (item = items; item && item->data; item = g_list_next(item)) {
1603 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1608 if (!gtk_recent_info_has_application(info, gfio))
1612 * We only support local files for now
1614 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1617 action_name = g_strdup_printf("RecentFile%u", i++);
1618 label = gtk_recent_info_get_display_name(info);
1620 action = g_object_new(GTK_TYPE_ACTION,
1621 "name", action_name,
1622 "label", label, NULL);
1624 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1625 gtk_recent_info_ref(info),
1626 (GDestroyNotify) gtk_recent_info_unref);
1629 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1631 gtk_action_group_add_action(ui->actiongroup, action);
1632 g_object_unref(action);
1634 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1635 "/MainMenu/FileMenu/FileRecentFiles",
1637 GTK_UI_MANAGER_MENUITEM, FALSE);
1639 g_free(action_name);
1645 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1649 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1650 gint x, gint y, GtkSelectionData *seldata,
1651 guint info, guint time, gpointer *data)
1653 struct gui *ui = (struct gui *) data;
1657 source = gtk_drag_get_source_widget(ctx);
1658 if (source && widget == gtk_widget_get_toplevel(source)) {
1659 gtk_drag_finish(ctx, FALSE, FALSE, time);
1663 uris = gtk_selection_data_get_uris(seldata);
1665 gtk_drag_finish(ctx, FALSE, FALSE, time);
1670 do_file_open_with_tab(ui, uris[0]);
1672 gtk_drag_finish(ctx, TRUE, FALSE, time);
1676 static void init_ui(int *argc, char **argv[], struct gui *ui)
1678 GtkSettings *settings;
1681 /* Magical g*thread incantation, you just need this thread stuff.
1682 * Without it, the update that happens in gfio_update_thread_status
1683 * doesn't really happen in a timely fashion, you need expose events
1685 #if !GLIB_CHECK_VERSION(2, 31, 0)
1686 if (!g_thread_supported())
1687 g_thread_init(NULL);
1692 gtk_init(argc, argv);
1693 settings = gtk_settings_get_default();
1694 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1695 #if !GLIB_CHECK_VERSION(2, 36, 0)
1698 gdk_color_parse("#fffff4", &gfio_color_lightyellow);
1699 gdk_color_parse("white", &gfio_color_white);
1701 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1702 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1703 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1705 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1706 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1708 ui->vbox = gtk_vbox_new(FALSE, 0);
1709 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1711 ui->uimanager = gtk_ui_manager_new();
1712 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1713 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1715 ui->recentmanager = gtk_recent_manager_get_default();
1716 add_recent_file_items(ui);
1718 ui->notebook = gtk_notebook_new();
1719 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1720 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1721 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1722 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1724 vbox = new_main_page(ui);
1725 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1726 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1727 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1729 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1731 gfio_ui_setup_log(ui);
1733 gtk_widget_show_all(ui->window);
1736 int main(int argc, char *argv[], char *envp[])
1738 if (initialize_fio(envp))
1740 if (fio_init_options())
1745 memset(&main_ui, 0, sizeof(main_ui));
1746 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1748 init_ui(&argc, &argv, &main_ui);
1750 gdk_threads_enter();
1752 gdk_threads_leave();
1754 g_hash_table_destroy(main_ui.ge_hash);