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         struct gui_entry *ge;
2420
2421         ge = get_ge_from_cur_tab(ui);
2422         if (ge && ge->client)
2423                 gopt_get_options_window(ui->window, &ge->client->o);
2424 }
2425
2426 static void start_job_entry(GtkWidget *w, gpointer data)
2427 {
2428         struct gui *ui = (struct gui *) data;
2429         struct gui_entry *ge;
2430
2431         ge = get_ge_from_cur_tab(ui);
2432         if (ge)
2433                 start_job_clicked(w, ge);
2434 }
2435
2436 static void view_results(GtkWidget *w, gpointer data)
2437 {
2438         struct gui *ui = (struct gui *) data;
2439         struct gfio_client *gc;
2440         struct gui_entry *ge;
2441
2442         ge = get_ge_from_cur_tab(ui);
2443         if (!ge)
2444                 return;
2445
2446         if (ge->results_window)
2447                 return;
2448
2449         gc = ge->client;
2450         if (gc && gc->nr_results)
2451                 gfio_display_end_results(gc);
2452 }
2453
2454 static void __update_graph_limits(struct gfio_graphs *g)
2455 {
2456         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2457         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2458 }
2459
2460 static void update_graph_limits(void)
2461 {
2462         struct flist_head *entry;
2463         struct gui_entry *ge;
2464
2465         __update_graph_limits(&main_ui.graphs);
2466
2467         flist_for_each(entry, &main_ui.list) {
2468                 ge = flist_entry(entry, struct gui_entry, list);
2469                 __update_graph_limits(&ge->graphs);
2470         }
2471 }
2472
2473 static void preferences(GtkWidget *w, gpointer data)
2474 {
2475         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2476         GtkWidget *hbox, *spin, *entry, *spin_int;
2477         int i;
2478
2479         dialog = gtk_dialog_new_with_buttons("Preferences",
2480                 GTK_WINDOW(main_ui.window),
2481                 GTK_DIALOG_DESTROY_WITH_PARENT,
2482                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2483                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2484                 NULL);
2485
2486         frame = gtk_frame_new("Graphing");
2487         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2488         vbox = gtk_vbox_new(FALSE, 6);
2489         gtk_container_add(GTK_CONTAINER(frame), vbox);
2490
2491         hbox = gtk_hbox_new(FALSE, 5);
2492         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2493         entry = gtk_label_new("Font face to use for graph labels");
2494         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2495
2496         font = gtk_font_button_new();
2497         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2498
2499         box = gtk_vbox_new(FALSE, 6);
2500         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2501
2502         hbox = gtk_hbox_new(FALSE, 5);
2503         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2504         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2505         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2506
2507         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2508
2509         box = gtk_vbox_new(FALSE, 6);
2510         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2511
2512         hbox = gtk_hbox_new(FALSE, 5);
2513         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2514         entry = gtk_label_new("Client ETA request interval (msec)");
2515         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2516
2517         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2518         frame = gtk_frame_new("Debug logging");
2519         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2520         vbox = gtk_vbox_new(FALSE, 6);
2521         gtk_container_add(GTK_CONTAINER(frame), vbox);
2522
2523         box = gtk_hbox_new(FALSE, 6);
2524         gtk_container_add(GTK_CONTAINER(vbox), box);
2525
2526         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2527
2528         for (i = 0; i < FD_DEBUG_MAX; i++) {
2529                 if (i == 7) {
2530                         box = gtk_hbox_new(FALSE, 6);
2531                         gtk_container_add(GTK_CONTAINER(vbox), box);
2532                 }
2533
2534
2535                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2536                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2537                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2538         }
2539
2540         gtk_widget_show_all(dialog);
2541
2542         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2543                 gtk_widget_destroy(dialog);
2544                 return;
2545         }
2546
2547         for (i = 0; i < FD_DEBUG_MAX; i++) {
2548                 int set;
2549
2550                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2551                 if (set)
2552                         fio_debug |= (1UL << i);
2553         }
2554
2555         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2556         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2557         update_graph_limits();
2558         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2559
2560         gtk_widget_destroy(dialog);
2561 }
2562
2563 static void about_dialog(GtkWidget *w, gpointer data)
2564 {
2565         const char *authors[] = {
2566                 "Jens Axboe <axboe@kernel.dk>",
2567                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2568                 NULL
2569         };
2570         const char *license[] = {
2571                 "Fio is free software; you can redistribute it and/or modify "
2572                 "it under the terms of the GNU General Public License as published by "
2573                 "the Free Software Foundation; either version 2 of the License, or "
2574                 "(at your option) any later version.\n",
2575                 "Fio is distributed in the hope that it will be useful, "
2576                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2577                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2578                 "GNU General Public License for more details.\n",
2579                 "You should have received a copy of the GNU General Public License "
2580                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2581                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2582         };
2583         char *license_trans;
2584
2585         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2586                                      license[2], "\n", NULL);
2587
2588         gtk_show_about_dialog(NULL,
2589                 "program-name", "gfio",
2590                 "comments", "Gtk2 UI for fio",
2591                 "license", license_trans,
2592                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2593                 "authors", authors,
2594                 "version", fio_version_string,
2595                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2596                 "logo-icon-name", "fio",
2597                 /* Must be last: */
2598                 "wrap-license", TRUE,
2599                 NULL);
2600
2601         g_free(license_trans);
2602 }
2603
2604 static GtkActionEntry menu_items[] = {
2605         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2606         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2607         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2608         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2609         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2610         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2611         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2612         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2613         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2614         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2615         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
2616         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
2617         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2618         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2619         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2620         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2621         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2622 };
2623 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2624
2625 static const gchar *ui_string = " \
2626         <ui> \
2627                 <menubar name=\"MainMenu\"> \
2628                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2629                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2630                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2631                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2632                                 <separator name=\"Separator1\"/> \
2633                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2634                                 <separator name=\"Separator2\"/> \
2635                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2636                                 <separator name=\"Separator3\"/> \
2637                                 <placeholder name=\"FileRecentFiles\"/> \
2638                                 <separator name=\"Separator4\"/> \
2639                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2640                         </menu> \
2641                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2642                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2643                                 <separator name=\"Separator5\"/> \
2644                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2645                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2646                                 <separator name=\"Separator6\"/> \
2647                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2648                         </menu>\
2649                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2650                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
2651                                 <separator name=\"Separator7\"/> \
2652                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2653                         </menu>\
2654                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2655                                 <menuitem name=\"About\" action=\"About\" /> \
2656                         </menu> \
2657                 </menubar> \
2658         </ui> \
2659 ";
2660
2661 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2662                                    struct gui *ui)
2663 {
2664         GtkActionGroup *action_group;
2665         GError *error = 0;
2666
2667         action_group = gtk_action_group_new("Menu");
2668         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2669
2670         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2671         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2672
2673         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2674
2675         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2676 }
2677
2678 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2679                    GtkWidget *vbox, GtkUIManager *ui_manager)
2680 {
2681         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2682 }
2683
2684 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2685 {
2686         struct gui_entry *ge = (struct gui_entry *) data;
2687         gint index;
2688
2689         index = gtk_combo_box_get_active(box);
2690
2691         multitext_set_entry(&ge->eta.iotype, index);
2692         multitext_set_entry(&ge->eta.bs, index);
2693         multitext_set_entry(&ge->eta.ioengine, index);
2694         multitext_set_entry(&ge->eta.iodepth, index);
2695 }
2696
2697 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2698 {
2699         struct gui_entry *ge = (struct gui_entry *) data;
2700
2701         multitext_free(&ge->eta.iotype);
2702         multitext_free(&ge->eta.bs);
2703         multitext_free(&ge->eta.ioengine);
2704         multitext_free(&ge->eta.iodepth);
2705 }
2706
2707 static GtkWidget *new_client_page(struct gui_entry *ge)
2708 {
2709         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2710         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2711
2712         main_vbox = gtk_vbox_new(FALSE, 3);
2713
2714         top_align = gtk_alignment_new(0, 0, 1, 0);
2715         top_vbox = gtk_vbox_new(FALSE, 3);
2716         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2717         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2718
2719         probe = gtk_frame_new("Job");
2720         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2721         probe_frame = gtk_vbox_new(FALSE, 3);
2722         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2723
2724         probe_box = gtk_hbox_new(FALSE, 3);
2725         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2726         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2727         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2728         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2729         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2730
2731         probe_box = gtk_hbox_new(FALSE, 3);
2732         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2733
2734         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2735         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2736         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2737         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2738         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
2739         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2740         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2741         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2742         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2743
2744         probe_box = gtk_hbox_new(FALSE, 3);
2745         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2746         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2747         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2748         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2749         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2750
2751         /*
2752          * Only add this if we have a commit rate
2753          */
2754 #if 0
2755         probe_box = gtk_hbox_new(FALSE, 3);
2756         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2757
2758         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2759         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2760
2761         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2762         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2763 #endif
2764
2765         /*
2766          * Set up a drawing area and IOPS and bandwidth graphs
2767          */
2768         ge->graphs.drawing_area = gtk_drawing_area_new();
2769         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2770                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2771         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2772         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2773                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2774         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2775                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2776         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2777         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2778                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2779         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2780                                         ge->graphs.drawing_area);
2781         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2782
2783         setup_graphs(&ge->graphs);
2784
2785         /*
2786          * Set up alignments for widgets at the bottom of ui, 
2787          * align bottom left, expand horizontally but not vertically
2788          */
2789         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2790         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2791         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2792         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2793
2794         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2795
2796         /*
2797          * Set up thread status progress bar
2798          */
2799         ge->thread_status_pb = gtk_progress_bar_new();
2800         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2801         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2802         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2803
2804
2805         return main_vbox;
2806 }
2807
2808 static GtkWidget *new_main_page(struct gui *ui)
2809 {
2810         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2811         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2812
2813         main_vbox = gtk_vbox_new(FALSE, 3);
2814
2815         /*
2816          * Set up alignments for widgets at the top of ui,
2817          * align top left, expand horizontally but not vertically
2818          */
2819         top_align = gtk_alignment_new(0, 0, 1, 0);
2820         top_vbox = gtk_vbox_new(FALSE, 0);
2821         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2822         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2823
2824         probe = gtk_frame_new("Run statistics");
2825         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2826         probe_frame = gtk_vbox_new(FALSE, 3);
2827         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2828
2829         probe_box = gtk_hbox_new(FALSE, 3);
2830         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2831         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2832         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2833         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2834         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2835         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2836
2837         /*
2838          * Only add this if we have a commit rate
2839          */
2840 #if 0
2841         probe_box = gtk_hbox_new(FALSE, 3);
2842         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2843
2844         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2845         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2846
2847         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2848         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2849 #endif
2850
2851         /*
2852          * Set up a drawing area and IOPS and bandwidth graphs
2853          */
2854         ui->graphs.drawing_area = gtk_drawing_area_new();
2855         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2856                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2857         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2858         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2859                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2860         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2861                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2862         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2863         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2864                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2865         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2866                                         ui->graphs.drawing_area);
2867         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2868                         TRUE, TRUE, 0);
2869
2870         setup_graphs(&ui->graphs);
2871
2872         /*
2873          * Set up alignments for widgets at the bottom of ui, 
2874          * align bottom left, expand horizontally but not vertically
2875          */
2876         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2877         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2878         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2879         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2880
2881         /*
2882          * Set up thread status progress bar
2883          */
2884         ui->thread_status_pb = gtk_progress_bar_new();
2885         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2886         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2887         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2888
2889         return main_vbox;
2890 }
2891
2892 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2893                                      guint page, gpointer data)
2894
2895 {
2896         struct gui *ui = (struct gui *) data;
2897         struct gui_entry *ge;
2898
2899         if (!page) {
2900                 set_job_menu_visible(ui, 0);
2901                 set_view_results_visible(ui, 0);
2902                 return TRUE;
2903         }
2904
2905         set_job_menu_visible(ui, 1);
2906         ge = get_ge_from_page(page, NULL);
2907         if (ge)
2908                 update_button_states(ui, ge);
2909
2910         return TRUE;
2911 }
2912
2913 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2914 {
2915         time_t time_a = gtk_recent_info_get_visited(a);
2916         time_t time_b = gtk_recent_info_get_visited(b);
2917
2918         return time_b - time_a;
2919 }
2920
2921 static void add_recent_file_items(struct gui *ui)
2922 {
2923         const gchar *gfio = g_get_application_name();
2924         GList *items, *item;
2925         int i = 0;
2926
2927         if (ui->recent_ui_id) {
2928                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2929                 gtk_ui_manager_ensure_update(ui->uimanager);
2930         }
2931         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2932
2933         if (ui->actiongroup) {
2934                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2935                 g_object_unref(ui->actiongroup);
2936         }
2937         ui->actiongroup = gtk_action_group_new("RecentFileActions");
2938
2939         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2940
2941         items = gtk_recent_manager_get_items(ui->recentmanager);
2942         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2943
2944         for (item = items; item && item->data; item = g_list_next(item)) {
2945                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2946                 gchar *action_name;
2947                 const gchar *label;
2948                 GtkAction *action;
2949
2950                 if (!gtk_recent_info_has_application(info, gfio))
2951                         continue;
2952
2953                 /*
2954                  * We only support local files for now
2955                  */
2956                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2957                         continue;
2958
2959                 action_name = g_strdup_printf("RecentFile%u", i++);
2960                 label = gtk_recent_info_get_display_name(info);
2961
2962                 action = g_object_new(GTK_TYPE_ACTION,
2963                                         "name", action_name,
2964                                         "label", label, NULL);
2965
2966                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2967                                         gtk_recent_info_ref(info),
2968                                         (GDestroyNotify) gtk_recent_info_unref);
2969
2970
2971                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2972
2973                 gtk_action_group_add_action(ui->actiongroup, action);
2974                 g_object_unref(action);
2975
2976                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2977                                         "/MainMenu/FileMenu/FileRecentFiles",
2978                                         label, action_name,
2979                                         GTK_UI_MANAGER_MENUITEM, FALSE);
2980
2981                 g_free(action_name);
2982
2983                 if (i == 8)
2984                         break;
2985         }
2986
2987         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2988         g_list_free(items);
2989 }
2990
2991 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2992                                    gint x, gint y, GtkSelectionData *data,
2993                                    guint info, guint time)
2994 {
2995         struct gui *ui = &main_ui;
2996         gchar **uris;
2997         GtkWidget *source;
2998         int i;
2999
3000         source = gtk_drag_get_source_widget(ctx);
3001         if (source && widget == gtk_widget_get_toplevel(source)) {
3002                 gtk_drag_finish(ctx, FALSE, FALSE, time);
3003                 return;
3004         }
3005
3006         uris = gtk_selection_data_get_uris(data);
3007         if (!uris) {
3008                 gtk_drag_finish(ctx, FALSE, FALSE, time);
3009                 return;
3010         }
3011
3012         i = 0;
3013         while (uris[i]) {
3014                 if (do_file_open_with_tab(ui, uris[i]))
3015                         break;
3016                 i++;
3017         }
3018
3019         gtk_drag_finish(ctx, TRUE, FALSE, time);
3020         g_strfreev(uris);
3021 }
3022
3023 static void init_ui(int *argc, char **argv[], struct gui *ui)
3024 {
3025         GtkSettings *settings;
3026         GtkWidget *vbox;
3027
3028         /* Magical g*thread incantation, you just need this thread stuff.
3029          * Without it, the update that happens in gfio_update_thread_status
3030          * doesn't really happen in a timely fashion, you need expose events
3031          */
3032         if (!g_thread_supported())
3033                 g_thread_init(NULL);
3034         gdk_threads_init();
3035
3036         gtk_init(argc, argv);
3037         settings = gtk_settings_get_default();
3038         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
3039         g_type_init();
3040         gdk_color_parse("white", &white);
3041         
3042         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3043         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
3044         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
3045
3046         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
3047         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
3048
3049         ui->vbox = gtk_vbox_new(FALSE, 0);
3050         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3051
3052         ui->uimanager = gtk_ui_manager_new();
3053         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3054         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3055
3056         ui->recentmanager = gtk_recent_manager_get_default();
3057         add_recent_file_items(ui);
3058
3059         ui->notebook = gtk_notebook_new();
3060         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3061         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3062         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3063         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3064
3065         vbox = new_main_page(ui);
3066         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3067         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3068         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3069
3070         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3071
3072         gfio_ui_setup_log(ui);
3073
3074         gtk_widget_show_all(ui->window);
3075 }
3076
3077 int main(int argc, char *argv[], char *envp[])
3078 {
3079         if (initialize_fio(envp))
3080                 return 1;
3081         if (fio_init_options())
3082                 return 1;
3083
3084         memset(&main_ui, 0, sizeof(main_ui));
3085         INIT_FLIST_HEAD(&main_ui.list);
3086
3087         init_ui(&argc, &argv, &main_ui);
3088
3089         gdk_threads_enter();
3090         gtk_main();
3091         gdk_threads_leave();
3092         return 0;
3093 }