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