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