* gfio - gui front end for fio - the flexible io tester
*
* Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
+ * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
*
* The license below covers all files distributed with fio unless otherwise
* noted in the file itself.
#include <malloc.h>
#include <glib.h>
+#include <cairo.h>
#include <gtk/gtk.h>
#include "fio.h"
+#include "graph.h"
+
+static int gfio_server_running;
static void gfio_update_thread_status(char *status_message, double perc);
GtkWidget *buttonbox;
GtkWidget *button[ARRAYSIZE(buttonspeclist)];
GtkWidget *scrolled_window;
- GtkWidget *textview;
+#define DRAWING_AREA_XDIM 1000
+#define DRAWING_AREA_YDIM 400
+ GtkWidget *drawing_area;
GtkWidget *error_info_bar;
GtkWidget *error_label;
GtkWidget *results_notebook;
struct eta_widget eta;
int connected;
pthread_t t;
+ pthread_t server_t;
+ struct graph *iops_graph;
+ struct graph *bandwidth_graph;
struct fio_client *client;
int nr_job_files;
char **job_files;
GtkWidget *disk_util_frame;
};
+static void setup_iops_graph(struct gui *ui)
+{
+ if (ui->iops_graph)
+ graph_free(ui->iops_graph);
+ ui->iops_graph = graph_new(DRAWING_AREA_XDIM / 2.0,
+ DRAWING_AREA_YDIM);
+ graph_title(ui->iops_graph, "IOPS");
+ graph_x_title(ui->iops_graph, "Time");
+ graph_y_title(ui->iops_graph, "IOPS");
+ graph_add_label(ui->iops_graph, "Read IOPS");
+ graph_add_label(ui->iops_graph, "Write IOPS");
+ graph_set_color(ui->iops_graph, "Read IOPS", 0.7, 0.0, 0.0);
+ graph_set_color(ui->iops_graph, "Write IOPS", 0.0, 0.0, 0.7);
+}
+
+static void setup_bandwidth_graph(struct gui *ui)
+{
+ if (ui->bandwidth_graph)
+ graph_free(ui->bandwidth_graph);
+ ui->bandwidth_graph = graph_new(DRAWING_AREA_XDIM / 2.0,
+ DRAWING_AREA_YDIM);
+ graph_title(ui->bandwidth_graph, "Bandwidth");
+ graph_x_title(ui->bandwidth_graph, "Time");
+ graph_y_title(ui->bandwidth_graph, "Bandwidth");
+ graph_add_label(ui->bandwidth_graph, "Read Bandwidth");
+ graph_add_label(ui->bandwidth_graph, "Write Bandwidth");
+ graph_set_color(ui->bandwidth_graph, "Read Bandwidth", 0.7, 0.0, 0.0);
+ graph_set_color(ui->bandwidth_graph, "Write Bandwidth", 0.0, 0.0, 0.7);
+}
+
static void clear_ui_info(struct gui *ui)
{
gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
ui->connected = 0;
gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
+ gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 1);
}
}
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
+ g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+ "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
gtk_widget_set_can_focus(tree_view, FALSE);
+ g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+ "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
gtk_widget_set_can_focus(tree_view, FALSE);
+ g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+ "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
gtk_widget_set_can_focus(tree_view, FALSE);
+ g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
+ "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
+
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
gdk_threads_leave();
}
+static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
+{
+ struct gui *ui = (struct gui *) p;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create(w->window);
+
+ cairo_set_source_rgb(cr, 0, 0, 0);
+
+ cairo_save(cr);
+ cairo_translate(cr, 0, 0);
+ line_graph_draw(ui->bandwidth_graph, cr);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ cairo_save(cr);
+ cairo_translate(cr, DRAWING_AREA_XDIM / 2.0, 0);
+ // DRAWING_AREA_YDIM * 0.05);
+ line_graph_draw(ui->iops_graph, cr);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ cairo_destroy(cr);
+
+ return FALSE;
+}
+
static void gfio_update_eta(struct jobs_eta *je)
{
static int eta_good;
gtk_entry_set_text(GTK_ENTRY(ui.eta.write_bw), rate_str[1]);
gtk_entry_set_text(GTK_ENTRY(ui.eta.write_iops), iops_str[1]);
+ graph_add_xy_data(ui.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0]);
+ graph_add_xy_data(ui.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1]);
+ graph_add_xy_data(ui.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0]);
+ graph_add_xy_data(ui.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1]);
+
free(rate_str[0]);
free(rate_str[1]);
free(iops_str[0]);
}
}
+static void *server_thread(void *arg)
+{
+ is_backend = 1;
+ gfio_server_running = 1;
+ fio_start_server(NULL);
+ gfio_server_running = 0;
+ return NULL;
+}
+
+static void gfio_start_server(struct gui *ui)
+{
+ if (!gfio_server_running) {
+ gfio_server_running = 1;
+ pthread_create(&ui->server_t, NULL, server_thread, NULL);
+ pthread_detach(ui->server_t);
+ }
+}
+
static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
gpointer data)
{
if (!ui->nr_job_files)
file_open(widget, data);
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
- fio_clients_connect();
- pthread_create(&ui->t, NULL, job_thread, NULL);
- gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 0);
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
+ if (!fio_clients_connect()) {
+ pthread_create(&ui->t, NULL, job_thread, NULL);
+ gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 0);
+ }
} else {
fio_clients_terminate();
gfio_set_connected(ui, 0);
}
}
+struct connection_widgets
+{
+ GtkWidget *hentry;
+ GtkWidget *combo;
+ GtkWidget *button;
+};
+
+static void hostname_cb(GtkEntry *entry, gpointer data)
+{
+ struct connection_widgets *cw = data;
+ int uses_net = 0, is_localhost = 0;
+ const gchar *text;
+ gchar *ctext;
+
+ /*
+ * Check whether to display the 'auto start backend' box
+ * or not. Show it if we are a localhost and using network,
+ * or using a socket.
+ */
+ ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
+ if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
+ uses_net = 1;
+ g_free(ctext);
+
+ if (uses_net) {
+ text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
+ if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
+ !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
+ !strcmp(text, "ip6-loopback"))
+ is_localhost = 1;
+ }
+
+ if (!uses_net || is_localhost) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
+ gtk_widget_set_sensitive(cw->button, 1);
+ } else {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
+ gtk_widget_set_sensitive(cw->button, 0);
+ }
+}
+
static int get_connection_details(char **host, int *port, int *type,
int *server_start)
{
- GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
- GtkWidget *button;
+ GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
+ struct connection_widgets cw;
char *typeentry;
dialog = gtk_dialog_new_with_buttons("Connection details",
hbox = gtk_hbox_new(TRUE, 10);
gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
- hentry = gtk_entry_new();
- gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
- gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
+ cw.hentry = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
+ gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
frame = gtk_frame_new("Port");
gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
hbox = gtk_hbox_new(TRUE, 4);
gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
- combo = gtk_combo_box_new_text();
- gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "IPv4");
- gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "IPv6");
- gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "local socket");
- gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
+ cw.combo = gtk_combo_box_new_text();
+ gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
+ gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
- gtk_container_add(GTK_CONTAINER(hbox), combo);
+ gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
frame = gtk_frame_new("Options");
gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
hbox = gtk_hbox_new(TRUE, 4);
gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
- button = gtk_check_button_new_with_label("Auto-spawn fio backend");
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
- gtk_widget_set_tooltip_text(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.");
- gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
+ cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
+ 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.");
+ gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
+
+ /*
+ * Connect edit signal, so we can show/not-show the auto start button
+ */
+ g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
+ g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
gtk_widget_show_all(dialog);
return 1;
}
- *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
+ *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
*port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
- typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo));
+ typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
if (!typeentry || !strncmp(typeentry, "IPv4", 4))
*type = Fio_client_ipv4;
else if (!strncmp(typeentry, "IPv6", 4))
*type = Fio_client_socket;
g_free(typeentry);
- *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
+ *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
gtk_widget_destroy(dialog);
return 0;
static void file_open(GtkWidget *w, gpointer data)
{
GtkWidget *dialog;
+ struct gui *ui = data;
GSList *filenames, *fn_glist;
GtkFileFilter *filter;
char *host;
int port, type, server_start;
dialog = gtk_file_chooser_dialog_new("Open File",
- GTK_WINDOW(ui.window),
+ GTK_WINDOW(ui->window),
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
filter = gtk_file_filter_new();
gtk_file_filter_add_pattern(filter, "*.fio");
gtk_file_filter_add_pattern(filter, "*.job");
+ gtk_file_filter_add_pattern(filter, "*.ini");
gtk_file_filter_add_mime_type(filter, "text/fio");
gtk_file_filter_set_name(filter, "Fio job file");
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
while (filenames != NULL) {
struct fio_client *client;
- ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
- ui.job_files[ui.nr_job_files] = strdup(filenames->data);
- ui.nr_job_files++;
+ ui->job_files = realloc(ui->job_files, (ui->nr_job_files + 1) * sizeof(char *));
+ ui->job_files[ui->nr_job_files] = strdup(filenames->data);
+ ui->nr_job_files++;
client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
if (!client) {
report_error(error);
g_error_free(error);
}
- gfio_client_added(&ui, client);
+ gfio_client_added(ui, client);
g_free(filenames->data);
filenames = g_slist_next(filenames);
}
free(host);
+
+ if (server_start)
+ gfio_start_server(ui);
err:
g_slist_free(fn_glist);
}
static void file_save(GtkWidget *w, gpointer data)
{
+ struct gui *ui = data;
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new("Save File",
- GTK_WINDOW(ui.window),
+ GTK_WINDOW(ui->window),
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
#endif
/*
- * Add a text box for text op messages
+ * Set up a drawing area and IOPS and bandwidth graphs
*/
- ui->textview = gtk_text_view_new();
- ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
- gtk_text_buffer_set_text(ui->text, "", -1);
- gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
- gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
+ ui->drawing_area = gtk_drawing_area_new();
+ gtk_widget_set_size_request(GTK_WIDGET(ui->drawing_area),
+ DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
+ g_signal_connect(G_OBJECT(ui->drawing_area), "expose_event",
+ G_CALLBACK (on_expose_drawing_area), ui);
ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
- gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ui->scrolled_window),
+ ui->drawing_area);
gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
TRUE, TRUE, 0);
+ setup_iops_graph(ui);
+ setup_bandwidth_graph(ui);
+
/*
* Set up alignments for widgets at the bottom of ui,
* align bottom left, expand horizontally but not vertically