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