goptions: don't emit change signals during setup
[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 "graph.h"
37
38 static int gfio_server_running;
39 static const char *gfio_graph_font;
40 static unsigned int gfio_graph_limit = 100;
41 static GdkColor white;
42
43 static void view_log(GtkWidget *w, gpointer data);
44
45 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
46
47 static void connect_clicked(GtkWidget *widget, gpointer data);
48 static void start_job_clicked(GtkWidget *widget, gpointer data);
49 static void send_clicked(GtkWidget *widget, gpointer data);
50
51 static struct button_spec {
52         const char *buttontext;
53         clickfunction f;
54         const char *tooltiptext[2];
55         const int start_sensitive;
56 } buttonspeclist[] = {
57         {
58           .buttontext           = "Connect",
59           .f                    = connect_clicked,
60           .tooltiptext          = { "Disconnect from host", "Connect to host" },
61           .start_sensitive      = 1,
62         },
63         {
64           .buttontext           = "Send",
65           .f                    = send_clicked,
66           .tooltiptext          = { "Send job description to host", NULL },
67           .start_sensitive      = 0,
68         },
69         {
70           .buttontext           = "Start Job",
71           .f                    = start_job_clicked,
72           .tooltiptext          = { "Start the current job on the server", NULL },
73           .start_sensitive      = 0,
74         },
75 };
76
77 static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc);
78 static void gfio_update_thread_status_all(char *status_message, double perc);
79 static void report_error(GError *error);
80
81 static struct graph *setup_iops_graph(void)
82 {
83         struct graph *g;
84
85         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
86         graph_title(g, "IOPS (IOs/sec)");
87         graph_x_title(g, "Time (secs)");
88         graph_add_label(g, "Read IOPS");
89         graph_add_label(g, "Write IOPS");
90         graph_set_color(g, "Read IOPS", 0.13, 0.54, 0.13);
91         graph_set_color(g, "Write IOPS", 1.0, 0.0, 0.0);
92         line_graph_set_data_count_limit(g, gfio_graph_limit);
93         graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
94         return g;
95 }
96
97 static struct graph *setup_bandwidth_graph(void)
98 {
99         struct graph *g;
100
101         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
102         graph_title(g, "Bandwidth (bytes/sec)");
103         graph_x_title(g, "Time (secs)");
104         graph_add_label(g, "Read Bandwidth");
105         graph_add_label(g, "Write Bandwidth");
106         graph_set_color(g, "Read Bandwidth", 0.13, 0.54, 0.13);
107         graph_set_color(g, "Write Bandwidth", 1.0, 0.0, 0.0);
108         graph_set_base_offset(g, 1);
109         line_graph_set_data_count_limit(g, 100);
110         graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
111         return g;
112 }
113
114 static void setup_graphs(struct gfio_graphs *g)
115 {
116         g->iops_graph = setup_iops_graph();
117         g->bandwidth_graph = setup_bandwidth_graph();
118 }
119
120 static void clear_ge_ui_info(struct gui_entry *ge)
121 {
122         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
123         gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
124         gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
125         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
126 #if 0
127         /* should we empty it... */
128         gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
129 #endif
130         multitext_update_entry(&ge->eta.iotype, 0, "");
131         multitext_update_entry(&ge->eta.bs, 0, "");
132         multitext_update_entry(&ge->eta.ioengine, 0, "");
133         multitext_update_entry(&ge->eta.iodepth, 0, "");
134         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
135         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
136         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
137         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
138         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
139         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
140 }
141
142 static void show_info_dialog(struct gui *ui, const char *title,
143                              const char *message)
144 {
145         GtkWidget *dialog, *content, *label;
146
147         dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window),
148                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
149                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
150
151         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
152         label = gtk_label_new(message);
153         gtk_container_add(GTK_CONTAINER(content), label);
154         gtk_widget_show_all(dialog);
155         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
156         gtk_dialog_run(GTK_DIALOG(dialog));
157         gtk_widget_destroy(dialog);
158 }
159
160 static void set_menu_entry_text(struct gui *ui, const char *path,
161                                 const char *text)
162 {
163         GtkWidget *w;
164
165         w = gtk_ui_manager_get_widget(ui->uimanager, path);
166         if (w)
167                 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
168         else
169                 fprintf(stderr, "gfio: can't find path %s\n", path);
170 }
171
172
173 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
174 {
175         GtkWidget *w;
176
177         w = gtk_ui_manager_get_widget(ui->uimanager, path);
178         if (w)
179                 gtk_widget_set_sensitive(w, show);
180         else
181                 fprintf(stderr, "gfio: can't find path %s\n", path);
182 }
183
184 static void set_job_menu_visible(struct gui *ui, int visible)
185 {
186         set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
187 }
188
189 static void set_view_results_visible(struct gui *ui, int visible)
190 {
191         set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
192 }
193
194 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
195 {
196         if (s->tooltiptext[sensitive])
197                 return s->tooltiptext[sensitive];
198
199         return s->tooltiptext[0];
200 }
201
202 static GtkWidget *add_button(GtkWidget *buttonbox,
203                              struct button_spec *buttonspec, gpointer data)
204 {
205         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
206         gboolean sens = buttonspec->start_sensitive;
207
208         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
209         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
210
211         sens = buttonspec->start_sensitive;
212         gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
213         gtk_widget_set_sensitive(button, sens);
214
215         return button;
216 }
217
218 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
219                         int nbuttons)
220 {
221         int i;
222
223         for (i = 0; i < nbuttons; i++)
224                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
225 }
226
227 /*
228  * Update sensitivity of job buttons and job menu items, based on the
229  * state of the client.
230  */
231 static void update_button_states(struct gui *ui, struct gui_entry *ge)
232 {
233         unsigned int connect_state, send_state, start_state, edit_state;
234         const char *connect_str = NULL;
235
236         switch (ge->state) {
237         default: {
238                 char tmp[80];
239
240                 sprintf(tmp, "Bad client state: %u\n", ge->state);
241                 show_info_dialog(ui, "Error", tmp);
242                 /* fall through to new state */
243                 }
244
245         case GE_STATE_NEW:
246                 connect_state = 1;
247                 edit_state = 1;
248                 connect_str = "Connect";
249                 send_state = 0;
250                 start_state = 0;
251                 break;
252         case GE_STATE_CONNECTED:
253                 connect_state = 1;
254                 edit_state = 1;
255                 connect_str = "Disconnect";
256                 send_state = 1;
257                 start_state = 0;
258                 break;
259         case GE_STATE_JOB_SENT:
260                 connect_state = 1;
261                 edit_state = 1;
262                 connect_str = "Disconnect";
263                 send_state = 0;
264                 start_state = 1;
265                 break;
266         case GE_STATE_JOB_STARTED:
267                 connect_state = 1;
268                 edit_state = 1;
269                 connect_str = "Disconnect";
270                 send_state = 0;
271                 start_state = 1;
272                 break;
273         case GE_STATE_JOB_RUNNING:
274                 connect_state = 1;
275                 edit_state = 0;
276                 connect_str = "Disconnect";
277                 send_state = 0;
278                 start_state = 0;
279                 break;
280         case GE_STATE_JOB_DONE:
281                 connect_state = 1;
282                 edit_state = 0;
283                 connect_str = "Connect";
284                 send_state = 0;
285                 start_state = 0;
286                 break;
287         }
288
289         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
290         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
291         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
292         gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
293         gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
294
295         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
296         set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
297
298         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
299         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
300         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
301
302         if (ge->client && ge->client->nr_results)
303                 set_view_results_visible(ui, 1);
304         else
305                 set_view_results_visible(ui, 0);
306 }
307
308 static void gfio_set_state(struct gui_entry *ge, unsigned int state)
309 {
310         ge->state = state;
311         update_button_states(ge->ui, ge);
312 }
313
314 static void gfio_ui_setup_log(struct gui *ui)
315 {
316         GtkTreeSelection *selection;
317         GtkListStore *model;
318         GtkWidget *tree_view;
319
320         model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
321
322         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
323         gtk_widget_set_can_focus(tree_view, FALSE);
324
325         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
326         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
327         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
328                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
329
330         tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
331         tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
332         tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
333         tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
334
335         ui->log_model = model;
336         ui->log_tree = tree_view;
337 }
338
339 static struct graph *setup_clat_graph(char *title, unsigned int *ovals,
340                                       fio_fp64_t *plist,
341                                       unsigned int len,
342                                       double xdim, double ydim)
343 {
344         struct graph *g;
345         int i;
346
347         g = graph_new(xdim, ydim, gfio_graph_font);
348         graph_title(g, title);
349         graph_x_title(g, "Percentile");
350         graph_y_title(g, "Time");
351
352         for (i = 0; i < len; i++) {
353                 char fbuf[8];
354
355                 sprintf(fbuf, "%2.2f%%", plist[i].u.f);
356                 graph_add_label(g, fbuf);
357                 graph_add_data(g, fbuf, (double) ovals[i]);
358         }
359
360         return g;
361 }
362
363 static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
364                                                fio_fp64_t *plist,
365                                                unsigned int len,
366                                                const char *base,
367                                                unsigned int scale)
368 {
369         GType types[FIO_IO_U_LIST_MAX_LEN];
370         GtkWidget *tree_view;
371         GtkTreeSelection *selection;
372         GtkListStore *model;
373         GtkTreeIter iter;
374         int i;
375
376         for (i = 0; i < len; i++)
377                 types[i] = G_TYPE_INT;
378
379         model = gtk_list_store_newv(len, types);
380
381         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
382         gtk_widget_set_can_focus(tree_view, FALSE);
383
384         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
385                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
386
387         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
388         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
389
390         for (i = 0; i < len; i++) {
391                 char fbuf[8];
392
393                 sprintf(fbuf, "%2.2f%%", plist[i].u.f);
394                 tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
395         }
396
397         gtk_list_store_append(model, &iter);
398
399         for (i = 0; i < len; i++) {
400                 if (scale)
401                         ovals[i] = (ovals[i] + 999) / 1000;
402                 gtk_list_store_set(model, &iter, i, ovals[i], -1);
403         }
404
405         return tree_view;
406 }
407
408 static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
409 {
410         struct graph *g = p;
411         cairo_t *cr;
412
413         cr = gdk_cairo_create(w->window);
414 #if 0
415         if (graph_has_tooltips(g)) {
416                 g_object_set(w, "has-tooltip", TRUE, NULL);
417                 g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g);
418         }
419 #endif
420         cairo_set_source_rgb(cr, 0, 0, 0);
421         bar_graph_draw(g, cr);
422         cairo_destroy(cr);
423
424         return FALSE;
425 }
426
427 static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event,
428                                        gpointer data)
429 {
430         struct graph *g = data;
431
432         graph_set_size(g, w->allocation.width, w->allocation.height);
433         graph_set_size(g, w->allocation.width, w->allocation.height);
434         graph_set_position(g, 0, 0);
435         return TRUE;
436 }
437
438 static void gfio_show_clat_percentiles(struct gfio_client *gc,
439                                        GtkWidget *vbox, struct thread_stat *ts,
440                                        int ddir)
441 {
442         unsigned int *io_u_plat = ts->io_u_plat[ddir];
443         unsigned long nr = ts->clat_stat[ddir].samples;
444         fio_fp64_t *plist = ts->percentile_list;
445         unsigned int *ovals, len, minv, maxv, scale_down;
446         const char *base;
447         GtkWidget *tree_view, *frame, *hbox, *drawing_area, *completion_vbox;
448         struct gui_entry *ge = gc->ge;
449         char tmp[64];
450
451         len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
452         if (!len)
453                 goto out;
454
455         /*
456          * We default to usecs, but if the value range is such that we
457          * should scale down to msecs, do that.
458          */
459         if (minv > 2000 && maxv > 99999) {
460                 scale_down = 1;
461                 base = "msec";
462         } else {
463                 scale_down = 0;
464                 base = "usec";
465         }
466
467         sprintf(tmp, "Completion percentiles (%s)", base);
468         tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
469         ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0);
470
471         frame = gtk_frame_new(tmp);
472         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
473
474         completion_vbox = gtk_vbox_new(FALSE, 3);
475         gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
476         hbox = gtk_hbox_new(FALSE, 3);
477         gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
478         drawing_area = gtk_drawing_area_new();
479         gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
480         gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &white);
481         gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
482         g_signal_connect(G_OBJECT(drawing_area), "expose_event", G_CALLBACK(on_expose_lat_drawing_area), ge->clat_graph);
483         g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->clat_graph);
484
485         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
486 out:
487         if (ovals)
488                 free(ovals);
489 }
490
491 static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
492                           unsigned long max, double mean, double dev)
493 {
494         const char *base = "(usec)";
495         GtkWidget *hbox, *label, *frame;
496         char *minp, *maxp;
497         char tmp[64];
498
499         if (!usec_to_msec(&min, &max, &mean, &dev))
500                 base = "(msec)";
501
502         minp = num2str(min, 6, 1, 0);
503         maxp = num2str(max, 6, 1, 0);
504
505         sprintf(tmp, "%s %s", name, base);
506         frame = gtk_frame_new(tmp);
507         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
508
509         hbox = gtk_hbox_new(FALSE, 3);
510         gtk_container_add(GTK_CONTAINER(frame), hbox);
511
512         label = new_info_label_in_frame(hbox, "Minimum");
513         gtk_label_set_text(GTK_LABEL(label), minp);
514         label = new_info_label_in_frame(hbox, "Maximum");
515         gtk_label_set_text(GTK_LABEL(label), maxp);
516         label = new_info_label_in_frame(hbox, "Average");
517         sprintf(tmp, "%5.02f", mean);
518         gtk_label_set_text(GTK_LABEL(label), tmp);
519         label = new_info_label_in_frame(hbox, "Standard deviation");
520         sprintf(tmp, "%5.02f", dev);
521         gtk_label_set_text(GTK_LABEL(label), tmp);
522
523         free(minp);
524         free(maxp);
525
526 }
527
528 #define GFIO_CLAT       1
529 #define GFIO_SLAT       2
530 #define GFIO_LAT        4
531
532 static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
533                                   struct group_run_stats *rs,
534                                   struct thread_stat *ts, int ddir)
535 {
536         const char *ddir_label[2] = { "Read", "Write" };
537         GtkWidget *frame, *label, *box, *vbox, *main_vbox;
538         unsigned long min[3], max[3], runt;
539         unsigned long long bw, iops;
540         unsigned int flags = 0;
541         double mean[3], dev[3];
542         char *io_p, *bw_p, *iops_p;
543         int i2p;
544
545         if (!ts->runtime[ddir])
546                 return;
547
548         i2p = is_power_of_2(rs->kb_base);
549         runt = ts->runtime[ddir];
550
551         bw = (1000 * ts->io_bytes[ddir]) / runt;
552         io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
553         bw_p = num2str(bw, 6, 1, i2p);
554
555         iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
556         iops_p = num2str(iops, 6, 1, 0);
557
558         box = gtk_hbox_new(FALSE, 3);
559         gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
560
561         frame = gtk_frame_new(ddir_label[ddir]);
562         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
563
564         main_vbox = gtk_vbox_new(FALSE, 3);
565         gtk_container_add(GTK_CONTAINER(frame), main_vbox);
566
567         box = gtk_hbox_new(FALSE, 3);
568         gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
569
570         label = new_info_label_in_frame(box, "IO");
571         gtk_label_set_text(GTK_LABEL(label), io_p);
572         label = new_info_label_in_frame(box, "Bandwidth");
573         gtk_label_set_text(GTK_LABEL(label), bw_p);
574         label = new_info_label_in_frame(box, "IOPS");
575         gtk_label_set_text(GTK_LABEL(label), iops_p);
576         label = new_info_label_in_frame(box, "Runtime (msec)");
577         label_set_int_value(label, ts->runtime[ddir]);
578
579         if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
580                 double p_of_agg = 100.0;
581                 const char *bw_str = "KB";
582                 char tmp[32];
583
584                 if (rs->agg[ddir]) {
585                         p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
586                         if (p_of_agg > 100.0)
587                                 p_of_agg = 100.0;
588                 }
589
590                 if (mean[0] > 999999.9) {
591                         min[0] /= 1000.0;
592                         max[0] /= 1000.0;
593                         mean[0] /= 1000.0;
594                         dev[0] /= 1000.0;
595                         bw_str = "MB";
596                 }
597
598                 sprintf(tmp, "Bandwidth (%s)", bw_str);
599                 frame = gtk_frame_new(tmp);
600                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
601
602                 box = gtk_hbox_new(FALSE, 3);
603                 gtk_container_add(GTK_CONTAINER(frame), box);
604
605                 label = new_info_label_in_frame(box, "Minimum");
606                 label_set_int_value(label, min[0]);
607                 label = new_info_label_in_frame(box, "Maximum");
608                 label_set_int_value(label, max[0]);
609                 label = new_info_label_in_frame(box, "Percentage of jobs");
610                 sprintf(tmp, "%3.2f%%", p_of_agg);
611                 gtk_label_set_text(GTK_LABEL(label), tmp);
612                 label = new_info_label_in_frame(box, "Average");
613                 sprintf(tmp, "%5.02f", mean[0]);
614                 gtk_label_set_text(GTK_LABEL(label), tmp);
615                 label = new_info_label_in_frame(box, "Standard deviation");
616                 sprintf(tmp, "%5.02f", dev[0]);
617                 gtk_label_set_text(GTK_LABEL(label), tmp);
618         }
619
620         if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
621                 flags |= GFIO_SLAT;
622         if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
623                 flags |= GFIO_CLAT;
624         if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
625                 flags |= GFIO_LAT;
626
627         if (flags) {
628                 frame = gtk_frame_new("Latency");
629                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
630
631                 vbox = gtk_vbox_new(FALSE, 3);
632                 gtk_container_add(GTK_CONTAINER(frame), vbox);
633
634                 if (flags & GFIO_SLAT)
635                         gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
636                 if (flags & GFIO_CLAT)
637                         gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
638                 if (flags & GFIO_LAT)
639                         gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
640         }
641
642         if (ts->clat_percentiles)
643                 gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
644
645         free(io_p);
646         free(bw_p);
647         free(iops_p);
648 }
649
650 static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
651                                             const char **labels,
652                                             unsigned int len,
653                                             double xdim, double ydim)
654 {
655         struct graph *g;
656         int i;
657
658         g = graph_new(xdim, ydim, gfio_graph_font);
659         graph_title(g, title);
660         graph_x_title(g, "Buckets");
661         graph_y_title(g, "Percent");
662
663         for (i = 0; i < len; i++) {
664                 graph_add_label(g, labels[i]);
665                 graph_add_data(g, labels[i], lat[i]);
666         }
667
668         return g;
669 }
670
671 static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels,
672                                           int num)
673 {
674         GtkWidget *tree_view;
675         GtkTreeSelection *selection;
676         GtkListStore *model;
677         GtkTreeIter iter;
678         GType *types;
679         int i;
680
681         types = malloc(num * sizeof(GType));
682
683         for (i = 0; i < num; i++)
684                 types[i] = G_TYPE_STRING;
685
686         model = gtk_list_store_newv(num, types);
687         free(types);
688         types = NULL;
689
690         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
691         gtk_widget_set_can_focus(tree_view, FALSE);
692
693         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
694                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
695
696         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
697         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
698
699         for (i = 0; i < num; i++)
700                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
701
702         gtk_list_store_append(model, &iter);
703
704         for (i = 0; i < num; i++) {
705                 char fbuf[32];
706
707                 if (lat[i] <= 0.0)
708                         sprintf(fbuf, "0.00");
709                 else
710                         sprintf(fbuf, "%3.2f%%", lat[i]);
711
712                 gtk_list_store_set(model, &iter, i, fbuf, -1);
713         }
714
715         return tree_view;
716 }
717
718 static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox,
719                                       struct thread_stat *ts)
720 {
721         double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR];
722         const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u",
723                                  "250u", "500u", "750u", "1m", "2m",
724                                  "4m", "10m", "20m", "50m", "100m",
725                                  "250m", "500m", "750m", "1s", "2s", ">= 2s" };
726         int start, end, i;
727         const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR;
728         GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area;
729         struct gui_entry *ge = gc->ge;
730
731         stat_calc_lat_u(ts, io_u_lat);
732         stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]);
733
734         /*
735          * Found out which first bucket has entries, and which last bucket
736          */
737         start = end = -1U;
738         for (i = 0; i < total; i++) {
739                 if (io_u_lat[i] == 0.00)
740                         continue;
741
742                 if (start == -1U)
743                         start = i;
744                 end = i;
745         }
746
747         /*
748          * No entries...
749          */
750         if (start == -1U)
751                 return;
752                 
753         tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1);
754         ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0);
755
756         frame = gtk_frame_new("Latency buckets");
757         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
758
759         completion_vbox = gtk_vbox_new(FALSE, 3);
760         gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
761         hbox = gtk_hbox_new(FALSE, 3);
762         gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
763
764         drawing_area = gtk_drawing_area_new();
765         gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
766         gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &white);
767         gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
768         g_signal_connect(G_OBJECT(drawing_area), "expose_event", G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph);
769         g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph);
770
771         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
772 }
773
774 static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
775 {
776         GtkWidget *box, *frame, *entry;
777         double usr_cpu, sys_cpu;
778         unsigned long runtime;
779         char tmp[32];
780
781         runtime = ts->total_run_time;
782         if (runtime) {
783                 double runt = (double) runtime;
784
785                 usr_cpu = (double) ts->usr_time * 100 / runt;
786                 sys_cpu = (double) ts->sys_time * 100 / runt;
787         } else {
788                 usr_cpu = 0;
789                 sys_cpu = 0;
790         }
791
792         frame = gtk_frame_new("OS resources");
793         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
794
795         box = gtk_hbox_new(FALSE, 3);
796         gtk_container_add(GTK_CONTAINER(frame), box);
797
798         entry = new_info_entry_in_frame(box, "User CPU");
799         sprintf(tmp, "%3.2f%%", usr_cpu);
800         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
801         entry = new_info_entry_in_frame(box, "System CPU");
802         sprintf(tmp, "%3.2f%%", sys_cpu);
803         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
804         entry = new_info_entry_in_frame(box, "Context switches");
805         entry_set_int_value(entry, ts->ctx);
806         entry = new_info_entry_in_frame(box, "Major faults");
807         entry_set_int_value(entry, ts->majf);
808         entry = new_info_entry_in_frame(box, "Minor faults");
809         entry_set_int_value(entry, ts->minf);
810 }
811 static void gfio_add_sc_depths_tree(GtkListStore *model,
812                                     struct thread_stat *ts, unsigned int len,
813                                     int submit)
814 {
815         double io_u_dist[FIO_IO_U_MAP_NR];
816         GtkTreeIter iter;
817         /* Bits 0, and 3-8 */
818         const int add_mask = 0x1f9;
819         int i, j;
820
821         if (submit)
822                 stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
823         else
824                 stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
825
826         gtk_list_store_append(model, &iter);
827
828         gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
829
830         for (i = 1, j = 0; i < len; i++) {
831                 char fbuf[32];
832
833                 if (!(add_mask & (1UL << (i - 1))))
834                         sprintf(fbuf, "0.0%%");
835                 else {
836                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
837                         j++;
838                 }
839
840                 gtk_list_store_set(model, &iter, i, fbuf, -1);
841         }
842
843 }
844
845 static void gfio_add_total_depths_tree(GtkListStore *model,
846                                        struct thread_stat *ts, unsigned int len)
847 {
848         double io_u_dist[FIO_IO_U_MAP_NR];
849         GtkTreeIter iter;
850         /* Bits 1-6, and 8 */
851         const int add_mask = 0x17e;
852         int i, j;
853
854         stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
855
856         gtk_list_store_append(model, &iter);
857
858         gtk_list_store_set(model, &iter, 0, "Total", -1);
859
860         for (i = 1, j = 0; i < len; i++) {
861                 char fbuf[32];
862
863                 if (!(add_mask & (1UL << (i - 1))))
864                         sprintf(fbuf, "0.0%%");
865                 else {
866                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
867                         j++;
868                 }
869
870                 gtk_list_store_set(model, &iter, i, fbuf, -1);
871         }
872
873 }
874
875 static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
876 {
877         GtkWidget *frame, *box, *tree_view;
878         GtkTreeSelection *selection;
879         GtkListStore *model;
880         GType types[FIO_IO_U_MAP_NR + 1];
881         int i;
882 #define NR_LABELS       10
883         const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
884
885         frame = gtk_frame_new("IO depths");
886         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
887
888         box = gtk_hbox_new(FALSE, 3);
889         gtk_container_add(GTK_CONTAINER(frame), box);
890
891         for (i = 0; i < NR_LABELS; i++)
892                 types[i] = G_TYPE_STRING;
893
894         model = gtk_list_store_newv(NR_LABELS, types);
895
896         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
897         gtk_widget_set_can_focus(tree_view, FALSE);
898
899         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
900                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
901
902         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
903         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
904
905         for (i = 0; i < NR_LABELS; i++)
906                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
907
908         gfio_add_total_depths_tree(model, ts, NR_LABELS);
909         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 1);
910         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 0);
911
912         gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
913 }
914
915 static gboolean results_window_delete(GtkWidget *w, gpointer data)
916 {
917         struct gui_entry *ge = (struct gui_entry *) data;
918
919         gtk_widget_destroy(w);
920         ge->results_window = NULL;
921         ge->results_notebook = NULL;
922         return TRUE;
923 }
924
925 static void results_close(GtkWidget *w, gpointer *data)
926 {
927         struct gui_entry *ge = (struct gui_entry *) data;
928
929         gtk_widget_destroy(ge->results_window);
930 }
931
932 static GtkActionEntry results_menu_items[] = {
933         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
934         { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
935         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(results_close) },
936 };
937 static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]);
938
939 static const gchar *results_ui_string = " \
940         <ui> \
941                 <menubar name=\"MainMenu\"> \
942                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
943                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
944                         </menu> \
945                         <menu name=\"GraphMenu\" action=\"GraphMenuAction\"> \
946                         </menu>\
947                 </menubar> \
948         </ui> \
949 ";
950
951 static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge)
952 {
953         GtkActionGroup *action_group;
954         GtkWidget *widget;
955         GError *error = 0;
956
957         ge->results_uimanager = gtk_ui_manager_new();
958
959         action_group = gtk_action_group_new("ResultsMenu");
960         gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge);
961
962         gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0);
963         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error);
964
965         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager));
966
967         widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu");
968         return widget;
969 }
970
971 static GtkWidget *get_results_window(struct gui_entry *ge)
972 {
973         GtkWidget *win, *notebook, *vbox;
974
975         if (ge->results_window)
976                 return ge->results_notebook;
977
978         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
979         gtk_window_set_title(GTK_WINDOW(win), "Results");
980         gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
981         g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
982         g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
983
984         vbox = gtk_vbox_new(FALSE, 0);
985         gtk_container_add(GTK_CONTAINER(win), vbox);
986
987         ge->results_menu = get_results_menubar(win, ge);
988         gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0);
989
990         notebook = gtk_notebook_new();
991         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
992         gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
993         gtk_container_add(GTK_CONTAINER(vbox), notebook);
994
995         ge->results_window = win;
996         ge->results_notebook = notebook;
997         return ge->results_notebook;
998 }
999
1000 static void disk_util_destroy(GtkWidget *w, gpointer data)
1001 {
1002         struct gui_entry *ge = (struct gui_entry *) data;
1003
1004         ge->disk_util_vbox = NULL;
1005         gtk_widget_destroy(w);
1006 }
1007
1008 static GtkWidget *get_scrolled_window(gint border_width)
1009 {
1010         GtkWidget *scroll;
1011
1012         scroll = gtk_scrolled_window_new(NULL, NULL);
1013         gtk_container_set_border_width(GTK_CONTAINER(scroll), border_width);
1014         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1015
1016         return scroll;
1017 }
1018
1019 static GtkWidget *gfio_disk_util_get_vbox(struct gui_entry *ge)
1020 {
1021         GtkWidget *vbox, *box, *scroll, *res_notebook;
1022
1023         if (ge->disk_util_vbox)
1024                 return ge->disk_util_vbox;
1025
1026         scroll = get_scrolled_window(5);
1027         vbox = gtk_vbox_new(FALSE, 3);
1028         box = gtk_hbox_new(FALSE, 0);
1029         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1030
1031         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1032         res_notebook = get_results_window(ge);
1033
1034         gtk_notebook_append_page(GTK_NOTEBOOK(res_notebook), scroll, gtk_label_new("Disk utilization"));
1035         ge->disk_util_vbox = box;
1036         g_signal_connect(vbox, "destroy", G_CALLBACK(disk_util_destroy), ge);
1037
1038         return ge->disk_util_vbox;
1039 }
1040
1041 static int __gfio_disk_util_show(GtkWidget *res_notebook,
1042                                  struct gfio_client *gc, struct cmd_du_pdu *p)
1043 {
1044         GtkWidget *box, *frame, *entry, *vbox, *util_vbox;
1045         struct gui_entry *ge = gc->ge;
1046         double util;
1047         char tmp[16];
1048
1049         util_vbox = gfio_disk_util_get_vbox(ge);
1050
1051         vbox = gtk_vbox_new(FALSE, 3);
1052         gtk_container_add(GTK_CONTAINER(util_vbox), vbox);
1053
1054         frame = gtk_frame_new((char *) p->dus.name);
1055         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
1056
1057         box = gtk_vbox_new(FALSE, 3);
1058         gtk_container_add(GTK_CONTAINER(frame), box);
1059
1060         frame = gtk_frame_new("Read");
1061         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1062         vbox = gtk_hbox_new(TRUE, 3);
1063         gtk_container_add(GTK_CONTAINER(frame), vbox);
1064         entry = new_info_entry_in_frame(vbox, "IOs");
1065         entry_set_int_value(entry, p->dus.ios[0]);
1066         entry = new_info_entry_in_frame(vbox, "Merges");
1067         entry_set_int_value(entry, p->dus.merges[0]);
1068         entry = new_info_entry_in_frame(vbox, "Sectors");
1069         entry_set_int_value(entry, p->dus.sectors[0]);
1070         entry = new_info_entry_in_frame(vbox, "Ticks");
1071         entry_set_int_value(entry, p->dus.ticks[0]);
1072
1073         frame = gtk_frame_new("Write");
1074         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1075         vbox = gtk_hbox_new(TRUE, 3);
1076         gtk_container_add(GTK_CONTAINER(frame), vbox);
1077         entry = new_info_entry_in_frame(vbox, "IOs");
1078         entry_set_int_value(entry, p->dus.ios[1]);
1079         entry = new_info_entry_in_frame(vbox, "Merges");
1080         entry_set_int_value(entry, p->dus.merges[1]);
1081         entry = new_info_entry_in_frame(vbox, "Sectors");
1082         entry_set_int_value(entry, p->dus.sectors[1]);
1083         entry = new_info_entry_in_frame(vbox, "Ticks");
1084         entry_set_int_value(entry, p->dus.ticks[1]);
1085
1086         frame = gtk_frame_new("Shared");
1087         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1088         vbox = gtk_hbox_new(TRUE, 3);
1089         gtk_container_add(GTK_CONTAINER(frame), vbox);
1090         entry = new_info_entry_in_frame(vbox, "IO ticks");
1091         entry_set_int_value(entry, p->dus.io_ticks);
1092         entry = new_info_entry_in_frame(vbox, "Time in queue");
1093         entry_set_int_value(entry, p->dus.time_in_queue);
1094
1095         util = 0.0;
1096         if (p->dus.msec)
1097                 util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec;
1098         if (util > 100.0)
1099                 util = 100.0;
1100
1101         sprintf(tmp, "%3.2f%%", util);
1102         entry = new_info_entry_in_frame(vbox, "Disk utilization");
1103         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1104
1105         gtk_widget_show_all(ge->results_window);
1106         return 0;
1107 }
1108
1109 static int gfio_disk_util_show(struct gfio_client *gc)
1110 {
1111         struct gui_entry *ge = gc->ge;
1112         GtkWidget *res_notebook;
1113         int i;
1114
1115         if (!gc->nr_du)
1116                 return 1;
1117
1118         res_notebook = get_results_window(ge);
1119
1120         for (i = 0; i < gc->nr_du; i++) {
1121                 struct cmd_du_pdu *p = &gc->du[i];
1122
1123                 __gfio_disk_util_show(res_notebook, gc, p);
1124         }
1125
1126         gtk_widget_show_all(ge->results_window);
1127         return 0;
1128 }
1129
1130 static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts,
1131                                  struct group_run_stats *rs)
1132 {
1133         unsigned int nr = gc->nr_results;
1134
1135         gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results));
1136         memcpy(&gc->results[nr].ts, ts, sizeof(*ts));
1137         memcpy(&gc->results[nr].gs, rs, sizeof(*rs));
1138         gc->nr_results++;
1139 }
1140
1141 static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc,
1142                                        struct thread_stat *ts,
1143                                        struct group_run_stats *rs)
1144 {
1145         GtkWidget *box, *vbox, *entry, *scroll;
1146
1147         scroll = gtk_scrolled_window_new(NULL, NULL);
1148         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1149         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1150
1151         vbox = gtk_vbox_new(FALSE, 3);
1152
1153         box = gtk_hbox_new(FALSE, 0);
1154         gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
1155
1156         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1157
1158         gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name));
1159
1160         entry = new_info_entry_in_frame(box, "Name");
1161         gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
1162         if (strlen(ts->description)) {
1163                 entry = new_info_entry_in_frame(box, "Description");
1164                 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
1165         }
1166         entry = new_info_entry_in_frame(box, "Group ID");
1167         entry_set_int_value(entry, ts->groupid);
1168         entry = new_info_entry_in_frame(box, "Jobs");
1169         entry_set_int_value(entry, ts->members);
1170         gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
1171         entry_set_int_value(entry, ts->error);
1172         entry = new_info_entry_in_frame(box, "PID");
1173         entry_set_int_value(entry, ts->pid);
1174
1175         if (ts->io_bytes[DDIR_READ])
1176                 gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_READ);
1177         if (ts->io_bytes[DDIR_WRITE])
1178                 gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_WRITE);
1179
1180         gfio_show_latency_buckets(gc, vbox, ts);
1181         gfio_show_cpu_usage(vbox, ts);
1182         gfio_show_io_depths(vbox, ts);
1183 }
1184
1185 static void gfio_display_end_results(struct gfio_client *gc)
1186 {
1187         struct gui_entry *ge = gc->ge;
1188         GtkWidget *res_notebook;
1189         int i;
1190
1191         res_notebook = get_results_window(ge);
1192
1193         for (i = 0; i < gc->nr_results; i++) {
1194                 struct end_results *e = &gc->results[i];
1195
1196                 __gfio_display_end_results(res_notebook, gc, &e->ts, &e->gs);
1197         }
1198
1199         if (gfio_disk_util_show(gc))
1200                 gtk_widget_show_all(ge->results_window);
1201 }
1202
1203 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
1204                             struct group_run_stats *rs)
1205 {
1206         struct gfio_client *gc = client->client_data;
1207         struct gui_entry *ge = gc->ge;
1208
1209         gfio_add_end_results(gc, ts, rs);
1210
1211         gdk_threads_enter();
1212         if (ge->results_window)
1213                 __gfio_display_end_results(ge->results_notebook, gc, ts, rs);
1214         else
1215                 gfio_display_end_results(gc);
1216         gdk_threads_leave();
1217 }
1218
1219 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
1220 {
1221         struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
1222         struct gui *ui = &main_ui;
1223         GtkTreeIter iter;
1224         struct tm *tm;
1225         time_t sec;
1226         char tmp[64], timebuf[80];
1227
1228         sec = p->log_sec;
1229         tm = localtime(&sec);
1230         strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm);
1231         sprintf(timebuf, "%s.%03ld", tmp, p->log_usec / 1000);
1232
1233         gdk_threads_enter();
1234
1235         gtk_list_store_append(ui->log_model, &iter);
1236         gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
1237         gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
1238         gtk_list_store_set(ui->log_model, &iter, 2, p->level, -1);
1239         gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
1240
1241         if (p->level == FIO_LOG_ERR)
1242                 view_log(NULL, (gpointer) ui);
1243
1244         gdk_threads_leave();
1245 }
1246
1247 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
1248 {
1249         struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
1250         struct gfio_client *gc = client->client_data;
1251         struct gui_entry *ge = gc->ge;
1252         unsigned int nr = gc->nr_du;
1253
1254         gc->du = realloc(gc->du, (nr + 1) * sizeof(struct cmd_du_pdu));
1255         memcpy(&gc->du[nr], p, sizeof(*p));
1256         gc->nr_du++;
1257
1258         gdk_threads_enter();
1259         if (ge->results_window)
1260                 __gfio_disk_util_show(ge->results_notebook, gc, p);
1261         else
1262                 gfio_disk_util_show(gc);
1263         gdk_threads_leave();
1264 }
1265
1266 extern int sum_stat_clients;
1267 extern struct thread_stat client_ts;
1268 extern struct group_run_stats client_gs;
1269
1270 static int sum_stat_nr;
1271
1272 static void gfio_thread_status_op(struct fio_client *client,
1273                                   struct fio_net_cmd *cmd)
1274 {
1275         struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
1276
1277         gfio_display_ts(client, &p->ts, &p->rs);
1278
1279         if (sum_stat_clients == 1)
1280                 return;
1281
1282         sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
1283         sum_group_stats(&client_gs, &p->rs);
1284
1285         client_ts.members++;
1286         client_ts.thread_number = p->ts.thread_number;
1287         client_ts.groupid = p->ts.groupid;
1288
1289         if (++sum_stat_nr == sum_stat_clients) {
1290                 strcpy(client_ts.name, "All clients");
1291                 gfio_display_ts(client, &client_ts, &client_gs);
1292         }
1293 }
1294
1295 static void gfio_group_stats_op(struct fio_client *client,
1296                                 struct fio_net_cmd *cmd)
1297 {
1298         /* We're ignoring group stats for now */
1299 }
1300
1301 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
1302                                    gpointer data)
1303 {
1304         struct gfio_graphs *g = data;
1305
1306         graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
1307         graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
1308         graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
1309         graph_set_position(g->bandwidth_graph, 0, 0);
1310         return TRUE;
1311 }
1312
1313 static void draw_graph(struct graph *g, cairo_t *cr)
1314 {
1315         line_graph_draw(g, cr);
1316         cairo_stroke(cr);
1317 }
1318
1319 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
1320                               gboolean keyboard_mode, GtkTooltip *tooltip,
1321                               gpointer data)
1322 {
1323         struct gfio_graphs *g = data;
1324         const char *text = NULL;
1325
1326         if (graph_contains_xy(g->iops_graph, x, y))
1327                 text = graph_find_tooltip(g->iops_graph, x, y);
1328         else if (graph_contains_xy(g->bandwidth_graph, x, y))
1329                 text = graph_find_tooltip(g->bandwidth_graph, x, y);
1330
1331         if (text) {
1332                 gtk_tooltip_set_text(tooltip, text);
1333                 return TRUE;
1334         }
1335
1336         return FALSE;
1337 }
1338
1339 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
1340 {
1341         struct gfio_graphs *g = p;
1342         cairo_t *cr;
1343
1344         cr = gdk_cairo_create(w->window);
1345
1346         if (graph_has_tooltips(g->iops_graph) ||
1347             graph_has_tooltips(g->bandwidth_graph)) {
1348                 g_object_set(w, "has-tooltip", TRUE, NULL);
1349                 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
1350         }
1351
1352         cairo_set_source_rgb(cr, 0, 0, 0);
1353         draw_graph(g->iops_graph, cr);
1354         draw_graph(g->bandwidth_graph, cr);
1355         cairo_destroy(cr);
1356
1357         return FALSE;
1358 }
1359
1360 /*
1361  * Client specific ETA
1362  */
1363 static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
1364 {
1365         struct gfio_client *gc = client->client_data;
1366         struct gui_entry *ge = gc->ge;
1367         static int eta_good;
1368         char eta_str[128];
1369         char output[256];
1370         char tmp[32];
1371         double perc = 0.0;
1372         int i2p = 0;
1373
1374         gdk_threads_enter();
1375
1376         eta_str[0] = '\0';
1377         output[0] = '\0';
1378
1379         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1380                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1381                 eta_to_str(eta_str, je->eta_sec);
1382         }
1383
1384         sprintf(tmp, "%u", je->nr_running);
1385         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
1386         sprintf(tmp, "%u", je->files_open);
1387         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
1388
1389 #if 0
1390         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1391         if (je->m_rate || je->t_rate) {
1392                 char *tr, *mr;
1393
1394                 mr = num2str(je->m_rate, 4, 0, i2p);
1395                 tr = num2str(je->t_rate, 4, 0, i2p);
1396                 gtk_entry_set_text(GTK_ENTRY(ge->eta);
1397                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1398                 free(tr);
1399                 free(mr);
1400         } else if (je->m_iops || je->t_iops)
1401                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1402
1403         gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
1404         gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
1405         gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
1406         gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
1407 #endif
1408
1409         if (je->eta_sec != INT_MAX && je->nr_running) {
1410                 char *iops_str[2];
1411                 char *rate_str[2];
1412
1413                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1414                         strcpy(output, "-.-% done");
1415                 else {
1416                         eta_good = 1;
1417                         perc *= 100.0;
1418                         sprintf(output, "%3.1f%% done", perc);
1419                 }
1420
1421                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1422                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1423
1424                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1425                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1426
1427                 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
1428                 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
1429                 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
1430                 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
1431
1432                 graph_add_xy_data(ge->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1433                 graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1434                 graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1435                 graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1436
1437                 free(rate_str[0]);
1438                 free(rate_str[1]);
1439                 free(iops_str[0]);
1440                 free(iops_str[1]);
1441         }
1442
1443         if (eta_str[0]) {
1444                 char *dst = output + strlen(output);
1445
1446                 sprintf(dst, " - %s", eta_str);
1447         }
1448                 
1449         gfio_update_thread_status(ge, output, perc);
1450         gdk_threads_leave();
1451 }
1452
1453 /*
1454  * Update ETA in main window for all clients
1455  */
1456 static void gfio_update_all_eta(struct jobs_eta *je)
1457 {
1458         struct gui *ui = &main_ui;
1459         static int eta_good;
1460         char eta_str[128];
1461         char output[256];
1462         double perc = 0.0;
1463         int i2p = 0;
1464
1465         gdk_threads_enter();
1466
1467         eta_str[0] = '\0';
1468         output[0] = '\0';
1469
1470         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1471                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1472                 eta_to_str(eta_str, je->eta_sec);
1473         }
1474
1475 #if 0
1476         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1477         if (je->m_rate || je->t_rate) {
1478                 char *tr, *mr;
1479
1480                 mr = num2str(je->m_rate, 4, 0, i2p);
1481                 tr = num2str(je->t_rate, 4, 0, i2p);
1482                 gtk_entry_set_text(GTK_ENTRY(ui->eta);
1483                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1484                 free(tr);
1485                 free(mr);
1486         } else if (je->m_iops || je->t_iops)
1487                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1488
1489         gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
1490         gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
1491         gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
1492         gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
1493 #endif
1494
1495         entry_set_int_value(ui->eta.jobs, je->nr_running);
1496
1497         if (je->eta_sec != INT_MAX && je->nr_running) {
1498                 char *iops_str[2];
1499                 char *rate_str[2];
1500
1501                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1502                         strcpy(output, "-.-% done");
1503                 else {
1504                         eta_good = 1;
1505                         perc *= 100.0;
1506                         sprintf(output, "%3.1f%% done", perc);
1507                 }
1508
1509                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1510                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1511
1512                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1513                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1514
1515                 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
1516                 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
1517                 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
1518                 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
1519
1520                 graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1521                 graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1522                 graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1523                 graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1524
1525                 free(rate_str[0]);
1526                 free(rate_str[1]);
1527                 free(iops_str[0]);
1528                 free(iops_str[1]);
1529         }
1530
1531         if (eta_str[0]) {
1532                 char *dst = output + strlen(output);
1533
1534                 sprintf(dst, " - %s", eta_str);
1535         }
1536                 
1537         gfio_update_thread_status_all(output, perc);
1538         gdk_threads_leave();
1539 }
1540
1541 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
1542 {
1543         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
1544         struct gfio_client *gc = client->client_data;
1545         struct gui_entry *ge = gc->ge;
1546         const char *os, *arch;
1547         char buf[64];
1548
1549         os = fio_get_os_string(probe->os);
1550         if (!os)
1551                 os = "unknown";
1552
1553         arch = fio_get_arch_string(probe->arch);
1554         if (!arch)
1555                 os = "unknown";
1556
1557         if (!client->name)
1558                 client->name = strdup((char *) probe->hostname);
1559
1560         gdk_threads_enter();
1561
1562         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
1563         gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
1564         gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
1565         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
1566         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
1567
1568         gfio_set_state(ge, GE_STATE_CONNECTED);
1569
1570         gdk_threads_leave();
1571 }
1572
1573 static void gfio_update_thread_status(struct gui_entry *ge,
1574                                       char *status_message, double perc)
1575 {
1576         static char message[100];
1577         const char *m = message;
1578
1579         strncpy(message, status_message, sizeof(message) - 1);
1580         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
1581         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
1582         gtk_widget_queue_draw(main_ui.window);
1583 }
1584
1585 static void gfio_update_thread_status_all(char *status_message, double perc)
1586 {
1587         struct gui *ui = &main_ui;
1588         static char message[100];
1589         const char *m = message;
1590
1591         strncpy(message, status_message, sizeof(message) - 1);
1592         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
1593         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
1594         gtk_widget_queue_draw(ui->window);
1595 }
1596
1597 static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd)
1598 {
1599         struct gfio_client *gc = client->client_data;
1600
1601         gdk_threads_enter();
1602         gfio_set_state(gc->ge, GE_STATE_NEW);
1603         gdk_threads_leave();
1604 }
1605
1606 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
1607 {
1608         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
1609         struct gfio_client *gc = client->client_data;
1610         struct thread_options *o = &gc->o;
1611         struct gui_entry *ge = gc->ge;
1612         char *c1, *c2, *c3, *c4;
1613         char tmp[80];
1614
1615         p->thread_number = le32_to_cpu(p->thread_number);
1616         p->groupid = le32_to_cpu(p->groupid);
1617         convert_thread_options_to_cpu(o, &p->top);
1618
1619         gdk_threads_enter();
1620
1621         gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
1622
1623         gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
1624         gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
1625
1626         sprintf(tmp, "%s %s", o->odirect ? "direct" : "buffered", ddir_str(o->td_ddir));
1627         multitext_add_entry(&ge->eta.iotype, tmp);
1628
1629         c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
1630         c2 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
1631         c3 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
1632         c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
1633         sprintf(tmp, "%s-%s/%s-%s", c1, c2, c3, c4);
1634         free(c1);
1635         free(c2);
1636         free(c3);
1637         free(c4);
1638         multitext_add_entry(&ge->eta.bs, tmp);
1639
1640         multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
1641
1642         sprintf(tmp, "%u", o->iodepth);
1643         multitext_add_entry(&ge->eta.iodepth, tmp);
1644
1645         multitext_set_entry(&ge->eta.iotype, 0);
1646         multitext_set_entry(&ge->eta.bs, 0);
1647         multitext_set_entry(&ge->eta.ioengine, 0);
1648         multitext_set_entry(&ge->eta.iodepth, 0);
1649
1650         gfio_set_state(ge, GE_STATE_JOB_SENT);
1651
1652         gdk_threads_leave();
1653 }
1654
1655 static void gfio_client_timed_out(struct fio_client *client)
1656 {
1657         struct gfio_client *gc = client->client_data;
1658         char buf[256];
1659
1660         gdk_threads_enter();
1661
1662         gfio_set_state(gc->ge, GE_STATE_NEW);
1663         clear_ge_ui_info(gc->ge);
1664
1665         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
1666         show_info_dialog(gc->ge->ui, "Network timeout", buf);
1667
1668         gdk_threads_leave();
1669 }
1670
1671 static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
1672 {
1673         struct gfio_client *gc = client->client_data;
1674
1675         gdk_threads_enter();
1676
1677         gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
1678
1679         if (gc->err_entry)
1680                 entry_set_int_value(gc->err_entry, client->error);
1681
1682         gdk_threads_leave();
1683 }
1684
1685 static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
1686 {
1687         struct gfio_client *gc = client->client_data;
1688
1689         gdk_threads_enter();
1690         gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
1691         gdk_threads_leave();
1692 }
1693
1694 static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
1695 {
1696         struct gfio_client *gc = client->client_data;
1697
1698         gdk_threads_enter();
1699         gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
1700         gdk_threads_leave();
1701 }
1702
1703 static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu)
1704 {
1705         printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples);
1706         free(pdu);
1707 }
1708
1709 struct client_ops gfio_client_ops = {
1710         .text                   = gfio_text_op,
1711         .disk_util              = gfio_disk_util_op,
1712         .thread_status          = gfio_thread_status_op,
1713         .group_stats            = gfio_group_stats_op,
1714         .jobs_eta               = gfio_update_client_eta,
1715         .eta                    = gfio_update_all_eta,
1716         .probe                  = gfio_probe_op,
1717         .quit                   = gfio_quit_op,
1718         .add_job                = gfio_add_job_op,
1719         .timed_out              = gfio_client_timed_out,
1720         .stop                   = gfio_client_stop,
1721         .start                  = gfio_client_start,
1722         .job_start              = gfio_client_job_start,
1723         .iolog                  = gfio_client_iolog,
1724         .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
1725         .stay_connected         = 1,
1726         .client_type            = FIO_CLIENT_TYPE_GUI,
1727 };
1728
1729 /*
1730  * FIXME: need more handling here
1731  */
1732 static void ge_destroy(struct gui_entry *ge)
1733 {
1734         struct gfio_client *gc = ge->client;
1735
1736         if (gc && gc->client) {
1737                 if (ge->state >= GE_STATE_CONNECTED)
1738                         fio_client_terminate(gc->client);
1739
1740                 fio_put_client(gc->client);
1741         }
1742
1743         flist_del(&ge->list);
1744         free(ge);
1745 }
1746
1747 static void ge_widget_destroy(GtkWidget *w, gpointer data)
1748 {
1749         struct gui_entry *ge = (struct gui_entry *) data;
1750
1751         ge_destroy(ge);
1752 }
1753
1754 static void gfio_quit(struct gui *ui)
1755 {
1756         struct gui_entry *ge;
1757
1758         while (!flist_empty(&ui->list)) {
1759                 ge = flist_entry(ui->list.next, struct gui_entry, list);
1760                 ge_destroy(ge);
1761         }
1762
1763         gtk_main_quit();
1764 }
1765
1766 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1767                 __attribute__((unused)) gpointer data)
1768 {
1769         gfio_quit(&main_ui);
1770 }
1771
1772 static void *job_thread(void *arg)
1773 {
1774         struct gui *ui = arg;
1775
1776         ui->handler_running = 1;
1777         fio_handle_clients(&gfio_client_ops);
1778         ui->handler_running = 0;
1779         return NULL;
1780 }
1781
1782 static int send_job_files(struct gui_entry *ge)
1783 {
1784         struct gfio_client *gc = ge->client;
1785         int i, ret = 0;
1786
1787         for (i = 0; i < ge->nr_job_files; i++) {
1788                 ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1789                 if (ret < 0) {
1790                         GError *error;
1791
1792                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1793                         report_error(error);
1794                         g_error_free(error);
1795                         break;
1796                 } else if (ret)
1797                         break;
1798
1799                 free(ge->job_files[i]);
1800                 ge->job_files[i] = NULL;
1801         }
1802         while (i < ge->nr_job_files) {
1803                 free(ge->job_files[i]);
1804                 ge->job_files[i] = NULL;
1805                 i++;
1806         }
1807
1808         free(ge->job_files);
1809         ge->job_files = NULL;
1810         ge->nr_job_files = 0;
1811         return ret;
1812 }
1813
1814 static void *server_thread(void *arg)
1815 {
1816         is_backend = 1;
1817         gfio_server_running = 1;
1818         fio_start_server(NULL);
1819         gfio_server_running = 0;
1820         return NULL;
1821 }
1822
1823 static void gfio_start_server(void)
1824 {
1825         struct gui *ui = &main_ui;
1826
1827         if (!gfio_server_running) {
1828                 gfio_server_running = 1;
1829                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1830                 pthread_detach(ui->server_t);
1831         }
1832 }
1833
1834 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1835                 gpointer data)
1836 {
1837         struct gui_entry *ge = data;
1838         struct gfio_client *gc = ge->client;
1839
1840         if (gc)
1841                 fio_start_client(gc->client);
1842 }
1843
1844 static void file_open(GtkWidget *w, gpointer data);
1845
1846 static void connect_clicked(GtkWidget *widget, gpointer data)
1847 {
1848         struct gui_entry *ge = data;
1849         struct gfio_client *gc = ge->client;
1850
1851         if (ge->state == GE_STATE_NEW) {
1852                 int ret;
1853
1854                 if (!ge->nr_job_files)
1855                         file_open(widget, ge->ui);
1856                 if (!ge->nr_job_files)
1857                         return;
1858
1859                 gc = ge->client;
1860
1861                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
1862                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1863                 ret = fio_client_connect(gc->client);
1864                 if (!ret) {
1865                         if (!ge->ui->handler_running)
1866                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
1867                         gfio_set_state(ge, GE_STATE_CONNECTED);
1868                 } else {
1869                         GError *error;
1870
1871                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
1872                         report_error(error);
1873                         g_error_free(error);
1874                 }
1875         } else {
1876                 fio_client_terminate(gc->client);
1877                 gfio_set_state(ge, GE_STATE_NEW);
1878                 clear_ge_ui_info(ge);
1879         }
1880 }
1881
1882 static void send_clicked(GtkWidget *widget, gpointer data)
1883 {
1884         struct gui_entry *ge = data;
1885
1886         if (send_job_files(ge)) {
1887                 GError *error;
1888
1889                 error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send one or more job files for client %s", ge->client->client->hostname);
1890                 report_error(error);
1891                 g_error_free(error);
1892
1893                 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
1894         }
1895 }
1896
1897 static void on_info_bar_response(GtkWidget *widget, gint response,
1898                                  gpointer data)
1899 {
1900         struct gui *ui = &main_ui;
1901
1902         if (response == GTK_RESPONSE_OK) {
1903                 gtk_widget_destroy(widget);
1904                 ui->error_info_bar = NULL;
1905         }
1906 }
1907
1908 static void report_error(GError *error)
1909 {
1910         struct gui *ui = &main_ui;
1911
1912         if (ui->error_info_bar == NULL) {
1913                 ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1914                                                                GTK_RESPONSE_OK,
1915                                                                NULL);
1916                 g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1917                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
1918                                               GTK_MESSAGE_ERROR);
1919                 
1920                 ui->error_label = gtk_label_new(error->message);
1921                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
1922                 gtk_container_add(GTK_CONTAINER(container), ui->error_label);
1923                 
1924                 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
1925                 gtk_widget_show_all(ui->vbox);
1926         } else {
1927                 char buffer[256];
1928                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1929                 gtk_label_set(GTK_LABEL(ui->error_label), buffer);
1930         }
1931 }
1932
1933 struct connection_widgets
1934 {
1935         GtkWidget *hentry;
1936         GtkWidget *combo;
1937         GtkWidget *button;
1938 };
1939
1940 static void hostname_cb(GtkEntry *entry, gpointer data)
1941 {
1942         struct connection_widgets *cw = data;
1943         int uses_net = 0, is_localhost = 0;
1944         const gchar *text;
1945         gchar *ctext;
1946
1947         /*
1948          * Check whether to display the 'auto start backend' box
1949          * or not. Show it if we are a localhost and using network,
1950          * or using a socket.
1951          */
1952         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1953         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1954                 uses_net = 1;
1955         g_free(ctext);
1956
1957         if (uses_net) {
1958                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1959                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1960                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1961                     !strcmp(text, "ip6-loopback"))
1962                         is_localhost = 1;
1963         }
1964
1965         if (!uses_net || is_localhost) {
1966                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1967                 gtk_widget_set_sensitive(cw->button, 1);
1968         } else {
1969                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1970                 gtk_widget_set_sensitive(cw->button, 0);
1971         }
1972 }
1973
1974 static int get_connection_details(char **host, int *port, int *type,
1975                                   int *server_start)
1976 {
1977         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1978         struct connection_widgets cw;
1979         char *typeentry;
1980
1981         dialog = gtk_dialog_new_with_buttons("Connection details",
1982                         GTK_WINDOW(main_ui.window),
1983                         GTK_DIALOG_DESTROY_WITH_PARENT,
1984                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1985                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1986
1987         frame = gtk_frame_new("Hostname / socket name");
1988         /* gtk_dialog_get_content_area() is 2.14 and newer */
1989         vbox = GTK_DIALOG(dialog)->vbox;
1990         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1991
1992         box = gtk_vbox_new(FALSE, 6);
1993         gtk_container_add(GTK_CONTAINER(frame), box);
1994
1995         hbox = gtk_hbox_new(TRUE, 10);
1996         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1997         cw.hentry = gtk_entry_new();
1998         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1999         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
2000
2001         frame = gtk_frame_new("Port");
2002         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2003         box = gtk_vbox_new(FALSE, 10);
2004         gtk_container_add(GTK_CONTAINER(frame), box);
2005
2006         hbox = gtk_hbox_new(TRUE, 4);
2007         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2008         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
2009
2010         frame = gtk_frame_new("Type");
2011         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2012         box = gtk_vbox_new(FALSE, 10);
2013         gtk_container_add(GTK_CONTAINER(frame), box);
2014
2015         hbox = gtk_hbox_new(TRUE, 4);
2016         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2017
2018         cw.combo = gtk_combo_box_new_text();
2019         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
2020         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
2021         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
2022         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
2023
2024         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
2025
2026         frame = gtk_frame_new("Options");
2027         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2028         box = gtk_vbox_new(FALSE, 10);
2029         gtk_container_add(GTK_CONTAINER(frame), box);
2030
2031         hbox = gtk_hbox_new(TRUE, 4);
2032         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2033
2034         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
2035         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
2036         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.");
2037         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2038
2039         /*
2040          * Connect edit signal, so we can show/not-show the auto start button
2041          */
2042         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
2043         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2044
2045         gtk_widget_show_all(dialog);
2046
2047         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2048                 gtk_widget_destroy(dialog);
2049                 return 1;
2050         }
2051
2052         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
2053         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2054
2055         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
2056         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
2057                 *type = Fio_client_ipv4;
2058         else if (!strncmp(typeentry, "IPv6", 4))
2059                 *type = Fio_client_ipv6;
2060         else
2061                 *type = Fio_client_socket;
2062         g_free(typeentry);
2063
2064         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2065
2066         gtk_widget_destroy(dialog);
2067         return 0;
2068 }
2069
2070 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
2071 {
2072         struct gfio_client *gc;
2073
2074         gc = malloc(sizeof(*gc));
2075         memset(gc, 0, sizeof(*gc));
2076         options_default_fill(&gc->o);
2077         gc->ge = ge;
2078         gc->client = fio_get_client(client);
2079
2080         ge->client = gc;
2081
2082         client->client_data = gc;
2083 }
2084
2085 static GtkWidget *new_client_page(struct gui_entry *ge);
2086
2087 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2088 {
2089         struct gui_entry *ge;
2090
2091         ge = malloc(sizeof(*ge));
2092         memset(ge, 0, sizeof(*ge));
2093         ge->state = GE_STATE_NEW;
2094         INIT_FLIST_HEAD(&ge->list);
2095         flist_add_tail(&ge->list, &ui->list);
2096         ge->ui = ui;
2097         return ge;
2098 }
2099
2100 static struct gui_entry *get_new_ge_with_tab(const char *name)
2101 {
2102         struct gui_entry *ge;
2103
2104         ge = alloc_new_gui_entry(&main_ui);
2105
2106         ge->vbox = new_client_page(ge);
2107         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
2108
2109         ge->page_label = gtk_label_new(name);
2110         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
2111
2112         gtk_widget_show_all(main_ui.window);
2113         return ge;
2114 }
2115
2116 static void file_new(GtkWidget *w, gpointer data)
2117 {
2118         struct gui *ui = (struct gui *) data;
2119         struct gui_entry *ge;
2120
2121         ge = get_new_ge_with_tab("Untitled");
2122         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2123 }
2124
2125 /*
2126  * Return the 'ge' corresponding to the tab. If the active tab is the
2127  * main tab, open a new tab.
2128  */
2129 static struct gui_entry *get_ge_from_page(gint cur_page, int *created)
2130 {
2131         struct flist_head *entry;
2132         struct gui_entry *ge;
2133
2134         if (!cur_page) {
2135                 if (created)
2136                         *created = 1;
2137                 return get_new_ge_with_tab("Untitled");
2138         }
2139
2140         if (created)
2141                 *created = 0;
2142
2143         flist_for_each(entry, &main_ui.list) {
2144                 ge = flist_entry(entry, struct gui_entry, list);
2145                 if (ge->page_num == cur_page)
2146                         return ge;
2147         }
2148
2149         return NULL;
2150 }
2151
2152 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2153 {
2154         gint cur_page;
2155
2156         /*
2157          * Main tab is tab 0, so any current page other than 0 holds
2158          * a ge entry.
2159          */
2160         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2161         if (cur_page)
2162                 return get_ge_from_page(cur_page, NULL);
2163
2164         return NULL;
2165 }
2166
2167 static void file_close(GtkWidget *w, gpointer data)
2168 {
2169         struct gui *ui = (struct gui *) data;
2170         struct gui_entry *ge;
2171
2172         /*
2173          * Can't close the main tab
2174          */
2175         ge = get_ge_from_cur_tab(ui);
2176         if (ge) {
2177                 gtk_widget_destroy(ge->vbox);
2178                 return;
2179         }
2180
2181         if (!flist_empty(&ui->list)) {
2182                 show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2183                 return;
2184         }
2185
2186         gfio_quit(ui);
2187 }
2188
2189 static void file_add_recent(struct gui *ui, const gchar *uri)
2190 {
2191         GtkRecentData grd;
2192
2193         memset(&grd, 0, sizeof(grd));
2194         grd.display_name = strdup("gfio");
2195         grd.description = strdup("Fio job file");
2196         grd.mime_type = strdup(GFIO_MIME);
2197         grd.app_name = strdup(g_get_application_name());
2198         grd.app_exec = strdup("gfio %f/%u");
2199
2200         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2201 }
2202
2203 static gchar *get_filename_from_uri(const gchar *uri)
2204 {
2205         if (strncmp(uri, "file://", 7))
2206                 return strdup(uri);
2207
2208         return strdup(uri + 7);
2209 }
2210
2211 static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2212                         int type, int port)
2213 {
2214         struct fio_client *client;
2215         gchar *filename;
2216
2217         filename = get_filename_from_uri(uri);
2218
2219         ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2220         ge->job_files[ge->nr_job_files] = strdup(filename);
2221         ge->nr_job_files++;
2222
2223         client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2224         if (!client) {
2225                 GError *error;
2226
2227                 error = g_error_new(g_quark_from_string("fio"), 1,
2228                                 "Failed to add client %s", host);
2229                 report_error(error);
2230                 g_error_free(error);
2231                 return 1;
2232         }
2233
2234         gfio_client_added(ge, client);
2235         file_add_recent(ge->ui, uri);
2236         return 0;
2237 }
2238
2239 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2240 {
2241         int port, type, server_start;
2242         struct gui_entry *ge;
2243         gint cur_page;
2244         char *host;
2245         int ret, ge_is_new = 0;
2246
2247         /*
2248          * Creates new tab if current tab is the main window, or the
2249          * current tab already has a client.
2250          */
2251         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2252         ge = get_ge_from_page(cur_page, &ge_is_new);
2253         if (ge->client) {
2254                 ge = get_new_ge_with_tab("Untitled");
2255                 ge_is_new = 1;
2256         }
2257
2258         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2259
2260         if (get_connection_details(&host, &port, &type, &server_start)) {
2261                 if (ge_is_new)
2262                         gtk_widget_destroy(ge->vbox);
2263                         
2264                 return 1;
2265         }
2266
2267         ret = do_file_open(ge, uri, host, type, port);
2268
2269         free(host);
2270
2271         if (!ret) {
2272                 if (server_start)
2273                         gfio_start_server();
2274         } else {
2275                 if (ge_is_new)
2276                         gtk_widget_destroy(ge->vbox);
2277         }
2278
2279         return ret;
2280 }
2281
2282 static void recent_open(GtkAction *action, gpointer data)
2283 {
2284         struct gui *ui = (struct gui *) data;
2285         GtkRecentInfo *info;
2286         const gchar *uri;
2287
2288         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2289         uri = gtk_recent_info_get_uri(info);
2290
2291         do_file_open_with_tab(ui, uri);
2292 }
2293
2294 static void file_open(GtkWidget *w, gpointer data)
2295 {
2296         struct gui *ui = data;
2297         GtkWidget *dialog;
2298         GSList *filenames, *fn_glist;
2299         GtkFileFilter *filter;
2300
2301         dialog = gtk_file_chooser_dialog_new("Open File",
2302                 GTK_WINDOW(ui->window),
2303                 GTK_FILE_CHOOSER_ACTION_OPEN,
2304                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2305                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2306                 NULL);
2307         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2308
2309         filter = gtk_file_filter_new();
2310         gtk_file_filter_add_pattern(filter, "*.fio");
2311         gtk_file_filter_add_pattern(filter, "*.job");
2312         gtk_file_filter_add_pattern(filter, "*.ini");
2313         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2314         gtk_file_filter_set_name(filter, "Fio job file");
2315         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2316
2317         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2318                 gtk_widget_destroy(dialog);
2319                 return;
2320         }
2321
2322         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2323
2324         gtk_widget_destroy(dialog);
2325
2326         filenames = fn_glist;
2327         while (filenames != NULL) {
2328                 if (do_file_open_with_tab(ui, filenames->data))
2329                         break;
2330                 filenames = g_slist_next(filenames);
2331         }
2332
2333         g_slist_free(fn_glist);
2334 }
2335
2336 static void file_save(GtkWidget *w, gpointer data)
2337 {
2338         struct gui *ui = data;
2339         GtkWidget *dialog;
2340
2341         dialog = gtk_file_chooser_dialog_new("Save File",
2342                 GTK_WINDOW(ui->window),
2343                 GTK_FILE_CHOOSER_ACTION_SAVE,
2344                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2345                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2346                 NULL);
2347
2348         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2349         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2350
2351         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2352                 char *filename;
2353
2354                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2355                 // save_job_file(filename);
2356                 g_free(filename);
2357         }
2358         gtk_widget_destroy(dialog);
2359 }
2360
2361 static void view_log_destroy(GtkWidget *w, gpointer data)
2362 {
2363         struct gui *ui = (struct gui *) data;
2364
2365         gtk_widget_ref(ui->log_tree);
2366         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2367         gtk_widget_destroy(w);
2368         ui->log_view = NULL;
2369 }
2370
2371 static void view_log(GtkWidget *w, gpointer data)
2372 {
2373         GtkWidget *win, *scroll, *vbox, *box;
2374         struct gui *ui = (struct gui *) data;
2375
2376         if (ui->log_view)
2377                 return;
2378
2379         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2380         gtk_window_set_title(GTK_WINDOW(win), "Log");
2381         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2382
2383         scroll = gtk_scrolled_window_new(NULL, NULL);
2384
2385         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2386
2387         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2388
2389         box = gtk_hbox_new(TRUE, 0);
2390         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2391         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2392         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2393
2394         vbox = gtk_vbox_new(TRUE, 5);
2395         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2396
2397         gtk_container_add(GTK_CONTAINER(win), vbox);
2398         gtk_widget_show_all(win);
2399 }
2400
2401 static void connect_job_entry(GtkWidget *w, gpointer data)
2402 {
2403         struct gui *ui = (struct gui *) data;
2404         struct gui_entry *ge;
2405         
2406         ge = get_ge_from_cur_tab(ui);
2407         if (ge)
2408                 connect_clicked(w, ge);
2409 }
2410
2411 static void send_job_entry(GtkWidget *w, gpointer data)
2412 {
2413         struct gui *ui = (struct gui *) data;
2414         struct gui_entry *ge;
2415
2416         ge = get_ge_from_cur_tab(ui);
2417         if (ge)
2418                 send_clicked(w, ge);
2419
2420 }
2421
2422 static void edit_job_entry(GtkWidget *w, gpointer data)
2423 {
2424         struct gui *ui = (struct gui *) data;
2425         struct gui_entry *ge;
2426
2427         ge = get_ge_from_cur_tab(ui);
2428         if (ge && ge->client)
2429                 gopt_get_options_window(ui->window, &ge->client->o);
2430 }
2431
2432 static void start_job_entry(GtkWidget *w, gpointer data)
2433 {
2434         struct gui *ui = (struct gui *) data;
2435         struct gui_entry *ge;
2436
2437         ge = get_ge_from_cur_tab(ui);
2438         if (ge)
2439                 start_job_clicked(w, ge);
2440 }
2441
2442 static void view_results(GtkWidget *w, gpointer data)
2443 {
2444         struct gui *ui = (struct gui *) data;
2445         struct gfio_client *gc;
2446         struct gui_entry *ge;
2447
2448         ge = get_ge_from_cur_tab(ui);
2449         if (!ge)
2450                 return;
2451
2452         if (ge->results_window)
2453                 return;
2454
2455         gc = ge->client;
2456         if (gc && gc->nr_results)
2457                 gfio_display_end_results(gc);
2458 }
2459
2460 static void __update_graph_limits(struct gfio_graphs *g)
2461 {
2462         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2463         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2464 }
2465
2466 static void update_graph_limits(void)
2467 {
2468         struct flist_head *entry;
2469         struct gui_entry *ge;
2470
2471         __update_graph_limits(&main_ui.graphs);
2472
2473         flist_for_each(entry, &main_ui.list) {
2474                 ge = flist_entry(entry, struct gui_entry, list);
2475                 __update_graph_limits(&ge->graphs);
2476         }
2477 }
2478
2479 static void preferences(GtkWidget *w, gpointer data)
2480 {
2481         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2482         GtkWidget *hbox, *spin, *entry, *spin_int;
2483         int i;
2484
2485         dialog = gtk_dialog_new_with_buttons("Preferences",
2486                 GTK_WINDOW(main_ui.window),
2487                 GTK_DIALOG_DESTROY_WITH_PARENT,
2488                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2489                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2490                 NULL);
2491
2492         frame = gtk_frame_new("Graphing");
2493         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2494         vbox = gtk_vbox_new(FALSE, 6);
2495         gtk_container_add(GTK_CONTAINER(frame), vbox);
2496
2497         hbox = gtk_hbox_new(FALSE, 5);
2498         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2499         entry = gtk_label_new("Font face to use for graph labels");
2500         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2501
2502         font = gtk_font_button_new();
2503         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2504
2505         box = gtk_vbox_new(FALSE, 6);
2506         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2507
2508         hbox = gtk_hbox_new(FALSE, 5);
2509         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2510         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2511         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2512
2513         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2514
2515         box = gtk_vbox_new(FALSE, 6);
2516         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2517
2518         hbox = gtk_hbox_new(FALSE, 5);
2519         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2520         entry = gtk_label_new("Client ETA request interval (msec)");
2521         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2522
2523         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2524         frame = gtk_frame_new("Debug logging");
2525         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2526         vbox = gtk_vbox_new(FALSE, 6);
2527         gtk_container_add(GTK_CONTAINER(frame), vbox);
2528
2529         box = gtk_hbox_new(FALSE, 6);
2530         gtk_container_add(GTK_CONTAINER(vbox), box);
2531
2532         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2533
2534         for (i = 0; i < FD_DEBUG_MAX; i++) {
2535                 if (i == 7) {
2536                         box = gtk_hbox_new(FALSE, 6);
2537                         gtk_container_add(GTK_CONTAINER(vbox), box);
2538                 }
2539
2540
2541                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2542                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2543                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2544         }
2545
2546         gtk_widget_show_all(dialog);
2547
2548         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2549                 gtk_widget_destroy(dialog);
2550                 return;
2551         }
2552
2553         for (i = 0; i < FD_DEBUG_MAX; i++) {
2554                 int set;
2555
2556                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2557                 if (set)
2558                         fio_debug |= (1UL << i);
2559         }
2560
2561         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2562         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2563         update_graph_limits();
2564         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2565
2566         gtk_widget_destroy(dialog);
2567 }
2568
2569 static void about_dialog(GtkWidget *w, gpointer data)
2570 {
2571         const char *authors[] = {
2572                 "Jens Axboe <axboe@kernel.dk>",
2573                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2574                 NULL
2575         };
2576         const char *license[] = {
2577                 "Fio is free software; you can redistribute it and/or modify "
2578                 "it under the terms of the GNU General Public License as published by "
2579                 "the Free Software Foundation; either version 2 of the License, or "
2580                 "(at your option) any later version.\n",
2581                 "Fio is distributed in the hope that it will be useful, "
2582                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2583                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2584                 "GNU General Public License for more details.\n",
2585                 "You should have received a copy of the GNU General Public License "
2586                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2587                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2588         };
2589         char *license_trans;
2590
2591         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2592                                      license[2], "\n", NULL);
2593
2594         gtk_show_about_dialog(NULL,
2595                 "program-name", "gfio",
2596                 "comments", "Gtk2 UI for fio",
2597                 "license", license_trans,
2598                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2599                 "authors", authors,
2600                 "version", fio_version_string,
2601                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2602                 "logo-icon-name", "fio",
2603                 /* Must be last: */
2604                 "wrap-license", TRUE,
2605                 NULL);
2606
2607         g_free(license_trans);
2608 }
2609
2610 static GtkActionEntry menu_items[] = {
2611         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2612         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2613         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2614         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2615         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2616         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2617         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2618         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2619         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2620         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2621         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
2622         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
2623         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2624         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2625         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2626         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2627         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2628 };
2629 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2630
2631 static const gchar *ui_string = " \
2632         <ui> \
2633                 <menubar name=\"MainMenu\"> \
2634                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2635                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2636                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2637                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2638                                 <separator name=\"Separator1\"/> \
2639                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2640                                 <separator name=\"Separator2\"/> \
2641                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2642                                 <separator name=\"Separator3\"/> \
2643                                 <placeholder name=\"FileRecentFiles\"/> \
2644                                 <separator name=\"Separator4\"/> \
2645                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2646                         </menu> \
2647                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2648                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2649                                 <separator name=\"Separator5\"/> \
2650                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2651                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2652                                 <separator name=\"Separator6\"/> \
2653                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2654                         </menu>\
2655                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2656                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
2657                                 <separator name=\"Separator7\"/> \
2658                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2659                         </menu>\
2660                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2661                                 <menuitem name=\"About\" action=\"About\" /> \
2662                         </menu> \
2663                 </menubar> \
2664         </ui> \
2665 ";
2666
2667 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2668                                    struct gui *ui)
2669 {
2670         GtkActionGroup *action_group;
2671         GError *error = 0;
2672
2673         action_group = gtk_action_group_new("Menu");
2674         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2675
2676         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2677         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2678
2679         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2680
2681         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2682 }
2683
2684 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2685                    GtkWidget *vbox, GtkUIManager *ui_manager)
2686 {
2687         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2688 }
2689
2690 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2691 {
2692         struct gui_entry *ge = (struct gui_entry *) data;
2693         gint index;
2694
2695         index = gtk_combo_box_get_active(box);
2696
2697         multitext_set_entry(&ge->eta.iotype, index);
2698         multitext_set_entry(&ge->eta.bs, index);
2699         multitext_set_entry(&ge->eta.ioengine, index);
2700         multitext_set_entry(&ge->eta.iodepth, index);
2701 }
2702
2703 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2704 {
2705         struct gui_entry *ge = (struct gui_entry *) data;
2706
2707         multitext_free(&ge->eta.iotype);
2708         multitext_free(&ge->eta.bs);
2709         multitext_free(&ge->eta.ioengine);
2710         multitext_free(&ge->eta.iodepth);
2711 }
2712
2713 static GtkWidget *new_client_page(struct gui_entry *ge)
2714 {
2715         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2716         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2717
2718         main_vbox = gtk_vbox_new(FALSE, 3);
2719
2720         top_align = gtk_alignment_new(0, 0, 1, 0);
2721         top_vbox = gtk_vbox_new(FALSE, 3);
2722         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2723         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2724
2725         probe = gtk_frame_new("Job");
2726         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2727         probe_frame = gtk_vbox_new(FALSE, 3);
2728         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2729
2730         probe_box = gtk_hbox_new(FALSE, 3);
2731         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2732         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2733         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2734         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2735         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2736
2737         probe_box = gtk_hbox_new(FALSE, 3);
2738         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2739
2740         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2741         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2742         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2743         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2744         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
2745         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2746         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2747         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2748         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2749
2750         probe_box = gtk_hbox_new(FALSE, 3);
2751         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2752         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2753         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2754         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2755         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2756
2757         /*
2758          * Only add this if we have a commit rate
2759          */
2760 #if 0
2761         probe_box = gtk_hbox_new(FALSE, 3);
2762         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2763
2764         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2765         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2766
2767         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2768         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2769 #endif
2770
2771         /*
2772          * Set up a drawing area and IOPS and bandwidth graphs
2773          */
2774         ge->graphs.drawing_area = gtk_drawing_area_new();
2775         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2776                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2777         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2778         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2779                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2780         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2781                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2782         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2783         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2784                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2785         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2786                                         ge->graphs.drawing_area);
2787         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2788
2789         setup_graphs(&ge->graphs);
2790
2791         /*
2792          * Set up alignments for widgets at the bottom of ui, 
2793          * align bottom left, expand horizontally but not vertically
2794          */
2795         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2796         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2797         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2798         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2799
2800         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2801
2802         /*
2803          * Set up thread status progress bar
2804          */
2805         ge->thread_status_pb = gtk_progress_bar_new();
2806         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2807         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2808         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2809
2810
2811         return main_vbox;
2812 }
2813
2814 static GtkWidget *new_main_page(struct gui *ui)
2815 {
2816         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2817         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2818
2819         main_vbox = gtk_vbox_new(FALSE, 3);
2820
2821         /*
2822          * Set up alignments for widgets at the top of ui,
2823          * align top left, expand horizontally but not vertically
2824          */
2825         top_align = gtk_alignment_new(0, 0, 1, 0);
2826         top_vbox = gtk_vbox_new(FALSE, 0);
2827         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2828         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2829
2830         probe = gtk_frame_new("Run statistics");
2831         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2832         probe_frame = gtk_vbox_new(FALSE, 3);
2833         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2834
2835         probe_box = gtk_hbox_new(FALSE, 3);
2836         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2837         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2838         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2839         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2840         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2841         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2842
2843         /*
2844          * Only add this if we have a commit rate
2845          */
2846 #if 0
2847         probe_box = gtk_hbox_new(FALSE, 3);
2848         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2849
2850         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2851         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2852
2853         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2854         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2855 #endif
2856
2857         /*
2858          * Set up a drawing area and IOPS and bandwidth graphs
2859          */
2860         ui->graphs.drawing_area = gtk_drawing_area_new();
2861         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2862                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2863         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2864         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2865                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2866         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2867                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2868         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2869         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2870                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2871         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2872                                         ui->graphs.drawing_area);
2873         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2874                         TRUE, TRUE, 0);
2875
2876         setup_graphs(&ui->graphs);
2877
2878         /*
2879          * Set up alignments for widgets at the bottom of ui, 
2880          * align bottom left, expand horizontally but not vertically
2881          */
2882         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2883         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2884         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2885         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2886
2887         /*
2888          * Set up thread status progress bar
2889          */
2890         ui->thread_status_pb = gtk_progress_bar_new();
2891         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2892         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2893         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2894
2895         return main_vbox;
2896 }
2897
2898 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2899                                      guint page, gpointer data)
2900
2901 {
2902         struct gui *ui = (struct gui *) data;
2903         struct gui_entry *ge;
2904
2905         if (!page) {
2906                 set_job_menu_visible(ui, 0);
2907                 set_view_results_visible(ui, 0);
2908                 return TRUE;
2909         }
2910
2911         set_job_menu_visible(ui, 1);
2912         ge = get_ge_from_page(page, NULL);
2913         if (ge)
2914                 update_button_states(ui, ge);
2915
2916         return TRUE;
2917 }
2918
2919 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2920 {
2921         time_t time_a = gtk_recent_info_get_visited(a);
2922         time_t time_b = gtk_recent_info_get_visited(b);
2923
2924         return time_b - time_a;
2925 }
2926
2927 static void add_recent_file_items(struct gui *ui)
2928 {
2929         const gchar *gfio = g_get_application_name();
2930         GList *items, *item;
2931         int i = 0;
2932
2933         if (ui->recent_ui_id) {
2934                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2935                 gtk_ui_manager_ensure_update(ui->uimanager);
2936         }
2937         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2938
2939         if (ui->actiongroup) {
2940                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2941                 g_object_unref(ui->actiongroup);
2942         }
2943         ui->actiongroup = gtk_action_group_new("RecentFileActions");
2944
2945         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2946
2947         items = gtk_recent_manager_get_items(ui->recentmanager);
2948         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2949
2950         for (item = items; item && item->data; item = g_list_next(item)) {
2951                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2952                 gchar *action_name;
2953                 const gchar *label;
2954                 GtkAction *action;
2955
2956                 if (!gtk_recent_info_has_application(info, gfio))
2957                         continue;
2958
2959                 /*
2960                  * We only support local files for now
2961                  */
2962                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2963                         continue;
2964
2965                 action_name = g_strdup_printf("RecentFile%u", i++);
2966                 label = gtk_recent_info_get_display_name(info);
2967
2968                 action = g_object_new(GTK_TYPE_ACTION,
2969                                         "name", action_name,
2970                                         "label", label, NULL);
2971
2972                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2973                                         gtk_recent_info_ref(info),
2974                                         (GDestroyNotify) gtk_recent_info_unref);
2975
2976
2977                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2978
2979                 gtk_action_group_add_action(ui->actiongroup, action);
2980                 g_object_unref(action);
2981
2982                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2983                                         "/MainMenu/FileMenu/FileRecentFiles",
2984                                         label, action_name,
2985                                         GTK_UI_MANAGER_MENUITEM, FALSE);
2986
2987                 g_free(action_name);
2988
2989                 if (i == 8)
2990                         break;
2991         }
2992
2993         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2994         g_list_free(items);
2995 }
2996
2997 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2998                                    gint x, gint y, GtkSelectionData *data,
2999                                    guint info, guint time)
3000 {
3001         struct gui *ui = &main_ui;
3002         gchar **uris;
3003         GtkWidget *source;
3004         int i;
3005
3006         source = gtk_drag_get_source_widget(ctx);
3007         if (source && widget == gtk_widget_get_toplevel(source)) {
3008                 gtk_drag_finish(ctx, FALSE, FALSE, time);
3009                 return;
3010         }
3011
3012         uris = gtk_selection_data_get_uris(data);
3013         if (!uris) {
3014                 gtk_drag_finish(ctx, FALSE, FALSE, time);
3015                 return;
3016         }
3017
3018         i = 0;
3019         while (uris[i]) {
3020                 if (do_file_open_with_tab(ui, uris[i]))
3021                         break;
3022                 i++;
3023         }
3024
3025         gtk_drag_finish(ctx, TRUE, FALSE, time);
3026         g_strfreev(uris);
3027 }
3028
3029 static void init_ui(int *argc, char **argv[], struct gui *ui)
3030 {
3031         GtkSettings *settings;
3032         GtkWidget *vbox;
3033
3034         /* Magical g*thread incantation, you just need this thread stuff.
3035          * Without it, the update that happens in gfio_update_thread_status
3036          * doesn't really happen in a timely fashion, you need expose events
3037          */
3038         if (!g_thread_supported())
3039                 g_thread_init(NULL);
3040         gdk_threads_init();
3041
3042         gtk_init(argc, argv);
3043         settings = gtk_settings_get_default();
3044         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
3045         g_type_init();
3046         gdk_color_parse("white", &white);
3047         
3048         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3049         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
3050         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
3051
3052         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
3053         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
3054
3055         ui->vbox = gtk_vbox_new(FALSE, 0);
3056         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3057
3058         ui->uimanager = gtk_ui_manager_new();
3059         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3060         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3061
3062         ui->recentmanager = gtk_recent_manager_get_default();
3063         add_recent_file_items(ui);
3064
3065         ui->notebook = gtk_notebook_new();
3066         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3067         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3068         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3069         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3070
3071         vbox = new_main_page(ui);
3072         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3073         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3074         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3075
3076         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3077
3078         gfio_ui_setup_log(ui);
3079
3080         gtk_widget_show_all(ui->window);
3081 }
3082
3083 int main(int argc, char *argv[], char *envp[])
3084 {
3085         if (initialize_fio(envp))
3086                 return 1;
3087         if (fio_init_options())
3088                 return 1;
3089
3090         memset(&main_ui, 0, sizeof(main_ui));
3091         INIT_FLIST_HEAD(&main_ui.list);
3092
3093         init_ui(&argc, &argv, &main_ui);
3094
3095         gdk_threads_enter();
3096         gtk_main();
3097         gdk_threads_leave();
3098         return 0;
3099 }