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