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