gfio: more error reporting
[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 = 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
1808         flist_del(&ge->list);
1809         free(ge);
1810 }
1811
1812 static struct gui_entry *get_new_ge_with_tab(const char *name)
1813 {
1814         struct gui_entry *ge;
1815
1816         ge = alloc_new_gui_entry(&main_ui);
1817
1818         ge->vbox = new_client_page(ge);
1819         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_destroy), ge);
1820
1821         ge->page_label = gtk_label_new(name);
1822         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
1823
1824         gtk_widget_show_all(main_ui.window);
1825         return ge;
1826 }
1827
1828 static void file_new(GtkWidget *w, gpointer data)
1829 {
1830         get_new_ge_with_tab("Untitled");
1831 }
1832
1833 /*
1834  * Return the 'ge' corresponding to the tab. If the active tab is the
1835  * main tab, open a new tab.
1836  */
1837 static struct gui_entry *get_ge_from_page(unsigned int cur_page)
1838 {
1839         struct flist_head *entry;
1840         struct gui_entry *ge;
1841
1842         if (!cur_page)
1843                 return get_new_ge_with_tab("Untitled");
1844
1845         flist_for_each(entry, &main_ui.list) {
1846                 ge = flist_entry(entry, struct gui_entry, list);
1847                 if (ge->page_num == cur_page)
1848                         return ge;
1849         }
1850
1851         return NULL;
1852 }
1853
1854 static void file_open(GtkWidget *w, gpointer data)
1855 {
1856         struct gui *ui = data;
1857         GtkWidget *dialog;
1858         GSList *filenames, *fn_glist;
1859         GtkFileFilter *filter;
1860         char *host;
1861         int port, type, server_start;
1862         struct gui_entry *ge;
1863         gint cur_page;
1864
1865         /*
1866          * Creates new tab if current tab is the main window, or the
1867          * current tab already has a client.
1868          */
1869         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
1870         ge = get_ge_from_page(cur_page);
1871         if (ge->client)
1872                 ge = get_new_ge_with_tab("Untitled");
1873
1874         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
1875
1876         dialog = gtk_file_chooser_dialog_new("Open File",
1877                 GTK_WINDOW(ui->window),
1878                 GTK_FILE_CHOOSER_ACTION_OPEN,
1879                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1880                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1881                 NULL);
1882         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1883
1884         filter = gtk_file_filter_new();
1885         gtk_file_filter_add_pattern(filter, "*.fio");
1886         gtk_file_filter_add_pattern(filter, "*.job");
1887         gtk_file_filter_add_pattern(filter, "*.ini");
1888         gtk_file_filter_add_mime_type(filter, "text/fio");
1889         gtk_file_filter_set_name(filter, "Fio job file");
1890         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1891
1892         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1893                 gtk_widget_destroy(dialog);
1894                 return;
1895         }
1896
1897         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1898
1899         gtk_widget_destroy(dialog);
1900
1901         if (get_connection_details(&host, &port, &type, &server_start))
1902                 goto err;
1903
1904         filenames = fn_glist;
1905         while (filenames != NULL) {
1906                 struct fio_client *client;
1907
1908                 ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
1909                 ge->job_files[ge->nr_job_files] = strdup(filenames->data);
1910                 ge->nr_job_files++;
1911
1912                 client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1913                 if (!client) {
1914                         GError *error;
1915
1916                         error = g_error_new(g_quark_from_string("fio"), 1,
1917                                         "Failed to add client %s", host);
1918                         report_error(error);
1919                         g_error_free(error);
1920                 }
1921                 gfio_client_added(ge, client);
1922                         
1923                 g_free(filenames->data);
1924                 filenames = g_slist_next(filenames);
1925         }
1926         free(host);
1927
1928         if (server_start)
1929                 gfio_start_server();
1930 err:
1931         g_slist_free(fn_glist);
1932 }
1933
1934 static void file_save(GtkWidget *w, gpointer data)
1935 {
1936         struct gui *ui = data;
1937         GtkWidget *dialog;
1938
1939         dialog = gtk_file_chooser_dialog_new("Save File",
1940                 GTK_WINDOW(ui->window),
1941                 GTK_FILE_CHOOSER_ACTION_SAVE,
1942                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1943                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1944                 NULL);
1945
1946         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1947         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1948
1949         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1950                 char *filename;
1951
1952                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1953                 // save_job_file(filename);
1954                 g_free(filename);
1955         }
1956         gtk_widget_destroy(dialog);
1957 }
1958
1959 static void view_log_destroy(GtkWidget *w, gpointer data)
1960 {
1961         struct gui *ui = (struct gui *) data;
1962
1963         gtk_widget_ref(ui->log_tree);
1964         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
1965         gtk_widget_destroy(w);
1966         ui->log_view = NULL;
1967 }
1968
1969 static void view_log(GtkWidget *w, gpointer data)
1970 {
1971         GtkWidget *win, *scroll, *vbox, *box;
1972         struct gui *ui = (struct gui *) data;
1973
1974         if (ui->log_view)
1975                 return;
1976
1977         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1978         gtk_window_set_title(GTK_WINDOW(win), "Log");
1979         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
1980
1981         scroll = gtk_scrolled_window_new(NULL, NULL);
1982
1983         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1984
1985         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1986
1987         box = gtk_hbox_new(TRUE, 0);
1988         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
1989         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
1990         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
1991
1992         vbox = gtk_vbox_new(TRUE, 5);
1993         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
1994
1995         gtk_container_add(GTK_CONTAINER(win), vbox);
1996         gtk_widget_show_all(win);
1997 }
1998
1999 static void preferences(GtkWidget *w, gpointer data)
2000 {
2001         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2002         int i;
2003
2004         dialog = gtk_dialog_new_with_buttons("Preferences",
2005                 GTK_WINDOW(main_ui.window),
2006                 GTK_DIALOG_DESTROY_WITH_PARENT,
2007                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2008                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2009                 NULL);
2010
2011         frame = gtk_frame_new("Debug logging");
2012         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2013
2014         vbox = gtk_vbox_new(FALSE, 6);
2015         gtk_container_add(GTK_CONTAINER(frame), vbox);
2016
2017         box = gtk_hbox_new(FALSE, 6);
2018         gtk_container_add(GTK_CONTAINER(vbox), box);
2019
2020         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2021
2022         for (i = 0; i < FD_DEBUG_MAX; i++) {
2023                 if (i == 7) {
2024                         box = gtk_hbox_new(FALSE, 6);
2025                         gtk_container_add(GTK_CONTAINER(vbox), box);
2026                 }
2027
2028
2029                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2030                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2031                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2032         }
2033
2034         frame = gtk_frame_new("Graph font");
2035         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2036         vbox = gtk_vbox_new(FALSE, 6);
2037         gtk_container_add(GTK_CONTAINER(frame), vbox);
2038
2039         font = gtk_font_button_new();
2040         gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5);
2041
2042         gtk_widget_show_all(dialog);
2043
2044         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2045                 gtk_widget_destroy(dialog);
2046                 return;
2047         }
2048
2049         for (i = 0; i < FD_DEBUG_MAX; i++) {
2050                 int set;
2051
2052                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2053                 if (set)
2054                         fio_debug |= (1UL << i);
2055         }
2056
2057         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2058         gtk_widget_destroy(dialog);
2059 }
2060
2061 static void about_dialog(GtkWidget *w, gpointer data)
2062 {
2063         const char *authors[] = {
2064                 "Jens Axboe <axboe@kernel.dk>",
2065                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2066                 NULL
2067         };
2068         const char *license[] = {
2069                 "Fio is free software; you can redistribute it and/or modify "
2070                 "it under the terms of the GNU General Public License as published by "
2071                 "the Free Software Foundation; either version 2 of the License, or "
2072                 "(at your option) any later version.\n",
2073                 "Fio is distributed in the hope that it will be useful, "
2074                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2075                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2076                 "GNU General Public License for more details.\n",
2077                 "You should have received a copy of the GNU General Public License "
2078                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2079                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2080         };
2081         char *license_trans;
2082
2083         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2084                                      license[2], "\n", NULL);
2085
2086         gtk_show_about_dialog(NULL,
2087                 "program-name", "gfio",
2088                 "comments", "Gtk2 UI for fio",
2089                 "license", license_trans,
2090                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2091                 "authors", authors,
2092                 "version", fio_version_string,
2093                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2094                 "logo-icon-name", "fio",
2095                 /* Must be last: */
2096                 "wrap-license", TRUE,
2097                 NULL);
2098
2099         g_free(license_trans);
2100 }
2101
2102 static GtkActionEntry menu_items[] = {
2103         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2104         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2105         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2106         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2107         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2108         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2109         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2110         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2111         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2112         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2113 };
2114 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2115
2116 static const gchar *ui_string = " \
2117         <ui> \
2118                 <menubar name=\"MainMenu\"> \
2119                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2120                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2121                                 <separator name=\"Separator1\"/> \
2122                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2123                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2124                                 <separator name=\"Separator2\"/> \
2125                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2126                                 <separator name=\"Separator3\"/> \
2127                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2128                         </menu> \
2129                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2130                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2131                         </menu>\
2132                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2133                                 <menuitem name=\"About\" action=\"About\" /> \
2134                         </menu> \
2135                 </menubar> \
2136         </ui> \
2137 ";
2138
2139 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2140                                    struct gui *ui)
2141 {
2142         GtkActionGroup *action_group = gtk_action_group_new("Menu");
2143         GError *error = 0;
2144
2145         action_group = gtk_action_group_new("Menu");
2146         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2147
2148         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2149         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2150
2151         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2152         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2153 }
2154
2155 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2156                    GtkWidget *vbox, GtkUIManager *ui_manager)
2157 {
2158         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2159 }
2160
2161 static GtkWidget *new_client_page(struct gui_entry *ge)
2162 {
2163         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2164         GdkColor white;
2165
2166         main_vbox = gtk_vbox_new(FALSE, 3);
2167
2168         ge->topalign = gtk_alignment_new(0, 0, 1, 0);
2169         ge->topvbox = gtk_vbox_new(FALSE, 3);
2170         gtk_container_add(GTK_CONTAINER(ge->topalign), ge->topvbox);
2171         gtk_box_pack_start(GTK_BOX(main_vbox), ge->topalign, FALSE, FALSE, 0);
2172
2173         probe = gtk_frame_new("Job");
2174         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2175         probe_frame = gtk_vbox_new(FALSE, 3);
2176         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2177
2178         probe_box = gtk_hbox_new(FALSE, 3);
2179         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2180         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2181         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2182         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2183         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2184
2185         probe_box = gtk_hbox_new(FALSE, 3);
2186         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2187
2188         ge->eta.name = new_info_entry_in_frame(probe_box, "Name");
2189         ge->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
2190         ge->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
2191         ge->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
2192         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2193         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2194
2195         probe_box = gtk_hbox_new(FALSE, 3);
2196         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2197         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2198         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2199         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2200         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2201
2202         /*
2203          * Only add this if we have a commit rate
2204          */
2205 #if 0
2206         probe_box = gtk_hbox_new(FALSE, 3);
2207         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2208
2209         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2210         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2211
2212         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2213         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2214 #endif
2215
2216         /*
2217          * Set up a drawing area and IOPS and bandwidth graphs
2218          */
2219         gdk_color_parse("white", &white);
2220         ge->graphs.drawing_area = gtk_drawing_area_new();
2221         ge->graphs.drawing_area_xdim = DRAWING_AREA_XDIM;
2222         ge->graphs.drawing_area_ydim = DRAWING_AREA_YDIM;
2223         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2224                 ge->graphs.drawing_area_xdim, ge->graphs.drawing_area_ydim);
2225         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2226         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2227                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2228         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2229                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2230         ge->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2231         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ge->scrolled_window),
2232                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2233         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ge->scrolled_window),
2234                                         ge->graphs.drawing_area);
2235         gtk_box_pack_start(GTK_BOX(main_vbox), ge->scrolled_window,
2236                         TRUE, TRUE, 0);
2237
2238         setup_graphs(&ge->graphs);
2239
2240         /*
2241          * Set up alignments for widgets at the bottom of ui, 
2242          * align bottom left, expand horizontally but not vertically
2243          */
2244         ge->bottomalign = gtk_alignment_new(0, 1, 1, 0);
2245         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2246         gtk_container_add(GTK_CONTAINER(ge->bottomalign), ge->buttonbox);
2247         gtk_box_pack_start(GTK_BOX(main_vbox), ge->bottomalign,
2248                                         FALSE, FALSE, 0);
2249
2250         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2251
2252         /*
2253          * Set up thread status progress bar
2254          */
2255         ge->thread_status_pb = gtk_progress_bar_new();
2256         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2257         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2258         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2259
2260
2261         return main_vbox;
2262 }
2263
2264 static GtkWidget *new_main_page(struct gui *ui)
2265 {
2266         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2267         GdkColor white;
2268
2269         main_vbox = gtk_vbox_new(FALSE, 3);
2270
2271         /*
2272          * Set up alignments for widgets at the top of ui,
2273          * align top left, expand horizontally but not vertically
2274          */
2275         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
2276         ui->topvbox = gtk_vbox_new(FALSE, 0);
2277         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
2278         gtk_box_pack_start(GTK_BOX(main_vbox), ui->topalign, FALSE, FALSE, 0);
2279
2280         probe = gtk_frame_new("Run statistics");
2281         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2282         probe_frame = gtk_vbox_new(FALSE, 3);
2283         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2284
2285         probe_box = gtk_hbox_new(FALSE, 3);
2286         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2287         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2288         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2289         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2290         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2291
2292         /*
2293          * Only add this if we have a commit rate
2294          */
2295 #if 0
2296         probe_box = gtk_hbox_new(FALSE, 3);
2297         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2298
2299         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2300         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2301
2302         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2303         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2304 #endif
2305
2306         /*
2307          * Set up a drawing area and IOPS and bandwidth graphs
2308          */
2309         gdk_color_parse("white", &white);
2310         ui->graphs.drawing_area = gtk_drawing_area_new();
2311         ui->graphs.drawing_area_xdim = DRAWING_AREA_XDIM;
2312         ui->graphs.drawing_area_ydim = DRAWING_AREA_YDIM;
2313         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2314                 ui->graphs.drawing_area_xdim, ui->graphs.drawing_area_ydim);
2315         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2316         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2317                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2318         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2319                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2320         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2321         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
2322                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2323         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ui->scrolled_window),
2324                                         ui->graphs.drawing_area);
2325         gtk_box_pack_start(GTK_BOX(main_vbox), ui->scrolled_window,
2326                         TRUE, TRUE, 0);
2327
2328         setup_graphs(&ui->graphs);
2329
2330         /*
2331          * Set up alignments for widgets at the bottom of ui, 
2332          * align bottom left, expand horizontally but not vertically
2333          */
2334         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
2335         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2336         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
2337         gtk_box_pack_start(GTK_BOX(main_vbox), ui->bottomalign,
2338                                         FALSE, FALSE, 0);
2339
2340         /*
2341          * Set up thread status progress bar
2342          */
2343         ui->thread_status_pb = gtk_progress_bar_new();
2344         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2345         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2346         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2347
2348         return main_vbox;
2349 }
2350
2351 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2352                                      guint page, gpointer data)
2353
2354 {
2355         return TRUE;
2356 }
2357
2358 static void init_ui(int *argc, char **argv[], struct gui *ui)
2359 {
2360         GtkSettings *settings;
2361         GtkUIManager *uimanager;
2362         GtkWidget *menu, *vbox;
2363
2364         /* Magical g*thread incantation, you just need this thread stuff.
2365          * Without it, the update that happens in gfio_update_thread_status
2366          * doesn't really happen in a timely fashion, you need expose events
2367          */
2368         if (!g_thread_supported())
2369                 g_thread_init(NULL);
2370         gdk_threads_init();
2371
2372         gtk_init(argc, argv);
2373         settings = gtk_settings_get_default();
2374         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
2375         g_type_init();
2376         
2377         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2378         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2379         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
2380
2381         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
2382         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
2383
2384         ui->vbox = gtk_vbox_new(FALSE, 0);
2385         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
2386
2387         uimanager = gtk_ui_manager_new();
2388         menu = get_menubar_menu(ui->window, uimanager, ui);
2389         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
2390
2391         ui->notebook = gtk_notebook_new();
2392         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
2393         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
2394
2395         vbox = new_main_page(ui);
2396
2397         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
2398
2399         gfio_ui_setup_log(ui);
2400
2401         gtk_widget_show_all(ui->window);
2402 }
2403
2404 int main(int argc, char *argv[], char *envp[])
2405 {
2406         if (initialize_fio(envp))
2407                 return 1;
2408         if (fio_init_options())
2409                 return 1;
2410
2411         memset(&main_ui, 0, sizeof(main_ui));
2412         INIT_FLIST_HEAD(&main_ui.list);
2413
2414         init_ui(&argc, &argv, &main_ui);
2415
2416         gdk_threads_enter();
2417         gtk_main();
2418         gdk_threads_leave();
2419         return 0;
2420 }