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