d0418a4585212939d7a5bee68b227dfceb0fe509
[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         GtkRecentData grd;
2155
2156         memset(&grd, 0, sizeof(grd));
2157         grd.display_name = strdup("gfio");
2158         grd.description = strdup("Fio job file");
2159         grd.mime_type = strdup(GFIO_MIME);
2160         grd.app_name = strdup(g_get_application_name());
2161         grd.app_exec = strdup("gfio %f/%u");
2162
2163         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
2164 }
2165
2166 static gchar *get_filename_from_uri(const gchar *uri)
2167 {
2168         if (strncmp(uri, "file://", 7))
2169                 return strdup(uri);
2170
2171         return strdup(uri + 7);
2172 }
2173
2174 static int do_file_open(struct gui_entry *ge, const gchar *uri, char *host,
2175                         int type, int port)
2176 {
2177         struct fio_client *client;
2178         gchar *filename;
2179
2180         filename = get_filename_from_uri(uri);
2181
2182         ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
2183         ge->job_files[ge->nr_job_files] = strdup(filename);
2184         ge->nr_job_files++;
2185
2186         client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
2187         if (!client) {
2188                 GError *error;
2189
2190                 error = g_error_new(g_quark_from_string("fio"), 1,
2191                                 "Failed to add client %s", host);
2192                 report_error(error);
2193                 g_error_free(error);
2194                 return 1;
2195         }
2196
2197         gfio_client_added(ge, client);
2198         file_add_recent(ge->ui, uri);
2199         return 0;
2200 }
2201
2202 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
2203 {
2204         int port, type, server_start;
2205         struct gui_entry *ge;
2206         gint cur_page;
2207         char *host;
2208         int ret, ge_is_new = 0;
2209
2210         /*
2211          * Creates new tab if current tab is the main window, or the
2212          * current tab already has a client.
2213          */
2214         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
2215         ge = get_ge_from_page(cur_page, &ge_is_new);
2216         if (ge->client) {
2217                 ge = get_new_ge_with_tab("Untitled");
2218                 ge_is_new = 1;
2219         }
2220
2221         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
2222
2223         if (get_connection_details(&host, &port, &type, &server_start)) {
2224                 if (ge_is_new)
2225                         gtk_widget_destroy(ge->vbox);
2226                         
2227                 return 1;
2228         }
2229
2230         ret = do_file_open(ge, uri, host, type, port);
2231
2232         free(host);
2233
2234         if (!ret) {
2235                 if (server_start)
2236                         gfio_start_server();
2237         } else {
2238                 if (ge_is_new)
2239                         gtk_widget_destroy(ge->vbox);
2240         }
2241
2242         return ret;
2243 }
2244
2245 static void recent_open(GtkAction *action, gpointer data)
2246 {
2247         struct gui *ui = (struct gui *) data;
2248         GtkRecentInfo *info;
2249         const gchar *uri;
2250
2251         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
2252         uri = gtk_recent_info_get_uri(info);
2253
2254         do_file_open_with_tab(ui, uri);
2255 }
2256
2257 static void file_open(GtkWidget *w, gpointer data)
2258 {
2259         struct gui *ui = data;
2260         GtkWidget *dialog;
2261         GSList *filenames, *fn_glist;
2262         GtkFileFilter *filter;
2263
2264         dialog = gtk_file_chooser_dialog_new("Open File",
2265                 GTK_WINDOW(ui->window),
2266                 GTK_FILE_CHOOSER_ACTION_OPEN,
2267                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2268                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2269                 NULL);
2270         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
2271
2272         filter = gtk_file_filter_new();
2273         gtk_file_filter_add_pattern(filter, "*.fio");
2274         gtk_file_filter_add_pattern(filter, "*.job");
2275         gtk_file_filter_add_pattern(filter, "*.ini");
2276         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
2277         gtk_file_filter_set_name(filter, "Fio job file");
2278         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2279
2280         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2281                 gtk_widget_destroy(dialog);
2282                 return;
2283         }
2284
2285         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
2286
2287         gtk_widget_destroy(dialog);
2288
2289         filenames = fn_glist;
2290         while (filenames != NULL) {
2291                 if (do_file_open_with_tab(ui, filenames->data))
2292                         break;
2293                 filenames = g_slist_next(filenames);
2294         }
2295
2296         g_slist_free(fn_glist);
2297 }
2298
2299 static void file_save(GtkWidget *w, gpointer data)
2300 {
2301         struct gui *ui = data;
2302         GtkWidget *dialog;
2303
2304         dialog = gtk_file_chooser_dialog_new("Save File",
2305                 GTK_WINDOW(ui->window),
2306                 GTK_FILE_CHOOSER_ACTION_SAVE,
2307                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2308                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2309                 NULL);
2310
2311         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2312         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
2313
2314         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2315                 char *filename;
2316
2317                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2318                 // save_job_file(filename);
2319                 g_free(filename);
2320         }
2321         gtk_widget_destroy(dialog);
2322 }
2323
2324 static void view_log_destroy(GtkWidget *w, gpointer data)
2325 {
2326         struct gui *ui = (struct gui *) data;
2327
2328         gtk_widget_ref(ui->log_tree);
2329         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
2330         gtk_widget_destroy(w);
2331         ui->log_view = NULL;
2332 }
2333
2334 static void view_log(GtkWidget *w, gpointer data)
2335 {
2336         GtkWidget *win, *scroll, *vbox, *box;
2337         struct gui *ui = (struct gui *) data;
2338
2339         if (ui->log_view)
2340                 return;
2341
2342         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2343         gtk_window_set_title(GTK_WINDOW(win), "Log");
2344         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2345
2346         scroll = gtk_scrolled_window_new(NULL, NULL);
2347
2348         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2349
2350         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2351
2352         box = gtk_hbox_new(TRUE, 0);
2353         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2354         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2355         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2356
2357         vbox = gtk_vbox_new(TRUE, 5);
2358         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2359
2360         gtk_container_add(GTK_CONTAINER(win), vbox);
2361         gtk_widget_show_all(win);
2362 }
2363
2364 static void connect_job_entry(GtkWidget *w, gpointer data)
2365 {
2366         struct gui *ui = (struct gui *) data;
2367         struct gui_entry *ge;
2368         
2369         ge = get_ge_from_cur_tab(ui);
2370         if (ge)
2371                 connect_clicked(w, ge);
2372 }
2373
2374 static void send_job_entry(GtkWidget *w, gpointer data)
2375 {
2376         struct gui *ui = (struct gui *) data;
2377         struct gui_entry *ge;
2378
2379         ge = get_ge_from_cur_tab(ui);
2380         if (ge)
2381                 send_clicked(w, ge);
2382
2383 }
2384
2385 static void edit_job_entry(GtkWidget *w, gpointer data)
2386 {
2387 }
2388
2389 static void start_job_entry(GtkWidget *w, gpointer data)
2390 {
2391         struct gui *ui = (struct gui *) data;
2392         struct gui_entry *ge;
2393
2394         ge = get_ge_from_cur_tab(ui);
2395         if (ge)
2396                 start_job_clicked(w, ge);
2397 }
2398
2399 static void __update_graph_limits(struct gfio_graphs *g)
2400 {
2401         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2402         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2403 }
2404
2405 static void update_graph_limits(void)
2406 {
2407         struct flist_head *entry;
2408         struct gui_entry *ge;
2409
2410         __update_graph_limits(&main_ui.graphs);
2411
2412         flist_for_each(entry, &main_ui.list) {
2413                 ge = flist_entry(entry, struct gui_entry, list);
2414                 __update_graph_limits(&ge->graphs);
2415         }
2416 }
2417
2418 static void preferences(GtkWidget *w, gpointer data)
2419 {
2420         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2421         GtkWidget *hbox, *spin, *entry, *spin_int;
2422         int i;
2423
2424         dialog = gtk_dialog_new_with_buttons("Preferences",
2425                 GTK_WINDOW(main_ui.window),
2426                 GTK_DIALOG_DESTROY_WITH_PARENT,
2427                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2428                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2429                 NULL);
2430
2431         frame = gtk_frame_new("Graphing");
2432         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2433         vbox = gtk_vbox_new(FALSE, 6);
2434         gtk_container_add(GTK_CONTAINER(frame), vbox);
2435
2436         hbox = gtk_hbox_new(FALSE, 5);
2437         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2438         entry = gtk_label_new("Font face to use for graph labels");
2439         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2440
2441         font = gtk_font_button_new();
2442         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2443
2444         box = gtk_vbox_new(FALSE, 6);
2445         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2446
2447         hbox = gtk_hbox_new(FALSE, 5);
2448         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2449         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2450         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2451
2452         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
2453
2454         box = gtk_vbox_new(FALSE, 6);
2455         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2456
2457         hbox = gtk_hbox_new(FALSE, 5);
2458         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2459         entry = gtk_label_new("Client ETA request interval (msec)");
2460         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2461
2462         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2463         frame = gtk_frame_new("Debug logging");
2464         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2465         vbox = gtk_vbox_new(FALSE, 6);
2466         gtk_container_add(GTK_CONTAINER(frame), vbox);
2467
2468         box = gtk_hbox_new(FALSE, 6);
2469         gtk_container_add(GTK_CONTAINER(vbox), box);
2470
2471         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2472
2473         for (i = 0; i < FD_DEBUG_MAX; i++) {
2474                 if (i == 7) {
2475                         box = gtk_hbox_new(FALSE, 6);
2476                         gtk_container_add(GTK_CONTAINER(vbox), box);
2477                 }
2478
2479
2480                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2481                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2482                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2483         }
2484
2485         gtk_widget_show_all(dialog);
2486
2487         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2488                 gtk_widget_destroy(dialog);
2489                 return;
2490         }
2491
2492         for (i = 0; i < FD_DEBUG_MAX; i++) {
2493                 int set;
2494
2495                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2496                 if (set)
2497                         fio_debug |= (1UL << i);
2498         }
2499
2500         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2501         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2502         update_graph_limits();
2503         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2504
2505         gtk_widget_destroy(dialog);
2506 }
2507
2508 static void about_dialog(GtkWidget *w, gpointer data)
2509 {
2510         const char *authors[] = {
2511                 "Jens Axboe <axboe@kernel.dk>",
2512                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2513                 NULL
2514         };
2515         const char *license[] = {
2516                 "Fio is free software; you can redistribute it and/or modify "
2517                 "it under the terms of the GNU General Public License as published by "
2518                 "the Free Software Foundation; either version 2 of the License, or "
2519                 "(at your option) any later version.\n",
2520                 "Fio is distributed in the hope that it will be useful, "
2521                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2522                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2523                 "GNU General Public License for more details.\n",
2524                 "You should have received a copy of the GNU General Public License "
2525                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2526                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2527         };
2528         char *license_trans;
2529
2530         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2531                                      license[2], "\n", NULL);
2532
2533         gtk_show_about_dialog(NULL,
2534                 "program-name", "gfio",
2535                 "comments", "Gtk2 UI for fio",
2536                 "license", license_trans,
2537                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2538                 "authors", authors,
2539                 "version", fio_version_string,
2540                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2541                 "logo-icon-name", "fio",
2542                 /* Must be last: */
2543                 "wrap-license", TRUE,
2544                 NULL);
2545
2546         g_free(license_trans);
2547 }
2548
2549 static GtkActionEntry menu_items[] = {
2550         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2551         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2552         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
2553         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2554         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2555         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
2556         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2557         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2558         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2559         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2560         { "ConnectJob", NULL, "Connect", "<Control>E", NULL, G_CALLBACK(connect_job_entry) },
2561         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
2562         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
2563         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
2564         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2565         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2566 };
2567 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2568
2569 static const gchar *ui_string = " \
2570         <ui> \
2571                 <menubar name=\"MainMenu\"> \
2572                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2573                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2574                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2575                                 <separator name=\"Separator1\"/> \
2576                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2577                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2578                                 <separator name=\"Separator2\"/> \
2579                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2580                                 <separator name=\"Separator3\"/> \
2581                                 <placeholder name=\"FileRecentFiles\"/> \
2582                                 <separator name=\"Separator4\"/> \
2583                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2584                         </menu> \
2585                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
2586                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
2587                                 <separator name=\"Separator5\"/> \
2588                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
2589                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
2590                                 <separator name=\"Separator6\"/> \
2591                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
2592                         </menu>\
2593                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2594                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2595                         </menu>\
2596                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2597                                 <menuitem name=\"About\" action=\"About\" /> \
2598                         </menu> \
2599                 </menubar> \
2600         </ui> \
2601 ";
2602
2603 static void set_job_menu_visible(struct gui *ui, int visible)
2604 {
2605         GtkWidget *job;
2606
2607         job = gtk_ui_manager_get_widget(ui->uimanager, "/MainMenu/JobMenu");
2608         gtk_widget_set_sensitive(job, visible);
2609 }
2610
2611 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2612                                    struct gui *ui)
2613 {
2614         GtkActionGroup *action_group = gtk_action_group_new("Menu");
2615         GError *error = 0;
2616
2617         action_group = gtk_action_group_new("Menu");
2618         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2619
2620         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2621         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2622
2623         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2624
2625         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2626 }
2627
2628 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2629                    GtkWidget *vbox, GtkUIManager *ui_manager)
2630 {
2631         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2632 }
2633
2634 static void combo_entry_changed(GtkComboBox *box, gpointer data)
2635 {
2636         struct gui_entry *ge = (struct gui_entry *) data;
2637         gint index;
2638
2639         index = gtk_combo_box_get_active(box);
2640
2641         multitext_set_entry(&ge->eta.iotype, index);
2642         multitext_set_entry(&ge->eta.ioengine, index);
2643         multitext_set_entry(&ge->eta.iodepth, index);
2644 }
2645
2646 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
2647 {
2648         struct gui_entry *ge = (struct gui_entry *) data;
2649
2650         multitext_free(&ge->eta.iotype);
2651         multitext_free(&ge->eta.ioengine);
2652         multitext_free(&ge->eta.iodepth);
2653 }
2654
2655 static GtkWidget *new_client_page(struct gui_entry *ge)
2656 {
2657         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2658         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2659         GdkColor white;
2660
2661         main_vbox = gtk_vbox_new(FALSE, 3);
2662
2663         top_align = gtk_alignment_new(0, 0, 1, 0);
2664         top_vbox = gtk_vbox_new(FALSE, 3);
2665         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2666         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2667
2668         probe = gtk_frame_new("Job");
2669         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2670         probe_frame = gtk_vbox_new(FALSE, 3);
2671         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2672
2673         probe_box = gtk_hbox_new(FALSE, 3);
2674         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2675         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2676         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2677         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2678         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2679
2680         probe_box = gtk_hbox_new(FALSE, 3);
2681         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2682
2683         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2684         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
2685         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
2686         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
2687         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
2688         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2689         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2690         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2691
2692         probe_box = gtk_hbox_new(FALSE, 3);
2693         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2694         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2695         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2696         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2697         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2698
2699         /*
2700          * Only add this if we have a commit rate
2701          */
2702 #if 0
2703         probe_box = gtk_hbox_new(FALSE, 3);
2704         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2705
2706         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2707         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2708
2709         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2710         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2711 #endif
2712
2713         /*
2714          * Set up a drawing area and IOPS and bandwidth graphs
2715          */
2716         gdk_color_parse("white", &white);
2717         ge->graphs.drawing_area = gtk_drawing_area_new();
2718         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2719                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2720         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2721         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2722                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2723         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2724                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2725         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2726         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2727                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2728         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2729                                         ge->graphs.drawing_area);
2730         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
2731
2732         setup_graphs(&ge->graphs);
2733
2734         /*
2735          * Set up alignments for widgets at the bottom of ui, 
2736          * align bottom left, expand horizontally but not vertically
2737          */
2738         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2739         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2740         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
2741         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2742
2743         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2744
2745         /*
2746          * Set up thread status progress bar
2747          */
2748         ge->thread_status_pb = gtk_progress_bar_new();
2749         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2750         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2751         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2752
2753
2754         return main_vbox;
2755 }
2756
2757 static GtkWidget *new_main_page(struct gui *ui)
2758 {
2759         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2760         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2761         GdkColor white;
2762
2763         main_vbox = gtk_vbox_new(FALSE, 3);
2764
2765         /*
2766          * Set up alignments for widgets at the top of ui,
2767          * align top left, expand horizontally but not vertically
2768          */
2769         top_align = gtk_alignment_new(0, 0, 1, 0);
2770         top_vbox = gtk_vbox_new(FALSE, 0);
2771         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
2772         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2773
2774         probe = gtk_frame_new("Run statistics");
2775         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2776         probe_frame = gtk_vbox_new(FALSE, 3);
2777         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2778
2779         probe_box = gtk_hbox_new(FALSE, 3);
2780         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2781         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2782         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2783         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2784         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2785         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2786
2787         /*
2788          * Only add this if we have a commit rate
2789          */
2790 #if 0
2791         probe_box = gtk_hbox_new(FALSE, 3);
2792         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2793
2794         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2795         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2796
2797         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2798         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2799 #endif
2800
2801         /*
2802          * Set up a drawing area and IOPS and bandwidth graphs
2803          */
2804         gdk_color_parse("white", &white);
2805         ui->graphs.drawing_area = gtk_drawing_area_new();
2806         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2807                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2808         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2809         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2810                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2811         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2812                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2813         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2814         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2815                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2816         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2817                                         ui->graphs.drawing_area);
2818         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
2819                         TRUE, TRUE, 0);
2820
2821         setup_graphs(&ui->graphs);
2822
2823         /*
2824          * Set up alignments for widgets at the bottom of ui, 
2825          * align bottom left, expand horizontally but not vertically
2826          */
2827         bottom_align = gtk_alignment_new(0, 1, 1, 0);
2828         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2829         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
2830         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2831
2832         /*
2833          * Set up thread status progress bar
2834          */
2835         ui->thread_status_pb = gtk_progress_bar_new();
2836         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2837         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2838         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2839
2840         return main_vbox;
2841 }
2842
2843 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2844                                      guint page, gpointer data)
2845
2846 {
2847         struct gui *ui = (struct gui *) data;
2848         struct gui_entry *ge;
2849
2850         if (!page) {
2851                 set_job_menu_visible(ui, 0);
2852                 return TRUE;
2853         }
2854
2855         set_job_menu_visible(ui, 1);
2856         ge = get_ge_from_page(page, NULL);
2857         if (ge)
2858                 update_button_states(ui, ge);
2859
2860         return TRUE;
2861 }
2862
2863 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
2864 {
2865         time_t time_a = gtk_recent_info_get_visited(a);
2866         time_t time_b = gtk_recent_info_get_visited(b);
2867
2868         return time_b - time_a;
2869 }
2870
2871 static void add_recent_file_items(struct gui *ui)
2872 {
2873         const gchar *gfio = g_get_application_name();
2874         GList *items, *item;
2875         int i = 0;
2876
2877         if (ui->recent_ui_id) {
2878                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
2879                 gtk_ui_manager_ensure_update(ui->uimanager);
2880         }
2881         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
2882
2883         if (ui->actiongroup) {
2884                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
2885                 g_object_unref(ui->actiongroup);
2886         }
2887         ui->actiongroup = gtk_action_group_new("RecentFileActions");
2888
2889         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
2890
2891         items = gtk_recent_manager_get_items(ui->recentmanager);
2892         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
2893
2894         for (item = items; item && item->data; item = g_list_next(item)) {
2895                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
2896                 gchar *action_name;
2897                 const gchar *label;
2898                 GtkAction *action;
2899
2900                 if (!gtk_recent_info_has_application(info, gfio))
2901                         continue;
2902
2903                 /*
2904                  * We only support local files for now
2905                  */
2906                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
2907                         continue;
2908
2909                 action_name = g_strdup_printf("RecentFile%u", i++);
2910                 label = gtk_recent_info_get_display_name(info);
2911
2912                 action = g_object_new(GTK_TYPE_ACTION,
2913                                         "name", action_name,
2914                                         "label", label, NULL);
2915
2916                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
2917                                         gtk_recent_info_ref(info),
2918                                         (GDestroyNotify) gtk_recent_info_unref);
2919
2920
2921                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
2922
2923                 gtk_action_group_add_action(ui->actiongroup, action);
2924                 g_object_unref(action);
2925
2926                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
2927                                         "/MainMenu/FileMenu/FileRecentFiles",
2928                                         label, action_name,
2929                                         GTK_UI_MANAGER_MENUITEM, FALSE);
2930
2931                 g_free(action_name);
2932
2933                 if (i == 8)
2934                         break;
2935         }
2936
2937         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
2938         g_list_free(items);
2939 }
2940
2941 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
2942                                    gint x, gint y, GtkSelectionData *data,
2943                                    guint info, guint time)
2944 {
2945         struct gui *ui = &main_ui;
2946         gchar **uris;
2947         GtkWidget *source;
2948         int i;
2949
2950         source = gtk_drag_get_source_widget(ctx);
2951         if (source && widget == gtk_widget_get_toplevel(source)) {
2952                 gtk_drag_finish(ctx, FALSE, FALSE, time);
2953                 return;
2954         }
2955
2956         uris = gtk_selection_data_get_uris(data);
2957         if (!uris) {
2958                 gtk_drag_finish(ctx, FALSE, FALSE, time);
2959                 return;
2960         }
2961
2962         i = 0;
2963         while (uris[i]) {
2964                 if (do_file_open_with_tab(ui, uris[i]))
2965                         break;
2966                 i++;
2967         }
2968
2969         gtk_drag_finish(ctx, TRUE, FALSE, time);
2970         g_strfreev(uris);
2971 }
2972
2973 static void init_ui(int *argc, char **argv[], struct gui *ui)
2974 {
2975         GtkSettings *settings;
2976         GtkWidget *vbox;
2977
2978         /* Magical g*thread incantation, you just need this thread stuff.
2979          * Without it, the update that happens in gfio_update_thread_status
2980          * doesn't really happen in a timely fashion, you need expose events
2981          */
2982         if (!g_thread_supported())
2983                 g_thread_init(NULL);
2984         gdk_threads_init();
2985
2986         gtk_init(argc, argv);
2987         settings = gtk_settings_get_default();
2988         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
2989         g_type_init();
2990         
2991         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2992         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2993         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
2994
2995         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
2996         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
2997
2998         ui->vbox = gtk_vbox_new(FALSE, 0);
2999         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
3000
3001         ui->uimanager = gtk_ui_manager_new();
3002         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
3003         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
3004
3005         ui->recentmanager = gtk_recent_manager_get_default();
3006         add_recent_file_items(ui);
3007
3008         ui->notebook = gtk_notebook_new();
3009         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
3010         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
3011         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
3012         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
3013
3014         vbox = new_main_page(ui);
3015         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
3016         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
3017         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
3018
3019         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
3020
3021         gfio_ui_setup_log(ui);
3022
3023         gtk_widget_show_all(ui->window);
3024 }
3025
3026 int main(int argc, char *argv[], char *envp[])
3027 {
3028         if (initialize_fio(envp))
3029                 return 1;
3030         if (fio_init_options())
3031                 return 1;
3032
3033         memset(&main_ui, 0, sizeof(main_ui));
3034         INIT_FLIST_HEAD(&main_ui.list);
3035
3036         init_ui(&argc, &argv, &main_ui);
3037
3038         gdk_threads_enter();
3039         gtk_main();
3040         gdk_threads_leave();
3041         return 0;
3042 }