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