Fix reversal of IOPS and BW graphs
[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 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_limits(struct gfio_graphs *g)
1051 {
1052         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1053         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1054 }
1055
1056 static void ge_update_lim_fn(gpointer key, gpointer value, gpointer data)
1057 {
1058         struct gui_entry *ge = (struct gui_entry *) value;
1059
1060         __update_graph_limits(&ge->graphs);
1061 }
1062
1063 static void update_graph_limits(void)
1064 {
1065         struct gui *ui = &main_ui;
1066
1067         __update_graph_limits(&ui->graphs);
1068
1069         g_hash_table_foreach(ui->ge_hash, ge_update_lim_fn, NULL);
1070 }
1071
1072 static void preferences(GtkWidget *w, gpointer data)
1073 {
1074         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1075         GtkWidget *hbox, *spin, *entry, *spin_int;
1076         struct gui *ui = (struct gui *) data;
1077         int i;
1078
1079         dialog = gtk_dialog_new_with_buttons("Preferences",
1080                 GTK_WINDOW(ui->window),
1081                 GTK_DIALOG_DESTROY_WITH_PARENT,
1082                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1083                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1084                 NULL);
1085
1086         frame = gtk_frame_new("Graphing");
1087         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1088         vbox = gtk_vbox_new(FALSE, 6);
1089         gtk_container_add(GTK_CONTAINER(frame), vbox);
1090
1091         hbox = gtk_hbox_new(FALSE, 5);
1092         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1093         entry = gtk_label_new("Font face to use for graph labels");
1094         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1095
1096         font = gtk_font_button_new();
1097         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1098
1099         box = gtk_vbox_new(FALSE, 6);
1100         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1101
1102         hbox = gtk_hbox_new(FALSE, 5);
1103         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1104         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1105         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1106
1107         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1108
1109         box = gtk_vbox_new(FALSE, 6);
1110         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1111
1112         hbox = gtk_hbox_new(FALSE, 5);
1113         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1114         entry = gtk_label_new("Client ETA request interval (msec)");
1115         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1116
1117         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1118         frame = gtk_frame_new("Debug logging");
1119         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1120         vbox = gtk_vbox_new(FALSE, 6);
1121         gtk_container_add(GTK_CONTAINER(frame), vbox);
1122
1123         box = gtk_hbox_new(FALSE, 6);
1124         gtk_container_add(GTK_CONTAINER(vbox), box);
1125
1126         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1127
1128         for (i = 0; i < FD_DEBUG_MAX; i++) {
1129                 if (i == 7) {
1130                         box = gtk_hbox_new(FALSE, 6);
1131                         gtk_container_add(GTK_CONTAINER(vbox), box);
1132                 }
1133
1134
1135                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1136                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1137                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1138         }
1139
1140         gtk_widget_show_all(dialog);
1141
1142         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1143                 gtk_widget_destroy(dialog);
1144                 return;
1145         }
1146
1147         for (i = 0; i < FD_DEBUG_MAX; i++) {
1148                 int set;
1149
1150                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1151                 if (set)
1152                         fio_debug |= (1UL << i);
1153         }
1154
1155         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1156         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1157         update_graph_limits();
1158         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1159
1160         gtk_widget_destroy(dialog);
1161 }
1162
1163 static void about_dialog(GtkWidget *w, gpointer data)
1164 {
1165         const char *authors[] = {
1166                 "Jens Axboe <axboe@kernel.dk>",
1167                 "Stephen Carmeron <stephenmcameron@gmail.com>",
1168                 NULL
1169         };
1170         const char *license[] = {
1171                 "Fio is free software; you can redistribute it and/or modify "
1172                 "it under the terms of the GNU General Public License as published by "
1173                 "the Free Software Foundation; either version 2 of the License, or "
1174                 "(at your option) any later version.\n",
1175                 "Fio is distributed in the hope that it will be useful, "
1176                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1177                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
1178                 "GNU General Public License for more details.\n",
1179                 "You should have received a copy of the GNU General Public License "
1180                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1181                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
1182         };
1183         char *license_trans;
1184
1185         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1186                                      license[2], "\n", NULL);
1187
1188         gtk_show_about_dialog(NULL,
1189                 "program-name", "gfio",
1190                 "comments", "Gtk2 UI for fio",
1191                 "license", license_trans,
1192                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1193                 "authors", authors,
1194                 "version", fio_version_string,
1195                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1196                 "logo-icon-name", "fio",
1197                 /* Must be last: */
1198                 "wrap-license", TRUE,
1199                 NULL);
1200
1201         g_free(license_trans);
1202 }
1203
1204 static GtkActionEntry menu_items[] = {
1205         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1206         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1207         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1208         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1209         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1210         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1211         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1212         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1213         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1214         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1215         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1216         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1217         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1218         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1219         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1220         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1221         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1222 };
1223 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1224
1225 static const gchar *ui_string = " \
1226         <ui> \
1227                 <menubar name=\"MainMenu\"> \
1228                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1229                                 <menuitem name=\"New\" action=\"NewFile\" /> \
1230                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1231                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1232                                 <separator name=\"Separator1\"/> \
1233                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1234                                 <separator name=\"Separator2\"/> \
1235                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1236                                 <separator name=\"Separator3\"/> \
1237                                 <placeholder name=\"FileRecentFiles\"/> \
1238                                 <separator name=\"Separator4\"/> \
1239                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1240                         </menu> \
1241                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1242                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1243                                 <separator name=\"Separator5\"/> \
1244                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1245                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1246                                 <separator name=\"Separator6\"/> \
1247                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1248                         </menu>\
1249                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1250                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1251                                 <separator name=\"Separator7\"/> \
1252                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1253                         </menu>\
1254                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1255                                 <menuitem name=\"About\" action=\"About\" /> \
1256                         </menu> \
1257                 </menubar> \
1258         </ui> \
1259 ";
1260
1261 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1262                                    struct gui *ui)
1263 {
1264         GtkActionGroup *action_group;
1265         GError *error = 0;
1266
1267         action_group = gtk_action_group_new("Menu");
1268         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1269
1270         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1271         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1272
1273         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1274
1275         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1276 }
1277
1278 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1279                    GtkWidget *vbox, GtkUIManager *ui_manager)
1280 {
1281         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1282 }
1283
1284 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1285 {
1286         struct gui_entry *ge = (struct gui_entry *) data;
1287         gint index;
1288
1289         index = gtk_combo_box_get_active(box);
1290
1291         multitext_set_entry(&ge->eta.iotype, index);
1292         multitext_set_entry(&ge->eta.bs, index);
1293         multitext_set_entry(&ge->eta.ioengine, index);
1294         multitext_set_entry(&ge->eta.iodepth, index);
1295 }
1296
1297 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1298 {
1299         struct gui_entry *ge = (struct gui_entry *) data;
1300
1301         multitext_free(&ge->eta.iotype);
1302         multitext_free(&ge->eta.bs);
1303         multitext_free(&ge->eta.ioengine);
1304         multitext_free(&ge->eta.iodepth);
1305 }
1306
1307 static GtkWidget *new_client_page(struct gui_entry *ge)
1308 {
1309         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1310         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1311
1312         main_vbox = gtk_vbox_new(FALSE, 3);
1313
1314         top_align = gtk_alignment_new(0, 0, 1, 0);
1315         top_vbox = gtk_vbox_new(FALSE, 3);
1316         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1317         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1318
1319         probe = gtk_frame_new("Job");
1320         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1321         probe_frame = gtk_vbox_new(FALSE, 3);
1322         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1323
1324         probe_box = gtk_hbox_new(FALSE, 3);
1325         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1326         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1327         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1328         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1329         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1330
1331         probe_box = gtk_hbox_new(FALSE, 3);
1332         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1333
1334         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1335         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1336         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1337         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1338         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1339         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1340         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1341         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1342         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1343
1344         probe_box = gtk_hbox_new(FALSE, 3);
1345         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1346         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1347         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1348         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1349         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1350
1351         /*
1352          * Only add this if we have a commit rate
1353          */
1354 #if 0
1355         probe_box = gtk_hbox_new(FALSE, 3);
1356         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1357
1358         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1359         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1360
1361         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1362         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1363 #endif
1364
1365         /*
1366          * Set up a drawing area and IOPS and bandwidth graphs
1367          */
1368         ge->graphs.drawing_area = gtk_drawing_area_new();
1369         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1370                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1371         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1372         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1373                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1374         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1375                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1376         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1377         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1378                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1379         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1380                                         ge->graphs.drawing_area);
1381         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1382
1383         setup_graphs(&ge->graphs);
1384
1385         /*
1386          * Set up alignments for widgets at the bottom of ui, 
1387          * align bottom left, expand horizontally but not vertically
1388          */
1389         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1390         ge->buttonbox = gtk_hbox_new(FALSE, 0);
1391         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1392         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1393
1394         add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1395
1396         /*
1397          * Set up thread status progress bar
1398          */
1399         ge->thread_status_pb = gtk_progress_bar_new();
1400         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1401         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1402         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1403
1404
1405         return main_vbox;
1406 }
1407
1408 static GtkWidget *new_main_page(struct gui *ui)
1409 {
1410         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1411         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1412
1413         main_vbox = gtk_vbox_new(FALSE, 3);
1414
1415         /*
1416          * Set up alignments for widgets at the top of ui,
1417          * align top left, expand horizontally but not vertically
1418          */
1419         top_align = gtk_alignment_new(0, 0, 1, 0);
1420         top_vbox = gtk_vbox_new(FALSE, 0);
1421         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1422         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1423
1424         probe = gtk_frame_new("Run statistics");
1425         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1426         probe_frame = gtk_vbox_new(FALSE, 3);
1427         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1428
1429         probe_box = gtk_hbox_new(FALSE, 3);
1430         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1431         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1432         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1433         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1434         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1435         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1436
1437         /*
1438          * Only add this if we have a commit rate
1439          */
1440 #if 0
1441         probe_box = gtk_hbox_new(FALSE, 3);
1442         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1443
1444         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1445         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1446
1447         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1448         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1449 #endif
1450
1451         /*
1452          * Set up a drawing area and IOPS and bandwidth graphs
1453          */
1454         ui->graphs.drawing_area = gtk_drawing_area_new();
1455         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1456                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1457         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1458         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1459                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1460         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1461                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
1462         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1463         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1464                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1465         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1466                                         ui->graphs.drawing_area);
1467         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1468                         TRUE, TRUE, 0);
1469
1470         setup_graphs(&ui->graphs);
1471
1472         /*
1473          * Set up alignments for widgets at the bottom of ui, 
1474          * align bottom left, expand horizontally but not vertically
1475          */
1476         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1477         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1478         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1479         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1480
1481         /*
1482          * Set up thread status progress bar
1483          */
1484         ui->thread_status_pb = gtk_progress_bar_new();
1485         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1486         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1487         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1488
1489         return main_vbox;
1490 }
1491
1492 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1493                                      guint page, gpointer data)
1494
1495 {
1496         struct gui *ui = (struct gui *) data;
1497         struct gui_entry *ge;
1498
1499         if (!page) {
1500                 set_job_menu_visible(ui, 0);
1501                 set_view_results_visible(ui, 0);
1502                 return TRUE;
1503         }
1504
1505         set_job_menu_visible(ui, 1);
1506         ge = get_ge_from_page(ui, page, NULL);
1507         if (ge)
1508                 update_button_states(ui, ge);
1509
1510         return TRUE;
1511 }
1512
1513 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1514 {
1515         time_t time_a = gtk_recent_info_get_visited(a);
1516         time_t time_b = gtk_recent_info_get_visited(b);
1517
1518         return time_b - time_a;
1519 }
1520
1521 static void add_recent_file_items(struct gui *ui)
1522 {
1523         const gchar *gfio = g_get_application_name();
1524         GList *items, *item;
1525         int i = 0;
1526
1527         if (ui->recent_ui_id) {
1528                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1529                 gtk_ui_manager_ensure_update(ui->uimanager);
1530         }
1531         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1532
1533         if (ui->actiongroup) {
1534                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1535                 g_object_unref(ui->actiongroup);
1536         }
1537         ui->actiongroup = gtk_action_group_new("RecentFileActions");
1538
1539         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1540
1541         items = gtk_recent_manager_get_items(ui->recentmanager);
1542         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1543
1544         for (item = items; item && item->data; item = g_list_next(item)) {
1545                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1546                 gchar *action_name;
1547                 const gchar *label;
1548                 GtkAction *action;
1549
1550                 if (!gtk_recent_info_has_application(info, gfio))
1551                         continue;
1552
1553                 /*
1554                  * We only support local files for now
1555                  */
1556                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1557                         continue;
1558
1559                 action_name = g_strdup_printf("RecentFile%u", i++);
1560                 label = gtk_recent_info_get_display_name(info);
1561
1562                 action = g_object_new(GTK_TYPE_ACTION,
1563                                         "name", action_name,
1564                                         "label", label, NULL);
1565
1566                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1567                                         gtk_recent_info_ref(info),
1568                                         (GDestroyNotify) gtk_recent_info_unref);
1569
1570
1571                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1572
1573                 gtk_action_group_add_action(ui->actiongroup, action);
1574                 g_object_unref(action);
1575
1576                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1577                                         "/MainMenu/FileMenu/FileRecentFiles",
1578                                         label, action_name,
1579                                         GTK_UI_MANAGER_MENUITEM, FALSE);
1580
1581                 g_free(action_name);
1582
1583                 if (i == 8)
1584                         break;
1585         }
1586
1587         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1588         g_list_free(items);
1589 }
1590
1591 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1592                                    gint x, gint y, GtkSelectionData *seldata,
1593                                    guint info, guint time, gpointer *data)
1594 {
1595         struct gui *ui = (struct gui *) data;
1596         gchar **uris;
1597         GtkWidget *source;
1598
1599         source = gtk_drag_get_source_widget(ctx);
1600         if (source && widget == gtk_widget_get_toplevel(source)) {
1601                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1602                 return;
1603         }
1604
1605         uris = gtk_selection_data_get_uris(seldata);
1606         if (!uris) {
1607                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1608                 return;
1609         }
1610
1611         if (uris[0])
1612                 do_file_open_with_tab(ui, uris[0]);
1613
1614         gtk_drag_finish(ctx, TRUE, FALSE, time);
1615         g_strfreev(uris);
1616 }
1617
1618 static void init_ui(int *argc, char **argv[], struct gui *ui)
1619 {
1620         GtkSettings *settings;
1621         GtkWidget *vbox;
1622
1623         /* Magical g*thread incantation, you just need this thread stuff.
1624          * Without it, the update that happens in gfio_update_thread_status
1625          * doesn't really happen in a timely fashion, you need expose events
1626          */
1627         if (!g_thread_supported())
1628                 g_thread_init(NULL);
1629         gdk_threads_init();
1630
1631         gtk_init(argc, argv);
1632         settings = gtk_settings_get_default();
1633         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1634         g_type_init();
1635         gdk_color_parse("white", &gfio_color_white);
1636         
1637         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1638         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1639         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1640
1641         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1642         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1643
1644         ui->vbox = gtk_vbox_new(FALSE, 0);
1645         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1646
1647         ui->uimanager = gtk_ui_manager_new();
1648         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1649         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1650
1651         ui->recentmanager = gtk_recent_manager_get_default();
1652         add_recent_file_items(ui);
1653
1654         ui->notebook = gtk_notebook_new();
1655         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1656         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1657         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1658         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1659
1660         vbox = new_main_page(ui);
1661         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1662         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1663         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1664
1665         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1666
1667         gfio_ui_setup_log(ui);
1668
1669         gtk_widget_show_all(ui->window);
1670 }
1671
1672 int main(int argc, char *argv[], char *envp[])
1673 {
1674         if (initialize_fio(envp))
1675                 return 1;
1676         if (fio_init_options())
1677                 return 1;
1678
1679         memset(&main_ui, 0, sizeof(main_ui));
1680         main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1681
1682         init_ui(&argc, &argv, &main_ui);
1683
1684         gdk_threads_enter();
1685         gtk_main();
1686         gdk_threads_leave();
1687
1688         g_hash_table_destroy(main_ui.ge_hash);
1689         return 0;
1690 }