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