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