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