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