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