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