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