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