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