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