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_options *gco;
620 struct gfio_client *gc;
622 gc = calloc(1, sizeof(*gc));
623 INIT_FLIST_HEAD(&gc->o_list);
626 gfio_set_client(gc, client);
629 * Just add a default set of options, need to consider how best
632 gco = calloc(1, sizeof(*gco));
633 options_default_fill(&gco->o);
634 flist_add_tail(&gco->list, &gc->o_list);
637 static void connect_clicked(GtkWidget *widget, gpointer data)
639 struct gui_entry *ge = data;
640 struct gfio_client *gc = ge->client;
642 if (ge->state == GE_STATE_NEW) {
646 file_open(widget, ge->ui);
653 struct fio_client *client;
655 if (get_connection_details(ge)) {
656 gfio_report_error(ge, "Failed to get connection details\n");
660 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
662 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
667 gfio_set_client(gc, client);
670 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
671 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
672 ret = fio_client_connect(gc->client);
674 if (!ge->ui->handler_running)
675 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
676 gfio_set_state(ge, GE_STATE_CONNECTED);
678 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
681 fio_client_terminate(gc->client);
682 gfio_set_state(ge, GE_STATE_NEW);
683 clear_ge_ui_info(ge);
687 static void send_clicked(GtkWidget *widget, gpointer data)
689 struct gui_entry *ge = data;
691 if (send_job_file(ge))
692 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
695 static GtkWidget *new_client_page(struct gui_entry *ge);
697 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
699 struct gui_entry *ge;
701 ge = malloc(sizeof(*ge));
702 memset(ge, 0, sizeof(*ge));
703 ge->state = GE_STATE_NEW;
708 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
710 struct gui_entry *ge;
712 ge = alloc_new_gui_entry(ui);
714 ge->vbox = new_client_page(ge);
715 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
717 ge->page_label = gtk_label_new(name);
718 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
720 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
722 gtk_widget_show_all(ui->window);
726 static void file_new(GtkWidget *w, gpointer data)
728 struct gui *ui = (struct gui *) data;
729 struct gui_entry *ge;
731 ge = get_new_ge_with_tab(ui, "Untitled");
732 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
736 * Return the 'ge' corresponding to the tab. If the active tab is the
737 * main tab, open a new tab.
739 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
745 return get_new_ge_with_tab(ui, "Untitled");
751 return g_hash_table_lookup(ui->ge_hash, &cur_page);
754 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
759 * Main tab is tab 0, so any current page other than 0 holds
762 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
764 return get_ge_from_page(ui, cur_page, NULL);
769 static void file_close(GtkWidget *w, gpointer data)
771 struct gui *ui = (struct gui *) data;
772 struct gui_entry *ge;
775 * Can't close the main tab
777 ge = get_ge_from_cur_tab(ui);
779 gtk_widget_destroy(ge->vbox);
783 if (g_hash_table_size(ui->ge_hash)) {
784 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
791 static void file_add_recent(struct gui *ui, const gchar *uri)
795 memset(&grd, 0, sizeof(grd));
796 grd.display_name = strdup("gfio");
797 grd.description = strdup("Fio job file");
798 grd.mime_type = strdup(GFIO_MIME);
799 grd.app_name = strdup(g_get_application_name());
800 grd.app_exec = strdup("gfio %f/%u");
802 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
805 static gchar *get_filename_from_uri(const gchar *uri)
807 if (strncmp(uri, "file://", 7))
810 return strdup(uri + 7);
813 static int do_file_open(struct gui_entry *ge, const gchar *uri)
815 struct fio_client *client;
817 assert(!ge->job_file);
819 ge->job_file = get_filename_from_uri(uri);
821 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
823 char *label = strdup(uri);
826 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
829 gfio_client_added(ge, client);
830 file_add_recent(ge->ui, uri);
834 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
842 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
844 struct gui_entry *ge;
846 int ret, ge_is_new = 0;
849 * Creates new tab if current tab is the main window, or the
850 * current tab already has a client.
852 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
853 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
855 ge = get_new_ge_with_tab(ui, "Untitled");
859 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
861 if (get_connection_details(ge)) {
863 gtk_widget_destroy(ge->vbox);
868 ret = do_file_open(ge, uri);
871 if (ge->server_start)
872 gfio_start_server(ui);
875 gtk_widget_destroy(ge->vbox);
881 static void recent_open(GtkAction *action, gpointer data)
883 struct gui *ui = (struct gui *) data;
887 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
888 uri = gtk_recent_info_get_uri(info);
890 do_file_open_with_tab(ui, uri);
893 static void file_open(GtkWidget *w, gpointer data)
895 struct gui *ui = data;
897 GtkFileFilter *filter;
900 dialog = gtk_file_chooser_dialog_new("Open File",
901 GTK_WINDOW(ui->window),
902 GTK_FILE_CHOOSER_ACTION_OPEN,
903 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
904 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
906 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
908 filter = gtk_file_filter_new();
909 gtk_file_filter_add_pattern(filter, "*.fio");
910 gtk_file_filter_add_pattern(filter, "*.job");
911 gtk_file_filter_add_pattern(filter, "*.ini");
912 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
913 gtk_file_filter_set_name(filter, "Fio job file");
914 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
916 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
917 gtk_widget_destroy(dialog);
921 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
923 gtk_widget_destroy(dialog);
925 do_file_open_with_tab(ui, filename);
929 static void file_save(GtkWidget *w, gpointer data)
931 struct gui *ui = data;
934 dialog = gtk_file_chooser_dialog_new("Save File",
935 GTK_WINDOW(ui->window),
936 GTK_FILE_CHOOSER_ACTION_SAVE,
937 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
938 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
941 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
942 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
944 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
947 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
948 // save_job_file(filename);
951 gtk_widget_destroy(dialog);
954 static void view_log_destroy(GtkWidget *w, gpointer data)
956 struct gui *ui = (struct gui *) data;
958 gtk_widget_ref(ui->log_tree);
959 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
960 gtk_widget_destroy(w);
964 void gfio_view_log(struct gui *ui)
966 GtkWidget *win, *scroll, *vbox, *box;
971 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
972 gtk_window_set_title(GTK_WINDOW(win), "Log");
973 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
975 scroll = gtk_scrolled_window_new(NULL, NULL);
977 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
979 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
981 box = gtk_hbox_new(TRUE, 0);
982 gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
983 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
984 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
986 vbox = gtk_vbox_new(TRUE, 5);
987 gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
989 gtk_container_add(GTK_CONTAINER(win), vbox);
990 gtk_widget_show_all(win);
993 static void view_log(GtkWidget *w, gpointer data)
995 struct gui *ui = (struct gui *) data;
1000 static void connect_job_entry(GtkWidget *w, gpointer data)
1002 struct gui *ui = (struct gui *) data;
1003 struct gui_entry *ge;
1005 ge = get_ge_from_cur_tab(ui);
1007 connect_clicked(w, ge);
1010 static void send_job_entry(GtkWidget *w, gpointer data)
1012 struct gui *ui = (struct gui *) data;
1013 struct gui_entry *ge;
1015 ge = get_ge_from_cur_tab(ui);
1017 send_clicked(w, ge);
1020 static void edit_job_entry(GtkWidget *w, gpointer data)
1022 struct gui *ui = (struct gui *) data;
1023 struct gui_entry *ge;
1025 ge = get_ge_from_cur_tab(ui);
1026 if (ge && ge->client)
1027 gopt_get_options_window(ui->window, ge->client);
1030 static void start_job_entry(GtkWidget *w, gpointer data)
1032 struct gui *ui = (struct gui *) data;
1033 struct gui_entry *ge;
1035 ge = get_ge_from_cur_tab(ui);
1037 start_job_clicked(w, ge);
1040 static void view_results(GtkWidget *w, gpointer data)
1042 struct gui *ui = (struct gui *) data;
1043 struct gfio_client *gc;
1044 struct gui_entry *ge;
1046 ge = get_ge_from_cur_tab(ui);
1050 if (ge->results_window)
1054 if (gc && gc->nr_results)
1055 gfio_display_end_results(gc);
1058 static void __update_graph_settings(struct gfio_graphs *g)
1060 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1061 graph_set_font(g->iops_graph, gfio_graph_font);
1062 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1063 graph_set_font(g->bandwidth_graph, gfio_graph_font);
1066 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1068 struct gui_entry *ge = (struct gui_entry *) value;
1071 __update_graph_settings(&ge->graphs);
1073 ev = gdk_event_new(GDK_EXPOSE);
1074 g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), "expose_event", GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1078 static void update_graph_limits(void)
1080 struct gui *ui = &main_ui;
1083 __update_graph_settings(&ui->graphs);
1085 ev = gdk_event_new(GDK_EXPOSE);
1086 g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), "expose_event", GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1089 g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1092 static void preferences(GtkWidget *w, gpointer data)
1094 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1095 GtkWidget *hbox, *spin, *entry, *spin_int;
1096 struct gui *ui = (struct gui *) data;
1099 dialog = gtk_dialog_new_with_buttons("Preferences",
1100 GTK_WINDOW(ui->window),
1101 GTK_DIALOG_DESTROY_WITH_PARENT,
1102 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1103 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1106 frame = gtk_frame_new("Graphing");
1107 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1108 vbox = gtk_vbox_new(FALSE, 6);
1109 gtk_container_add(GTK_CONTAINER(frame), vbox);
1111 hbox = gtk_hbox_new(FALSE, 5);
1112 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1113 entry = gtk_label_new("Font face to use for graph labels");
1114 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1116 font = gtk_font_button_new_with_font(gfio_graph_font);
1117 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1119 box = gtk_vbox_new(FALSE, 6);
1120 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1122 hbox = gtk_hbox_new(FALSE, 5);
1123 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1124 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1125 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1127 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1129 box = gtk_vbox_new(FALSE, 6);
1130 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1132 hbox = gtk_hbox_new(FALSE, 5);
1133 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1134 entry = gtk_label_new("Client ETA request interval (msec)");
1135 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1137 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1138 frame = gtk_frame_new("Debug logging");
1139 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1140 vbox = gtk_vbox_new(FALSE, 6);
1141 gtk_container_add(GTK_CONTAINER(frame), vbox);
1143 box = gtk_hbox_new(FALSE, 6);
1144 gtk_container_add(GTK_CONTAINER(vbox), box);
1146 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1148 for (i = 0; i < FD_DEBUG_MAX; i++) {
1150 box = gtk_hbox_new(FALSE, 6);
1151 gtk_container_add(GTK_CONTAINER(vbox), box);
1155 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1156 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1157 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1160 gtk_widget_show_all(dialog);
1162 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1163 gtk_widget_destroy(dialog);
1167 for (i = 0; i < FD_DEBUG_MAX; i++) {
1170 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1172 fio_debug |= (1UL << i);
1175 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1176 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1177 update_graph_limits();
1178 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1180 gtk_widget_destroy(dialog);
1183 static void about_dialog(GtkWidget *w, gpointer data)
1185 const char *authors[] = {
1186 "Jens Axboe <axboe@kernel.dk>",
1187 "Stephen Carmeron <stephenmcameron@gmail.com>",
1190 const char *license[] = {
1191 "Fio is free software; you can redistribute it and/or modify "
1192 "it under the terms of the GNU General Public License as published by "
1193 "the Free Software Foundation; either version 2 of the License, or "
1194 "(at your option) any later version.\n",
1195 "Fio is distributed in the hope that it will be useful, "
1196 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1197 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1198 "GNU General Public License for more details.\n",
1199 "You should have received a copy of the GNU General Public License "
1200 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1201 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1203 char *license_trans;
1205 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1206 license[2], "\n", NULL);
1208 gtk_show_about_dialog(NULL,
1209 "program-name", "gfio",
1210 "comments", "Gtk2 UI for fio",
1211 "license", license_trans,
1212 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1214 "version", fio_version_string,
1215 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1216 "logo-icon-name", "fio",
1218 "wrap-license", TRUE,
1221 g_free(license_trans);
1224 static GtkActionEntry menu_items[] = {
1225 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1226 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1227 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1228 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1229 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1230 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1231 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1232 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1233 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1234 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1235 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1236 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1237 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1238 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1239 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1240 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1241 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1243 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1245 static const gchar *ui_string = " \
1247 <menubar name=\"MainMenu\"> \
1248 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1249 <menuitem name=\"New\" action=\"NewFile\" /> \
1250 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1251 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1252 <separator name=\"Separator1\"/> \
1253 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1254 <separator name=\"Separator2\"/> \
1255 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1256 <separator name=\"Separator3\"/> \
1257 <placeholder name=\"FileRecentFiles\"/> \
1258 <separator name=\"Separator4\"/> \
1259 <menuitem name=\"Quit\" action=\"Quit\" /> \
1261 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1262 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1263 <separator name=\"Separator5\"/> \
1264 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1265 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1266 <separator name=\"Separator6\"/> \
1267 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1269 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1270 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1271 <separator name=\"Separator7\"/> \
1272 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1274 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1275 <menuitem name=\"About\" action=\"About\" /> \
1281 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1284 GtkActionGroup *action_group;
1287 action_group = gtk_action_group_new("Menu");
1288 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1290 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1291 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1293 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1295 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1298 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1299 GtkWidget *vbox, GtkUIManager *ui_manager)
1301 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1304 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1306 struct gui_entry *ge = (struct gui_entry *) data;
1309 index = gtk_combo_box_get_active(box);
1311 multitext_set_entry(&ge->eta.iotype, index);
1312 multitext_set_entry(&ge->eta.bs, index);
1313 multitext_set_entry(&ge->eta.ioengine, index);
1314 multitext_set_entry(&ge->eta.iodepth, index);
1317 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1319 struct gui_entry *ge = (struct gui_entry *) data;
1321 multitext_free(&ge->eta.iotype);
1322 multitext_free(&ge->eta.bs);
1323 multitext_free(&ge->eta.ioengine);
1324 multitext_free(&ge->eta.iodepth);
1327 static GtkWidget *new_client_page(struct gui_entry *ge)
1329 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1330 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1332 main_vbox = gtk_vbox_new(FALSE, 3);
1334 top_align = gtk_alignment_new(0, 0, 1, 0);
1335 top_vbox = gtk_vbox_new(FALSE, 3);
1336 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1337 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1339 probe = gtk_frame_new("Job");
1340 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1341 probe_frame = gtk_vbox_new(FALSE, 3);
1342 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1344 probe_box = gtk_hbox_new(FALSE, 3);
1345 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1346 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1347 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1348 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1349 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1351 probe_box = gtk_hbox_new(FALSE, 3);
1352 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1354 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1355 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1356 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1357 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1358 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1359 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1360 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1361 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1362 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1364 probe_box = gtk_hbox_new(FALSE, 3);
1365 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1366 ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1367 ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1368 ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1369 ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1372 * Only add this if we have a commit rate
1375 probe_box = gtk_hbox_new(FALSE, 3);
1376 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1378 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1379 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1381 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1382 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1386 * Set up a drawing area and IOPS and bandwidth graphs
1388 ge->graphs.drawing_area = gtk_drawing_area_new();
1389 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1390 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1391 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1392 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1393 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1394 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1395 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1396 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1397 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1398 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1399 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1400 ge->graphs.drawing_area);
1401 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1403 setup_graphs(&ge->graphs);
1406 * Set up alignments for widgets at the bottom of ui,
1407 * align bottom left, expand horizontally but not vertically
1409 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1410 ge->buttonbox = gtk_hbox_new(FALSE, 0);
1411 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1412 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1414 add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1417 * Set up thread status progress bar
1419 ge->thread_status_pb = gtk_progress_bar_new();
1420 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1421 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1422 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1428 static GtkWidget *new_main_page(struct gui *ui)
1430 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1431 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1433 main_vbox = gtk_vbox_new(FALSE, 3);
1436 * Set up alignments for widgets at the top of ui,
1437 * align top left, expand horizontally but not vertically
1439 top_align = gtk_alignment_new(0, 0, 1, 0);
1440 top_vbox = gtk_vbox_new(FALSE, 0);
1441 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1442 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1444 probe = gtk_frame_new("Run statistics");
1445 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1446 probe_frame = gtk_vbox_new(FALSE, 3);
1447 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1449 probe_box = gtk_hbox_new(FALSE, 3);
1450 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1451 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1452 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1453 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1454 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1455 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1458 * Only add this if we have a commit rate
1461 probe_box = gtk_hbox_new(FALSE, 3);
1462 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1464 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1465 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1467 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1468 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1472 * Set up a drawing area and IOPS and bandwidth graphs
1474 ui->graphs.drawing_area = gtk_drawing_area_new();
1475 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1476 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1477 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1478 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1479 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1480 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1481 G_CALLBACK(on_config_drawing_area), &ui->graphs);
1482 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1483 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1484 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1485 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1486 ui->graphs.drawing_area);
1487 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1490 setup_graphs(&ui->graphs);
1493 * Set up alignments for widgets at the bottom of ui,
1494 * align bottom left, expand horizontally but not vertically
1496 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1497 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1498 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1499 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1502 * Set up thread status progress bar
1504 ui->thread_status_pb = gtk_progress_bar_new();
1505 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1506 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1507 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1512 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1513 guint page, gpointer data)
1516 struct gui *ui = (struct gui *) data;
1517 struct gui_entry *ge;
1520 set_job_menu_visible(ui, 0);
1521 set_view_results_visible(ui, 0);
1525 set_job_menu_visible(ui, 1);
1526 ge = get_ge_from_page(ui, page, NULL);
1528 update_button_states(ui, ge);
1533 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1535 time_t time_a = gtk_recent_info_get_visited(a);
1536 time_t time_b = gtk_recent_info_get_visited(b);
1538 return time_b - time_a;
1541 static void add_recent_file_items(struct gui *ui)
1543 const gchar *gfio = g_get_application_name();
1544 GList *items, *item;
1547 if (ui->recent_ui_id) {
1548 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1549 gtk_ui_manager_ensure_update(ui->uimanager);
1551 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1553 if (ui->actiongroup) {
1554 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1555 g_object_unref(ui->actiongroup);
1557 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1559 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1561 items = gtk_recent_manager_get_items(ui->recentmanager);
1562 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1564 for (item = items; item && item->data; item = g_list_next(item)) {
1565 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1570 if (!gtk_recent_info_has_application(info, gfio))
1574 * We only support local files for now
1576 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1579 action_name = g_strdup_printf("RecentFile%u", i++);
1580 label = gtk_recent_info_get_display_name(info);
1582 action = g_object_new(GTK_TYPE_ACTION,
1583 "name", action_name,
1584 "label", label, NULL);
1586 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1587 gtk_recent_info_ref(info),
1588 (GDestroyNotify) gtk_recent_info_unref);
1591 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1593 gtk_action_group_add_action(ui->actiongroup, action);
1594 g_object_unref(action);
1596 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1597 "/MainMenu/FileMenu/FileRecentFiles",
1599 GTK_UI_MANAGER_MENUITEM, FALSE);
1601 g_free(action_name);
1607 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1611 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1612 gint x, gint y, GtkSelectionData *seldata,
1613 guint info, guint time, gpointer *data)
1615 struct gui *ui = (struct gui *) data;
1619 source = gtk_drag_get_source_widget(ctx);
1620 if (source && widget == gtk_widget_get_toplevel(source)) {
1621 gtk_drag_finish(ctx, FALSE, FALSE, time);
1625 uris = gtk_selection_data_get_uris(seldata);
1627 gtk_drag_finish(ctx, FALSE, FALSE, time);
1632 do_file_open_with_tab(ui, uris[0]);
1634 gtk_drag_finish(ctx, TRUE, FALSE, time);
1638 static void init_ui(int *argc, char **argv[], struct gui *ui)
1640 GtkSettings *settings;
1643 /* Magical g*thread incantation, you just need this thread stuff.
1644 * Without it, the update that happens in gfio_update_thread_status
1645 * doesn't really happen in a timely fashion, you need expose events
1647 if (!g_thread_supported())
1648 g_thread_init(NULL);
1651 gtk_init(argc, argv);
1652 settings = gtk_settings_get_default();
1653 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1655 gdk_color_parse("white", &gfio_color_white);
1657 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1658 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1659 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1661 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1662 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1664 ui->vbox = gtk_vbox_new(FALSE, 0);
1665 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1667 ui->uimanager = gtk_ui_manager_new();
1668 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1669 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1671 ui->recentmanager = gtk_recent_manager_get_default();
1672 add_recent_file_items(ui);
1674 ui->notebook = gtk_notebook_new();
1675 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1676 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1677 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1678 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1680 vbox = new_main_page(ui);
1681 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1682 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1683 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1685 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1687 gfio_ui_setup_log(ui);
1689 gtk_widget_show_all(ui->window);
1692 int main(int argc, char *argv[], char *envp[])
1694 if (initialize_fio(envp))
1696 if (fio_init_options())
1699 memset(&main_ui, 0, sizeof(main_ui));
1700 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1702 init_ui(&argc, &argv, &main_ui);
1704 gdk_threads_enter();
1706 gdk_threads_leave();
1708 g_hash_table_destroy(main_ui.ge_hash);