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