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