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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
40 static int gfio_server_running;
41 static unsigned int gfio_graph_limit = 100;
43 GdkColor gfio_color_white;
44 const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
46 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
48 static void connect_clicked(GtkWidget *widget, gpointer data);
49 static void start_job_clicked(GtkWidget *widget, gpointer data);
50 static void send_clicked(GtkWidget *widget, gpointer data);
52 static struct button_spec {
53 const char *buttontext;
55 const char *tooltiptext[2];
56 const int start_sensitive;
57 } buttonspeclist[] = {
59 .buttontext = "Connect",
61 .tooltiptext = { "Disconnect from host", "Connect to host" },
67 .tooltiptext = { "Send job description to host", NULL },
71 .buttontext = "Start Job",
72 .f = start_job_clicked,
73 .tooltiptext = { "Start the current job on the server", NULL },
78 static void setup_iops_graph(struct gfio_graphs *gg)
82 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
83 graph_title(g, "IOPS (IOs/sec)");
84 graph_x_title(g, "Time (secs)");
85 gg->read_iops = graph_add_label(g, "Read IOPS");
86 gg->write_iops = graph_add_label(g, "Write IOPS");
87 graph_set_color(g, gg->read_iops, 0.13, 0.54, 0.13);
88 graph_set_color(g, gg->write_iops, 1.0, 0.0, 0.0);
89 line_graph_set_data_count_limit(g, gfio_graph_limit);
90 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
91 graph_set_graph_all_zeroes(g, 0);
95 static void setup_bandwidth_graph(struct gfio_graphs *gg)
99 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
100 graph_title(g, "Bandwidth (bytes/sec)");
101 graph_x_title(g, "Time (secs)");
102 gg->read_bw = graph_add_label(g, "Read Bandwidth");
103 gg->write_bw = graph_add_label(g, "Write Bandwidth");
104 graph_set_color(g, gg->read_bw, 0.13, 0.54, 0.13);
105 graph_set_color(g, gg->write_bw, 1.0, 0.0, 0.0);
106 graph_set_base_offset(g, 1);
107 line_graph_set_data_count_limit(g, 100);
108 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
109 graph_set_graph_all_zeroes(g, 0);
110 gg->bandwidth_graph = g;
113 static void setup_graphs(struct gfio_graphs *g)
116 setup_bandwidth_graph(g);
119 void clear_ge_ui_info(struct gui_entry *ge)
121 gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
122 gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
123 gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
124 gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
126 /* should we empty it... */
127 gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
129 multitext_update_entry(&ge->eta.iotype, 0, "");
130 multitext_update_entry(&ge->eta.bs, 0, "");
131 multitext_update_entry(&ge->eta.ioengine, 0, "");
132 multitext_update_entry(&ge->eta.iodepth, 0, "");
133 gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
134 gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
135 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
136 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
137 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
138 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
141 static void set_menu_entry_text(struct gui *ui, const char *path,
146 w = gtk_ui_manager_get_widget(ui->uimanager, path);
148 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
150 fprintf(stderr, "gfio: can't find path %s\n", path);
154 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
158 w = gtk_ui_manager_get_widget(ui->uimanager, path);
160 gtk_widget_set_sensitive(w, show);
162 fprintf(stderr, "gfio: can't find path %s\n", path);
165 static void set_job_menu_visible(struct gui *ui, int visible)
167 set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
170 static void set_view_results_visible(struct gui *ui, int visible)
172 set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
175 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
177 if (s->tooltiptext[sensitive])
178 return s->tooltiptext[sensitive];
180 return s->tooltiptext[0];
183 static GtkWidget *add_button(GtkWidget *buttonbox,
184 struct button_spec *buttonspec, gpointer data)
186 GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
187 gboolean sens = buttonspec->start_sensitive;
189 g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
190 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
192 sens = buttonspec->start_sensitive;
193 gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
194 gtk_widget_set_sensitive(button, sens);
199 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
204 for (i = 0; i < nbuttons; i++)
205 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
209 * Update sensitivity of job buttons and job menu items, based on the
210 * state of the client.
212 static void update_button_states(struct gui *ui, struct gui_entry *ge)
214 unsigned int connect_state, send_state, start_state, edit_state;
215 const char *connect_str = NULL;
219 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
220 /* fall through to new state */
224 connect_str = "Connect";
228 case GE_STATE_CONNECTED:
231 connect_str = "Disconnect";
235 case GE_STATE_JOB_SENT:
238 connect_str = "Disconnect";
242 case GE_STATE_JOB_STARTED:
245 connect_str = "Disconnect";
249 case GE_STATE_JOB_RUNNING:
252 connect_str = "Disconnect";
256 case GE_STATE_JOB_DONE:
259 connect_str = "Connect";
265 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
266 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
267 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
268 gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
269 gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
271 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
272 set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
274 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
275 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
276 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
278 if (ge->client && ge->client->nr_results)
279 set_view_results_visible(ui, 1);
281 set_view_results_visible(ui, 0);
284 void gfio_set_state(struct gui_entry *ge, unsigned int state)
287 update_button_states(ge->ui, ge);
290 static void gfio_ui_setup_log(struct gui *ui)
292 GtkTreeSelection *selection;
294 GtkWidget *tree_view;
296 model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
298 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
299 gtk_widget_set_can_focus(tree_view, FALSE);
301 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
302 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
303 g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
304 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
306 tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
307 tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
308 tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
309 tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
311 ui->log_model = model;
312 ui->log_tree = tree_view;
315 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
318 struct gfio_graphs *g = data;
320 graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
321 graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
322 graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
323 graph_set_position(g->bandwidth_graph, 0, 0);
327 static void draw_graph(struct graph *g, cairo_t *cr)
329 line_graph_draw(g, cr);
333 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
334 gboolean keyboard_mode, GtkTooltip *tooltip,
337 struct gfio_graphs *g = data;
338 const char *text = NULL;
340 if (graph_contains_xy(g->iops_graph, x, y))
341 text = graph_find_tooltip(g->iops_graph, x, y);
342 else if (graph_contains_xy(g->bandwidth_graph, x, y))
343 text = graph_find_tooltip(g->bandwidth_graph, x, y);
346 gtk_tooltip_set_text(tooltip, text);
353 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
355 struct gfio_graphs *g = p;
358 cr = gdk_cairo_create(w->window);
360 if (graph_has_tooltips(g->iops_graph) ||
361 graph_has_tooltips(g->bandwidth_graph)) {
362 g_object_set(w, "has-tooltip", TRUE, NULL);
363 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
366 cairo_set_source_rgb(cr, 0, 0, 0);
367 draw_graph(g->iops_graph, cr);
368 draw_graph(g->bandwidth_graph, cr);
375 * FIXME: need more handling here
377 static void ge_destroy(struct gui_entry *ge)
379 struct gfio_client *gc = ge->client;
383 if (ge->state >= GE_STATE_CONNECTED)
384 fio_client_terminate(gc->client);
386 fio_put_client(gc->client);
391 g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
398 static void ge_widget_destroy(GtkWidget *w, gpointer data)
400 struct gui_entry *ge = (struct gui_entry *) data;
405 static void gfio_quit(struct gui *ui)
410 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
411 __attribute__((unused)) gpointer data)
413 struct gui *ui = (struct gui *) data;
418 static void *job_thread(void *arg)
420 struct gui *ui = arg;
422 ui->handler_running = 1;
423 fio_handle_clients(&gfio_client_ops);
424 ui->handler_running = 0;
428 static int send_job_file(struct gui_entry *ge)
430 struct gfio_client *gc = ge->client;
433 ret = fio_client_send_ini(gc->client, ge->job_file);
437 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
441 static void *server_thread(void *arg)
444 gfio_server_running = 1;
445 fio_start_server(NULL);
446 gfio_server_running = 0;
450 static void gfio_start_server(struct gui *ui)
452 if (!gfio_server_running) {
453 gfio_server_running = 1;
454 pthread_create(&ui->server_t, NULL, server_thread, NULL);
455 pthread_detach(ui->server_t);
459 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
462 struct gui_entry *ge = data;
463 struct gfio_client *gc = ge->client;
466 fio_start_client(gc->client);
469 static void file_open(GtkWidget *w, gpointer data);
471 struct connection_widgets
478 static void hostname_cb(GtkEntry *entry, gpointer data)
480 struct connection_widgets *cw = data;
481 int uses_net = 0, is_localhost = 0;
486 * Check whether to display the 'auto start backend' box
487 * or not. Show it if we are a localhost and using network,
490 ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
491 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
496 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
497 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
498 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
499 !strcmp(text, "ip6-loopback"))
503 if (!uses_net || is_localhost) {
504 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
505 gtk_widget_set_sensitive(cw->button, 1);
507 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
508 gtk_widget_set_sensitive(cw->button, 0);
512 static int get_connection_details(struct gui_entry *ge)
514 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
515 struct connection_widgets cw;
516 struct gui *ui = ge->ui;
522 dialog = gtk_dialog_new_with_buttons("Connection details",
523 GTK_WINDOW(ui->window),
524 GTK_DIALOG_DESTROY_WITH_PARENT,
525 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
526 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
528 frame = gtk_frame_new("Hostname / socket name");
529 /* gtk_dialog_get_content_area() is 2.14 and newer */
530 vbox = GTK_DIALOG(dialog)->vbox;
531 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
533 box = gtk_vbox_new(FALSE, 6);
534 gtk_container_add(GTK_CONTAINER(frame), box);
536 hbox = gtk_hbox_new(TRUE, 10);
537 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
538 cw.hentry = gtk_entry_new();
539 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
540 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
542 frame = gtk_frame_new("Port");
543 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
544 box = gtk_vbox_new(FALSE, 10);
545 gtk_container_add(GTK_CONTAINER(frame), box);
547 hbox = gtk_hbox_new(TRUE, 4);
548 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
549 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
551 frame = gtk_frame_new("Type");
552 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
553 box = gtk_vbox_new(FALSE, 10);
554 gtk_container_add(GTK_CONTAINER(frame), box);
556 hbox = gtk_hbox_new(TRUE, 4);
557 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
559 cw.combo = gtk_combo_box_new_text();
560 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
561 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
562 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
563 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
565 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
567 frame = gtk_frame_new("Options");
568 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
569 box = gtk_vbox_new(FALSE, 10);
570 gtk_container_add(GTK_CONTAINER(frame), box);
572 hbox = gtk_hbox_new(TRUE, 4);
573 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
575 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
576 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
577 gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
578 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
581 * Connect edit signal, so we can show/not-show the auto start button
583 g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
584 g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
586 gtk_widget_show_all(dialog);
588 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
589 gtk_widget_destroy(dialog);
593 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
594 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
596 typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
597 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
598 ge->type = Fio_client_ipv4;
599 else if (!strncmp(typeentry, "IPv6", 4))
600 ge->type = Fio_client_ipv6;
602 ge->type = Fio_client_socket;
605 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
607 gtk_widget_destroy(dialog);
611 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
613 gc->client = fio_get_client(client);
614 client->client_data = gc;
617 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
619 struct gfio_client *gc;
621 gc = malloc(sizeof(*gc));
622 memset(gc, 0, sizeof(*gc));
623 options_default_fill(&gc->o);
626 gfio_set_client(gc, client);
629 static void connect_clicked(GtkWidget *widget, gpointer data)
631 struct gui_entry *ge = data;
632 struct gfio_client *gc = ge->client;
634 if (ge->state == GE_STATE_NEW) {
638 file_open(widget, ge->ui);
645 struct fio_client *client;
647 if (get_connection_details(ge)) {
648 gfio_report_error(ge, "Failed to get connection details\n");
652 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
654 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
659 gfio_set_client(gc, client);
662 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
663 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
664 ret = fio_client_connect(gc->client);
666 if (!ge->ui->handler_running)
667 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
668 gfio_set_state(ge, GE_STATE_CONNECTED);
670 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
673 fio_client_terminate(gc->client);
674 gfio_set_state(ge, GE_STATE_NEW);
675 clear_ge_ui_info(ge);
679 static void send_clicked(GtkWidget *widget, gpointer data)
681 struct gui_entry *ge = data;
683 if (send_job_file(ge))
684 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
687 static GtkWidget *new_client_page(struct gui_entry *ge);
689 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
691 struct gui_entry *ge;
693 ge = malloc(sizeof(*ge));
694 memset(ge, 0, sizeof(*ge));
695 ge->state = GE_STATE_NEW;
700 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
702 struct gui_entry *ge;
704 ge = alloc_new_gui_entry(ui);
706 ge->vbox = new_client_page(ge);
707 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
709 ge->page_label = gtk_label_new(name);
710 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
712 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
714 gtk_widget_show_all(ui->window);
718 static void file_new(GtkWidget *w, gpointer data)
720 struct gui *ui = (struct gui *) data;
721 struct gui_entry *ge;
723 ge = get_new_ge_with_tab(ui, "Untitled");
724 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
728 * Return the 'ge' corresponding to the tab. If the active tab is the
729 * main tab, open a new tab.
731 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
737 return get_new_ge_with_tab(ui, "Untitled");
743 return g_hash_table_lookup(ui->ge_hash, &cur_page);
746 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
751 * Main tab is tab 0, so any current page other than 0 holds
754 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
756 return get_ge_from_page(ui, cur_page, NULL);
761 static void file_close(GtkWidget *w, gpointer data)
763 struct gui *ui = (struct gui *) data;
764 struct gui_entry *ge;
767 * Can't close the main tab
769 ge = get_ge_from_cur_tab(ui);
771 gtk_widget_destroy(ge->vbox);
775 if (g_hash_table_size(ui->ge_hash)) {
776 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
783 static void file_add_recent(struct gui *ui, const gchar *uri)
787 memset(&grd, 0, sizeof(grd));
788 grd.display_name = strdup("gfio");
789 grd.description = strdup("Fio job file");
790 grd.mime_type = strdup(GFIO_MIME);
791 grd.app_name = strdup(g_get_application_name());
792 grd.app_exec = strdup("gfio %f/%u");
794 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
797 static gchar *get_filename_from_uri(const gchar *uri)
799 if (strncmp(uri, "file://", 7))
802 return strdup(uri + 7);
805 static int do_file_open(struct gui_entry *ge, const gchar *uri)
807 struct fio_client *client;
809 assert(!ge->job_file);
811 ge->job_file = get_filename_from_uri(uri);
813 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
815 char *label = strdup(uri);
818 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
821 gfio_client_added(ge, client);
822 file_add_recent(ge->ui, uri);
826 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
834 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
836 struct gui_entry *ge;
838 int ret, ge_is_new = 0;
841 * Creates new tab if current tab is the main window, or the
842 * current tab already has a client.
844 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
845 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
847 ge = get_new_ge_with_tab(ui, "Untitled");
851 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
853 if (get_connection_details(ge)) {
855 gtk_widget_destroy(ge->vbox);
860 ret = do_file_open(ge, uri);
863 if (ge->server_start)
864 gfio_start_server(ui);
867 gtk_widget_destroy(ge->vbox);
873 static void recent_open(GtkAction *action, gpointer data)
875 struct gui *ui = (struct gui *) data;
879 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
880 uri = gtk_recent_info_get_uri(info);
882 do_file_open_with_tab(ui, uri);
885 static void file_open(GtkWidget *w, gpointer data)
887 struct gui *ui = data;
889 GtkFileFilter *filter;
892 dialog = gtk_file_chooser_dialog_new("Open File",
893 GTK_WINDOW(ui->window),
894 GTK_FILE_CHOOSER_ACTION_OPEN,
895 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
896 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
898 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
900 filter = gtk_file_filter_new();
901 gtk_file_filter_add_pattern(filter, "*.fio");
902 gtk_file_filter_add_pattern(filter, "*.job");
903 gtk_file_filter_add_pattern(filter, "*.ini");
904 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
905 gtk_file_filter_set_name(filter, "Fio job file");
906 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
908 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
909 gtk_widget_destroy(dialog);
913 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
915 gtk_widget_destroy(dialog);
917 do_file_open_with_tab(ui, filename);
921 static void file_save(GtkWidget *w, gpointer data)
923 struct gui *ui = data;
926 dialog = gtk_file_chooser_dialog_new("Save File",
927 GTK_WINDOW(ui->window),
928 GTK_FILE_CHOOSER_ACTION_SAVE,
929 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
930 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
933 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
934 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
936 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
939 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
940 // save_job_file(filename);
943 gtk_widget_destroy(dialog);
946 static void view_log_destroy(GtkWidget *w, gpointer data)
948 struct gui *ui = (struct gui *) data;
950 gtk_widget_ref(ui->log_tree);
951 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
952 gtk_widget_destroy(w);
956 void gfio_view_log(struct gui *ui)
958 GtkWidget *win, *scroll, *vbox, *box;
963 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
964 gtk_window_set_title(GTK_WINDOW(win), "Log");
965 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
967 scroll = gtk_scrolled_window_new(NULL, NULL);
969 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
971 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
973 box = gtk_hbox_new(TRUE, 0);
974 gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
975 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
976 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
978 vbox = gtk_vbox_new(TRUE, 5);
979 gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
981 gtk_container_add(GTK_CONTAINER(win), vbox);
982 gtk_widget_show_all(win);
985 static void view_log(GtkWidget *w, gpointer data)
987 struct gui *ui = (struct gui *) data;
992 static void connect_job_entry(GtkWidget *w, gpointer data)
994 struct gui *ui = (struct gui *) data;
995 struct gui_entry *ge;
997 ge = get_ge_from_cur_tab(ui);
999 connect_clicked(w, ge);
1002 static void send_job_entry(GtkWidget *w, gpointer data)
1004 struct gui *ui = (struct gui *) data;
1005 struct gui_entry *ge;
1007 ge = get_ge_from_cur_tab(ui);
1009 send_clicked(w, ge);
1012 static void edit_job_entry(GtkWidget *w, gpointer data)
1014 struct gui *ui = (struct gui *) data;
1015 struct gui_entry *ge;
1017 ge = get_ge_from_cur_tab(ui);
1018 if (ge && ge->client)
1019 gopt_get_options_window(ui->window, &ge->client->o);
1022 static void start_job_entry(GtkWidget *w, gpointer data)
1024 struct gui *ui = (struct gui *) data;
1025 struct gui_entry *ge;
1027 ge = get_ge_from_cur_tab(ui);
1029 start_job_clicked(w, ge);
1032 static void view_results(GtkWidget *w, gpointer data)
1034 struct gui *ui = (struct gui *) data;
1035 struct gfio_client *gc;
1036 struct gui_entry *ge;
1038 ge = get_ge_from_cur_tab(ui);
1042 if (ge->results_window)
1046 if (gc && gc->nr_results)
1047 gfio_display_end_results(gc);
1050 static void __update_graph_settings(struct gfio_graphs *g)
1052 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1053 graph_set_font(g->iops_graph, gfio_graph_font);
1054 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1055 graph_set_font(g->bandwidth_graph, gfio_graph_font);
1058 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1060 struct gui_entry *ge = (struct gui_entry *) value;
1063 __update_graph_settings(&ge->graphs);
1065 ev = gdk_event_new(GDK_EXPOSE);
1066 g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), "expose_event", GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1070 static void update_graph_limits(void)
1072 struct gui *ui = &main_ui;
1075 __update_graph_settings(&ui->graphs);
1077 ev = gdk_event_new(GDK_EXPOSE);
1078 g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), "expose_event", GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1081 g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1084 static void preferences(GtkWidget *w, gpointer data)
1086 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1087 GtkWidget *hbox, *spin, *entry, *spin_int;
1088 struct gui *ui = (struct gui *) data;
1091 dialog = gtk_dialog_new_with_buttons("Preferences",
1092 GTK_WINDOW(ui->window),
1093 GTK_DIALOG_DESTROY_WITH_PARENT,
1094 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1095 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1098 frame = gtk_frame_new("Graphing");
1099 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1100 vbox = gtk_vbox_new(FALSE, 6);
1101 gtk_container_add(GTK_CONTAINER(frame), vbox);
1103 hbox = gtk_hbox_new(FALSE, 5);
1104 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1105 entry = gtk_label_new("Font face to use for graph labels");
1106 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1108 font = gtk_font_button_new_with_font(gfio_graph_font);
1109 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1111 box = gtk_vbox_new(FALSE, 6);
1112 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1114 hbox = gtk_hbox_new(FALSE, 5);
1115 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1116 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1117 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1119 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1121 box = gtk_vbox_new(FALSE, 6);
1122 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1124 hbox = gtk_hbox_new(FALSE, 5);
1125 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1126 entry = gtk_label_new("Client ETA request interval (msec)");
1127 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1129 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1130 frame = gtk_frame_new("Debug logging");
1131 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1132 vbox = gtk_vbox_new(FALSE, 6);
1133 gtk_container_add(GTK_CONTAINER(frame), vbox);
1135 box = gtk_hbox_new(FALSE, 6);
1136 gtk_container_add(GTK_CONTAINER(vbox), box);
1138 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1140 for (i = 0; i < FD_DEBUG_MAX; i++) {
1142 box = gtk_hbox_new(FALSE, 6);
1143 gtk_container_add(GTK_CONTAINER(vbox), box);
1147 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1148 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1149 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1152 gtk_widget_show_all(dialog);
1154 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1155 gtk_widget_destroy(dialog);
1159 for (i = 0; i < FD_DEBUG_MAX; i++) {
1162 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1164 fio_debug |= (1UL << i);
1167 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1168 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1169 update_graph_limits();
1170 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1172 gtk_widget_destroy(dialog);
1175 static void about_dialog(GtkWidget *w, gpointer data)
1177 const char *authors[] = {
1178 "Jens Axboe <axboe@kernel.dk>",
1179 "Stephen Carmeron <stephenmcameron@gmail.com>",
1182 const char *license[] = {
1183 "Fio is free software; you can redistribute it and/or modify "
1184 "it under the terms of the GNU General Public License as published by "
1185 "the Free Software Foundation; either version 2 of the License, or "
1186 "(at your option) any later version.\n",
1187 "Fio is distributed in the hope that it will be useful, "
1188 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1189 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1190 "GNU General Public License for more details.\n",
1191 "You should have received a copy of the GNU General Public License "
1192 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1193 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1195 char *license_trans;
1197 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1198 license[2], "\n", NULL);
1200 gtk_show_about_dialog(NULL,
1201 "program-name", "gfio",
1202 "comments", "Gtk2 UI for fio",
1203 "license", license_trans,
1204 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1206 "version", fio_version_string,
1207 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1208 "logo-icon-name", "fio",
1210 "wrap-license", TRUE,
1213 g_free(license_trans);
1216 static GtkActionEntry menu_items[] = {
1217 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1218 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1219 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1220 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1221 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1222 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1223 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1224 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1225 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1226 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1227 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1228 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1229 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1230 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1231 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1232 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1233 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1235 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1237 static const gchar *ui_string = " \
1239 <menubar name=\"MainMenu\"> \
1240 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1241 <menuitem name=\"New\" action=\"NewFile\" /> \
1242 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1243 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1244 <separator name=\"Separator1\"/> \
1245 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1246 <separator name=\"Separator2\"/> \
1247 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1248 <separator name=\"Separator3\"/> \
1249 <placeholder name=\"FileRecentFiles\"/> \
1250 <separator name=\"Separator4\"/> \
1251 <menuitem name=\"Quit\" action=\"Quit\" /> \
1253 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1254 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1255 <separator name=\"Separator5\"/> \
1256 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1257 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1258 <separator name=\"Separator6\"/> \
1259 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1261 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1262 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1263 <separator name=\"Separator7\"/> \
1264 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1266 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1267 <menuitem name=\"About\" action=\"About\" /> \
1273 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1276 GtkActionGroup *action_group;
1279 action_group = gtk_action_group_new("Menu");
1280 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1282 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1283 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1285 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1287 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1290 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1291 GtkWidget *vbox, GtkUIManager *ui_manager)
1293 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1296 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1298 struct gui_entry *ge = (struct gui_entry *) data;
1301 index = gtk_combo_box_get_active(box);
1303 multitext_set_entry(&ge->eta.iotype, index);
1304 multitext_set_entry(&ge->eta.bs, index);
1305 multitext_set_entry(&ge->eta.ioengine, index);
1306 multitext_set_entry(&ge->eta.iodepth, index);
1309 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1311 struct gui_entry *ge = (struct gui_entry *) data;
1313 multitext_free(&ge->eta.iotype);
1314 multitext_free(&ge->eta.bs);
1315 multitext_free(&ge->eta.ioengine);
1316 multitext_free(&ge->eta.iodepth);
1319 static GtkWidget *new_client_page(struct gui_entry *ge)
1321 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1322 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1324 main_vbox = gtk_vbox_new(FALSE, 3);
1326 top_align = gtk_alignment_new(0, 0, 1, 0);
1327 top_vbox = gtk_vbox_new(FALSE, 3);
1328 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1329 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1331 probe = gtk_frame_new("Job");
1332 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1333 probe_frame = gtk_vbox_new(FALSE, 3);
1334 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1336 probe_box = gtk_hbox_new(FALSE, 3);
1337 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1338 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1339 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1340 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1341 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1343 probe_box = gtk_hbox_new(FALSE, 3);
1344 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1346 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1347 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1348 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1349 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1350 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1351 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1352 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1353 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1354 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1356 probe_box = gtk_hbox_new(FALSE, 3);
1357 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1358 ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1359 ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1360 ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1361 ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1364 * Only add this if we have a commit rate
1367 probe_box = gtk_hbox_new(FALSE, 3);
1368 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1370 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1371 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1373 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1374 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1378 * Set up a drawing area and IOPS and bandwidth graphs
1380 ge->graphs.drawing_area = gtk_drawing_area_new();
1381 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1382 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1383 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1384 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1385 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1386 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1387 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1388 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1389 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1390 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1391 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1392 ge->graphs.drawing_area);
1393 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1395 setup_graphs(&ge->graphs);
1398 * Set up alignments for widgets at the bottom of ui,
1399 * align bottom left, expand horizontally but not vertically
1401 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1402 ge->buttonbox = gtk_hbox_new(FALSE, 0);
1403 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1404 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1406 add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1409 * Set up thread status progress bar
1411 ge->thread_status_pb = gtk_progress_bar_new();
1412 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1413 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1414 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1420 static GtkWidget *new_main_page(struct gui *ui)
1422 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1423 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1425 main_vbox = gtk_vbox_new(FALSE, 3);
1428 * Set up alignments for widgets at the top of ui,
1429 * align top left, expand horizontally but not vertically
1431 top_align = gtk_alignment_new(0, 0, 1, 0);
1432 top_vbox = gtk_vbox_new(FALSE, 0);
1433 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1434 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1436 probe = gtk_frame_new("Run statistics");
1437 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1438 probe_frame = gtk_vbox_new(FALSE, 3);
1439 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1441 probe_box = gtk_hbox_new(FALSE, 3);
1442 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1443 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1444 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1445 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1446 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1447 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1450 * Only add this if we have a commit rate
1453 probe_box = gtk_hbox_new(FALSE, 3);
1454 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1456 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1457 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1459 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1460 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1464 * Set up a drawing area and IOPS and bandwidth graphs
1466 ui->graphs.drawing_area = gtk_drawing_area_new();
1467 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1468 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1469 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1470 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1471 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1472 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1473 G_CALLBACK(on_config_drawing_area), &ui->graphs);
1474 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1475 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1476 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1477 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1478 ui->graphs.drawing_area);
1479 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1482 setup_graphs(&ui->graphs);
1485 * Set up alignments for widgets at the bottom of ui,
1486 * align bottom left, expand horizontally but not vertically
1488 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1489 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1490 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1491 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1494 * Set up thread status progress bar
1496 ui->thread_status_pb = gtk_progress_bar_new();
1497 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1498 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1499 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1504 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1505 guint page, gpointer data)
1508 struct gui *ui = (struct gui *) data;
1509 struct gui_entry *ge;
1512 set_job_menu_visible(ui, 0);
1513 set_view_results_visible(ui, 0);
1517 set_job_menu_visible(ui, 1);
1518 ge = get_ge_from_page(ui, page, NULL);
1520 update_button_states(ui, ge);
1525 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1527 time_t time_a = gtk_recent_info_get_visited(a);
1528 time_t time_b = gtk_recent_info_get_visited(b);
1530 return time_b - time_a;
1533 static void add_recent_file_items(struct gui *ui)
1535 const gchar *gfio = g_get_application_name();
1536 GList *items, *item;
1539 if (ui->recent_ui_id) {
1540 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1541 gtk_ui_manager_ensure_update(ui->uimanager);
1543 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1545 if (ui->actiongroup) {
1546 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1547 g_object_unref(ui->actiongroup);
1549 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1551 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1553 items = gtk_recent_manager_get_items(ui->recentmanager);
1554 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1556 for (item = items; item && item->data; item = g_list_next(item)) {
1557 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1562 if (!gtk_recent_info_has_application(info, gfio))
1566 * We only support local files for now
1568 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1571 action_name = g_strdup_printf("RecentFile%u", i++);
1572 label = gtk_recent_info_get_display_name(info);
1574 action = g_object_new(GTK_TYPE_ACTION,
1575 "name", action_name,
1576 "label", label, NULL);
1578 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1579 gtk_recent_info_ref(info),
1580 (GDestroyNotify) gtk_recent_info_unref);
1583 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1585 gtk_action_group_add_action(ui->actiongroup, action);
1586 g_object_unref(action);
1588 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1589 "/MainMenu/FileMenu/FileRecentFiles",
1591 GTK_UI_MANAGER_MENUITEM, FALSE);
1593 g_free(action_name);
1599 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1603 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1604 gint x, gint y, GtkSelectionData *seldata,
1605 guint info, guint time, gpointer *data)
1607 struct gui *ui = (struct gui *) data;
1611 source = gtk_drag_get_source_widget(ctx);
1612 if (source && widget == gtk_widget_get_toplevel(source)) {
1613 gtk_drag_finish(ctx, FALSE, FALSE, time);
1617 uris = gtk_selection_data_get_uris(seldata);
1619 gtk_drag_finish(ctx, FALSE, FALSE, time);
1624 do_file_open_with_tab(ui, uris[0]);
1626 gtk_drag_finish(ctx, TRUE, FALSE, time);
1630 static void init_ui(int *argc, char **argv[], struct gui *ui)
1632 GtkSettings *settings;
1635 /* Magical g*thread incantation, you just need this thread stuff.
1636 * Without it, the update that happens in gfio_update_thread_status
1637 * doesn't really happen in a timely fashion, you need expose events
1639 if (!g_thread_supported())
1640 g_thread_init(NULL);
1643 gtk_init(argc, argv);
1644 settings = gtk_settings_get_default();
1645 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1647 gdk_color_parse("white", &gfio_color_white);
1649 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1650 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1651 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1653 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1654 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1656 ui->vbox = gtk_vbox_new(FALSE, 0);
1657 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1659 ui->uimanager = gtk_ui_manager_new();
1660 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1661 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1663 ui->recentmanager = gtk_recent_manager_get_default();
1664 add_recent_file_items(ui);
1666 ui->notebook = gtk_notebook_new();
1667 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1668 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1669 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1670 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1672 vbox = new_main_page(ui);
1673 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1674 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1675 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1677 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1679 gfio_ui_setup_log(ui);
1681 gtk_widget_show_all(ui->window);
1684 int main(int argc, char *argv[], char *envp[])
1686 if (initialize_fio(envp))
1688 if (fio_init_options())
1691 memset(&main_ui, 0, sizeof(main_ui));
1692 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1694 init_ui(&argc, &argv, &main_ui);
1696 gdk_threads_enter();
1698 gdk_threads_leave();
1700 g_hash_table_destroy(main_ui.ge_hash);