gfio: use glib hash for tab lookup
[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                 gfio_client_added(ge, client);
814                 file_add_recent(ge->ui, uri);
815                 return 0;
816         }
817
818         gfio_report_error(ge, "Failed to add client %s\n", ge->host);
819         free(ge->host);
820         ge->host = NULL;
821         free(ge->job_file);
822         ge->job_file = NULL;
823         return 1;
824 }
825
826 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
827 {
828         struct gui_entry *ge;
829         gint cur_page;
830         int ret, ge_is_new = 0;
831
832         /*
833          * Creates new tab if current tab is the main window, or the
834          * current tab already has a client.
835          */
836         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
837         ge = get_ge_from_page(ui, cur_page, &ge_is_new);
838         if (ge->client) {
839                 ge = get_new_ge_with_tab(ui, "Untitled");
840                 ge_is_new = 1;
841         }
842
843         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
844
845         if (get_connection_details(ge)) {
846                 if (ge_is_new)
847                         gtk_widget_destroy(ge->vbox);
848                         
849                 return 1;
850         }
851
852         ret = do_file_open(ge, uri);
853
854         if (!ret) {
855                 if (ge->server_start)
856                         gfio_start_server(ui);
857         } else {
858                 if (ge_is_new)
859                         gtk_widget_destroy(ge->vbox);
860         }
861
862         return ret;
863 }
864
865 static void recent_open(GtkAction *action, gpointer data)
866 {
867         struct gui *ui = (struct gui *) data;
868         GtkRecentInfo *info;
869         const gchar *uri;
870
871         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
872         uri = gtk_recent_info_get_uri(info);
873
874         do_file_open_with_tab(ui, uri);
875 }
876
877 static void file_open(GtkWidget *w, gpointer data)
878 {
879         struct gui *ui = data;
880         GtkWidget *dialog;
881         GtkFileFilter *filter;
882         gchar *filename;
883
884         dialog = gtk_file_chooser_dialog_new("Open File",
885                 GTK_WINDOW(ui->window),
886                 GTK_FILE_CHOOSER_ACTION_OPEN,
887                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
888                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
889                 NULL);
890         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
891
892         filter = gtk_file_filter_new();
893         gtk_file_filter_add_pattern(filter, "*.fio");
894         gtk_file_filter_add_pattern(filter, "*.job");
895         gtk_file_filter_add_pattern(filter, "*.ini");
896         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
897         gtk_file_filter_set_name(filter, "Fio job file");
898         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
899
900         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
901                 gtk_widget_destroy(dialog);
902                 return;
903         }
904
905         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
906
907         gtk_widget_destroy(dialog);
908
909         do_file_open_with_tab(ui, filename);
910         g_free(filename);
911 }
912
913 static void file_save(GtkWidget *w, gpointer data)
914 {
915         struct gui *ui = data;
916         GtkWidget *dialog;
917
918         dialog = gtk_file_chooser_dialog_new("Save File",
919                 GTK_WINDOW(ui->window),
920                 GTK_FILE_CHOOSER_ACTION_SAVE,
921                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
922                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
923                 NULL);
924
925         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
926         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
927
928         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
929                 char *filename;
930
931                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
932                 // save_job_file(filename);
933                 g_free(filename);
934         }
935         gtk_widget_destroy(dialog);
936 }
937
938 static void view_log_destroy(GtkWidget *w, gpointer data)
939 {
940         struct gui *ui = (struct gui *) data;
941
942         gtk_widget_ref(ui->log_tree);
943         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
944         gtk_widget_destroy(w);
945         ui->log_view = NULL;
946 }
947
948 void gfio_view_log(struct gui *ui)
949 {
950         GtkWidget *win, *scroll, *vbox, *box;
951
952         if (ui->log_view)
953                 return;
954
955         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
956         gtk_window_set_title(GTK_WINDOW(win), "Log");
957         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
958
959         scroll = gtk_scrolled_window_new(NULL, NULL);
960
961         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
962
963         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
964
965         box = gtk_hbox_new(TRUE, 0);
966         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
967         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
968         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
969
970         vbox = gtk_vbox_new(TRUE, 5);
971         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
972
973         gtk_container_add(GTK_CONTAINER(win), vbox);
974         gtk_widget_show_all(win);
975 }
976
977 static void view_log(GtkWidget *w, gpointer data)
978 {
979         struct gui *ui = (struct gui *) data;
980
981         gfio_view_log(ui);
982 }
983
984 static void connect_job_entry(GtkWidget *w, gpointer data)
985 {
986         struct gui *ui = (struct gui *) data;
987         struct gui_entry *ge;
988         
989         ge = get_ge_from_cur_tab(ui);
990         if (ge)
991                 connect_clicked(w, ge);
992 }
993
994 static void send_job_entry(GtkWidget *w, gpointer data)
995 {
996         struct gui *ui = (struct gui *) data;
997         struct gui_entry *ge;
998
999         ge = get_ge_from_cur_tab(ui);
1000         if (ge)
1001                 send_clicked(w, ge);
1002 }
1003
1004 static void edit_job_entry(GtkWidget *w, gpointer data)
1005 {
1006         struct gui *ui = (struct gui *) data;
1007         struct gui_entry *ge;
1008
1009         ge = get_ge_from_cur_tab(ui);
1010         if (ge && ge->client)
1011                 gopt_get_options_window(ui->window, &ge->client->o);
1012 }
1013
1014 static void start_job_entry(GtkWidget *w, gpointer data)
1015 {
1016         struct gui *ui = (struct gui *) data;
1017         struct gui_entry *ge;
1018
1019         ge = get_ge_from_cur_tab(ui);
1020         if (ge)
1021                 start_job_clicked(w, ge);
1022 }
1023
1024 static void view_results(GtkWidget *w, gpointer data)
1025 {
1026         struct gui *ui = (struct gui *) data;
1027         struct gfio_client *gc;
1028         struct gui_entry *ge;
1029
1030         ge = get_ge_from_cur_tab(ui);
1031         if (!ge)
1032                 return;
1033
1034         if (ge->results_window)
1035                 return;
1036
1037         gc = ge->client;
1038         if (gc && gc->nr_results)
1039                 gfio_display_end_results(gc);
1040 }
1041
1042 static void __update_graph_limits(struct gfio_graphs *g)
1043 {
1044         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1045         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1046 }
1047
1048 static void ge_update_lim_fn(gpointer key, gpointer value, gpointer data)
1049 {
1050         struct gui_entry *ge = (struct gui_entry *) value;
1051
1052         __update_graph_limits(&ge->graphs);
1053 }
1054
1055 static void update_graph_limits(void)
1056 {
1057         struct gui *ui = &main_ui;
1058
1059         __update_graph_limits(&ui->graphs);
1060
1061         g_hash_table_foreach(ui->ge_hash, ge_update_lim_fn, NULL);
1062 }
1063
1064 static void preferences(GtkWidget *w, gpointer data)
1065 {
1066         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1067         GtkWidget *hbox, *spin, *entry, *spin_int;
1068         struct gui *ui = (struct gui *) data;
1069         int i;
1070
1071         dialog = gtk_dialog_new_with_buttons("Preferences",
1072                 GTK_WINDOW(ui->window),
1073                 GTK_DIALOG_DESTROY_WITH_PARENT,
1074                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1075                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1076                 NULL);
1077
1078         frame = gtk_frame_new("Graphing");
1079         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1080         vbox = gtk_vbox_new(FALSE, 6);
1081         gtk_container_add(GTK_CONTAINER(frame), vbox);
1082
1083         hbox = gtk_hbox_new(FALSE, 5);
1084         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1085         entry = gtk_label_new("Font face to use for graph labels");
1086         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1087
1088         font = gtk_font_button_new();
1089         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1090
1091         box = gtk_vbox_new(FALSE, 6);
1092         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1093
1094         hbox = gtk_hbox_new(FALSE, 5);
1095         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1096         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1097         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1098
1099         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1100
1101         box = gtk_vbox_new(FALSE, 6);
1102         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1103
1104         hbox = gtk_hbox_new(FALSE, 5);
1105         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1106         entry = gtk_label_new("Client ETA request interval (msec)");
1107         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1108
1109         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1110         frame = gtk_frame_new("Debug logging");
1111         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1112         vbox = gtk_vbox_new(FALSE, 6);
1113         gtk_container_add(GTK_CONTAINER(frame), vbox);
1114
1115         box = gtk_hbox_new(FALSE, 6);
1116         gtk_container_add(GTK_CONTAINER(vbox), box);
1117
1118         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1119
1120         for (i = 0; i < FD_DEBUG_MAX; i++) {
1121                 if (i == 7) {
1122                         box = gtk_hbox_new(FALSE, 6);
1123                         gtk_container_add(GTK_CONTAINER(vbox), box);
1124                 }
1125
1126
1127                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1128                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1129                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1130         }
1131
1132         gtk_widget_show_all(dialog);
1133
1134         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1135                 gtk_widget_destroy(dialog);
1136                 return;
1137         }
1138
1139         for (i = 0; i < FD_DEBUG_MAX; i++) {
1140                 int set;
1141
1142                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1143                 if (set)
1144                         fio_debug |= (1UL << i);
1145         }
1146
1147         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1148         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1149         update_graph_limits();
1150         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1151
1152         gtk_widget_destroy(dialog);
1153 }
1154
1155 static void about_dialog(GtkWidget *w, gpointer data)
1156 {
1157         const char *authors[] = {
1158                 "Jens Axboe <axboe@kernel.dk>",
1159                 "Stephen Carmeron <stephenmcameron@gmail.com>",
1160                 NULL
1161         };
1162         const char *license[] = {
1163                 "Fio is free software; you can redistribute it and/or modify "
1164                 "it under the terms of the GNU General Public License as published by "
1165                 "the Free Software Foundation; either version 2 of the License, or "
1166                 "(at your option) any later version.\n",
1167                 "Fio is distributed in the hope that it will be useful, "
1168                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1169                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
1170                 "GNU General Public License for more details.\n",
1171                 "You should have received a copy of the GNU General Public License "
1172                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1173                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
1174         };
1175         char *license_trans;
1176
1177         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1178                                      license[2], "\n", NULL);
1179
1180         gtk_show_about_dialog(NULL,
1181                 "program-name", "gfio",
1182                 "comments", "Gtk2 UI for fio",
1183                 "license", license_trans,
1184                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1185                 "authors", authors,
1186                 "version", fio_version_string,
1187                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1188                 "logo-icon-name", "fio",
1189                 /* Must be last: */
1190                 "wrap-license", TRUE,
1191                 NULL);
1192
1193         g_free(license_trans);
1194 }
1195
1196 static GtkActionEntry menu_items[] = {
1197         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1198         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1199         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1200         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1201         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1202         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1203         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1204         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1205         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1206         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1207         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1208         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1209         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1210         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1211         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1212         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1213         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1214 };
1215 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1216
1217 static const gchar *ui_string = " \
1218         <ui> \
1219                 <menubar name=\"MainMenu\"> \
1220                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1221                                 <menuitem name=\"New\" action=\"NewFile\" /> \
1222                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1223                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1224                                 <separator name=\"Separator1\"/> \
1225                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1226                                 <separator name=\"Separator2\"/> \
1227                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1228                                 <separator name=\"Separator3\"/> \
1229                                 <placeholder name=\"FileRecentFiles\"/> \
1230                                 <separator name=\"Separator4\"/> \
1231                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1232                         </menu> \
1233                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1234                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1235                                 <separator name=\"Separator5\"/> \
1236                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1237                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1238                                 <separator name=\"Separator6\"/> \
1239                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1240                         </menu>\
1241                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1242                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1243                                 <separator name=\"Separator7\"/> \
1244                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1245                         </menu>\
1246                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1247                                 <menuitem name=\"About\" action=\"About\" /> \
1248                         </menu> \
1249                 </menubar> \
1250         </ui> \
1251 ";
1252
1253 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1254                                    struct gui *ui)
1255 {
1256         GtkActionGroup *action_group;
1257         GError *error = 0;
1258
1259         action_group = gtk_action_group_new("Menu");
1260         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1261
1262         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1263         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1264
1265         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1266
1267         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1268 }
1269
1270 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1271                    GtkWidget *vbox, GtkUIManager *ui_manager)
1272 {
1273         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1274 }
1275
1276 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1277 {
1278         struct gui_entry *ge = (struct gui_entry *) data;
1279         gint index;
1280
1281         index = gtk_combo_box_get_active(box);
1282
1283         multitext_set_entry(&ge->eta.iotype, index);
1284         multitext_set_entry(&ge->eta.bs, index);
1285         multitext_set_entry(&ge->eta.ioengine, index);
1286         multitext_set_entry(&ge->eta.iodepth, index);
1287 }
1288
1289 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1290 {
1291         struct gui_entry *ge = (struct gui_entry *) data;
1292
1293         multitext_free(&ge->eta.iotype);
1294         multitext_free(&ge->eta.bs);
1295         multitext_free(&ge->eta.ioengine);
1296         multitext_free(&ge->eta.iodepth);
1297 }
1298
1299 static GtkWidget *new_client_page(struct gui_entry *ge)
1300 {
1301         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1302         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1303
1304         main_vbox = gtk_vbox_new(FALSE, 3);
1305
1306         top_align = gtk_alignment_new(0, 0, 1, 0);
1307         top_vbox = gtk_vbox_new(FALSE, 3);
1308         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1309         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1310
1311         probe = gtk_frame_new("Job");
1312         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1313         probe_frame = gtk_vbox_new(FALSE, 3);
1314         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1315
1316         probe_box = gtk_hbox_new(FALSE, 3);
1317         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1318         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1319         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1320         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1321         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1322
1323         probe_box = gtk_hbox_new(FALSE, 3);
1324         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1325
1326         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1327         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1328         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1329         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1330         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1331         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1332         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1333         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1334         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1335
1336         probe_box = gtk_hbox_new(FALSE, 3);
1337         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1338         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1339         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1340         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1341         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1342
1343         /*
1344          * Only add this if we have a commit rate
1345          */
1346 #if 0
1347         probe_box = gtk_hbox_new(FALSE, 3);
1348         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1349
1350         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1351         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1352
1353         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1354         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1355 #endif
1356
1357         /*
1358          * Set up a drawing area and IOPS and bandwidth graphs
1359          */
1360         ge->graphs.drawing_area = gtk_drawing_area_new();
1361         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1362                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1363         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1364         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1365                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1366         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1367                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1368         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1369         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1370                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1371         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1372                                         ge->graphs.drawing_area);
1373         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1374
1375         setup_graphs(&ge->graphs);
1376
1377         /*
1378          * Set up alignments for widgets at the bottom of ui, 
1379          * align bottom left, expand horizontally but not vertically
1380          */
1381         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1382         ge->buttonbox = gtk_hbox_new(FALSE, 0);
1383         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1384         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1385
1386         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
1387
1388         /*
1389          * Set up thread status progress bar
1390          */
1391         ge->thread_status_pb = gtk_progress_bar_new();
1392         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1393         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1394         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1395
1396
1397         return main_vbox;
1398 }
1399
1400 static GtkWidget *new_main_page(struct gui *ui)
1401 {
1402         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1403         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1404
1405         main_vbox = gtk_vbox_new(FALSE, 3);
1406
1407         /*
1408          * Set up alignments for widgets at the top of ui,
1409          * align top left, expand horizontally but not vertically
1410          */
1411         top_align = gtk_alignment_new(0, 0, 1, 0);
1412         top_vbox = gtk_vbox_new(FALSE, 0);
1413         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1414         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1415
1416         probe = gtk_frame_new("Run statistics");
1417         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1418         probe_frame = gtk_vbox_new(FALSE, 3);
1419         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1420
1421         probe_box = gtk_hbox_new(FALSE, 3);
1422         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1423         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1424         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1425         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1426         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1427         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1428
1429         /*
1430          * Only add this if we have a commit rate
1431          */
1432 #if 0
1433         probe_box = gtk_hbox_new(FALSE, 3);
1434         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1435
1436         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1437         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1438
1439         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1440         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1441 #endif
1442
1443         /*
1444          * Set up a drawing area and IOPS and bandwidth graphs
1445          */
1446         ui->graphs.drawing_area = gtk_drawing_area_new();
1447         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1448                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1449         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1450         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1451                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1452         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1453                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
1454         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1455         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1456                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1457         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1458                                         ui->graphs.drawing_area);
1459         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1460                         TRUE, TRUE, 0);
1461
1462         setup_graphs(&ui->graphs);
1463
1464         /*
1465          * Set up alignments for widgets at the bottom of ui, 
1466          * align bottom left, expand horizontally but not vertically
1467          */
1468         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1469         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1470         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1471         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1472
1473         /*
1474          * Set up thread status progress bar
1475          */
1476         ui->thread_status_pb = gtk_progress_bar_new();
1477         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1478         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1479         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1480
1481         return main_vbox;
1482 }
1483
1484 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1485                                      guint page, gpointer data)
1486
1487 {
1488         struct gui *ui = (struct gui *) data;
1489         struct gui_entry *ge;
1490
1491         if (!page) {
1492                 set_job_menu_visible(ui, 0);
1493                 set_view_results_visible(ui, 0);
1494                 return TRUE;
1495         }
1496
1497         set_job_menu_visible(ui, 1);
1498         ge = get_ge_from_page(ui, page, NULL);
1499         if (ge)
1500                 update_button_states(ui, ge);
1501
1502         return TRUE;
1503 }
1504
1505 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1506 {
1507         time_t time_a = gtk_recent_info_get_visited(a);
1508         time_t time_b = gtk_recent_info_get_visited(b);
1509
1510         return time_b - time_a;
1511 }
1512
1513 static void add_recent_file_items(struct gui *ui)
1514 {
1515         const gchar *gfio = g_get_application_name();
1516         GList *items, *item;
1517         int i = 0;
1518
1519         if (ui->recent_ui_id) {
1520                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1521                 gtk_ui_manager_ensure_update(ui->uimanager);
1522         }
1523         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1524
1525         if (ui->actiongroup) {
1526                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1527                 g_object_unref(ui->actiongroup);
1528         }
1529         ui->actiongroup = gtk_action_group_new("RecentFileActions");
1530
1531         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1532
1533         items = gtk_recent_manager_get_items(ui->recentmanager);
1534         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1535
1536         for (item = items; item && item->data; item = g_list_next(item)) {
1537                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1538                 gchar *action_name;
1539                 const gchar *label;
1540                 GtkAction *action;
1541
1542                 if (!gtk_recent_info_has_application(info, gfio))
1543                         continue;
1544
1545                 /*
1546                  * We only support local files for now
1547                  */
1548                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1549                         continue;
1550
1551                 action_name = g_strdup_printf("RecentFile%u", i++);
1552                 label = gtk_recent_info_get_display_name(info);
1553
1554                 action = g_object_new(GTK_TYPE_ACTION,
1555                                         "name", action_name,
1556                                         "label", label, NULL);
1557
1558                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1559                                         gtk_recent_info_ref(info),
1560                                         (GDestroyNotify) gtk_recent_info_unref);
1561
1562
1563                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1564
1565                 gtk_action_group_add_action(ui->actiongroup, action);
1566                 g_object_unref(action);
1567
1568                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1569                                         "/MainMenu/FileMenu/FileRecentFiles",
1570                                         label, action_name,
1571                                         GTK_UI_MANAGER_MENUITEM, FALSE);
1572
1573                 g_free(action_name);
1574
1575                 if (i == 8)
1576                         break;
1577         }
1578
1579         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1580         g_list_free(items);
1581 }
1582
1583 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1584                                    gint x, gint y, GtkSelectionData *seldata,
1585                                    guint info, guint time, gpointer *data)
1586 {
1587         struct gui *ui = (struct gui *) data;
1588         gchar **uris;
1589         GtkWidget *source;
1590
1591         source = gtk_drag_get_source_widget(ctx);
1592         if (source && widget == gtk_widget_get_toplevel(source)) {
1593                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1594                 return;
1595         }
1596
1597         uris = gtk_selection_data_get_uris(seldata);
1598         if (!uris) {
1599                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1600                 return;
1601         }
1602
1603         if (uris[0])
1604                 do_file_open_with_tab(ui, uris[0]);
1605
1606         gtk_drag_finish(ctx, TRUE, FALSE, time);
1607         g_strfreev(uris);
1608 }
1609
1610 static void init_ui(int *argc, char **argv[], struct gui *ui)
1611 {
1612         GtkSettings *settings;
1613         GtkWidget *vbox;
1614
1615         /* Magical g*thread incantation, you just need this thread stuff.
1616          * Without it, the update that happens in gfio_update_thread_status
1617          * doesn't really happen in a timely fashion, you need expose events
1618          */
1619         if (!g_thread_supported())
1620                 g_thread_init(NULL);
1621         gdk_threads_init();
1622
1623         gtk_init(argc, argv);
1624         settings = gtk_settings_get_default();
1625         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1626         g_type_init();
1627         gdk_color_parse("white", &gfio_color_white);
1628         
1629         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1630         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1631         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1632
1633         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1634         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1635
1636         ui->vbox = gtk_vbox_new(FALSE, 0);
1637         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1638
1639         ui->uimanager = gtk_ui_manager_new();
1640         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1641         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1642
1643         ui->recentmanager = gtk_recent_manager_get_default();
1644         add_recent_file_items(ui);
1645
1646         ui->notebook = gtk_notebook_new();
1647         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1648         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1649         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1650         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1651
1652         vbox = new_main_page(ui);
1653         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1654         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1655         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1656
1657         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1658
1659         gfio_ui_setup_log(ui);
1660
1661         gtk_widget_show_all(ui->window);
1662 }
1663
1664 int main(int argc, char *argv[], char *envp[])
1665 {
1666         if (initialize_fio(envp))
1667                 return 1;
1668         if (fio_init_options())
1669                 return 1;
1670
1671         memset(&main_ui, 0, sizeof(main_ui));
1672         main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1673
1674         init_ui(&argc, &argv, &main_ui);
1675
1676         gdk_threads_enter();
1677         gtk_main();
1678         gdk_threads_leave();
1679
1680         g_hash_table_destroy(main_ui.ge_hash);
1681         return 0;
1682 }