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