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