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