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