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