Merge branch 'master' into gfio
[fio.git] / gfio.c
... / ...
CommitLineData
1/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
5 * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
6 *
7 * The license below covers all files distributed with fio unless otherwise
8 * noted in the file itself.
9 *
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.
13 *
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.
18 *
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
22 *
23 */
24#include <locale.h>
25#include <malloc.h>
26#include <string.h>
27
28#include <glib.h>
29#include <cairo.h>
30#include <gtk/gtk.h>
31
32#include "fio.h"
33#include "gfio.h"
34#include "ghelpers.h"
35#include "goptions.h"
36#include "gerror.h"
37#include "gclient.h"
38#include "graph.h"
39
40static int gfio_server_running;
41static unsigned int gfio_graph_limit = 100;
42
43GdkColor gfio_color_white;
44GdkColor gfio_color_lightyellow;
45const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
46
47typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
48
49static void connect_clicked(GtkWidget *widget, gpointer data);
50static void start_job_clicked(GtkWidget *widget, gpointer data);
51static void send_clicked(GtkWidget *widget, gpointer data);
52
53static struct button_spec {
54 const char *buttontext;
55 clickfunction f;
56 const char *tooltiptext[2];
57 const int start_sensitive;
58} buttonspeclist[] = {
59 {
60 .buttontext = "Connect",
61 .f = connect_clicked,
62 .tooltiptext = { "Disconnect from host", "Connect to host" },
63 .start_sensitive = 1,
64 },
65 {
66 .buttontext = "Send",
67 .f = send_clicked,
68 .tooltiptext = { "Send job description to host", NULL },
69 .start_sensitive = 0,
70 },
71 {
72 .buttontext = "Start Job",
73 .f = start_job_clicked,
74 .tooltiptext = { "Start the current job on the server", NULL },
75 .start_sensitive = 0,
76 },
77};
78
79static void setup_iops_graph(struct gfio_graphs *gg)
80{
81 struct graph *g;
82
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 graph_set_color(g, gg->read_iops, 0.13, 0.54, 0.13);
89 graph_set_color(g, gg->write_iops, 1.0, 0.0, 0.0);
90 line_graph_set_data_count_limit(g, gfio_graph_limit);
91 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
92 graph_set_graph_all_zeroes(g, 0);
93 gg->iops_graph = g;
94}
95
96static void setup_bandwidth_graph(struct gfio_graphs *gg)
97{
98 struct graph *g;
99
100 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
101 graph_title(g, "Bandwidth (bytes/sec)");
102 graph_x_title(g, "Time (secs)");
103 gg->read_bw = graph_add_label(g, "Read Bandwidth");
104 gg->write_bw = graph_add_label(g, "Write Bandwidth");
105 graph_set_color(g, gg->read_bw, 0.13, 0.54, 0.13);
106 graph_set_color(g, gg->write_bw, 1.0, 0.0, 0.0);
107 graph_set_base_offset(g, 1);
108 line_graph_set_data_count_limit(g, 100);
109 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
110 graph_set_graph_all_zeroes(g, 0);
111 gg->bandwidth_graph = g;
112}
113
114static void setup_graphs(struct gfio_graphs *g)
115{
116 setup_iops_graph(g);
117 setup_bandwidth_graph(g);
118}
119
120void clear_ge_ui_info(struct gui_entry *ge)
121{
122 gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
123 gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
124 gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
125 gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
126#if 0
127 /* should we empty it... */
128 gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
129#endif
130 multitext_update_entry(&ge->eta.iotype, 0, "");
131 multitext_update_entry(&ge->eta.bs, 0, "");
132 multitext_update_entry(&ge->eta.ioengine, 0, "");
133 multitext_update_entry(&ge->eta.iodepth, 0, "");
134 gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
135 gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
136 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
137 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
138 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
139 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
140}
141
142static void set_menu_entry_text(struct gui *ui, const char *path,
143 const char *text)
144{
145 GtkWidget *w;
146
147 w = gtk_ui_manager_get_widget(ui->uimanager, path);
148 if (w)
149 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
150 else
151 fprintf(stderr, "gfio: can't find path %s\n", path);
152}
153
154
155static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
156{
157 GtkWidget *w;
158
159 w = gtk_ui_manager_get_widget(ui->uimanager, path);
160 if (w)
161 gtk_widget_set_sensitive(w, show);
162 else
163 fprintf(stderr, "gfio: can't find path %s\n", path);
164}
165
166static void set_job_menu_visible(struct gui *ui, int visible)
167{
168 set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
169}
170
171static void set_view_results_visible(struct gui *ui, int visible)
172{
173 set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
174}
175
176static const char *get_button_tooltip(struct button_spec *s, int sensitive)
177{
178 if (s->tooltiptext[sensitive])
179 return s->tooltiptext[sensitive];
180
181 return s->tooltiptext[0];
182}
183
184static GtkWidget *add_button(GtkWidget *buttonbox,
185 struct button_spec *buttonspec, gpointer data)
186{
187 GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
188 gboolean sens = buttonspec->start_sensitive;
189
190 g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
191 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
192
193 sens = buttonspec->start_sensitive;
194 gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
195 gtk_widget_set_sensitive(button, sens);
196
197 return button;
198}
199
200static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
201 int nbuttons)
202{
203 int i;
204
205 for (i = 0; i < nbuttons; i++)
206 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
207}
208
209/*
210 * Update sensitivity of job buttons and job menu items, based on the
211 * state of the client.
212 */
213static void update_button_states(struct gui *ui, struct gui_entry *ge)
214{
215 unsigned int connect_state, send_state, start_state, edit_state;
216 const char *connect_str = NULL;
217
218 switch (ge->state) {
219 default:
220 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
221 /* fall through to new state */
222 case GE_STATE_NEW:
223 connect_state = 1;
224 edit_state = 1;
225 connect_str = "Connect";
226 send_state = 0;
227 start_state = 0;
228 break;
229 case GE_STATE_CONNECTED:
230 connect_state = 1;
231 edit_state = 1;
232 connect_str = "Disconnect";
233 send_state = 1;
234 start_state = 0;
235 break;
236 case GE_STATE_JOB_SENT:
237 connect_state = 1;
238 edit_state = 1;
239 connect_str = "Disconnect";
240 send_state = 0;
241 start_state = 1;
242 break;
243 case GE_STATE_JOB_STARTED:
244 connect_state = 1;
245 edit_state = 1;
246 connect_str = "Disconnect";
247 send_state = 0;
248 start_state = 1;
249 break;
250 case GE_STATE_JOB_RUNNING:
251 connect_state = 1;
252 edit_state = 0;
253 connect_str = "Disconnect";
254 send_state = 0;
255 start_state = 0;
256 break;
257 case GE_STATE_JOB_DONE:
258 connect_state = 1;
259 edit_state = 0;
260 connect_str = "Connect";
261 send_state = 0;
262 start_state = 0;
263 break;
264 }
265
266 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
267 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
268 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
269 gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
270 gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
271
272 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
273 set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
274
275 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
276 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
277 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
278
279 if (ge->client && ge->client->nr_results)
280 set_view_results_visible(ui, 1);
281 else
282 set_view_results_visible(ui, 0);
283}
284
285void gfio_set_state(struct gui_entry *ge, unsigned int state)
286{
287 ge->state = state;
288 update_button_states(ge->ui, ge);
289}
290
291static void gfio_ui_setup_log(struct gui *ui)
292{
293 GtkTreeSelection *selection;
294 GtkListStore *model;
295 GtkWidget *tree_view;
296
297 model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
298
299 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
300 gtk_widget_set_can_focus(tree_view, FALSE);
301
302 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
303 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
304 g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
305 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
306
307 tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
308 tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
309 tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
310 tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
311
312 ui->log_model = model;
313 ui->log_tree = tree_view;
314}
315
316static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
317 gpointer data)
318{
319 guint width = gtk_widget_get_allocated_width(w);
320 guint height = gtk_widget_get_allocated_height(w);
321 struct gfio_graphs *g = data;
322
323 graph_set_size(g->iops_graph, width / 2.0, height);
324 graph_set_position(g->iops_graph, width / 2.0, 0.0);
325 graph_set_size(g->bandwidth_graph, width / 2.0, height);
326 graph_set_position(g->bandwidth_graph, 0, 0);
327 return TRUE;
328}
329
330static void draw_graph(struct graph *g, cairo_t *cr)
331{
332 line_graph_draw(g, cr);
333 cairo_stroke(cr);
334}
335
336static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
337 gboolean keyboard_mode, GtkTooltip *tooltip,
338 gpointer data)
339{
340 struct gfio_graphs *g = data;
341 const char *text = NULL;
342
343 if (graph_contains_xy(g->iops_graph, x, y))
344 text = graph_find_tooltip(g->iops_graph, x, y);
345 else if (graph_contains_xy(g->bandwidth_graph, x, y))
346 text = graph_find_tooltip(g->bandwidth_graph, x, y);
347
348 if (text) {
349 gtk_tooltip_set_text(tooltip, text);
350 return TRUE;
351 }
352
353 return FALSE;
354}
355
356static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
357{
358 struct gfio_graphs *g = p;
359 cairo_t *cr;
360
361 cr = gdk_cairo_create(gtk_widget_get_window(w));
362
363 if (graph_has_tooltips(g->iops_graph) ||
364 graph_has_tooltips(g->bandwidth_graph)) {
365 g_object_set(w, "has-tooltip", TRUE, NULL);
366 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
367 }
368
369 cairo_set_source_rgb(cr, 0, 0, 0);
370 draw_graph(g->iops_graph, cr);
371 draw_graph(g->bandwidth_graph, cr);
372 cairo_destroy(cr);
373
374 return FALSE;
375}
376
377/*
378 * FIXME: need more handling here
379 */
380static void ge_destroy(struct gui_entry *ge)
381{
382 struct gfio_client *gc = ge->client;
383
384 if (gc) {
385 if (gc->client) {
386 if (ge->state >= GE_STATE_CONNECTED)
387 fio_client_terminate(gc->client);
388
389 fio_put_client(gc->client);
390 }
391 free(gc);
392 }
393
394 g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
395
396 free(ge->job_file);
397 free(ge->host);
398 free(ge);
399}
400
401static void ge_widget_destroy(GtkWidget *w, gpointer data)
402{
403 struct gui_entry *ge = (struct gui_entry *) data;
404
405 ge_destroy(ge);
406}
407
408static void gfio_quit(struct gui *ui)
409{
410 gtk_main_quit();
411}
412
413static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
414 gpointer data)
415{
416 struct gui *ui = (struct gui *) data;
417
418 gfio_quit(ui);
419}
420
421static void *job_thread(void *arg)
422{
423 struct gui *ui = arg;
424
425 ui->handler_running = 1;
426 fio_handle_clients(&gfio_client_ops);
427 ui->handler_running = 0;
428 return NULL;
429}
430
431static int send_job_file(struct gui_entry *ge)
432{
433 struct gfio_client *gc = ge->client;
434 int ret = 0;
435
436 /*
437 * Prune old options, we are expecting the return options
438 * when the job file is parsed remotely and returned to us.
439 */
440 while (!flist_empty(&gc->o_list)) {
441 struct gfio_client_options *gco;
442
443 gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
444 flist_del(&gco->list);
445 free(gco);
446 }
447
448 ret = fio_client_send_ini(gc->client, ge->job_file);
449 if (!ret)
450 return 0;
451
452 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
453 return 1;
454}
455
456static void *server_thread(void *arg)
457{
458 is_backend = 1;
459 gfio_server_running = 1;
460 fio_start_server(NULL);
461 gfio_server_running = 0;
462 return NULL;
463}
464
465static void gfio_start_server(struct gui *ui)
466{
467 if (!gfio_server_running) {
468 gfio_server_running = 1;
469 pthread_create(&ui->server_t, NULL, server_thread, NULL);
470 pthread_detach(ui->server_t);
471 }
472}
473
474static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
475 gpointer data)
476{
477 struct gui_entry *ge = data;
478 struct gfio_client *gc = ge->client;
479
480 if (gc)
481 fio_start_client(gc->client);
482}
483
484static void file_open(GtkWidget *w, gpointer data);
485
486struct connection_widgets
487{
488 GtkWidget *hentry;
489 GtkWidget *combo;
490 GtkWidget *button;
491};
492
493static void hostname_cb(GtkEntry *entry, gpointer data)
494{
495 struct connection_widgets *cw = data;
496 int uses_net = 0, is_localhost = 0;
497 const gchar *text;
498 gchar *ctext;
499
500 /*
501 * Check whether to display the 'auto start backend' box
502 * or not. Show it if we are a localhost and using network,
503 * or using a socket.
504 */
505 ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
506 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
507 uses_net = 1;
508 g_free(ctext);
509
510 if (uses_net) {
511 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
512 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
513 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
514 !strcmp(text, "ip6-loopback"))
515 is_localhost = 1;
516 }
517
518 if (!uses_net || is_localhost) {
519 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
520 gtk_widget_set_sensitive(cw->button, 1);
521 } else {
522 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
523 gtk_widget_set_sensitive(cw->button, 0);
524 }
525}
526
527static int get_connection_details(struct gui_entry *ge)
528{
529 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
530 struct connection_widgets cw;
531 struct gui *ui = ge->ui;
532 char *typeentry;
533
534 if (ge->host)
535 return 0;
536
537 dialog = gtk_dialog_new_with_buttons("Connection details",
538 GTK_WINDOW(ui->window),
539 GTK_DIALOG_DESTROY_WITH_PARENT,
540 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
541 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
542
543 frame = gtk_frame_new("Hostname / socket name");
544 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
545 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
546
547 box = gtk_vbox_new(FALSE, 6);
548 gtk_container_add(GTK_CONTAINER(frame), box);
549
550 hbox = gtk_hbox_new(TRUE, 10);
551 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
552 cw.hentry = gtk_entry_new();
553 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
554 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
555
556 frame = gtk_frame_new("Port");
557 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
558 box = gtk_vbox_new(FALSE, 10);
559 gtk_container_add(GTK_CONTAINER(frame), box);
560
561 hbox = gtk_hbox_new(TRUE, 4);
562 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
563 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
564
565 frame = gtk_frame_new("Type");
566 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
567 box = gtk_vbox_new(FALSE, 10);
568 gtk_container_add(GTK_CONTAINER(frame), box);
569
570 hbox = gtk_hbox_new(TRUE, 4);
571 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
572
573 cw.combo = gtk_combo_box_text_new();
574 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
575 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
576 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
577 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
578
579 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
580
581 frame = gtk_frame_new("Options");
582 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
583 box = gtk_vbox_new(FALSE, 10);
584 gtk_container_add(GTK_CONTAINER(frame), box);
585
586 hbox = gtk_hbox_new(TRUE, 4);
587 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
588
589 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
591 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.");
592 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
593
594 /*
595 * Connect edit signal, so we can show/not-show the auto start button
596 */
597 g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
598 g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
599
600 gtk_widget_show_all(dialog);
601
602 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
603 gtk_widget_destroy(dialog);
604 return 1;
605 }
606
607 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
608 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
609
610 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
611 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
612 ge->type = Fio_client_ipv4;
613 else if (!strncmp(typeentry, "IPv6", 4))
614 ge->type = Fio_client_ipv6;
615 else
616 ge->type = Fio_client_socket;
617 g_free(typeentry);
618
619 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
620
621 gtk_widget_destroy(dialog);
622 return 0;
623}
624
625static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
626{
627 gc->client = fio_get_client(client);
628 client->client_data = gc;
629}
630
631static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
632{
633 struct gfio_client_options *gco;
634 struct gfio_client *gc;
635
636 gc = calloc(1, sizeof(*gc));
637 INIT_FLIST_HEAD(&gc->o_list);
638 gc->ge = ge;
639 ge->client = gc;
640 gfio_set_client(gc, client);
641
642 /*
643 * Just add a default set of options, need to consider how best
644 * to handle this
645 */
646 gco = calloc(1, sizeof(*gco));
647 INIT_FLIST_HEAD(&gco->list);
648 options_default_fill(&gco->o);
649 flist_add_tail(&gco->list, &gc->o_list);
650 gc->o_list_nr++;
651}
652
653static void connect_clicked(GtkWidget *widget, gpointer data)
654{
655 struct gui_entry *ge = data;
656 struct gfio_client *gc = ge->client;
657
658 if (ge->state == GE_STATE_NEW) {
659 int ret;
660
661 if (!ge->job_file)
662 file_open(widget, ge->ui);
663 if (!ge->job_file)
664 return;
665
666 gc = ge->client;
667
668 if (!gc->client) {
669 struct fio_client *client;
670
671 if (get_connection_details(ge)) {
672 gfio_report_error(ge, "Failed to get connection details\n");
673 return;
674 }
675
676 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
677 if (!client) {
678 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
679 free(ge->host);
680 ge->host = NULL;
681 return;
682 }
683 gfio_set_client(gc, client);
684 }
685
686 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
687 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
688 ret = fio_client_connect(gc->client);
689 if (!ret) {
690 if (!ge->ui->handler_running)
691 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
692 gfio_set_state(ge, GE_STATE_CONNECTED);
693 } else {
694 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
695 }
696 } else {
697 fio_client_terminate(gc->client);
698 gfio_set_state(ge, GE_STATE_NEW);
699 clear_ge_ui_info(ge);
700 }
701}
702
703static void send_clicked(GtkWidget *widget, gpointer data)
704{
705 struct gui_entry *ge = data;
706
707 if (send_job_file(ge))
708 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
709}
710
711static GtkWidget *new_client_page(struct gui_entry *ge);
712
713static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
714{
715 struct gui_entry *ge;
716
717 ge = malloc(sizeof(*ge));
718 memset(ge, 0, sizeof(*ge));
719 ge->state = GE_STATE_NEW;
720 ge->ui = ui;
721 return ge;
722}
723
724static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
725{
726 struct gui_entry *ge;
727
728 ge = alloc_new_gui_entry(ui);
729
730 ge->vbox = new_client_page(ge);
731 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
732
733 ge->page_label = gtk_label_new(name);
734 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
735
736 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
737
738 gtk_widget_show_all(ui->window);
739 return ge;
740}
741
742static void file_new(GtkWidget *w, gpointer data)
743{
744 struct gui *ui = (struct gui *) data;
745 struct gui_entry *ge;
746
747 ge = get_new_ge_with_tab(ui, "Untitled");
748 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
749}
750
751/*
752 * Return the 'ge' corresponding to the tab. If the active tab is the
753 * main tab, open a new tab.
754 */
755static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
756 int *created)
757{
758 if (!cur_page) {
759 if (created)
760 *created = 1;
761 return get_new_ge_with_tab(ui, "Untitled");
762 }
763
764 if (created)
765 *created = 0;
766
767 return g_hash_table_lookup(ui->ge_hash, &cur_page);
768}
769
770static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
771{
772 gint cur_page;
773
774 /*
775 * Main tab is tab 0, so any current page other than 0 holds
776 * a ge entry.
777 */
778 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
779 if (cur_page)
780 return get_ge_from_page(ui, cur_page, NULL);
781
782 return NULL;
783}
784
785static void file_close(GtkWidget *w, gpointer data)
786{
787 struct gui *ui = (struct gui *) data;
788 struct gui_entry *ge;
789
790 /*
791 * Can't close the main tab
792 */
793 ge = get_ge_from_cur_tab(ui);
794 if (ge) {
795 gtk_widget_destroy(ge->vbox);
796 return;
797 }
798
799 if (g_hash_table_size(ui->ge_hash)) {
800 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
801 return;
802 }
803
804 gfio_quit(ui);
805}
806
807static void file_add_recent(struct gui *ui, const gchar *uri)
808{
809 GtkRecentData grd;
810
811 memset(&grd, 0, sizeof(grd));
812 grd.display_name = strdup("gfio");
813 grd.description = strdup("Fio job file");
814 grd.mime_type = strdup(GFIO_MIME);
815 grd.app_name = strdup(g_get_application_name());
816 grd.app_exec = strdup("gfio %f/%u");
817
818 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
819}
820
821static gchar *get_filename_from_uri(const gchar *uri)
822{
823 if (strncmp(uri, "file://", 7))
824 return strdup(uri);
825
826 return strdup(uri + 7);
827}
828
829static int do_file_open(struct gui_entry *ge, const gchar *uri)
830{
831 struct fio_client *client;
832
833 assert(!ge->job_file);
834
835 ge->job_file = get_filename_from_uri(uri);
836
837 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
838 if (client) {
839 char *label = strdup(uri);
840
841 basename(label);
842 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
843 free(label);
844
845 gfio_client_added(ge, client);
846 file_add_recent(ge->ui, uri);
847 return 0;
848 }
849
850 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
851 free(ge->host);
852 ge->host = NULL;
853 free(ge->job_file);
854 ge->job_file = NULL;
855 return 1;
856}
857
858static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
859{
860 struct gui_entry *ge;
861 gint cur_page;
862 int ret, ge_is_new = 0;
863
864 /*
865 * Creates new tab if current tab is the main window, or the
866 * current tab already has a client.
867 */
868 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
869 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
870 if (ge->client) {
871 ge = get_new_ge_with_tab(ui, "Untitled");
872 ge_is_new = 1;
873 }
874
875 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
876
877 if (get_connection_details(ge)) {
878 if (ge_is_new)
879 gtk_widget_destroy(ge->vbox);
880
881 return 1;
882 }
883
884 ret = do_file_open(ge, uri);
885
886 if (!ret) {
887 if (ge->server_start)
888 gfio_start_server(ui);
889 } else {
890 if (ge_is_new)
891 gtk_widget_destroy(ge->vbox);
892 }
893
894 return ret;
895}
896
897static void recent_open(GtkAction *action, gpointer data)
898{
899 struct gui *ui = (struct gui *) data;
900 GtkRecentInfo *info;
901 const gchar *uri;
902
903 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
904 uri = gtk_recent_info_get_uri(info);
905
906 do_file_open_with_tab(ui, uri);
907}
908
909static void file_open(GtkWidget *w, gpointer data)
910{
911 struct gui *ui = data;
912 GtkWidget *dialog;
913 GtkFileFilter *filter;
914 gchar *filename;
915
916 dialog = gtk_file_chooser_dialog_new("Open File",
917 GTK_WINDOW(ui->window),
918 GTK_FILE_CHOOSER_ACTION_OPEN,
919 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
920 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
921 NULL);
922 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
923
924 filter = gtk_file_filter_new();
925 gtk_file_filter_add_pattern(filter, "*.fio");
926 gtk_file_filter_add_pattern(filter, "*.job");
927 gtk_file_filter_add_pattern(filter, "*.ini");
928 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
929 gtk_file_filter_set_name(filter, "Fio job file");
930 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
931
932 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
933 gtk_widget_destroy(dialog);
934 return;
935 }
936
937 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
938
939 gtk_widget_destroy(dialog);
940
941 do_file_open_with_tab(ui, filename);
942 g_free(filename);
943}
944
945static void file_save(GtkWidget *w, gpointer data)
946{
947 struct gui *ui = data;
948 GtkWidget *dialog;
949
950 dialog = gtk_file_chooser_dialog_new("Save File",
951 GTK_WINDOW(ui->window),
952 GTK_FILE_CHOOSER_ACTION_SAVE,
953 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
954 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
955 NULL);
956
957 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
958 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
959
960 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
961 char *filename;
962
963 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
964 // save_job_file(filename);
965 g_free(filename);
966 }
967 gtk_widget_destroy(dialog);
968}
969
970static void view_log_destroy(GtkWidget *w, gpointer data)
971{
972 struct gui *ui = (struct gui *) data;
973
974 g_object_ref(G_OBJECT(ui->log_tree));
975 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
976 gtk_widget_destroy(w);
977 ui->log_view = NULL;
978}
979
980void gfio_view_log(struct gui *ui)
981{
982 GtkWidget *win, *scroll, *vbox, *box;
983
984 if (ui->log_view)
985 return;
986
987 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
988 gtk_window_set_title(GTK_WINDOW(win), "Log");
989 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
990
991 scroll = gtk_scrolled_window_new(NULL, NULL);
992
993 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
994
995 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
996
997 box = gtk_hbox_new(TRUE, 0);
998 gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
999 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
1000 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
1001
1002 vbox = gtk_vbox_new(TRUE, 5);
1003 gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
1004
1005 gtk_container_add(GTK_CONTAINER(win), vbox);
1006 gtk_widget_show_all(win);
1007}
1008
1009static void view_log(GtkWidget *w, gpointer data)
1010{
1011 struct gui *ui = (struct gui *) data;
1012
1013 gfio_view_log(ui);
1014}
1015
1016static void connect_job_entry(GtkWidget *w, gpointer data)
1017{
1018 struct gui *ui = (struct gui *) data;
1019 struct gui_entry *ge;
1020
1021 ge = get_ge_from_cur_tab(ui);
1022 if (ge)
1023 connect_clicked(w, ge);
1024}
1025
1026static void send_job_entry(GtkWidget *w, gpointer data)
1027{
1028 struct gui *ui = (struct gui *) data;
1029 struct gui_entry *ge;
1030
1031 ge = get_ge_from_cur_tab(ui);
1032 if (ge)
1033 send_clicked(w, ge);
1034}
1035
1036static void edit_job_entry(GtkWidget *w, gpointer data)
1037{
1038 struct gui *ui = (struct gui *) data;
1039 struct gui_entry *ge;
1040
1041 ge = get_ge_from_cur_tab(ui);
1042 if (ge && ge->client)
1043 gopt_get_options_window(ui->window, ge->client);
1044}
1045
1046static void start_job_entry(GtkWidget *w, gpointer data)
1047{
1048 struct gui *ui = (struct gui *) data;
1049 struct gui_entry *ge;
1050
1051 ge = get_ge_from_cur_tab(ui);
1052 if (ge)
1053 start_job_clicked(w, ge);
1054}
1055
1056static void view_results(GtkWidget *w, gpointer data)
1057{
1058 struct gui *ui = (struct gui *) data;
1059 struct gfio_client *gc;
1060 struct gui_entry *ge;
1061
1062 ge = get_ge_from_cur_tab(ui);
1063 if (!ge)
1064 return;
1065
1066 if (ge->results_window)
1067 return;
1068
1069 gc = ge->client;
1070 if (gc && gc->nr_results)
1071 gfio_display_end_results(gc);
1072}
1073
1074static void __update_graph_settings(struct gfio_graphs *g)
1075{
1076 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1077 graph_set_font(g->iops_graph, gfio_graph_font);
1078 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1079 graph_set_font(g->bandwidth_graph, gfio_graph_font);
1080}
1081
1082static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1083{
1084 struct gui_entry *ge = (struct gui_entry *) value;
1085 GdkEvent *ev;
1086
1087 __update_graph_settings(&ge->graphs);
1088
1089 ev = gdk_event_new(GDK_EXPOSE);
1090 g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1091 gdk_event_free(ev);
1092}
1093
1094static void update_graph_limits(void)
1095{
1096 struct gui *ui = &main_ui;
1097 GdkEvent *ev;
1098
1099 __update_graph_settings(&ui->graphs);
1100
1101 ev = gdk_event_new(GDK_EXPOSE);
1102 g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1103 gdk_event_free(ev);
1104
1105 g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1106}
1107
1108static void preferences(GtkWidget *w, gpointer data)
1109{
1110 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1111 GtkWidget *hbox, *spin, *entry, *spin_int;
1112 struct gui *ui = (struct gui *) data;
1113 int i;
1114
1115 dialog = gtk_dialog_new_with_buttons("Preferences",
1116 GTK_WINDOW(ui->window),
1117 GTK_DIALOG_DESTROY_WITH_PARENT,
1118 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1119 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1120 NULL);
1121
1122 frame = gtk_frame_new("Graphing");
1123 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1124 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1125 vbox = gtk_vbox_new(FALSE, 6);
1126 gtk_container_add(GTK_CONTAINER(frame), vbox);
1127
1128 hbox = gtk_hbox_new(FALSE, 5);
1129 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1130 entry = gtk_label_new("Font face to use for graph labels");
1131 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1132
1133 font = gtk_font_button_new_with_font(gfio_graph_font);
1134 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1135
1136 box = gtk_vbox_new(FALSE, 6);
1137 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1138
1139 hbox = gtk_hbox_new(FALSE, 5);
1140 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1141 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1142 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1143
1144 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1145
1146 box = gtk_vbox_new(FALSE, 6);
1147 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1148
1149 hbox = gtk_hbox_new(FALSE, 5);
1150 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1151 entry = gtk_label_new("Client ETA request interval (msec)");
1152 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1153
1154 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1155 frame = gtk_frame_new("Debug logging");
1156 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1157 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1158 vbox = gtk_vbox_new(FALSE, 6);
1159 gtk_container_add(GTK_CONTAINER(frame), vbox);
1160
1161 box = gtk_hbox_new(FALSE, 6);
1162 gtk_container_add(GTK_CONTAINER(vbox), box);
1163
1164 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1165
1166 for (i = 0; i < FD_DEBUG_MAX; i++) {
1167 if (i == 7) {
1168 box = gtk_hbox_new(FALSE, 6);
1169 gtk_container_add(GTK_CONTAINER(vbox), box);
1170 }
1171
1172
1173 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1174 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1175 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1176 }
1177
1178 gtk_widget_show_all(dialog);
1179
1180 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1181 gtk_widget_destroy(dialog);
1182 return;
1183 }
1184
1185 for (i = 0; i < FD_DEBUG_MAX; i++) {
1186 int set;
1187
1188 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1189 if (set)
1190 fio_debug |= (1UL << i);
1191 }
1192
1193 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1194 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1195 update_graph_limits();
1196 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1197
1198 gtk_widget_destroy(dialog);
1199}
1200
1201static void about_dialog(GtkWidget *w, gpointer data)
1202{
1203 const char *authors[] = {
1204 "Jens Axboe <axboe@kernel.dk>",
1205 "Stephen Carmeron <stephenmcameron@gmail.com>",
1206 NULL
1207 };
1208 const char *license[] = {
1209 "Fio is free software; you can redistribute it and/or modify "
1210 "it under the terms of the GNU General Public License as published by "
1211 "the Free Software Foundation; either version 2 of the License, or "
1212 "(at your option) any later version.\n",
1213 "Fio is distributed in the hope that it will be useful, "
1214 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1215 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1216 "GNU General Public License for more details.\n",
1217 "You should have received a copy of the GNU General Public License "
1218 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1219 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1220 };
1221 char *license_trans;
1222
1223 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1224 license[2], "\n", NULL);
1225
1226 gtk_show_about_dialog(NULL,
1227 "program-name", "gfio",
1228 "comments", "Gtk2 UI for fio",
1229 "license", license_trans,
1230 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1231 "authors", authors,
1232 "version", fio_version_string,
1233 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1234 "logo-icon-name", "fio",
1235 /* Must be last: */
1236 "wrap-license", TRUE,
1237 NULL);
1238
1239 g_free(license_trans);
1240}
1241
1242static GtkActionEntry menu_items[] = {
1243 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1244 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1245 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1246 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1247 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1248 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1249 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1250 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1251 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1252 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1253 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1254 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1255 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1256 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1257 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1258 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1259 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1260};
1261static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1262
1263static const gchar *ui_string = " \
1264 <ui> \
1265 <menubar name=\"MainMenu\"> \
1266 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1267 <menuitem name=\"New\" action=\"NewFile\" /> \
1268 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1269 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1270 <separator name=\"Separator1\"/> \
1271 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1272 <separator name=\"Separator2\"/> \
1273 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1274 <separator name=\"Separator3\"/> \
1275 <placeholder name=\"FileRecentFiles\"/> \
1276 <separator name=\"Separator4\"/> \
1277 <menuitem name=\"Quit\" action=\"Quit\" /> \
1278 </menu> \
1279 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1280 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1281 <separator name=\"Separator5\"/> \
1282 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1283 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1284 <separator name=\"Separator6\"/> \
1285 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1286 </menu>\
1287 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1288 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1289 <separator name=\"Separator7\"/> \
1290 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1291 </menu>\
1292 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1293 <menuitem name=\"About\" action=\"About\" /> \
1294 </menu> \
1295 </menubar> \
1296 </ui> \
1297";
1298
1299static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1300 struct gui *ui)
1301{
1302 GtkActionGroup *action_group;
1303 GError *error = 0;
1304
1305 action_group = gtk_action_group_new("Menu");
1306 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1307
1308 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1309 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1310
1311 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1312
1313 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1314}
1315
1316void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1317 GtkWidget *vbox, GtkUIManager *ui_manager)
1318{
1319 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1320}
1321
1322static void combo_entry_changed(GtkComboBox *box, gpointer data)
1323{
1324 struct gui_entry *ge = (struct gui_entry *) data;
1325 gint index;
1326
1327 index = gtk_combo_box_get_active(box);
1328
1329 multitext_set_entry(&ge->eta.iotype, index);
1330 multitext_set_entry(&ge->eta.bs, index);
1331 multitext_set_entry(&ge->eta.ioengine, index);
1332 multitext_set_entry(&ge->eta.iodepth, index);
1333}
1334
1335static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1336{
1337 struct gui_entry *ge = (struct gui_entry *) data;
1338
1339 multitext_free(&ge->eta.iotype);
1340 multitext_free(&ge->eta.bs);
1341 multitext_free(&ge->eta.ioengine);
1342 multitext_free(&ge->eta.iodepth);
1343}
1344
1345static GtkWidget *new_client_page(struct gui_entry *ge)
1346{
1347 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1348 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1349
1350 main_vbox = gtk_vbox_new(FALSE, 3);
1351
1352 top_align = gtk_alignment_new(0, 0, 1, 0);
1353 top_vbox = gtk_vbox_new(FALSE, 3);
1354 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1355 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1356
1357 probe = gtk_frame_new("Job");
1358 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1359 probe_frame = gtk_vbox_new(FALSE, 3);
1360 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1361
1362 probe_box = gtk_hbox_new(FALSE, 3);
1363 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1364 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1365 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1366 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1367 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1368
1369 probe_box = gtk_hbox_new(FALSE, 3);
1370 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1371
1372 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1373 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1374 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1375 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1376 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1377 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1378 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1379 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1380 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1381
1382 probe_box = gtk_hbox_new(FALSE, 3);
1383 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1384 ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1385 ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1386 ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1387 ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1388
1389 /*
1390 * Only add this if we have a commit rate
1391 */
1392#if 0
1393 probe_box = gtk_hbox_new(FALSE, 3);
1394 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1395
1396 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1397 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1398
1399 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1400 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1401#endif
1402
1403 /*
1404 * Set up a drawing area and IOPS and bandwidth graphs
1405 */
1406 ge->graphs.drawing_area = gtk_drawing_area_new();
1407 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1408 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1409 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
1410 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
1411 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1412 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1413 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1414 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1415 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1416 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1417 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1418 ge->graphs.drawing_area);
1419 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1420
1421 setup_graphs(&ge->graphs);
1422
1423 /*
1424 * Set up alignments for widgets at the bottom of ui,
1425 * align bottom left, expand horizontally but not vertically
1426 */
1427 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1428 ge->buttonbox = gtk_hbox_new(FALSE, 0);
1429 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1430 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1431
1432 add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1433
1434 /*
1435 * Set up thread status progress bar
1436 */
1437 ge->thread_status_pb = gtk_progress_bar_new();
1438 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1439 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1440 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1441
1442
1443 return main_vbox;
1444}
1445
1446static GtkWidget *new_main_page(struct gui *ui)
1447{
1448 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1449 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1450
1451 main_vbox = gtk_vbox_new(FALSE, 3);
1452
1453 /*
1454 * Set up alignments for widgets at the top of ui,
1455 * align top left, expand horizontally but not vertically
1456 */
1457 top_align = gtk_alignment_new(0, 0, 1, 0);
1458 top_vbox = gtk_vbox_new(FALSE, 0);
1459 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1460 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1461
1462 probe = gtk_frame_new("Run statistics");
1463 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1464 probe_frame = gtk_vbox_new(FALSE, 3);
1465 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1466
1467 probe_box = gtk_hbox_new(FALSE, 3);
1468 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1469 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1470 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1471 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1472 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1473 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1474
1475 /*
1476 * Only add this if we have a commit rate
1477 */
1478#if 0
1479 probe_box = gtk_hbox_new(FALSE, 3);
1480 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1481
1482 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1483 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1484
1485 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1486 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1487#endif
1488
1489 /*
1490 * Set up a drawing area and IOPS and bandwidth graphs
1491 */
1492 ui->graphs.drawing_area = gtk_drawing_area_new();
1493 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1494 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1495 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
1496 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
1497 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1498 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1499 G_CALLBACK(on_config_drawing_area), &ui->graphs);
1500 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1501 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1502 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1503 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1504 ui->graphs.drawing_area);
1505 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1506 TRUE, TRUE, 0);
1507
1508 setup_graphs(&ui->graphs);
1509
1510 /*
1511 * Set up alignments for widgets at the bottom of ui,
1512 * align bottom left, expand horizontally but not vertically
1513 */
1514 bottom_align = gtk_alignment_new(0, 1, 1, 0);
1515 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1516 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1517 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1518
1519 /*
1520 * Set up thread status progress bar
1521 */
1522 ui->thread_status_pb = gtk_progress_bar_new();
1523 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1524 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1525 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1526
1527 return main_vbox;
1528}
1529
1530static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1531 guint page, gpointer data)
1532
1533{
1534 struct gui *ui = (struct gui *) data;
1535 struct gui_entry *ge;
1536
1537 if (!page) {
1538 set_job_menu_visible(ui, 0);
1539 set_view_results_visible(ui, 0);
1540 return TRUE;
1541 }
1542
1543 set_job_menu_visible(ui, 1);
1544 ge = get_ge_from_page(ui, page, NULL);
1545 if (ge)
1546 update_button_states(ui, ge);
1547
1548 return TRUE;
1549}
1550
1551static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1552{
1553 time_t time_a = gtk_recent_info_get_visited(a);
1554 time_t time_b = gtk_recent_info_get_visited(b);
1555
1556 return time_b - time_a;
1557}
1558
1559static void add_recent_file_items(struct gui *ui)
1560{
1561 const gchar *gfio = g_get_application_name();
1562 GList *items, *item;
1563 int i = 0;
1564
1565 if (ui->recent_ui_id) {
1566 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1567 gtk_ui_manager_ensure_update(ui->uimanager);
1568 }
1569 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1570
1571 if (ui->actiongroup) {
1572 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1573 g_object_unref(ui->actiongroup);
1574 }
1575 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1576
1577 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1578
1579 items = gtk_recent_manager_get_items(ui->recentmanager);
1580 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1581
1582 for (item = items; item && item->data; item = g_list_next(item)) {
1583 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1584 gchar *action_name;
1585 const gchar *label;
1586 GtkAction *action;
1587
1588 if (!gtk_recent_info_has_application(info, gfio))
1589 continue;
1590
1591 /*
1592 * We only support local files for now
1593 */
1594 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1595 continue;
1596
1597 action_name = g_strdup_printf("RecentFile%u", i++);
1598 label = gtk_recent_info_get_display_name(info);
1599
1600 action = g_object_new(GTK_TYPE_ACTION,
1601 "name", action_name,
1602 "label", label, NULL);
1603
1604 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1605 gtk_recent_info_ref(info),
1606 (GDestroyNotify) gtk_recent_info_unref);
1607
1608
1609 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1610
1611 gtk_action_group_add_action(ui->actiongroup, action);
1612 g_object_unref(action);
1613
1614 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1615 "/MainMenu/FileMenu/FileRecentFiles",
1616 label, action_name,
1617 GTK_UI_MANAGER_MENUITEM, FALSE);
1618
1619 g_free(action_name);
1620
1621 if (i == 8)
1622 break;
1623 }
1624
1625 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1626 g_list_free(items);
1627}
1628
1629static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1630 gint x, gint y, GtkSelectionData *seldata,
1631 guint info, guint time, gpointer *data)
1632{
1633 struct gui *ui = (struct gui *) data;
1634 gchar **uris;
1635 GtkWidget *source;
1636
1637 source = gtk_drag_get_source_widget(ctx);
1638 if (source && widget == gtk_widget_get_toplevel(source)) {
1639 gtk_drag_finish(ctx, FALSE, FALSE, time);
1640 return;
1641 }
1642
1643 uris = gtk_selection_data_get_uris(seldata);
1644 if (!uris) {
1645 gtk_drag_finish(ctx, FALSE, FALSE, time);
1646 return;
1647 }
1648
1649 if (uris[0])
1650 do_file_open_with_tab(ui, uris[0]);
1651
1652 gtk_drag_finish(ctx, TRUE, FALSE, time);
1653 g_strfreev(uris);
1654}
1655
1656static void init_ui(int *argc, char **argv[], struct gui *ui)
1657{
1658 GtkSettings *settings;
1659 GtkWidget *vbox;
1660
1661 /* Magical g*thread incantation, you just need this thread stuff.
1662 * Without it, the update that happens in gfio_update_thread_status
1663 * doesn't really happen in a timely fashion, you need expose events
1664 */
1665 if (!g_thread_supported())
1666 g_thread_init(NULL);
1667 gdk_threads_init();
1668
1669 gtk_init(argc, argv);
1670 settings = gtk_settings_get_default();
1671 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1672 g_type_init();
1673 gdk_color_parse("#ffffee", &gfio_color_lightyellow);
1674 gdk_color_parse("white", &gfio_color_white);
1675
1676 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1677 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1678 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1679
1680 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1681 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1682
1683 ui->vbox = gtk_vbox_new(FALSE, 0);
1684 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1685
1686 ui->uimanager = gtk_ui_manager_new();
1687 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1688 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1689
1690 ui->recentmanager = gtk_recent_manager_get_default();
1691 add_recent_file_items(ui);
1692
1693 ui->notebook = gtk_notebook_new();
1694 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1695 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1696 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1697 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1698
1699 vbox = new_main_page(ui);
1700 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1701 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1702 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1703
1704 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1705
1706 gfio_ui_setup_log(ui);
1707
1708 gtk_widget_show_all(ui->window);
1709}
1710
1711int main(int argc, char *argv[], char *envp[])
1712{
1713 if (initialize_fio(envp))
1714 return 1;
1715 if (fio_init_options())
1716 return 1;
1717
1718 gopt_init();
1719
1720 memset(&main_ui, 0, sizeof(main_ui));
1721 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1722
1723 init_ui(&argc, &argv, &main_ui);
1724
1725 gdk_threads_enter();
1726 gtk_main();
1727 gdk_threads_leave();
1728
1729 g_hash_table_destroy(main_ui.ge_hash);
1730
1731 gopt_exit();
1732 return 0;
1733}