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