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