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