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