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