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