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