gfio: fix attempt to add multiple entries to disk util frame
[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_vbox;
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         free(io_p);
897         free(bw_p);
898         free(iops_p);
899 }
900
901 static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
902                                             const char **labels,
903                                             unsigned int len,
904                                             double xdim, double ydim)
905 {
906         struct graph *g;
907         int i;
908
909         g = graph_new(xdim, ydim, gfio_graph_font);
910         graph_title(g, title);
911         graph_x_title(g, "Buckets");
912
913         for (i = 0; i < len; i++) {
914                 graph_add_label(g, labels[i]);
915                 graph_add_data(g, labels[i], lat[i]);
916         }
917
918         return g;
919 }
920
921 static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels,
922                                           int num)
923 {
924         GtkWidget *tree_view;
925         GtkTreeSelection *selection;
926         GtkListStore *model;
927         GtkTreeIter iter;
928         GType *types;
929         int i;
930
931         types = malloc(num * sizeof(GType));
932
933         for (i = 0; i < num; i++)
934                 types[i] = G_TYPE_STRING;
935
936         model = gtk_list_store_newv(num, types);
937         free(types);
938         types = NULL;
939
940         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
941         gtk_widget_set_can_focus(tree_view, FALSE);
942
943         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
944                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
945
946         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
947         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
948
949         for (i = 0; i < num; i++)
950                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
951
952         gtk_list_store_append(model, &iter);
953
954         for (i = 0; i < num; i++) {
955                 char fbuf[32];
956
957                 if (lat[i] <= 0.0)
958                         sprintf(fbuf, "0.00");
959                 else
960                         sprintf(fbuf, "%3.2f%%", lat[i]);
961
962                 gtk_list_store_set(model, &iter, i, fbuf, -1);
963         }
964
965         return tree_view;
966 }
967
968 static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox,
969                                       struct thread_stat *ts)
970 {
971         double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR];
972         const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u",
973                                  "250u", "500u", "750u", "1m", "2m",
974                                  "4m", "10m", "20m", "50m", "100m",
975                                  "250m", "500m", "750m", "1s", "2s", ">= 2s" };
976         int start, end, i;
977         const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR;
978         GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area;
979         struct gui_entry *ge = gc->ge;
980
981         stat_calc_lat_u(ts, io_u_lat);
982         stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]);
983
984         /*
985          * Found out which first bucket has entries, and which last bucket
986          */
987         start = end = -1U;
988         for (i = 0; i < total; i++) {
989                 if (io_u_lat[i] == 0.00)
990                         continue;
991
992                 if (start == -1U)
993                         start = i;
994                 end = i;
995         }
996
997         /*
998          * No entries...
999          */
1000         if (start == -1U)
1001                 return;
1002                 
1003         tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1);
1004         ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0);
1005
1006         frame = gtk_frame_new("Latency buckets");
1007         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1008
1009         completion_vbox = gtk_vbox_new(FALSE, 3);
1010         gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
1011         hbox = gtk_hbox_new(FALSE, 3);
1012         gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
1013
1014         drawing_area = gtk_drawing_area_new();
1015         gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
1016         gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &white);
1017         gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
1018         g_signal_connect(G_OBJECT(drawing_area), "expose_event", G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph);
1019         g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph);
1020
1021         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
1022 }
1023
1024 static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
1025 {
1026         GtkWidget *box, *frame, *entry;
1027         double usr_cpu, sys_cpu;
1028         unsigned long runtime;
1029         char tmp[32];
1030
1031         runtime = ts->total_run_time;
1032         if (runtime) {
1033                 double runt = (double) runtime;
1034
1035                 usr_cpu = (double) ts->usr_time * 100 / runt;
1036                 sys_cpu = (double) ts->sys_time * 100 / runt;
1037         } else {
1038                 usr_cpu = 0;
1039                 sys_cpu = 0;
1040         }
1041
1042         frame = gtk_frame_new("OS resources");
1043         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1044
1045         box = gtk_hbox_new(FALSE, 3);
1046         gtk_container_add(GTK_CONTAINER(frame), box);
1047
1048         entry = new_info_entry_in_frame(box, "User CPU");
1049         sprintf(tmp, "%3.2f%%", usr_cpu);
1050         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1051         entry = new_info_entry_in_frame(box, "System CPU");
1052         sprintf(tmp, "%3.2f%%", sys_cpu);
1053         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1054         entry = new_info_entry_in_frame(box, "Context switches");
1055         entry_set_int_value(entry, ts->ctx);
1056         entry = new_info_entry_in_frame(box, "Major faults");
1057         entry_set_int_value(entry, ts->majf);
1058         entry = new_info_entry_in_frame(box, "Minor faults");
1059         entry_set_int_value(entry, ts->minf);
1060 }
1061 static void gfio_add_sc_depths_tree(GtkListStore *model,
1062                                     struct thread_stat *ts, unsigned int len,
1063                                     int submit)
1064 {
1065         double io_u_dist[FIO_IO_U_MAP_NR];
1066         GtkTreeIter iter;
1067         /* Bits 0, and 3-8 */
1068         const int add_mask = 0x1f9;
1069         int i, j;
1070
1071         if (submit)
1072                 stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
1073         else
1074                 stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
1075
1076         gtk_list_store_append(model, &iter);
1077
1078         gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
1079
1080         for (i = 1, j = 0; i < len; i++) {
1081                 char fbuf[32];
1082
1083                 if (!(add_mask & (1UL << (i - 1))))
1084                         sprintf(fbuf, "0.0%%");
1085                 else {
1086                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
1087                         j++;
1088                 }
1089
1090                 gtk_list_store_set(model, &iter, i, fbuf, -1);
1091         }
1092
1093 }
1094
1095 static void gfio_add_total_depths_tree(GtkListStore *model,
1096                                        struct thread_stat *ts, unsigned int len)
1097 {
1098         double io_u_dist[FIO_IO_U_MAP_NR];
1099         GtkTreeIter iter;
1100         /* Bits 1-6, and 8 */
1101         const int add_mask = 0x17e;
1102         int i, j;
1103
1104         stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
1105
1106         gtk_list_store_append(model, &iter);
1107
1108         gtk_list_store_set(model, &iter, 0, "Total", -1);
1109
1110         for (i = 1, j = 0; i < len; i++) {
1111                 char fbuf[32];
1112
1113                 if (!(add_mask & (1UL << (i - 1))))
1114                         sprintf(fbuf, "0.0%%");
1115                 else {
1116                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
1117                         j++;
1118                 }
1119
1120                 gtk_list_store_set(model, &iter, i, fbuf, -1);
1121         }
1122
1123 }
1124
1125 static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
1126 {
1127         GtkWidget *frame, *box, *tree_view;
1128         GtkTreeSelection *selection;
1129         GtkListStore *model;
1130         GType types[FIO_IO_U_MAP_NR + 1];
1131         int i;
1132 #define NR_LABELS       10
1133         const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
1134
1135         frame = gtk_frame_new("IO depths");
1136         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1137
1138         box = gtk_hbox_new(FALSE, 3);
1139         gtk_container_add(GTK_CONTAINER(frame), box);
1140
1141         for (i = 0; i < NR_LABELS; i++)
1142                 types[i] = G_TYPE_STRING;
1143
1144         model = gtk_list_store_newv(NR_LABELS, types);
1145
1146         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1147         gtk_widget_set_can_focus(tree_view, FALSE);
1148
1149         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
1150                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
1151
1152         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
1153         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
1154
1155         for (i = 0; i < NR_LABELS; i++)
1156                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
1157
1158         gfio_add_total_depths_tree(model, ts, NR_LABELS);
1159         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 1);
1160         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 0);
1161
1162         gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
1163 }
1164
1165 static gboolean results_window_delete(GtkWidget *w, gpointer data)
1166 {
1167         struct gui_entry *ge = (struct gui_entry *) data;
1168
1169         gtk_widget_destroy(w);
1170         ge->results_window = NULL;
1171         ge->results_notebook = NULL;
1172         return TRUE;
1173 }
1174
1175 static void results_close(GtkWidget *w, gpointer *data)
1176 {
1177         struct gui_entry *ge = (struct gui_entry *) data;
1178
1179         gtk_widget_destroy(ge->results_window);
1180 }
1181
1182 static GtkActionEntry results_menu_items[] = {
1183         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1184         { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
1185         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(results_close) },
1186 };
1187 static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]);
1188
1189 static const gchar *results_ui_string = " \
1190         <ui> \
1191                 <menubar name=\"MainMenu\"> \
1192                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1193                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1194                         </menu> \
1195                         <menu name=\"GraphMenu\" action=\"GraphMenuAction\"> \
1196                         </menu>\
1197                 </menubar> \
1198         </ui> \
1199 ";
1200
1201 static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge)
1202 {
1203         GtkActionGroup *action_group;
1204         GtkWidget *widget;
1205         GError *error = 0;
1206
1207         ge->results_uimanager = gtk_ui_manager_new();
1208
1209         action_group = gtk_action_group_new("ResultsMenu");
1210         gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge);
1211
1212         gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0);
1213         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error);
1214
1215         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager));
1216
1217         widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu");
1218         return widget;
1219 }
1220
1221 static GtkWidget *get_results_window(struct gui_entry *ge)
1222 {
1223         GtkWidget *win, *notebook, *vbox;
1224
1225         if (ge->results_window)
1226                 return ge->results_notebook;
1227
1228         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1229         gtk_window_set_title(GTK_WINDOW(win), "Results");
1230         gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
1231         g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
1232         g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
1233
1234         vbox = gtk_vbox_new(FALSE, 0);
1235         gtk_container_add(GTK_CONTAINER(win), vbox);
1236
1237         ge->results_menu = get_results_menubar(win, ge);
1238         gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0);
1239
1240         notebook = gtk_notebook_new();
1241         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
1242         gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
1243         gtk_container_add(GTK_CONTAINER(vbox), notebook);
1244
1245         ge->results_window = win;
1246         ge->results_notebook = notebook;
1247         return ge->results_notebook;
1248 }
1249
1250 static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts,
1251                                  struct group_run_stats *rs)
1252 {
1253         unsigned int nr = gc->nr_results;
1254
1255         gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results));
1256         memcpy(&gc->results[nr].ts, ts, sizeof(*ts));
1257         memcpy(&gc->results[nr].gs, rs, sizeof(*rs));
1258         gc->nr_results++;
1259 }
1260
1261 static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc,
1262                                        struct thread_stat *ts,
1263                                        struct group_run_stats *rs)
1264 {
1265         GtkWidget *box, *vbox, *entry, *scroll;
1266
1267         scroll = gtk_scrolled_window_new(NULL, NULL);
1268         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1269         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1270
1271         vbox = gtk_vbox_new(FALSE, 3);
1272
1273         box = gtk_hbox_new(FALSE, 0);
1274         gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
1275
1276         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
1277
1278         gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name));
1279
1280         gc->results_widget = vbox;
1281
1282         entry = new_info_entry_in_frame(box, "Name");
1283         gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
1284         if (strlen(ts->description)) {
1285                 entry = new_info_entry_in_frame(box, "Description");
1286                 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
1287         }
1288         entry = new_info_entry_in_frame(box, "Group ID");
1289         entry_set_int_value(entry, ts->groupid);
1290         entry = new_info_entry_in_frame(box, "Jobs");
1291         entry_set_int_value(entry, ts->members);
1292         gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
1293         entry_set_int_value(entry, ts->error);
1294         entry = new_info_entry_in_frame(box, "PID");
1295         entry_set_int_value(entry, ts->pid);
1296
1297         if (ts->io_bytes[DDIR_READ])
1298                 gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_READ);
1299         if (ts->io_bytes[DDIR_WRITE])
1300                 gfio_show_ddir_status(gc, vbox, rs, ts, DDIR_WRITE);
1301
1302         gfio_show_latency_buckets(gc, vbox, ts);
1303         gfio_show_cpu_usage(vbox, ts);
1304         gfio_show_io_depths(vbox, ts);
1305 }
1306
1307 static void gfio_display_end_results(struct gfio_client *gc)
1308 {
1309         struct gui_entry *ge = gc->ge;
1310         GtkWidget *res_notebook;
1311         int i;
1312
1313         res_notebook = get_results_window(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_notebook, gc, &e->ts, &e->gs);
1319         }
1320
1321         gtk_widget_show_all(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_vbox) {
1378                 frame = gtk_frame_new("Disk utilization");
1379                 gtk_box_pack_start(GTK_BOX(gc->results_widget), frame, FALSE, FALSE, 5);
1380                 vbox = gtk_vbox_new(FALSE, 3);
1381                 gtk_container_add(GTK_CONTAINER(frame), vbox);
1382                 gc->disk_util_vbox = vbox;
1383         }
1384
1385         vbox = gtk_vbox_new(FALSE, 3);
1386         gtk_container_add(GTK_CONTAINER(gc->disk_util_vbox), vbox);
1387
1388         frame = gtk_frame_new((char *) p->dus.name);
1389         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
1390
1391         box = gtk_vbox_new(FALSE, 3);
1392         gtk_container_add(GTK_CONTAINER(frame), box);
1393
1394         frame = gtk_frame_new("Read");
1395         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1396         vbox = gtk_hbox_new(TRUE, 3);
1397         gtk_container_add(GTK_CONTAINER(frame), vbox);
1398         entry = new_info_entry_in_frame(vbox, "IOs");
1399         entry_set_int_value(entry, p->dus.ios[0]);
1400         entry = new_info_entry_in_frame(vbox, "Merges");
1401         entry_set_int_value(entry, p->dus.merges[0]);
1402         entry = new_info_entry_in_frame(vbox, "Sectors");
1403         entry_set_int_value(entry, p->dus.sectors[0]);
1404         entry = new_info_entry_in_frame(vbox, "Ticks");
1405         entry_set_int_value(entry, p->dus.ticks[0]);
1406
1407         frame = gtk_frame_new("Write");
1408         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1409         vbox = gtk_hbox_new(TRUE, 3);
1410         gtk_container_add(GTK_CONTAINER(frame), vbox);
1411         entry = new_info_entry_in_frame(vbox, "IOs");
1412         entry_set_int_value(entry, p->dus.ios[1]);
1413         entry = new_info_entry_in_frame(vbox, "Merges");
1414         entry_set_int_value(entry, p->dus.merges[1]);
1415         entry = new_info_entry_in_frame(vbox, "Sectors");
1416         entry_set_int_value(entry, p->dus.sectors[1]);
1417         entry = new_info_entry_in_frame(vbox, "Ticks");
1418         entry_set_int_value(entry, p->dus.ticks[1]);
1419
1420         frame = gtk_frame_new("Shared");
1421         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
1422         vbox = gtk_hbox_new(TRUE, 3);
1423         gtk_container_add(GTK_CONTAINER(frame), vbox);
1424         entry = new_info_entry_in_frame(vbox, "IO ticks");
1425         entry_set_int_value(entry, p->dus.io_ticks);
1426         entry = new_info_entry_in_frame(vbox, "Time in queue");
1427         entry_set_int_value(entry, p->dus.time_in_queue);
1428
1429         util = 0.0;
1430         if (p->dus.msec)
1431                 util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec;
1432         if (util > 100.0)
1433                 util = 100.0;
1434
1435         sprintf(tmp, "%3.2f%%", util);
1436         entry = new_info_entry_in_frame(vbox, "Disk utilization");
1437         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1438
1439         gtk_widget_show_all(gc->results_widget);
1440 out:
1441         gdk_threads_leave();
1442 }
1443
1444 extern int sum_stat_clients;
1445 extern struct thread_stat client_ts;
1446 extern struct group_run_stats client_gs;
1447
1448 static int sum_stat_nr;
1449
1450 static void gfio_thread_status_op(struct fio_client *client,
1451                                   struct fio_net_cmd *cmd)
1452 {
1453         struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
1454
1455         gfio_display_ts(client, &p->ts, &p->rs);
1456
1457         if (sum_stat_clients == 1)
1458                 return;
1459
1460         sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
1461         sum_group_stats(&client_gs, &p->rs);
1462
1463         client_ts.members++;
1464         client_ts.groupid = p->ts.groupid;
1465
1466         if (++sum_stat_nr == sum_stat_clients) {
1467                 strcpy(client_ts.name, "All clients");
1468                 gfio_display_ts(client, &client_ts, &client_gs);
1469         }
1470 }
1471
1472 static void gfio_group_stats_op(struct fio_client *client,
1473                                 struct fio_net_cmd *cmd)
1474 {
1475         /* We're ignoring group stats for now */
1476 }
1477
1478 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
1479                                    gpointer data)
1480 {
1481         struct gfio_graphs *g = data;
1482
1483         graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
1484         graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
1485         graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
1486         graph_set_position(g->bandwidth_graph, 0, 0);
1487         return TRUE;
1488 }
1489
1490 static void draw_graph(struct graph *g, cairo_t *cr)
1491 {
1492         line_graph_draw(g, cr);
1493         cairo_stroke(cr);
1494 }
1495
1496 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
1497                               gboolean keyboard_mode, GtkTooltip *tooltip,
1498                               gpointer data)
1499 {
1500         struct gfio_graphs *g = data;
1501         const char *text = NULL;
1502
1503         if (graph_contains_xy(g->iops_graph, x, y))
1504                 text = graph_find_tooltip(g->iops_graph, x, y);
1505         else if (graph_contains_xy(g->bandwidth_graph, x, y))
1506                 text = graph_find_tooltip(g->bandwidth_graph, x, y);
1507
1508         if (text) {
1509                 gtk_tooltip_set_text(tooltip, text);
1510                 return TRUE;
1511         }
1512
1513         return FALSE;
1514 }
1515
1516 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
1517 {
1518         struct gfio_graphs *g = p;
1519         cairo_t *cr;
1520
1521         cr = gdk_cairo_create(w->window);
1522
1523         if (graph_has_tooltips(g->iops_graph) ||
1524             graph_has_tooltips(g->bandwidth_graph)) {
1525                 g_object_set(w, "has-tooltip", TRUE, NULL);
1526                 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
1527         }
1528
1529         cairo_set_source_rgb(cr, 0, 0, 0);
1530         draw_graph(g->iops_graph, cr);
1531         draw_graph(g->bandwidth_graph, cr);
1532         cairo_destroy(cr);
1533
1534         return FALSE;
1535 }
1536
1537 /*
1538  * Client specific ETA
1539  */
1540 static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
1541 {
1542         struct gfio_client *gc = client->client_data;
1543         struct gui_entry *ge = gc->ge;
1544         static int eta_good;
1545         char eta_str[128];
1546         char output[256];
1547         char tmp[32];
1548         double perc = 0.0;
1549         int i2p = 0;
1550
1551         gdk_threads_enter();
1552
1553         eta_str[0] = '\0';
1554         output[0] = '\0';
1555
1556         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1557                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1558                 eta_to_str(eta_str, je->eta_sec);
1559         }
1560
1561         sprintf(tmp, "%u", je->nr_running);
1562         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
1563         sprintf(tmp, "%u", je->files_open);
1564         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
1565
1566 #if 0
1567         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1568         if (je->m_rate || je->t_rate) {
1569                 char *tr, *mr;
1570
1571                 mr = num2str(je->m_rate, 4, 0, i2p);
1572                 tr = num2str(je->t_rate, 4, 0, i2p);
1573                 gtk_entry_set_text(GTK_ENTRY(ge->eta);
1574                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1575                 free(tr);
1576                 free(mr);
1577         } else if (je->m_iops || je->t_iops)
1578                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1579
1580         gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
1581         gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
1582         gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
1583         gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
1584 #endif
1585
1586         if (je->eta_sec != INT_MAX && je->nr_running) {
1587                 char *iops_str[2];
1588                 char *rate_str[2];
1589
1590                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1591                         strcpy(output, "-.-% done");
1592                 else {
1593                         eta_good = 1;
1594                         perc *= 100.0;
1595                         sprintf(output, "%3.1f%% done", perc);
1596                 }
1597
1598                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1599                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1600
1601                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1602                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1603
1604                 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
1605                 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
1606                 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
1607                 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
1608
1609                 graph_add_xy_data(ge->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1610                 graph_add_xy_data(ge->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1611                 graph_add_xy_data(ge->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1612                 graph_add_xy_data(ge->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1613
1614                 free(rate_str[0]);
1615                 free(rate_str[1]);
1616                 free(iops_str[0]);
1617                 free(iops_str[1]);
1618         }
1619
1620         if (eta_str[0]) {
1621                 char *dst = output + strlen(output);
1622
1623                 sprintf(dst, " - %s", eta_str);
1624         }
1625                 
1626         gfio_update_thread_status(ge, output, perc);
1627         gdk_threads_leave();
1628 }
1629
1630 /*
1631  * Update ETA in main window for all clients
1632  */
1633 static void gfio_update_all_eta(struct jobs_eta *je)
1634 {
1635         struct gui *ui = &main_ui;
1636         static int eta_good;
1637         char eta_str[128];
1638         char output[256];
1639         double perc = 0.0;
1640         int i2p = 0;
1641
1642         gdk_threads_enter();
1643
1644         eta_str[0] = '\0';
1645         output[0] = '\0';
1646
1647         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1648                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1649                 eta_to_str(eta_str, je->eta_sec);
1650         }
1651
1652 #if 0
1653         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1654         if (je->m_rate || je->t_rate) {
1655                 char *tr, *mr;
1656
1657                 mr = num2str(je->m_rate, 4, 0, i2p);
1658                 tr = num2str(je->t_rate, 4, 0, i2p);
1659                 gtk_entry_set_text(GTK_ENTRY(ui->eta);
1660                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1661                 free(tr);
1662                 free(mr);
1663         } else if (je->m_iops || je->t_iops)
1664                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1665
1666         gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
1667         gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
1668         gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
1669         gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
1670 #endif
1671
1672         entry_set_int_value(ui->eta.jobs, je->nr_running);
1673
1674         if (je->eta_sec != INT_MAX && je->nr_running) {
1675                 char *iops_str[2];
1676                 char *rate_str[2];
1677
1678                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1679                         strcpy(output, "-.-% done");
1680                 else {
1681                         eta_good = 1;
1682                         perc *= 100.0;
1683                         sprintf(output, "%3.1f%% done", perc);
1684                 }
1685
1686                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1687                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1688
1689                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1690                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1691
1692                 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
1693                 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
1694                 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
1695                 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
1696
1697                 graph_add_xy_data(ui->graphs.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0], iops_str[0]);
1698                 graph_add_xy_data(ui->graphs.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1], iops_str[1]);
1699                 graph_add_xy_data(ui->graphs.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0], rate_str[0]);
1700                 graph_add_xy_data(ui->graphs.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1], rate_str[1]);
1701
1702                 free(rate_str[0]);
1703                 free(rate_str[1]);
1704                 free(iops_str[0]);
1705                 free(iops_str[1]);
1706         }
1707
1708         if (eta_str[0]) {
1709                 char *dst = output + strlen(output);
1710
1711                 sprintf(dst, " - %s", eta_str);
1712         }
1713                 
1714         gfio_update_thread_status_all(output, perc);
1715         gdk_threads_leave();
1716 }
1717
1718 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
1719 {
1720         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
1721         struct gfio_client *gc = client->client_data;
1722         struct gui_entry *ge = gc->ge;
1723         const char *os, *arch;
1724         char buf[64];
1725
1726         os = fio_get_os_string(probe->os);
1727         if (!os)
1728                 os = "unknown";
1729
1730         arch = fio_get_arch_string(probe->arch);
1731         if (!arch)
1732                 os = "unknown";
1733
1734         if (!client->name)
1735                 client->name = strdup((char *) probe->hostname);
1736
1737         gdk_threads_enter();
1738
1739         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
1740         gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
1741         gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
1742         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
1743         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), buf);
1744
1745         gfio_set_state(ge, GE_STATE_CONNECTED);
1746
1747         gdk_threads_leave();
1748 }
1749
1750 static void gfio_update_thread_status(struct gui_entry *ge,
1751                                       char *status_message, double perc)
1752 {
1753         static char message[100];
1754         const char *m = message;
1755
1756         strncpy(message, status_message, sizeof(message) - 1);
1757         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
1758         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
1759         gtk_widget_queue_draw(main_ui.window);
1760 }
1761
1762 static void gfio_update_thread_status_all(char *status_message, double perc)
1763 {
1764         struct gui *ui = &main_ui;
1765         static char message[100];
1766         const char *m = message;
1767
1768         strncpy(message, status_message, sizeof(message) - 1);
1769         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
1770         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
1771         gtk_widget_queue_draw(ui->window);
1772 }
1773
1774 static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd)
1775 {
1776         struct gfio_client *gc = client->client_data;
1777
1778         gdk_threads_enter();
1779         gfio_set_state(gc->ge, GE_STATE_NEW);
1780         gdk_threads_leave();
1781 }
1782
1783 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
1784 {
1785         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
1786         struct gfio_client *gc = client->client_data;
1787         struct thread_options *o = &gc->o;
1788         struct gui_entry *ge = gc->ge;
1789         char tmp[8];
1790
1791         convert_thread_options_to_cpu(o, &p->top);
1792
1793         gdk_threads_enter();
1794
1795         gtk_label_set_text(GTK_LABEL(ge->page_label), (gchar *) o->name);
1796
1797         gtk_combo_box_append_text(GTK_COMBO_BOX(ge->eta.names), (gchar *) o->name);
1798         gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
1799
1800         multitext_add_entry(&ge->eta.iotype, ddir_str(o->td_ddir));
1801         multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
1802
1803         sprintf(tmp, "%u", o->iodepth);
1804         multitext_add_entry(&ge->eta.iodepth, tmp);
1805
1806         multitext_set_entry(&ge->eta.iotype, 0);
1807         multitext_set_entry(&ge->eta.ioengine, 0);
1808         multitext_set_entry(&ge->eta.iodepth, 0);
1809
1810         gc->job_added++;
1811
1812         gfio_set_state(ge, GE_STATE_JOB_SENT);
1813
1814         gdk_threads_leave();
1815 }
1816
1817 static void gfio_client_timed_out(struct fio_client *client)
1818 {
1819         struct gfio_client *gc = client->client_data;
1820         char buf[256];
1821
1822         gdk_threads_enter();
1823
1824         gfio_set_state(gc->ge, GE_STATE_NEW);
1825         clear_ge_ui_info(gc->ge);
1826
1827         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
1828         show_info_dialog(gc->ge->ui, "Network timeout", buf);
1829
1830         gdk_threads_leave();
1831 }
1832
1833 static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
1834 {
1835         struct gfio_client *gc = client->client_data;
1836
1837         gdk_threads_enter();
1838
1839         gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
1840
1841         if (gc->err_entry)
1842                 entry_set_int_value(gc->err_entry, client->error);
1843
1844         gdk_threads_leave();
1845 }
1846
1847 static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
1848 {
1849         struct gfio_client *gc = client->client_data;
1850
1851         gdk_threads_enter();
1852         gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
1853         gdk_threads_leave();
1854 }
1855
1856 static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
1857 {
1858         struct gfio_client *gc = client->client_data;
1859
1860         gdk_threads_enter();
1861         gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
1862         gdk_threads_leave();
1863 }
1864
1865 static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu)
1866 {
1867         printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples);
1868         free(pdu);
1869 }
1870
1871 struct client_ops gfio_client_ops = {
1872         .text                   = gfio_text_op,
1873         .disk_util              = gfio_disk_util_op,
1874         .thread_status          = gfio_thread_status_op,
1875         .group_stats            = gfio_group_stats_op,
1876         .jobs_eta               = gfio_update_client_eta,
1877         .eta                    = gfio_update_all_eta,
1878         .probe                  = gfio_probe_op,
1879         .quit                   = gfio_quit_op,
1880         .add_job                = gfio_add_job_op,
1881         .timed_out              = gfio_client_timed_out,
1882         .stop                   = gfio_client_stop,
1883         .start                  = gfio_client_start,
1884         .job_start              = gfio_client_job_start,
1885         .iolog                  = gfio_client_iolog,
1886         .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
1887         .stay_connected         = 1,
1888         .client_type            = FIO_CLIENT_TYPE_GUI,
1889 };
1890
1891 /*
1892  * FIXME: need more handling here
1893  */
1894 static void ge_destroy(struct gui_entry *ge)
1895 {
1896         struct gfio_client *gc = ge->client;
1897
1898         if (gc && gc->client) {
1899                 if (ge->state >= GE_STATE_CONNECTED)
1900                         fio_client_terminate(gc->client);
1901
1902                 fio_put_client(gc->client);
1903         }
1904
1905         flist_del(&ge->list);
1906         free(ge);
1907 }
1908
1909 static void ge_widget_destroy(GtkWidget *w, gpointer data)
1910 {
1911 }
1912
1913 static void gfio_quit(struct gui *ui)
1914 {
1915         struct gui_entry *ge;
1916
1917         while (!flist_empty(&ui->list)) {
1918                 ge = flist_entry(ui->list.next, struct gui_entry, list);
1919                 ge_destroy(ge);
1920         }
1921
1922         gtk_main_quit();
1923 }
1924
1925 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1926                 __attribute__((unused)) gpointer data)
1927 {
1928         gfio_quit(data);
1929 }
1930
1931 static void *job_thread(void *arg)
1932 {
1933         struct gui *ui = arg;
1934
1935         ui->handler_running = 1;
1936         fio_handle_clients(&gfio_client_ops);
1937         ui->handler_running = 0;
1938         return NULL;
1939 }
1940
1941 static int send_job_files(struct gui_entry *ge)
1942 {
1943         struct gfio_client *gc = ge->client;
1944         int i, ret = 0;
1945
1946         for (i = 0; i < ge->nr_job_files; i++) {
1947                 ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1948                 if (ret < 0) {
1949                         GError *error;
1950
1951                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1952                         report_error(error);
1953                         g_error_free(error);
1954                         break;
1955                 } else if (ret)
1956                         break;
1957
1958                 free(ge->job_files[i]);
1959                 ge->job_files[i] = NULL;
1960         }
1961         while (i < ge->nr_job_files) {
1962                 free(ge->job_files[i]);
1963                 ge->job_files[i] = NULL;
1964                 i++;
1965         }
1966
1967         free(ge->job_files);
1968         ge->job_files = NULL;
1969         ge->nr_job_files = 0;
1970         return ret;
1971 }
1972
1973 static void *server_thread(void *arg)
1974 {
1975         is_backend = 1;
1976         gfio_server_running = 1;
1977         fio_start_server(NULL);
1978         gfio_server_running = 0;
1979         return NULL;
1980 }
1981
1982 static void gfio_start_server(void)
1983 {
1984         struct gui *ui = &main_ui;
1985
1986         if (!gfio_server_running) {
1987                 gfio_server_running = 1;
1988                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1989                 pthread_detach(ui->server_t);
1990         }
1991 }
1992
1993 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1994                 gpointer data)
1995 {
1996         struct gui_entry *ge = data;
1997         struct gfio_client *gc = ge->client;
1998
1999         if (gc)
2000                 fio_start_client(gc->client);
2001 }
2002
2003 static void file_open(GtkWidget *w, gpointer data);
2004
2005 static void connect_clicked(GtkWidget *widget, gpointer data)
2006 {
2007         struct gui_entry *ge = data;
2008         struct gfio_client *gc = ge->client;
2009
2010         if (ge->state == GE_STATE_NEW) {
2011                 int ret;
2012
2013                 if (!ge->nr_job_files)
2014                         file_open(widget, ge->ui);
2015                 if (!ge->nr_job_files)
2016                         return;
2017
2018                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
2019                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2020                 ret = fio_client_connect(gc->client);
2021                 if (!ret) {
2022                         if (!ge->ui->handler_running)
2023                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
2024                         gfio_set_state(ge, GE_STATE_CONNECTED);
2025                 } else {
2026                         GError *error;
2027
2028                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
2029                         report_error(error);
2030                         g_error_free(error);
2031                 }
2032         } else {
2033                 fio_client_terminate(gc->client);
2034                 gfio_set_state(ge, GE_STATE_NEW);
2035                 clear_ge_ui_info(ge);
2036         }
2037 }
2038
2039 static void send_clicked(GtkWidget *widget, gpointer data)
2040 {
2041         struct gui_entry *ge = data;
2042
2043         if (send_job_files(ge)) {
2044                 GError *error;
2045
2046                 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);
2047                 report_error(error);
2048                 g_error_free(error);
2049
2050                 gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
2051         }
2052 }
2053
2054 static GtkWidget *add_button(GtkWidget *buttonbox,
2055                              struct button_spec *buttonspec, gpointer data)
2056 {
2057         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
2058
2059         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
2060         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
2061         gtk_widget_set_tooltip_text(button, buttonspec->tooltiptext);
2062         gtk_widget_set_sensitive(button, !buttonspec->start_insensitive);
2063
2064         return button;
2065 }
2066
2067 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
2068                         int nbuttons)
2069 {
2070         int i;
2071
2072         for (i = 0; i < nbuttons; i++)
2073                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
2074 }
2075
2076 static void on_info_bar_response(GtkWidget *widget, gint response,
2077                                  gpointer data)
2078 {
2079         struct gui *ui = &main_ui;
2080
2081         if (response == GTK_RESPONSE_OK) {
2082                 gtk_widget_destroy(widget);
2083                 ui->error_info_bar = NULL;
2084         }
2085 }
2086
2087 void report_error(GError *error)
2088 {
2089         struct gui *ui = &main_ui;
2090
2091         if (ui->error_info_bar == NULL) {
2092                 ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
2093                                                                GTK_RESPONSE_OK,
2094                                                                NULL);
2095                 g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
2096                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
2097                                               GTK_MESSAGE_ERROR);
2098                 
2099                 ui->error_label = gtk_label_new(error->message);
2100                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
2101                 gtk_container_add(GTK_CONTAINER(container), ui->error_label);
2102                 
2103                 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
2104                 gtk_widget_show_all(ui->vbox);
2105         } else {
2106                 char buffer[256];
2107                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
2108                 gtk_label_set(GTK_LABEL(ui->error_label), buffer);
2109         }
2110 }
2111
2112 struct connection_widgets
2113 {
2114         GtkWidget *hentry;
2115         GtkWidget *combo;
2116         GtkWidget *button;
2117 };
2118
2119 static void hostname_cb(GtkEntry *entry, gpointer data)
2120 {
2121         struct connection_widgets *cw = data;
2122         int uses_net = 0, is_localhost = 0;
2123         const gchar *text;
2124         gchar *ctext;
2125
2126         /*
2127          * Check whether to display the 'auto start backend' box
2128          * or not. Show it if we are a localhost and using network,
2129          * or using a socket.
2130          */
2131         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
2132         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
2133                 uses_net = 1;
2134         g_free(ctext);
2135
2136         if (uses_net) {
2137                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
2138                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
2139                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
2140                     !strcmp(text, "ip6-loopback"))
2141                         is_localhost = 1;
2142         }
2143
2144         if (!uses_net || is_localhost) {
2145                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
2146                 gtk_widget_set_sensitive(cw->button, 1);
2147         } else {
2148                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
2149                 gtk_widget_set_sensitive(cw->button, 0);
2150         }
2151 }
2152
2153 static int get_connection_details(char **host, int *port, int *type,
2154                                   int *server_start)
2155 {
2156         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
2157         struct connection_widgets cw;
2158         char *typeentry;
2159
2160         dialog = gtk_dialog_new_with_buttons("Connection details",
2161                         GTK_WINDOW(main_ui.window),
2162                         GTK_DIALOG_DESTROY_WITH_PARENT,
2163                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2164                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
2165
2166         frame = gtk_frame_new("Hostname / socket name");
2167         /* gtk_dialog_get_content_area() is 2.14 and newer */
2168         vbox = GTK_DIALOG(dialog)->vbox;
2169         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2170
2171         box = gtk_vbox_new(FALSE, 6);
2172         gtk_container_add(GTK_CONTAINER(frame), box);
2173
2174         hbox = gtk_hbox_new(TRUE, 10);
2175         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2176         cw.hentry = gtk_entry_new();
2177         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
2178         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
2179
2180         frame = gtk_frame_new("Port");
2181         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2182         box = gtk_vbox_new(FALSE, 10);
2183         gtk_container_add(GTK_CONTAINER(frame), box);
2184
2185         hbox = gtk_hbox_new(TRUE, 4);
2186         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2187         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
2188
2189         frame = gtk_frame_new("Type");
2190         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2191         box = gtk_vbox_new(FALSE, 10);
2192         gtk_container_add(GTK_CONTAINER(frame), box);
2193
2194         hbox = gtk_hbox_new(TRUE, 4);
2195         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2196
2197         cw.combo = gtk_combo_box_new_text();
2198         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
2199         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
2200         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
2201         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
2202
2203         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
2204
2205         frame = gtk_frame_new("Options");
2206         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
2207         box = gtk_vbox_new(FALSE, 10);
2208         gtk_container_add(GTK_CONTAINER(frame), box);
2209
2210         hbox = gtk_hbox_new(TRUE, 4);
2211         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
2212
2213         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
2214         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
2215         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.");
2216         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2217
2218         /*
2219          * Connect edit signal, so we can show/not-show the auto start button
2220          */
2221         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
2222         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2223
2224         gtk_widget_show_all(dialog);
2225
2226         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2227                 gtk_widget_destroy(dialog);
2228                 return 1;
2229         }
2230
2231         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
2232         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2233
2234         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
2235         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
2236                 *type = Fio_client_ipv4;
2237         else if (!strncmp(typeentry, "IPv6", 4))
2238                 *type = Fio_client_ipv6;
2239         else
2240                 *type = Fio_client_socket;
2241         g_free(typeentry);
2242
2243         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2244
2245         gtk_widget_destroy(dialog);
2246         return 0;
2247 }
2248
2249 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
2250 {
2251         struct gfio_client *gc;
2252
2253         gc = malloc(sizeof(*gc));
2254         memset(gc, 0, sizeof(*gc));
2255         gc->ge = ge;
2256         gc->client = fio_get_client(client);
2257
2258         ge->client = gc;
2259
2260         client->client_data = gc;
2261 }
2262
2263 static GtkWidget *new_client_page(struct gui_entry *ge);
2264
2265 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
2266 {
2267         struct gui_entry *ge;
2268
2269         ge = malloc(sizeof(*ge));
2270         memset(ge, 0, sizeof(*ge));
2271         ge->state = GE_STATE_NEW;
2272         INIT_FLIST_HEAD(&ge->list);
2273         flist_add_tail(&ge->list, &ui->list);
2274         ge->ui = ui;
2275         return ge;
2276 }
2277
2278 static struct gui_entry *get_new_ge_with_tab(const char *name)
2279 {
2280         struct gui_entry *ge;
2281
2282         ge = alloc_new_gui_entry(&main_ui);
2283
2284         ge->vbox = new_client_page(ge);
2285         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
2286
2287         ge->page_label = gtk_label_new(name);
2288         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
2289
2290         gtk_widget_show_all(main_ui.window);
2291         return ge;
2292 }
2293
2294 static void file_new(GtkWidget *w, gpointer data)
2295 {
2296         struct gui *ui = (struct gui *) data;
2297         struct gui_entry *ge;
2298
2299         ge = get_new_ge_with_tab("Untitled");
2300         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2301 }
2302
2303 /*
2304  * Return the 'ge' corresponding to the tab. If the active tab is the
2305  * main tab, open a new tab.
2306  */
2307 static struct gui_entry *get_ge_from_page(gint cur_page, int *created)
2308 {
2309         struct flist_head *entry;
2310         struct gui_entry *ge;
2311
2312         if (!cur_page) {
2313                 if (created)
2314                         *created = 1;
2315                 return get_new_ge_with_tab("Untitled");
2316         }
2317
2318         if (created)
2319                 *created = 0;
2320
2321         flist_for_each(entry, &main_ui.list) {
2322                 ge = flist_entry(entry, struct gui_entry, list);
2323                 if (ge->page_num == cur_page)
2324                         return ge;
2325         }
2326
2327         return NULL;
2328 }
2329
2330 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
2331 {
2332         gint cur_page;
2333
2334         /*
2335          * Main tab is tab 0, so any current page other than 0 holds
2336          * a ge entry.
2337          */
2338         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2339         if (cur_page)
2340                 return get_ge_from_page(cur_page, NULL);
2341
2342         return NULL;
2343 }
2344
2345 static void file_close(GtkWidget *w, gpointer data)
2346 {
2347         struct gui *ui = (struct gui *) data;
2348         struct gui_entry *ge;
2349
2350         /*
2351          * Can't close the main tab
2352          */
2353         ge = get_ge_from_cur_tab(ui);
2354         if (ge) {
2355                 gtk_widget_destroy(ge->vbox);
2356                 return;
2357         }
2358
2359         if (!flist_empty(&ui->list)) {
2360                 show_info_dialog(ui, "Error", "The main page view cannot be closed\n");
2361                 return;
2362         }
2363
2364         gfio_quit(ui);
2365 }
2366
2367 static void file_add_recent(struct gui *ui, const gchar *uri)
2368 {
2369         GtkRecentData grd;
2370
2371         memset(&grd, 0, sizeof(grd));
2372         grd.display_name = strdup("gfio");
2373         grd.description = strdup("Fio job file");
2374         grd.mime_type = strdup(GFIO_MIME);
2375         grd.app_name = strdup(g_get_application_name());
2376         grd.app_exec = strdup("gfio %f/%u");
2377
2378         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2379 }
2380
2381 static gchar *get_filename_from_uri(const gchar *uri)
2382 {
2383         if (strncmp(uri, "file://", 7))
2384                 return strdup(uri);
2385
2386         return strdup(uri + 7);
2387 }
2388
2389 static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2390                         int type, int port)
2391 {
2392         struct fio_client *client;
2393         gchar *filename;
2394
2395         filename = get_filename_from_uri(uri);
2396
2397         ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2398         ge->job_files[ge->nr_job_files] = strdup(filename);
2399         ge->nr_job_files++;
2400
2401         client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2402         if (!client) {
2403                 GError *error;
2404
2405                 error = g_error_new(g_quark_from_string("fio"), 1,
2406                                 "Failed to add client %s", host);
2407                 report_error(error);
2408                 g_error_free(error);
2409                 return 1;
2410         }
2411
2412         gfio_client_added(ge, client);
2413         file_add_recent(ge->ui, uri);
2414         return 0;
2415 }
2416
2417 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2418 {
2419         int port, type, server_start;
2420         struct gui_entry *ge;
2421         gint cur_page;
2422         char *host;
2423         int ret, ge_is_new = 0;
2424
2425         /*
2426          * Creates new tab if current tab is the main window, or the
2427          * current tab already has a client.
2428          */
2429         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2430         ge = get_ge_from_page(cur_page, &ge_is_new);
2431         if (ge->client) {
2432                 ge = get_new_ge_with_tab("Untitled");
2433                 ge_is_new = 1;
2434         }
2435
2436         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2437
2438         if (get_connection_details(&host, &port, &type, &server_start)) {
2439                 if (ge_is_new)
2440                         gtk_widget_destroy(ge->vbox);
2441                         
2442                 return 1;
2443         }
2444
2445         ret = do_file_open(ge, uri, host, type, port);
2446
2447         free(host);
2448
2449         if (!ret) {
2450                 if (server_start)
2451                         gfio_start_server();
2452         } else {
2453                 if (ge_is_new)
2454                         gtk_widget_destroy(ge->vbox);
2455         }
2456
2457         return ret;
2458 }
2459
2460 static void recent_open(GtkAction *action, gpointer data)
2461 {
2462         struct gui *ui = (struct gui *) data;
2463         GtkRecentInfo *info;
2464         const gchar *uri;
2465
2466         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2467         uri = gtk_recent_info_get_uri(info);
2468
2469         do_file_open_with_tab(ui, uri);
2470 }
2471
2472 static void file_open(GtkWidget *w, gpointer data)
2473 {
2474         struct gui *ui = data;
2475         GtkWidget *dialog;
2476         GSList *filenames, *fn_glist;
2477         GtkFileFilter *filter;
2478
2479         dialog = gtk_file_chooser_dialog_new("Open File",
2480                 GTK_WINDOW(ui->window),
2481                 GTK_FILE_CHOOSER_ACTION_OPEN,
2482                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2483                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2484                 NULL);
2485         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2486
2487         filter = gtk_file_filter_new();
2488         gtk_file_filter_add_pattern(filter, "*.fio");
2489         gtk_file_filter_add_pattern(filter, "*.job");
2490         gtk_file_filter_add_pattern(filter, "*.ini");
2491         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2492         gtk_file_filter_set_name(filter, "Fio job file");
2493         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2494
2495         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2496                 gtk_widget_destroy(dialog);
2497                 return;
2498         }
2499
2500         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2501
2502         gtk_widget_destroy(dialog);
2503
2504         filenames = fn_glist;
2505         while (filenames != NULL) {
2506                 if (do_file_open_with_tab(ui, filenames->data))
2507                         break;
2508                 filenames = g_slist_next(filenames);
2509         }
2510
2511         g_slist_free(fn_glist);
2512 }
2513
2514 static void file_save(GtkWidget *w, gpointer data)
2515 {
2516         struct gui *ui = data;
2517         GtkWidget *dialog;
2518
2519         dialog = gtk_file_chooser_dialog_new("Save File",
2520                 GTK_WINDOW(ui->window),
2521                 GTK_FILE_CHOOSER_ACTION_SAVE,
2522                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2523                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2524                 NULL);
2525
2526         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2527         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2528
2529         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2530                 char *filename;
2531
2532                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2533                 // save_job_file(filename);
2534                 g_free(filename);
2535         }
2536         gtk_widget_destroy(dialog);
2537 }
2538
2539 static void view_log_destroy(GtkWidget *w, gpointer data)
2540 {
2541         struct gui *ui = (struct gui *) data;
2542
2543         gtk_widget_ref(ui->log_tree);
2544         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2545         gtk_widget_destroy(w);
2546         ui->log_view = NULL;
2547 }
2548
2549 static void view_log(GtkWidget *w, gpointer data)
2550 {
2551         GtkWidget *win, *scroll, *vbox, *box;
2552         struct gui *ui = (struct gui *) data;
2553
2554         if (ui->log_view)
2555                 return;
2556
2557         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2558         gtk_window_set_title(GTK_WINDOW(win), "Log");
2559         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2560
2561         scroll = gtk_scrolled_window_new(NULL, NULL);
2562
2563         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2564
2565         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2566
2567         box = gtk_hbox_new(TRUE, 0);
2568         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2569         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2570         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2571
2572         vbox = gtk_vbox_new(TRUE, 5);
2573         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2574
2575         gtk_container_add(GTK_CONTAINER(win), vbox);
2576         gtk_widget_show_all(win);
2577 }
2578
2579 static void connect_job_entry(GtkWidget *w, gpointer data)
2580 {
2581         struct gui *ui = (struct gui *) data;
2582         struct gui_entry *ge;
2583         
2584         ge = get_ge_from_cur_tab(ui);
2585         if (ge)
2586                 connect_clicked(w, ge);
2587 }
2588
2589 static void send_job_entry(GtkWidget *w, gpointer data)
2590 {
2591         struct gui *ui = (struct gui *) data;
2592         struct gui_entry *ge;
2593
2594         ge = get_ge_from_cur_tab(ui);
2595         if (ge)
2596                 send_clicked(w, ge);
2597
2598 }
2599
2600 static void edit_job_entry(GtkWidget *w, gpointer data)
2601 {
2602 }
2603
2604 static void start_job_entry(GtkWidget *w, gpointer data)
2605 {
2606         struct gui *ui = (struct gui *) data;
2607         struct gui_entry *ge;
2608
2609         ge = get_ge_from_cur_tab(ui);
2610         if (ge)
2611                 start_job_clicked(w, ge);
2612 }
2613
2614 static void view_results(GtkWidget *w, gpointer data)
2615 {
2616         struct gui *ui = (struct gui *) data;
2617         struct gfio_client *gc;
2618         struct gui_entry *ge;
2619
2620         ge = get_ge_from_cur_tab(ui);
2621         if (!ge)
2622                 return;
2623
2624         if (ge->results_window)
2625                 return;
2626
2627         gc = ge->client;
2628         if (gc && gc->nr_results)
2629                 gfio_display_end_results(gc);
2630 }
2631
2632
2633 static void __update_graph_limits(struct gfio_graphs *g)
2634 {
2635         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2636         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2637 }
2638
2639 static void update_graph_limits(void)
2640 {
2641         struct flist_head *entry;
2642         struct gui_entry *ge;
2643
2644         __update_graph_limits(&main_ui.graphs);
2645
2646         flist_for_each(entry, &main_ui.list) {
2647                 ge = flist_entry(entry, struct gui_entry, list);
2648                 __update_graph_limits(&ge->graphs);
2649         }
2650 }
2651
2652 static void preferences(GtkWidget *w, gpointer data)
2653 {
2654         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2655         GtkWidget *hbox, *spin, *entry, *spin_int;
2656         int i;
2657
2658         dialog = gtk_dialog_new_with_buttons("Preferences",
2659                 GTK_WINDOW(main_ui.window),
2660                 GTK_DIALOG_DESTROY_WITH_PARENT,
2661                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2662                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2663                 NULL);
2664
2665         frame = gtk_frame_new("Graphing");
2666         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2667         vbox = gtk_vbox_new(FALSE, 6);
2668         gtk_container_add(GTK_CONTAINER(frame), vbox);
2669
2670         hbox = gtk_hbox_new(FALSE, 5);
2671         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2672         entry = gtk_label_new("Font face to use for graph labels");
2673         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2674
2675         font = gtk_font_button_new();
2676         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2677
2678         box = gtk_vbox_new(FALSE, 6);
2679         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2680
2681         hbox = gtk_hbox_new(FALSE, 5);
2682         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2683         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2684         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2685
2686         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2687
2688         box = gtk_vbox_new(FALSE, 6);
2689         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2690
2691         hbox = gtk_hbox_new(FALSE, 5);
2692         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2693         entry = gtk_label_new("Client ETA request interval (msec)");
2694         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2695
2696         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2697         frame = gtk_frame_new("Debug logging");
2698         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2699         vbox = gtk_vbox_new(FALSE, 6);
2700         gtk_container_add(GTK_CONTAINER(frame), vbox);
2701
2702         box = gtk_hbox_new(FALSE, 6);
2703         gtk_container_add(GTK_CONTAINER(vbox), box);
2704
2705         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2706
2707         for (i = 0; i < FD_DEBUG_MAX; i++) {
2708                 if (i == 7) {
2709                         box = gtk_hbox_new(FALSE, 6);
2710                         gtk_container_add(GTK_CONTAINER(vbox), box);
2711                 }
2712
2713
2714                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2715                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2716                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2717         }
2718
2719         gtk_widget_show_all(dialog);
2720
2721         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2722                 gtk_widget_destroy(dialog);
2723                 return;
2724         }
2725
2726         for (i = 0; i < FD_DEBUG_MAX; i++) {
2727                 int set;
2728
2729                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2730                 if (set)
2731                         fio_debug |= (1UL << i);
2732         }
2733
2734         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2735         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2736         update_graph_limits();
2737         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2738
2739         gtk_widget_destroy(dialog);
2740 }
2741
2742 static void about_dialog(GtkWidget *w, gpointer data)
2743 {
2744         const char *authors[] = {
2745                 "Jens Axboe <axboe@kernel.dk>",
2746                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2747                 NULL
2748         };
2749         const char *license[] = {
2750                 "Fio is free software; you can redistribute it and/or modify "
2751                 "it under the terms of the GNU General Public License as published by "
2752                 "the Free Software Foundation; either version 2 of the License, or "
2753                 "(at your option) any later version.\n",
2754                 "Fio is distributed in the hope that it will be useful, "
2755                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2756                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2757                 "GNU General Public License for more details.\n",
2758                 "You should have received a copy of the GNU General Public License "
2759                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2760                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2761         };
2762         char *license_trans;
2763
2764         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2765                                      license[2], "\n", NULL);
2766
2767         gtk_show_about_dialog(NULL,
2768                 "program-name", "gfio",
2769                 "comments", "Gtk2 UI for fio",
2770                 "license", license_trans,
2771                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2772                 "authors", authors,
2773                 "version", fio_version_string,
2774                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2775                 "logo-icon-name", "fio",
2776                 /* Must be last: */
2777                 "wrap-license", TRUE,
2778                 NULL);
2779
2780         g_free(license_trans);
2781 }
2782
2783 static GtkActionEntry menu_items[] = {
2784         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2785         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2786         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2787         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2788         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2789         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2790         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2791         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2792         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2793         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2794         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
2795         { "ConnectJob", NULL, "Connect", "<Control>E", NULL, G_CALLBACK(connect_job_entry) },
2796         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2797         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2798         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2799         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2800         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2801 };
2802 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2803
2804 static const gchar *ui_string = " \
2805         <ui> \
2806                 <menubar name=\"MainMenu\"> \
2807                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2808                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2809                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2810                                 <separator name=\"Separator1\"/> \
2811                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2812                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2813                                 <separator name=\"Separator2\"/> \
2814                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2815                                 <separator name=\"Separator3\"/> \
2816                                 <placeholder name=\"FileRecentFiles\"/> \
2817                                 <separator name=\"Separator4\"/> \
2818                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2819                         </menu> \
2820                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2821                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2822                                 <separator name=\"Separator5\"/> \
2823                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2824                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2825                                 <separator name=\"Separator6\"/> \
2826                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2827                         </menu>\
2828                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2829                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
2830                                 <separator name=\"Separator7\"/> \
2831                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2832                         </menu>\
2833                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2834                                 <menuitem name=\"About\" action=\"About\" /> \
2835                         </menu> \
2836                 </menubar> \
2837         </ui> \
2838 ";
2839
2840 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2841                                    struct gui *ui)
2842 {
2843         GtkActionGroup *action_group;
2844         GError *error = 0;
2845
2846         action_group = gtk_action_group_new("Menu");
2847         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2848
2849         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2850         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2851
2852         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2853
2854         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2855 }
2856
2857 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2858                    GtkWidget *vbox, GtkUIManager *ui_manager)
2859 {
2860         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2861 }
2862
2863 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2864 {
2865         struct gui_entry *ge = (struct gui_entry *) data;
2866         gint index;
2867
2868         index = gtk_combo_box_get_active(box);
2869
2870         multitext_set_entry(&ge->eta.iotype, index);
2871         multitext_set_entry(&ge->eta.ioengine, index);
2872         multitext_set_entry(&ge->eta.iodepth, index);
2873 }
2874
2875 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2876 {
2877         struct gui_entry *ge = (struct gui_entry *) data;
2878
2879         multitext_free(&ge->eta.iotype);
2880         multitext_free(&ge->eta.ioengine);
2881         multitext_free(&ge->eta.iodepth);
2882 }
2883
2884 static GtkWidget *new_client_page(struct gui_entry *ge)
2885 {
2886         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2887         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2888
2889         main_vbox = gtk_vbox_new(FALSE, 3);
2890
2891         top_align = gtk_alignment_new(0, 0, 1, 0);
2892         top_vbox = gtk_vbox_new(FALSE, 3);
2893         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2894         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2895
2896         probe = gtk_frame_new("Job");
2897         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2898         probe_frame = gtk_vbox_new(FALSE, 3);
2899         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2900
2901         probe_box = gtk_hbox_new(FALSE, 3);
2902         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2903         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2904         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2905         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2906         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2907
2908         probe_box = gtk_hbox_new(FALSE, 3);
2909         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2910
2911         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2912         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2913         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2914         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2915         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2916         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2917         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2918         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2919
2920         probe_box = gtk_hbox_new(FALSE, 3);
2921         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2922         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2923         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2924         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2925         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2926
2927         /*
2928          * Only add this if we have a commit rate
2929          */
2930 #if 0
2931         probe_box = gtk_hbox_new(FALSE, 3);
2932         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2933
2934         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2935         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2936
2937         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2938         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2939 #endif
2940
2941         /*
2942          * Set up a drawing area and IOPS and bandwidth graphs
2943          */
2944         ge->graphs.drawing_area = gtk_drawing_area_new();
2945         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2946                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2947         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2948         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2949                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2950         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2951                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2952         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2953         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2954                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2955         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2956                                         ge->graphs.drawing_area);
2957         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2958
2959         setup_graphs(&ge->graphs);
2960
2961         /*
2962          * Set up alignments for widgets at the bottom of ui, 
2963          * align bottom left, expand horizontally but not vertically
2964          */
2965         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2966         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2967         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2968         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2969
2970         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2971
2972         /*
2973          * Set up thread status progress bar
2974          */
2975         ge->thread_status_pb = gtk_progress_bar_new();
2976         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2977         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2978         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2979
2980
2981         return main_vbox;
2982 }
2983
2984 static GtkWidget *new_main_page(struct gui *ui)
2985 {
2986         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2987         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2988
2989         main_vbox = gtk_vbox_new(FALSE, 3);
2990
2991         /*
2992          * Set up alignments for widgets at the top of ui,
2993          * align top left, expand horizontally but not vertically
2994          */
2995         top_align = gtk_alignment_new(0, 0, 1, 0);
2996         top_vbox = gtk_vbox_new(FALSE, 0);
2997         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2998         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2999
3000         probe = gtk_frame_new("Run statistics");
3001         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
3002         probe_frame = gtk_vbox_new(FALSE, 3);
3003         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
3004
3005         probe_box = gtk_hbox_new(FALSE, 3);
3006         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
3007         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
3008         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
3009         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
3010         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
3011         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
3012
3013         /*
3014          * Only add this if we have a commit rate
3015          */
3016 #if 0
3017         probe_box = gtk_hbox_new(FALSE, 3);
3018         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
3019
3020         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
3021         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
3022
3023         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
3024         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
3025 #endif
3026
3027         /*
3028          * Set up a drawing area and IOPS and bandwidth graphs
3029          */
3030         ui->graphs.drawing_area = gtk_drawing_area_new();
3031         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
3032                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
3033         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
3034         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
3035                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
3036         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
3037                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
3038         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
3039         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
3040                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3041         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
3042                                         ui->graphs.drawing_area);
3043         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
3044                         TRUE, TRUE, 0);
3045
3046         setup_graphs(&ui->graphs);
3047
3048         /*
3049          * Set up alignments for widgets at the bottom of ui, 
3050          * align bottom left, expand horizontally but not vertically
3051          */
3052         bottom_align = gtk_alignment_new(0, 1, 1, 0);
3053         ui->buttonbox = gtk_hbox_new(FALSE, 0);
3054         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
3055         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
3056
3057         /*
3058          * Set up thread status progress bar
3059          */
3060         ui->thread_status_pb = gtk_progress_bar_new();
3061         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
3062         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
3063         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
3064
3065         return main_vbox;
3066 }
3067
3068 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
3069                                      guint page, gpointer data)
3070
3071 {
3072         struct gui *ui = (struct gui *) data;
3073         struct gui_entry *ge;
3074
3075         if (!page) {
3076                 set_job_menu_visible(ui, 0);
3077                 set_view_results_visible(ui, 0);
3078                 return TRUE;
3079         }
3080
3081         set_job_menu_visible(ui, 1);
3082         ge = get_ge_from_page(page, NULL);
3083         if (ge)
3084                 update_button_states(ui, ge);
3085
3086         return TRUE;
3087 }
3088
3089 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
3090 {
3091         time_t time_a = gtk_recent_info_get_visited(a);
3092         time_t time_b = gtk_recent_info_get_visited(b);
3093
3094         return time_b - time_a;
3095 }
3096
3097 static void add_recent_file_items(struct gui *ui)
3098 {
3099         const gchar *gfio = g_get_application_name();
3100         GList *items, *item;
3101         int i = 0;
3102
3103         if (ui->recent_ui_id) {
3104                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
3105                 gtk_ui_manager_ensure_update(ui->uimanager);
3106         }
3107         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
3108
3109         if (ui->actiongroup) {
3110                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
3111                 g_object_unref(ui->actiongroup);
3112         }
3113         ui->actiongroup = gtk_action_group_new("RecentFileActions");
3114
3115         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
3116
3117         items = gtk_recent_manager_get_items(ui->recentmanager);
3118         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
3119
3120         for (item = items; item && item->data; item = g_list_next(item)) {
3121                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
3122                 gchar *action_name;
3123                 const gchar *label;
3124                 GtkAction *action;
3125
3126                 if (!gtk_recent_info_has_application(info, gfio))
3127                         continue;
3128
3129                 /*
3130                  * We only support local files for now
3131                  */
3132                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
3133                         continue;
3134
3135                 action_name = g_strdup_printf("RecentFile%u", i++);
3136                 label = gtk_recent_info_get_display_name(info);
3137
3138                 action = g_object_new(GTK_TYPE_ACTION,
3139                                         "name", action_name,
3140                                         "label", label, NULL);
3141
3142                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
3143                                         gtk_recent_info_ref(info),
3144                                         (GDestroyNotify) gtk_recent_info_unref);
3145
3146
3147                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
3148
3149                 gtk_action_group_add_action(ui->actiongroup, action);
3150                 g_object_unref(action);
3151
3152                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
3153                                         "/MainMenu/FileMenu/FileRecentFiles",
3154                                         label, action_name,
3155                                         GTK_UI_MANAGER_MENUITEM, FALSE);
3156
3157                 g_free(action_name);
3158
3159                 if (i == 8)
3160                         break;
3161         }
3162
3163         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
3164         g_list_free(items);
3165 }
3166
3167 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
3168                                    gint x, gint y, GtkSelectionData *data,
3169                                    guint info, guint time)
3170 {
3171         struct gui *ui = &main_ui;
3172         gchar **uris;
3173         GtkWidget *source;
3174         int i;
3175
3176         source = gtk_drag_get_source_widget(ctx);
3177         if (source && widget == gtk_widget_get_toplevel(source)) {
3178                 gtk_drag_finish(ctx, FALSE, FALSE, time);
3179                 return;
3180         }
3181
3182         uris = gtk_selection_data_get_uris(data);
3183         if (!uris) {
3184                 gtk_drag_finish(ctx, FALSE, FALSE, time);
3185                 return;
3186         }
3187
3188         i = 0;
3189         while (uris[i]) {
3190                 if (do_file_open_with_tab(ui, uris[i]))
3191                         break;
3192                 i++;
3193         }
3194
3195         gtk_drag_finish(ctx, TRUE, FALSE, time);
3196         g_strfreev(uris);
3197 }
3198
3199 static void init_ui(int *argc, char **argv[], struct gui *ui)
3200 {
3201         GtkSettings *settings;
3202         GtkWidget *vbox;
3203
3204         /* Magical g*thread incantation, you just need this thread stuff.
3205          * Without it, the update that happens in gfio_update_thread_status
3206          * doesn't really happen in a timely fashion, you need expose events
3207          */
3208         if (!g_thread_supported())
3209                 g_thread_init(NULL);
3210         gdk_threads_init();
3211
3212         gtk_init(argc, argv);
3213         settings = gtk_settings_get_default();
3214         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
3215         g_type_init();
3216         gdk_color_parse("white", &white);
3217         
3218         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3219         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
3220         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
3221
3222         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
3223         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
3224
3225         ui->vbox = gtk_vbox_new(FALSE, 0);
3226         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3227
3228         ui->uimanager = gtk_ui_manager_new();
3229         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3230         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3231
3232         ui->recentmanager = gtk_recent_manager_get_default();
3233         add_recent_file_items(ui);
3234
3235         ui->notebook = gtk_notebook_new();
3236         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3237         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3238         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3239         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3240
3241         vbox = new_main_page(ui);
3242         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3243         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3244         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3245
3246         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3247
3248         gfio_ui_setup_log(ui);
3249
3250         gtk_widget_show_all(ui->window);
3251 }
3252
3253 int main(int argc, char *argv[], char *envp[])
3254 {
3255         if (initialize_fio(envp))
3256                 return 1;
3257         if (fio_init_options())
3258                 return 1;
3259
3260         memset(&main_ui, 0, sizeof(main_ui));
3261         INIT_FLIST_HEAD(&main_ui.list);
3262
3263         init_ui(&argc, &argv, &main_ui);
3264
3265         gdk_threads_enter();
3266         gtk_main();
3267         gdk_threads_leave();
3268         return 0;
3269 }