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 GdkColor gfio_color_lightyellow;
45 const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
47 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
49 static void connect_clicked(GtkWidget *widget, gpointer data);
50 static void start_job_clicked(GtkWidget *widget, gpointer data);
51 static void send_clicked(GtkWidget *widget, gpointer data);
53 static struct button_spec {
54 const char *buttontext;
56 const char *tooltiptext[2];
57 const int start_sensitive;
58 } buttonspeclist[] = {
60 .buttontext = "Connect",
62 .tooltiptext = { "Disconnect from host", "Connect to host" },
68 .tooltiptext = { "Send job description to host", NULL },
72 .buttontext = "Start Job",
73 .f = start_job_clicked,
74 .tooltiptext = { "Start the current job on the server", NULL },
79 static void setup_iops_graph(struct gfio_graphs *gg)
83 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
84 graph_title(g, "IOPS (IOs/sec)");
85 graph_x_title(g, "Time (secs)");
86 gg->read_iops = graph_add_label(g, "Read IOPS");
87 gg->write_iops = graph_add_label(g, "Write IOPS");
88 gg->trim_iops = graph_add_label(g, "Trim IOPS");
89 graph_set_color(g, gg->read_iops, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
90 graph_set_color(g, gg->write_iops, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
91 graph_set_color(g, gg->trim_iops, GFIO_IOPS_R, GFIO_IOPS_G, GFIO_IOPS_B);
92 line_graph_set_data_count_limit(g, gfio_graph_limit);
93 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
94 graph_set_graph_all_zeroes(g, 0);
98 static void setup_bandwidth_graph(struct gfio_graphs *gg)
102 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
103 graph_title(g, "Bandwidth (bytes/sec)");
104 graph_x_title(g, "Time (secs)");
105 gg->read_bw = graph_add_label(g, "Read Bandwidth");
106 gg->write_bw = graph_add_label(g, "Write Bandwidth");
107 gg->trim_bw = graph_add_label(g, "Trim Bandwidth");
108 graph_set_color(g, gg->read_bw, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
109 graph_set_color(g, gg->write_bw, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
110 graph_set_color(g, gg->trim_bw, GFIO_IOPS_R, GFIO_IOPS_G, GFIO_IOPS_B);
111 graph_set_base_offset(g, 1);
112 line_graph_set_data_count_limit(g, 100);
113 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
114 graph_set_graph_all_zeroes(g, 0);
115 gg->bandwidth_graph = g;
118 static void setup_graphs(struct gfio_graphs *g)
121 setup_bandwidth_graph(g);
124 void clear_ge_ui_info(struct gui_entry *ge)
126 gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
127 gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
128 gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
129 gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
131 /* should we empty it... */
132 gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
134 multitext_update_entry(&ge->eta.iotype, 0, "");
135 multitext_update_entry(&ge->eta.bs, 0, "");
136 multitext_update_entry(&ge->eta.ioengine, 0, "");
137 multitext_update_entry(&ge->eta.iodepth, 0, "");
138 gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
139 gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
140 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
141 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
142 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
143 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
146 static void set_menu_entry_text(struct gui *ui, const char *path,
151 w = gtk_ui_manager_get_widget(ui->uimanager, path);
153 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
155 fprintf(stderr, "gfio: can't find path %s\n", path);
159 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
163 w = gtk_ui_manager_get_widget(ui->uimanager, path);
165 gtk_widget_set_sensitive(w, show);
167 fprintf(stderr, "gfio: can't find path %s\n", path);
170 static void set_job_menu_visible(struct gui *ui, int visible)
172 set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
175 static void set_view_results_visible(struct gui *ui, int visible)
177 set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
180 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
182 if (s->tooltiptext[sensitive])
183 return s->tooltiptext[sensitive];
185 return s->tooltiptext[0];
188 static GtkWidget *add_button(GtkWidget *buttonbox,
189 struct button_spec *buttonspec, gpointer data)
191 GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
192 gboolean sens = buttonspec->start_sensitive;
194 g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
195 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
197 sens = buttonspec->start_sensitive;
198 gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
199 gtk_widget_set_sensitive(button, sens);
204 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
209 for (i = 0; i < nbuttons; i++)
210 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
214 * Update sensitivity of job buttons and job menu items, based on the
215 * state of the client.
217 static void update_button_states(struct gui *ui, struct gui_entry *ge)
219 unsigned int connect_state, send_state, start_state, edit_state;
220 const char *connect_str = NULL;
224 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
225 /* fall through to new state */
229 connect_str = "Connect";
233 case GE_STATE_CONNECTED:
236 connect_str = "Disconnect";
240 case GE_STATE_JOB_SENT:
243 connect_str = "Disconnect";
247 case GE_STATE_JOB_STARTED:
250 connect_str = "Disconnect";
254 case GE_STATE_JOB_RUNNING:
257 connect_str = "Disconnect";
261 case GE_STATE_JOB_DONE:
264 connect_str = "Connect";
270 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
271 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
272 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
273 gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
274 gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
276 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
277 set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
279 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
280 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
281 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
283 if (ge->client && ge->client->nr_results)
284 set_view_results_visible(ui, 1);
286 set_view_results_visible(ui, 0);
289 void gfio_set_state(struct gui_entry *ge, unsigned int state)
292 update_button_states(ge->ui, ge);
295 static void gfio_ui_setup_log(struct gui *ui)
297 GtkTreeSelection *selection;
299 GtkWidget *tree_view;
301 model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
303 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
304 gtk_widget_set_can_focus(tree_view, FALSE);
306 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
307 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
308 g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
309 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
311 tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
312 tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
313 tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
314 tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
316 ui->log_model = model;
317 ui->log_tree = tree_view;
320 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
323 guint width = gtk_widget_get_allocated_width(w);
324 guint height = gtk_widget_get_allocated_height(w);
325 struct gfio_graphs *g = data;
327 graph_set_size(g->iops_graph, width / 2.0, height);
328 graph_set_position(g->iops_graph, width / 2.0, 0.0);
329 graph_set_size(g->bandwidth_graph, width / 2.0, height);
330 graph_set_position(g->bandwidth_graph, 0, 0);
334 static void draw_graph(struct graph *g, cairo_t *cr)
336 line_graph_draw(g, cr);
340 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
341 gboolean keyboard_mode, GtkTooltip *tooltip,
344 struct gfio_graphs *g = data;
345 const char *text = NULL;
347 if (graph_contains_xy(g->iops_graph, x, y))
348 text = graph_find_tooltip(g->iops_graph, x, y);
349 else if (graph_contains_xy(g->bandwidth_graph, x, y))
350 text = graph_find_tooltip(g->bandwidth_graph, x, y);
353 gtk_tooltip_set_text(tooltip, text);
360 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
362 struct gfio_graphs *g = p;
365 cr = gdk_cairo_create(gtk_widget_get_window(w));
367 if (graph_has_tooltips(g->iops_graph) ||
368 graph_has_tooltips(g->bandwidth_graph)) {
369 g_object_set(w, "has-tooltip", TRUE, NULL);
370 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
373 cairo_set_source_rgb(cr, 0, 0, 0);
374 draw_graph(g->iops_graph, cr);
375 draw_graph(g->bandwidth_graph, cr);
382 * FIXME: need more handling here
384 static void ge_destroy(struct gui_entry *ge)
386 struct gfio_client *gc = ge->client;
390 if (ge->state >= GE_STATE_CONNECTED)
391 fio_client_terminate(gc->client);
393 fio_put_client(gc->client);
398 g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
405 static void ge_widget_destroy(GtkWidget *w, gpointer data)
407 struct gui_entry *ge = (struct gui_entry *) data;
412 static void gfio_quit(struct gui *ui)
417 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
420 struct gui *ui = (struct gui *) data;
425 static void *job_thread(void *arg)
427 struct gui *ui = arg;
429 ui->handler_running = 1;
430 fio_handle_clients(&gfio_client_ops);
431 ui->handler_running = 0;
435 static int send_job_file(struct gui_entry *ge)
437 struct gfio_client *gc = ge->client;
441 * Prune old options, we are expecting the return options
442 * when the job file is parsed remotely and returned to us.
444 while (!flist_empty(&gc->o_list)) {
445 struct gfio_client_options *gco;
447 gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
448 flist_del(&gco->list);
452 ret = fio_client_send_ini(gc->client, ge->job_file);
456 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
460 static void *server_thread(void *arg)
463 gfio_server_running = 1;
464 fio_start_server(NULL);
465 gfio_server_running = 0;
469 static void gfio_start_server(struct gui *ui)
471 if (!gfio_server_running) {
472 gfio_server_running = 1;
473 pthread_create(&ui->server_t, NULL, server_thread, NULL);
474 pthread_detach(ui->server_t);
478 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
481 struct gui_entry *ge = data;
482 struct gfio_client *gc = ge->client;
485 fio_start_client(gc->client);
488 static void file_open(GtkWidget *w, gpointer data);
490 struct connection_widgets
497 static void hostname_cb(GtkEntry *entry, gpointer data)
499 struct connection_widgets *cw = data;
500 int uses_net = 0, is_localhost = 0;
505 * Check whether to display the 'auto start backend' box
506 * or not. Show it if we are a localhost and using network,
509 ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
510 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
515 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
516 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
517 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
518 !strcmp(text, "ip6-loopback"))
522 if (!uses_net || is_localhost) {
523 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
524 gtk_widget_set_sensitive(cw->button, 1);
526 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
527 gtk_widget_set_sensitive(cw->button, 0);
531 static int get_connection_details(struct gui_entry *ge)
533 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
534 struct connection_widgets cw;
535 struct gui *ui = ge->ui;
541 dialog = gtk_dialog_new_with_buttons("Connection details",
542 GTK_WINDOW(ui->window),
543 GTK_DIALOG_DESTROY_WITH_PARENT,
544 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
545 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
547 frame = gtk_frame_new("Hostname / socket name");
548 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
549 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
551 box = gtk_vbox_new(FALSE, 6);
552 gtk_container_add(GTK_CONTAINER(frame), box);
554 hbox = gtk_hbox_new(TRUE, 10);
555 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
556 cw.hentry = gtk_entry_new();
557 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
558 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
560 frame = gtk_frame_new("Port");
561 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
562 box = gtk_vbox_new(FALSE, 10);
563 gtk_container_add(GTK_CONTAINER(frame), box);
565 hbox = gtk_hbox_new(TRUE, 4);
566 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
567 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
569 frame = gtk_frame_new("Type");
570 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
571 box = gtk_vbox_new(FALSE, 10);
572 gtk_container_add(GTK_CONTAINER(frame), box);
574 hbox = gtk_hbox_new(TRUE, 4);
575 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
577 cw.combo = gtk_combo_box_text_new();
578 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
579 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
580 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
581 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
583 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
585 frame = gtk_frame_new("Options");
586 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
587 box = gtk_vbox_new(FALSE, 10);
588 gtk_container_add(GTK_CONTAINER(frame), box);
590 hbox = gtk_hbox_new(TRUE, 4);
591 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
593 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
594 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
595 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.");
596 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
599 * Connect edit signal, so we can show/not-show the auto start button
601 g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
602 g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
604 gtk_widget_show_all(dialog);
606 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
607 gtk_widget_destroy(dialog);
611 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
612 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
614 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
615 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
616 ge->type = Fio_client_ipv4;
617 else if (!strncmp(typeentry, "IPv6", 4))
618 ge->type = Fio_client_ipv6;
620 ge->type = Fio_client_socket;
623 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
625 gtk_widget_destroy(dialog);
629 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
631 gc->client = fio_get_client(client);
632 client->client_data = gc;
635 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
637 struct gfio_client_options *gco;
638 struct gfio_client *gc;
640 gc = calloc(1, sizeof(*gc));
641 INIT_FLIST_HEAD(&gc->o_list);
644 gfio_set_client(gc, client);
647 * Just add a default set of options, need to consider how best
650 gco = calloc(1, sizeof(*gco));
651 INIT_FLIST_HEAD(&gco->list);
652 options_default_fill(&gco->o);
653 flist_add_tail(&gco->list, &gc->o_list);
657 static void connect_clicked(GtkWidget *widget, gpointer data)
659 struct gui_entry *ge = data;
660 struct gfio_client *gc = ge->client;
662 if (ge->state == GE_STATE_NEW) {
666 file_open(widget, ge->ui);
673 struct fio_client *client;
675 if (get_connection_details(ge)) {
676 gfio_report_error(ge, "Failed to get connection details\n");
680 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
682 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
687 gfio_set_client(gc, client);
690 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
691 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
692 ret = fio_client_connect(gc->client);
694 if (!ge->ui->handler_running)
695 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
696 gfio_set_state(ge, GE_STATE_CONNECTED);
698 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
701 fio_client_terminate(gc->client);
702 gfio_set_state(ge, GE_STATE_NEW);
703 clear_ge_ui_info(ge);
707 static void send_clicked(GtkWidget *widget, gpointer data)
709 struct gui_entry *ge = data;
711 if (send_job_file(ge))
712 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
715 static GtkWidget *new_client_page(struct gui_entry *ge);
717 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
719 struct gui_entry *ge;
721 ge = malloc(sizeof(*ge));
722 memset(ge, 0, sizeof(*ge));
723 ge->state = GE_STATE_NEW;
728 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
730 struct gui_entry *ge;
732 ge = alloc_new_gui_entry(ui);
734 ge->vbox = new_client_page(ge);
735 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
737 ge->page_label = gtk_label_new(name);
738 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
740 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
742 gtk_widget_show_all(ui->window);
746 static void file_new(GtkWidget *w, gpointer data)
748 struct gui *ui = (struct gui *) data;
749 struct gui_entry *ge;
751 ge = get_new_ge_with_tab(ui, "Untitled");
752 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
756 * Return the 'ge' corresponding to the tab. If the active tab is the
757 * main tab, open a new tab.
759 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
765 return get_new_ge_with_tab(ui, "Untitled");
771 return g_hash_table_lookup(ui->ge_hash, &cur_page);
774 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
779 * Main tab is tab 0, so any current page other than 0 holds
782 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
784 return get_ge_from_page(ui, cur_page, NULL);
789 static void file_close(GtkWidget *w, gpointer data)
791 struct gui *ui = (struct gui *) data;
792 struct gui_entry *ge;
795 * Can't close the main tab
797 ge = get_ge_from_cur_tab(ui);
799 gtk_widget_destroy(ge->vbox);
803 if (g_hash_table_size(ui->ge_hash)) {
804 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
811 static void file_add_recent(struct gui *ui, const gchar *uri)
815 memset(&grd, 0, sizeof(grd));
816 grd.display_name = strdup("gfio");
817 grd.description = strdup("Fio job file");
818 grd.mime_type = strdup(GFIO_MIME);
819 grd.app_name = strdup(g_get_application_name());
820 grd.app_exec = strdup("gfio %f/%u");
822 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
825 static gchar *get_filename_from_uri(const gchar *uri)
827 if (strncmp(uri, "file://", 7))
830 return strdup(uri + 7);
833 static int do_file_open(struct gui_entry *ge, const gchar *uri)
835 struct fio_client *client;
837 assert(!ge->job_file);
839 ge->job_file = get_filename_from_uri(uri);
841 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
843 char *label = strdup(uri);
846 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
849 gfio_client_added(ge, client);
850 file_add_recent(ge->ui, uri);
854 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
862 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
864 struct gui_entry *ge;
866 int ret, ge_is_new = 0;
869 * Creates new tab if current tab is the main window, or the
870 * current tab already has a client.
872 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
873 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
875 ge = get_new_ge_with_tab(ui, "Untitled");
879 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
881 if (get_connection_details(ge)) {
883 gtk_widget_destroy(ge->vbox);
888 ret = do_file_open(ge, uri);
891 if (ge->server_start)
892 gfio_start_server(ui);
895 gtk_widget_destroy(ge->vbox);
901 static void recent_open(GtkAction *action, gpointer data)
903 struct gui *ui = (struct gui *) data;
907 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
908 uri = gtk_recent_info_get_uri(info);
910 do_file_open_with_tab(ui, uri);
913 static void file_open(GtkWidget *w, gpointer data)
915 struct gui *ui = data;
917 GtkFileFilter *filter;
920 dialog = gtk_file_chooser_dialog_new("Open File",
921 GTK_WINDOW(ui->window),
922 GTK_FILE_CHOOSER_ACTION_OPEN,
923 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
924 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
926 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
928 filter = gtk_file_filter_new();
929 gtk_file_filter_add_pattern(filter, "*.fio");
930 gtk_file_filter_add_pattern(filter, "*.job");
931 gtk_file_filter_add_pattern(filter, "*.ini");
932 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
933 gtk_file_filter_set_name(filter, "Fio job file");
934 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
936 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
937 gtk_widget_destroy(dialog);
941 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
943 gtk_widget_destroy(dialog);
945 do_file_open_with_tab(ui, filename);
949 static void file_save(GtkWidget *w, gpointer data)
951 struct gui *ui = data;
954 dialog = gtk_file_chooser_dialog_new("Save File",
955 GTK_WINDOW(ui->window),
956 GTK_FILE_CHOOSER_ACTION_SAVE,
957 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
958 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
961 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
962 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
964 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
967 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
968 // save_job_file(filename);
971 gtk_widget_destroy(dialog);
974 static void view_log_destroy(GtkWidget *w, gpointer data)
976 struct gui *ui = (struct gui *) data;
978 g_object_ref(G_OBJECT(ui->log_tree));
979 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
980 gtk_widget_destroy(w);
984 void gfio_view_log(struct gui *ui)
986 GtkWidget *win, *scroll, *vbox, *box;
991 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
992 gtk_window_set_title(GTK_WINDOW(win), "Log");
993 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
995 scroll = gtk_scrolled_window_new(NULL, NULL);
997 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
999 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1001 box = gtk_hbox_new(TRUE, 0);
1002 gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
1003 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
1004 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
1006 vbox = gtk_vbox_new(TRUE, 5);
1007 gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
1009 gtk_container_add(GTK_CONTAINER(win), vbox);
1010 gtk_widget_show_all(win);
1013 static void view_log(GtkWidget *w, gpointer data)
1015 struct gui *ui = (struct gui *) data;
1020 static void connect_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);
1027 connect_clicked(w, ge);
1030 static void send_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 send_clicked(w, ge);
1040 static void edit_job_entry(GtkWidget *w, gpointer data)
1042 struct gui *ui = (struct gui *) data;
1043 struct gui_entry *ge;
1045 ge = get_ge_from_cur_tab(ui);
1046 if (ge && ge->client)
1047 gopt_get_options_window(ui->window, ge->client);
1050 static void start_job_entry(GtkWidget *w, gpointer data)
1052 struct gui *ui = (struct gui *) data;
1053 struct gui_entry *ge;
1055 ge = get_ge_from_cur_tab(ui);
1057 start_job_clicked(w, ge);
1060 static void view_results(GtkWidget *w, gpointer data)
1062 struct gui *ui = (struct gui *) data;
1063 struct gfio_client *gc;
1064 struct gui_entry *ge;
1066 ge = get_ge_from_cur_tab(ui);
1070 if (ge->results_window)
1074 if (gc && gc->nr_results)
1075 gfio_display_end_results(gc);
1078 static void __update_graph_settings(struct gfio_graphs *g)
1080 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1081 graph_set_font(g->iops_graph, gfio_graph_font);
1082 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1083 graph_set_font(g->bandwidth_graph, gfio_graph_font);
1086 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1088 struct gui_entry *ge = (struct gui_entry *) value;
1091 __update_graph_settings(&ge->graphs);
1093 ev = gdk_event_new(GDK_EXPOSE);
1094 g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1098 static void update_graph_limits(void)
1100 struct gui *ui = &main_ui;
1103 __update_graph_settings(&ui->graphs);
1105 ev = gdk_event_new(GDK_EXPOSE);
1106 g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1109 g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1112 static void preferences(GtkWidget *w, gpointer data)
1114 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1115 GtkWidget *hbox, *spin, *entry, *spin_int;
1116 struct gui *ui = (struct gui *) data;
1119 dialog = gtk_dialog_new_with_buttons("Preferences",
1120 GTK_WINDOW(ui->window),
1121 GTK_DIALOG_DESTROY_WITH_PARENT,
1122 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1123 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1126 frame = gtk_frame_new("Graphing");
1127 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1128 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1129 vbox = gtk_vbox_new(FALSE, 6);
1130 gtk_container_add(GTK_CONTAINER(frame), vbox);
1132 hbox = gtk_hbox_new(FALSE, 5);
1133 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1134 entry = gtk_label_new("Font face to use for graph labels");
1135 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1137 font = gtk_font_button_new_with_font(gfio_graph_font);
1138 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1140 box = gtk_vbox_new(FALSE, 6);
1141 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1143 hbox = gtk_hbox_new(FALSE, 5);
1144 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1145 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1146 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1148 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1150 box = gtk_vbox_new(FALSE, 6);
1151 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1153 hbox = gtk_hbox_new(FALSE, 5);
1154 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1155 entry = gtk_label_new("Client ETA request interval (msec)");
1156 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1158 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1159 frame = gtk_frame_new("Debug logging");
1160 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1161 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1162 vbox = gtk_vbox_new(FALSE, 6);
1163 gtk_container_add(GTK_CONTAINER(frame), vbox);
1165 box = gtk_hbox_new(FALSE, 6);
1166 gtk_container_add(GTK_CONTAINER(vbox), box);
1168 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1170 for (i = 0; i < FD_DEBUG_MAX; i++) {
1172 box = gtk_hbox_new(FALSE, 6);
1173 gtk_container_add(GTK_CONTAINER(vbox), box);
1177 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1178 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1179 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1182 gtk_widget_show_all(dialog);
1184 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1185 gtk_widget_destroy(dialog);
1189 for (i = 0; i < FD_DEBUG_MAX; i++) {
1192 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1194 fio_debug |= (1UL << i);
1197 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1198 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1199 update_graph_limits();
1200 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1202 gtk_widget_destroy(dialog);
1205 static void about_dialog(GtkWidget *w, gpointer data)
1207 const char *authors[] = {
1208 "Jens Axboe <axboe@kernel.dk>",
1209 "Stephen Carmeron <stephenmcameron@gmail.com>",
1212 const char *license[] = {
1213 "Fio is free software; you can redistribute it and/or modify "
1214 "it under the terms of the GNU General Public License as published by "
1215 "the Free Software Foundation; either version 2 of the License, or "
1216 "(at your option) any later version.\n",
1217 "Fio is distributed in the hope that it will be useful, "
1218 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1219 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1220 "GNU General Public License for more details.\n",
1221 "You should have received a copy of the GNU General Public License "
1222 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1223 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1225 char *license_trans;
1227 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1228 license[2], "\n", NULL);
1230 gtk_show_about_dialog(NULL,
1231 "program-name", "gfio",
1232 "comments", "Gtk2 UI for fio",
1233 "license", license_trans,
1234 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1236 "version", fio_version_string,
1237 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1238 "logo-icon-name", "fio",
1240 "wrap-license", TRUE,
1243 g_free(license_trans);
1246 static GtkActionEntry menu_items[] = {
1247 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1248 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1249 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1250 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1251 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1252 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1253 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1254 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1255 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1256 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1257 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1258 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1259 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1260 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1261 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1262 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1263 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1265 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1267 static const gchar *ui_string = " \
1269 <menubar name=\"MainMenu\"> \
1270 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1271 <menuitem name=\"New\" action=\"NewFile\" /> \
1272 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1273 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1274 <separator name=\"Separator1\"/> \
1275 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1276 <separator name=\"Separator2\"/> \
1277 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1278 <separator name=\"Separator3\"/> \
1279 <placeholder name=\"FileRecentFiles\"/> \
1280 <separator name=\"Separator4\"/> \
1281 <menuitem name=\"Quit\" action=\"Quit\" /> \
1283 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1284 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1285 <separator name=\"Separator5\"/> \
1286 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1287 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1288 <separator name=\"Separator6\"/> \
1289 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1291 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1292 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1293 <separator name=\"Separator7\"/> \
1294 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1296 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1297 <menuitem name=\"About\" action=\"About\" /> \
1303 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1306 GtkActionGroup *action_group;
1309 action_group = gtk_action_group_new("Menu");
1310 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1312 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1313 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1315 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1317 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1320 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1321 GtkWidget *vbox, GtkUIManager *ui_manager)
1323 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1326 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1328 struct gui_entry *ge = (struct gui_entry *) data;
1331 index = gtk_combo_box_get_active(box);
1333 multitext_set_entry(&ge->eta.iotype, index);
1334 multitext_set_entry(&ge->eta.bs, index);
1335 multitext_set_entry(&ge->eta.ioengine, index);
1336 multitext_set_entry(&ge->eta.iodepth, index);
1339 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1341 struct gui_entry *ge = (struct gui_entry *) data;
1343 multitext_free(&ge->eta.iotype);
1344 multitext_free(&ge->eta.bs);
1345 multitext_free(&ge->eta.ioengine);
1346 multitext_free(&ge->eta.iodepth);
1349 static void fill_color_from_rgb(GdkColor *c, gfloat r, gfloat g, gfloat b)
1354 memset(c, 0, sizeof(*c));
1358 sprintf(tmp, "#%02x%02x%02x", R, G, B);
1359 gdk_color_parse(tmp, c);
1362 static GtkWidget *new_client_page(struct gui_entry *ge)
1364 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1365 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1368 main_vbox = gtk_vbox_new(FALSE, 3);
1370 top_align = gtk_alignment_new(0, 0, 1, 0);
1371 top_vbox = gtk_vbox_new(FALSE, 3);
1372 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1373 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1375 probe = gtk_frame_new("Job");
1376 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1377 probe_frame = gtk_vbox_new(FALSE, 3);
1378 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1380 probe_box = gtk_hbox_new(FALSE, 3);
1381 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1382 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1383 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1384 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1385 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1387 probe_box = gtk_hbox_new(FALSE, 3);
1388 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1390 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1391 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1392 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1393 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1394 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1395 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1396 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1397 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1398 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1400 probe_box = gtk_hbox_new(FALSE, 3);
1401 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1402 ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1403 ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1404 ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1405 ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1406 ge->eta.trim_bw = new_info_entry_in_frame(probe_box, "Trim BW");
1407 ge->eta.trim_iops = new_info_entry_in_frame(probe_box, "IOPS");
1409 fill_color_from_rgb(&color, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1410 gtk_widget_modify_text(ge->eta.read_bw, GTK_STATE_NORMAL, &color);
1411 gtk_widget_modify_text(ge->eta.read_iops, GTK_STATE_NORMAL, &color);
1413 fill_color_from_rgb(&color, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1414 gtk_widget_modify_text(ge->eta.write_bw, GTK_STATE_NORMAL, &color);
1415 gtk_widget_modify_text(ge->eta.write_iops, GTK_STATE_NORMAL, &color);
1417 fill_color_from_rgb(&color, GFIO_IOPS_R, GFIO_IOPS_G, GFIO_IOPS_B);
1418 gtk_widget_modify_text(ge->eta.trim_bw, GTK_STATE_NORMAL, &color);
1419 gtk_widget_modify_text(ge->eta.trim_iops, GTK_STATE_NORMAL, &color);
1422 * Only add this if we have a commit rate
1425 probe_box = gtk_hbox_new(FALSE, 3);
1426 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1428 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1429 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1431 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1432 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1436 * Set up a drawing area and IOPS and bandwidth graphs
1438 ge->graphs.drawing_area = gtk_drawing_area_new();
1439 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1440 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1441 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
1442 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
1443 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1444 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1445 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1446 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1447 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1448 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1449 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1450 ge->graphs.drawing_area);
1451 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1453 setup_graphs(&ge->graphs);
1456 * Set up alignments for widgets at the bottom of ui,
1457 * align bottom left, expand horizontally but not vertically
1459 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1460 ge->buttonbox = gtk_hbox_new(FALSE, 0);
1461 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1462 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1464 add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1467 * Set up thread status progress bar
1469 ge->thread_status_pb = gtk_progress_bar_new();
1470 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1471 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1472 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1478 static GtkWidget *new_main_page(struct gui *ui)
1480 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1481 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1483 main_vbox = gtk_vbox_new(FALSE, 3);
1486 * Set up alignments for widgets at the top of ui,
1487 * align top left, expand horizontally but not vertically
1489 top_align = gtk_alignment_new(0, 0, 1, 0);
1490 top_vbox = gtk_vbox_new(FALSE, 0);
1491 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1492 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1494 probe = gtk_frame_new("Run statistics");
1495 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1496 probe_frame = gtk_vbox_new(FALSE, 3);
1497 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1499 probe_box = gtk_hbox_new(FALSE, 3);
1500 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1501 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1502 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1503 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1504 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1505 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1506 ui->eta.trim_bw = new_info_entry_in_frame(probe_box, "Trim BW");
1507 ui->eta.trim_iops = new_info_entry_in_frame(probe_box, "IOPS");
1511 * Only add this if we have a commit rate
1514 probe_box = gtk_hbox_new(FALSE, 3);
1515 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1517 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1518 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1520 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1521 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1525 * Set up a drawing area and IOPS and bandwidth graphs
1527 ui->graphs.drawing_area = gtk_drawing_area_new();
1528 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1529 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1530 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
1531 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
1532 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1533 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1534 G_CALLBACK(on_config_drawing_area), &ui->graphs);
1535 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1536 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1537 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1538 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1539 ui->graphs.drawing_area);
1540 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1543 setup_graphs(&ui->graphs);
1546 * Set up alignments for widgets at the bottom of ui,
1547 * align bottom left, expand horizontally but not vertically
1549 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1550 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1551 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1552 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1555 * Set up thread status progress bar
1557 ui->thread_status_pb = gtk_progress_bar_new();
1558 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1559 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1560 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1565 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1566 guint page, gpointer data)
1569 struct gui *ui = (struct gui *) data;
1570 struct gui_entry *ge;
1573 set_job_menu_visible(ui, 0);
1574 set_view_results_visible(ui, 0);
1578 set_job_menu_visible(ui, 1);
1579 ge = get_ge_from_page(ui, page, NULL);
1581 update_button_states(ui, ge);
1586 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1588 time_t time_a = gtk_recent_info_get_visited(a);
1589 time_t time_b = gtk_recent_info_get_visited(b);
1591 return time_b - time_a;
1594 static void add_recent_file_items(struct gui *ui)
1596 const gchar *gfio = g_get_application_name();
1597 GList *items, *item;
1600 if (ui->recent_ui_id) {
1601 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1602 gtk_ui_manager_ensure_update(ui->uimanager);
1604 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1606 if (ui->actiongroup) {
1607 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1608 g_object_unref(ui->actiongroup);
1610 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1612 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1614 items = gtk_recent_manager_get_items(ui->recentmanager);
1615 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1617 for (item = items; item && item->data; item = g_list_next(item)) {
1618 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1623 if (!gtk_recent_info_has_application(info, gfio))
1627 * We only support local files for now
1629 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1632 action_name = g_strdup_printf("RecentFile%u", i++);
1633 label = gtk_recent_info_get_display_name(info);
1635 action = g_object_new(GTK_TYPE_ACTION,
1636 "name", action_name,
1637 "label", label, NULL);
1639 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1640 gtk_recent_info_ref(info),
1641 (GDestroyNotify) gtk_recent_info_unref);
1644 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1646 gtk_action_group_add_action(ui->actiongroup, action);
1647 g_object_unref(action);
1649 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1650 "/MainMenu/FileMenu/FileRecentFiles",
1652 GTK_UI_MANAGER_MENUITEM, FALSE);
1654 g_free(action_name);
1660 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1664 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1665 gint x, gint y, GtkSelectionData *seldata,
1666 guint info, guint time, gpointer *data)
1668 struct gui *ui = (struct gui *) data;
1672 source = gtk_drag_get_source_widget(ctx);
1673 if (source && widget == gtk_widget_get_toplevel(source)) {
1674 gtk_drag_finish(ctx, FALSE, FALSE, time);
1678 uris = gtk_selection_data_get_uris(seldata);
1680 gtk_drag_finish(ctx, FALSE, FALSE, time);
1685 do_file_open_with_tab(ui, uris[0]);
1687 gtk_drag_finish(ctx, TRUE, FALSE, time);
1691 static void init_ui(int *argc, char **argv[], struct gui *ui)
1693 GtkSettings *settings;
1696 /* Magical g*thread incantation, you just need this thread stuff.
1697 * Without it, the update that happens in gfio_update_thread_status
1698 * doesn't really happen in a timely fashion, you need expose events
1700 if (!g_thread_supported())
1701 g_thread_init(NULL);
1704 gtk_init(argc, argv);
1705 settings = gtk_settings_get_default();
1706 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1708 gdk_color_parse("#ffffee", &gfio_color_lightyellow);
1709 gdk_color_parse("white", &gfio_color_white);
1711 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1712 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1713 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1715 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1716 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1718 ui->vbox = gtk_vbox_new(FALSE, 0);
1719 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1721 ui->uimanager = gtk_ui_manager_new();
1722 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1723 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1725 ui->recentmanager = gtk_recent_manager_get_default();
1726 add_recent_file_items(ui);
1728 ui->notebook = gtk_notebook_new();
1729 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1730 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1731 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1732 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1734 vbox = new_main_page(ui);
1735 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1736 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1737 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1739 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1741 gfio_ui_setup_log(ui);
1743 gtk_widget_show_all(ui->window);
1746 int main(int argc, char *argv[], char *envp[])
1748 if (initialize_fio(envp))
1750 if (fio_init_options())
1755 memset(&main_ui, 0, sizeof(main_ui));
1756 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1758 init_ui(&argc, &argv, &main_ui);
1760 gdk_threads_enter();
1762 gdk_threads_leave();
1764 g_hash_table_destroy(main_ui.ge_hash);