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