Separate the act of adding and starting a job
[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         gdk_threads_enter();
1028         printf("gfio_group_stats_op called\n");
1029         fio_client_ops.group_stats(client, cmd);
1030         gdk_threads_leave();
1031 }
1032
1033 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event)
1034 {
1035         ui.drawing_area_xdim = w->allocation.width;
1036         ui.drawing_area_ydim = w->allocation.height;
1037         return TRUE;
1038 }
1039
1040 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
1041 {
1042         struct gui *ui = (struct gui *) p;
1043         cairo_t *cr;
1044
1045         graph_set_size(ui->iops_graph, ui->drawing_area_xdim / 2.0,
1046                                         ui->drawing_area_ydim);
1047         graph_set_size(ui->bandwidth_graph, ui->drawing_area_xdim / 2.0,
1048                                         ui->drawing_area_ydim);
1049         cr = gdk_cairo_create(w->window);
1050
1051         cairo_set_source_rgb(cr, 0, 0, 0);
1052
1053         cairo_save(cr);
1054         cairo_translate(cr, 0, 0);
1055         line_graph_draw(ui->bandwidth_graph, cr);
1056         cairo_stroke(cr);
1057         cairo_restore(cr);
1058
1059         cairo_save(cr);
1060         cairo_translate(cr, ui->drawing_area_xdim / 2.0, 0);
1061         line_graph_draw(ui->iops_graph, cr);
1062         cairo_stroke(cr);
1063         cairo_restore(cr);
1064         cairo_destroy(cr);
1065
1066         return FALSE;
1067 }
1068
1069 static void gfio_update_eta(struct jobs_eta *je)
1070 {
1071         static int eta_good;
1072         char eta_str[128];
1073         char output[256];
1074         char tmp[32];
1075         double perc = 0.0;
1076         int i2p = 0;
1077
1078         gdk_threads_enter();
1079
1080         eta_str[0] = '\0';
1081         output[0] = '\0';
1082
1083         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
1084                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
1085                 eta_to_str(eta_str, je->eta_sec);
1086         }
1087
1088         sprintf(tmp, "%u", je->nr_running);
1089         gtk_entry_set_text(GTK_ENTRY(ui.eta.jobs), tmp);
1090         sprintf(tmp, "%u", je->files_open);
1091         gtk_entry_set_text(GTK_ENTRY(ui.eta.files), tmp);
1092
1093 #if 0
1094         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
1095         if (je->m_rate || je->t_rate) {
1096                 char *tr, *mr;
1097
1098                 mr = num2str(je->m_rate, 4, 0, i2p);
1099                 tr = num2str(je->t_rate, 4, 0, i2p);
1100                 gtk_entry_set_text(GTK_ENTRY(ui.eta);
1101                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
1102                 free(tr);
1103                 free(mr);
1104         } else if (je->m_iops || je->t_iops)
1105                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
1106
1107         gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_bw), "---");
1108         gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_iops), "---");
1109         gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_bw), "---");
1110         gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_iops), "---");
1111 #endif
1112
1113         if (je->eta_sec != INT_MAX && je->nr_running) {
1114                 char *iops_str[2];
1115                 char *rate_str[2];
1116
1117                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
1118                         strcpy(output, "-.-% done");
1119                 else {
1120                         eta_good = 1;
1121                         perc *= 100.0;
1122                         sprintf(output, "%3.1f%% done", perc);
1123                 }
1124
1125                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
1126                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
1127
1128                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
1129                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
1130
1131                 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_bw), rate_str[0]);
1132                 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_iops), iops_str[0]);
1133                 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_bw), rate_str[1]);
1134                 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_iops), iops_str[1]);
1135
1136                 graph_add_xy_data(ui.iops_graph, "Read IOPS", je->elapsed_sec, je->iops[0]);
1137                 graph_add_xy_data(ui.iops_graph, "Write IOPS", je->elapsed_sec, je->iops[1]);
1138                 graph_add_xy_data(ui.bandwidth_graph, "Read Bandwidth", je->elapsed_sec, je->rate[0]);
1139                 graph_add_xy_data(ui.bandwidth_graph, "Write Bandwidth", je->elapsed_sec, je->rate[1]);
1140
1141                 free(rate_str[0]);
1142                 free(rate_str[1]);
1143                 free(iops_str[0]);
1144                 free(iops_str[1]);
1145         }
1146
1147         if (eta_str[0]) {
1148                 char *dst = output + strlen(output);
1149
1150                 sprintf(dst, " - %s", eta_str);
1151         }
1152                 
1153         gfio_update_thread_status(output, perc);
1154         gdk_threads_leave();
1155 }
1156
1157 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
1158 {
1159         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
1160         struct gfio_client *gc = client->client_data;
1161         struct gui *ui = gc->ui;
1162         const char *os, *arch;
1163         char buf[64];
1164
1165         os = fio_get_os_string(probe->os);
1166         if (!os)
1167                 os = "unknown";
1168
1169         arch = fio_get_arch_string(probe->arch);
1170         if (!arch)
1171                 os = "unknown";
1172
1173         if (!client->name)
1174                 client->name = strdup((char *) probe->hostname);
1175
1176         gdk_threads_enter();
1177
1178         gtk_label_set_text(GTK_LABEL(ui->probe.hostname), (char *) probe->hostname);
1179         gtk_label_set_text(GTK_LABEL(ui->probe.os), os);
1180         gtk_label_set_text(GTK_LABEL(ui->probe.arch), arch);
1181         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
1182         gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), buf);
1183
1184         gfio_set_connected(ui, 1);
1185
1186         gdk_threads_leave();
1187 }
1188
1189 static void gfio_update_thread_status(char *status_message, double perc)
1190 {
1191         static char message[100];
1192         const char *m = message;
1193
1194         strncpy(message, status_message, sizeof(message) - 1);
1195         gtk_progress_bar_set_text(
1196                 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
1197         gtk_progress_bar_set_fraction(
1198                 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
1199         gtk_widget_queue_draw(ui.window);
1200 }
1201
1202 static void gfio_quit_op(struct fio_client *client)
1203 {
1204         struct gfio_client *gc = client->client_data;
1205
1206         gdk_threads_enter();
1207         gfio_set_connected(gc->ui, 0);
1208         gdk_threads_leave();
1209 }
1210
1211 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
1212 {
1213         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
1214         struct gfio_client *gc = client->client_data;
1215         struct thread_options *o = &gc->o;
1216         struct gui *ui = gc->ui;
1217         char tmp[8];
1218
1219         convert_thread_options_to_cpu(o, &p->top);
1220
1221         gdk_threads_enter();
1222
1223         gtk_entry_set_text(GTK_ENTRY(ui->eta.name), (gchar *) o->name);
1224         gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), ddir_str(o->td_ddir));
1225         gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), (gchar *) o->ioengine);
1226
1227         sprintf(tmp, "%u", o->iodepth);
1228         gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
1229
1230         gc->job_added++;
1231
1232         gdk_threads_leave();
1233 }
1234
1235 static void gfio_client_timed_out(struct fio_client *client)
1236 {
1237         struct gfio_client *gc = client->client_data;
1238         GtkWidget *dialog, *label, *content;
1239         char buf[256];
1240
1241         gdk_threads_enter();
1242
1243         gfio_set_connected(gc->ui, 0);
1244         clear_ui_info(gc->ui);
1245
1246         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
1247
1248         dialog = gtk_dialog_new_with_buttons("Timed out!",
1249                         GTK_WINDOW(gc->ui->window),
1250                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1251                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1252
1253         /* gtk_dialog_get_content_area() is 2.14 and newer */
1254         content = GTK_DIALOG(dialog)->vbox;
1255
1256         label = gtk_label_new((const gchar *) buf);
1257         gtk_container_add(GTK_CONTAINER(content), label);
1258         gtk_widget_show_all(dialog);
1259         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1260
1261         gtk_dialog_run(GTK_DIALOG(dialog));
1262         gtk_widget_destroy(dialog);
1263
1264         gdk_threads_leave();
1265 }
1266
1267 static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
1268 {
1269         struct gfio_client *gc = client->client_data;
1270
1271         gdk_threads_enter();
1272
1273         gfio_set_connected(gc->ui, 0);
1274
1275         if (gc->err_entry)
1276                 entry_set_int_value(gc->err_entry, client->error);
1277
1278         gdk_threads_leave();
1279 }
1280
1281 struct client_ops gfio_client_ops = {
1282         .text_op                = gfio_text_op,
1283         .disk_util              = gfio_disk_util_op,
1284         .thread_status          = gfio_thread_status_op,
1285         .group_stats            = gfio_group_stats_op,
1286         .eta                    = gfio_update_eta,
1287         .probe                  = gfio_probe_op,
1288         .quit                   = gfio_quit_op,
1289         .add_job                = gfio_add_job_op,
1290         .timed_out              = gfio_client_timed_out,
1291         .stop                   = gfio_client_stop,
1292         .stay_connected         = 1,
1293 };
1294
1295 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1296                 __attribute__((unused)) gpointer data)
1297 {
1298         gtk_main_quit();
1299 }
1300
1301 static void *job_thread(void *arg)
1302 {
1303         fio_handle_clients(&gfio_client_ops);
1304         return NULL;
1305 }
1306
1307 static int send_job_files(struct gui *ui)
1308 {
1309         int i, ret = 0;
1310
1311         for (i = 0; i < ui->nr_job_files; i++) {
1312                 ret = fio_clients_send_ini(ui->job_files[i]);
1313                 if (ret)
1314                         break;
1315
1316                 free(ui->job_files[i]);
1317                 ui->job_files[i] = NULL;
1318         }
1319         while (i < ui->nr_job_files) {
1320                 free(ui->job_files[i]);
1321                 ui->job_files[i] = NULL;
1322                 i++;
1323         }
1324
1325         return ret;
1326 }
1327
1328 static void *server_thread(void *arg)
1329 {
1330         is_backend = 1;
1331         gfio_server_running = 1;
1332         fio_start_server(NULL);
1333         gfio_server_running = 0;
1334         return NULL;
1335 }
1336
1337 static void gfio_start_server(struct gui *ui)
1338 {
1339         if (!gfio_server_running) {
1340                 gfio_server_running = 1;
1341                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1342                 pthread_detach(ui->server_t);
1343         }
1344 }
1345
1346 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1347                 gpointer data)
1348 {
1349         struct gui *ui = data;
1350         struct gfio_client *gc = ui->client;
1351
1352         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
1353         fio_net_send_simple_cmd(gc->client->fd, FIO_NET_CMD_RUN, 0, NULL);
1354 }
1355
1356 static void file_open(GtkWidget *w, gpointer data);
1357
1358 static void connect_clicked(GtkWidget *widget, gpointer data)
1359 {
1360         struct gui *ui = data;
1361
1362         if (!ui->connected) {
1363                 if (!ui->nr_job_files)
1364                         file_open(widget, data);
1365                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
1366                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1367                 if (!fio_clients_connect()) {
1368                         pthread_create(&ui->t, NULL, job_thread, NULL);
1369                         gtk_widget_set_sensitive(ui->button[CONNECT_BUTTON], 0);
1370                         gtk_widget_set_sensitive(ui->button[SEND_BUTTON], 1);
1371                 }
1372         } else {
1373                 fio_clients_terminate();
1374                 gfio_set_connected(ui, 0);
1375                 clear_ui_info(ui);
1376         }
1377 }
1378
1379 static void send_clicked(GtkWidget *widget, gpointer data)
1380 {
1381         struct gui *ui = data;
1382
1383         if (send_job_files(ui)) {
1384                 printf("Yeah, I didn't really like those options too much.\n");
1385                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
1386         }
1387
1388         gtk_widget_set_sensitive(ui->button[SEND_BUTTON], 0);
1389         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
1390 }
1391
1392 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
1393                         struct button_spec *buttonspec)
1394 {
1395         ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
1396         g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
1397         gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
1398         gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
1399         gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
1400 }
1401
1402 static void add_buttons(struct gui *ui,
1403                                 struct button_spec *buttonlist,
1404                                 int nbuttons)
1405 {
1406         int i;
1407
1408         for (i = 0; i < nbuttons; i++)
1409                 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
1410 }
1411
1412 static void on_info_bar_response(GtkWidget *widget, gint response,
1413                                  gpointer data)
1414 {
1415         if (response == GTK_RESPONSE_OK) {
1416                 gtk_widget_destroy(widget);
1417                 ui.error_info_bar = NULL;
1418         }
1419 }
1420
1421 void report_error(GError *error)
1422 {
1423         if (ui.error_info_bar == NULL) {
1424                 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1425                                                                GTK_RESPONSE_OK,
1426                                                                NULL);
1427                 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1428                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
1429                                               GTK_MESSAGE_ERROR);
1430                 
1431                 ui.error_label = gtk_label_new(error->message);
1432                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
1433                 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
1434                 
1435                 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
1436                 gtk_widget_show_all(ui.vbox);
1437         } else {
1438                 char buffer[256];
1439                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1440                 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
1441         }
1442 }
1443
1444 struct connection_widgets
1445 {
1446         GtkWidget *hentry;
1447         GtkWidget *combo;
1448         GtkWidget *button;
1449 };
1450
1451 static void hostname_cb(GtkEntry *entry, gpointer data)
1452 {
1453         struct connection_widgets *cw = data;
1454         int uses_net = 0, is_localhost = 0;
1455         const gchar *text;
1456         gchar *ctext;
1457
1458         /*
1459          * Check whether to display the 'auto start backend' box
1460          * or not. Show it if we are a localhost and using network,
1461          * or using a socket.
1462          */
1463         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1464         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1465                 uses_net = 1;
1466         g_free(ctext);
1467
1468         if (uses_net) {
1469                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1470                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1471                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1472                     !strcmp(text, "ip6-loopback"))
1473                         is_localhost = 1;
1474         }
1475
1476         if (!uses_net || is_localhost) {
1477                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1478                 gtk_widget_set_sensitive(cw->button, 1);
1479         } else {
1480                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1481                 gtk_widget_set_sensitive(cw->button, 0);
1482         }
1483 }
1484
1485 static int get_connection_details(char **host, int *port, int *type,
1486                                   int *server_start)
1487 {
1488         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1489         struct connection_widgets cw;
1490         char *typeentry;
1491
1492         dialog = gtk_dialog_new_with_buttons("Connection details",
1493                         GTK_WINDOW(ui.window),
1494                         GTK_DIALOG_DESTROY_WITH_PARENT,
1495                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1496                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1497
1498         frame = gtk_frame_new("Hostname / socket name");
1499         /* gtk_dialog_get_content_area() is 2.14 and newer */
1500         vbox = GTK_DIALOG(dialog)->vbox;
1501         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1502
1503         box = gtk_vbox_new(FALSE, 6);
1504         gtk_container_add(GTK_CONTAINER(frame), box);
1505
1506         hbox = gtk_hbox_new(TRUE, 10);
1507         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1508         cw.hentry = gtk_entry_new();
1509         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1510         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
1511
1512         frame = gtk_frame_new("Port");
1513         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1514         box = gtk_vbox_new(FALSE, 10);
1515         gtk_container_add(GTK_CONTAINER(frame), box);
1516
1517         hbox = gtk_hbox_new(TRUE, 4);
1518         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1519         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
1520
1521         frame = gtk_frame_new("Type");
1522         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1523         box = gtk_vbox_new(FALSE, 10);
1524         gtk_container_add(GTK_CONTAINER(frame), box);
1525
1526         hbox = gtk_hbox_new(TRUE, 4);
1527         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1528
1529         cw.combo = gtk_combo_box_new_text();
1530         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
1531         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
1532         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
1533         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
1534
1535         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
1536
1537         frame = gtk_frame_new("Options");
1538         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1539         box = gtk_vbox_new(FALSE, 10);
1540         gtk_container_add(GTK_CONTAINER(frame), box);
1541
1542         hbox = gtk_hbox_new(TRUE, 4);
1543         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1544
1545         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
1546         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
1547         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.");
1548         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
1549
1550         /*
1551          * Connect edit signal, so we can show/not-show the auto start button
1552          */
1553         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
1554         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
1555
1556         gtk_widget_show_all(dialog);
1557
1558         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1559                 gtk_widget_destroy(dialog);
1560                 return 1;
1561         }
1562
1563         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
1564         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
1565
1566         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
1567         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
1568                 *type = Fio_client_ipv4;
1569         else if (!strncmp(typeentry, "IPv6", 4))
1570                 *type = Fio_client_ipv6;
1571         else
1572                 *type = Fio_client_socket;
1573         g_free(typeentry);
1574
1575         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
1576
1577         gtk_widget_destroy(dialog);
1578         return 0;
1579 }
1580
1581 static void gfio_client_added(struct gui *ui, struct fio_client *client)
1582 {
1583         struct gfio_client *gc;
1584
1585         gc = malloc(sizeof(*gc));
1586         memset(gc, 0, sizeof(*gc));
1587         gc->ui = ui;
1588         gc->client = client;
1589
1590         ui->client = gc;
1591
1592         client->client_data = gc;
1593 }
1594
1595 static void file_open(GtkWidget *w, gpointer data)
1596 {
1597         GtkWidget *dialog;
1598         struct gui *ui = data;
1599         GSList *filenames, *fn_glist;
1600         GtkFileFilter *filter;
1601         char *host;
1602         int port, type, server_start;
1603
1604         dialog = gtk_file_chooser_dialog_new("Open File",
1605                 GTK_WINDOW(ui->window),
1606                 GTK_FILE_CHOOSER_ACTION_OPEN,
1607                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1608                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1609                 NULL);
1610         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1611
1612         filter = gtk_file_filter_new();
1613         gtk_file_filter_add_pattern(filter, "*.fio");
1614         gtk_file_filter_add_pattern(filter, "*.job");
1615         gtk_file_filter_add_pattern(filter, "*.ini");
1616         gtk_file_filter_add_mime_type(filter, "text/fio");
1617         gtk_file_filter_set_name(filter, "Fio job file");
1618         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1619
1620         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1621                 gtk_widget_destroy(dialog);
1622                 return;
1623         }
1624
1625         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1626
1627         gtk_widget_destroy(dialog);
1628
1629         if (get_connection_details(&host, &port, &type, &server_start))
1630                 goto err;
1631
1632         filenames = fn_glist;
1633         while (filenames != NULL) {
1634                 struct fio_client *client;
1635
1636                 ui->job_files = realloc(ui->job_files, (ui->nr_job_files + 1) * sizeof(char *));
1637                 ui->job_files[ui->nr_job_files] = strdup(filenames->data);
1638                 ui->nr_job_files++;
1639
1640                 client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1641                 if (!client) {
1642                         GError *error;
1643
1644                         error = g_error_new(g_quark_from_string("fio"), 1,
1645                                         "Failed to add client %s", host);
1646                         report_error(error);
1647                         g_error_free(error);
1648                 }
1649                 gfio_client_added(ui, client);
1650                         
1651                 g_free(filenames->data);
1652                 filenames = g_slist_next(filenames);
1653         }
1654         free(host);
1655
1656         if (server_start)
1657                 gfio_start_server(ui);
1658 err:
1659         g_slist_free(fn_glist);
1660 }
1661
1662 static void file_save(GtkWidget *w, gpointer data)
1663 {
1664         struct gui *ui = data;
1665         GtkWidget *dialog;
1666
1667         dialog = gtk_file_chooser_dialog_new("Save File",
1668                 GTK_WINDOW(ui->window),
1669                 GTK_FILE_CHOOSER_ACTION_SAVE,
1670                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1671                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1672                 NULL);
1673
1674         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1675         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1676
1677         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1678                 char *filename;
1679
1680                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1681                 // save_job_file(filename);
1682                 g_free(filename);
1683         }
1684         gtk_widget_destroy(dialog);
1685 }
1686
1687 static void view_log_destroy(GtkWidget *w, gpointer data)
1688 {
1689         struct gui *ui = (struct gui *) data;
1690
1691         gtk_widget_ref(ui->log_tree);
1692         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
1693         gtk_widget_destroy(w);
1694         ui->log_view = NULL;
1695 }
1696
1697 static void view_log(GtkWidget *w, gpointer data)
1698 {
1699         GtkWidget *win, *scroll, *vbox, *box;
1700         struct gui *ui = (struct gui *) data;
1701
1702         if (ui->log_view)
1703                 return;
1704
1705         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1706         gtk_window_set_title(GTK_WINDOW(win), "Log");
1707         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
1708
1709         scroll = gtk_scrolled_window_new(NULL, NULL);
1710
1711         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1712
1713         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1714
1715         box = gtk_hbox_new(TRUE, 0);
1716         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
1717         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
1718         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
1719
1720         vbox = gtk_vbox_new(TRUE, 5);
1721         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
1722
1723         gtk_container_add(GTK_CONTAINER(win), vbox);
1724         gtk_widget_show_all(win);
1725 }
1726
1727 static void preferences(GtkWidget *w, gpointer data)
1728 {
1729         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1730         int i;
1731
1732         dialog = gtk_dialog_new_with_buttons("Preferences",
1733                 GTK_WINDOW(ui.window),
1734                 GTK_DIALOG_DESTROY_WITH_PARENT,
1735                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1736                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1737                 NULL);
1738
1739         frame = gtk_frame_new("Debug logging");
1740         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1741
1742         vbox = gtk_vbox_new(FALSE, 6);
1743         gtk_container_add(GTK_CONTAINER(frame), vbox);
1744
1745         box = gtk_hbox_new(FALSE, 6);
1746         gtk_container_add(GTK_CONTAINER(vbox), box);
1747
1748         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1749
1750         for (i = 0; i < FD_DEBUG_MAX; i++) {
1751                 if (i == 7) {
1752                         box = gtk_hbox_new(FALSE, 6);
1753                         gtk_container_add(GTK_CONTAINER(vbox), box);
1754                 }
1755
1756
1757                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1758                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1759                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1760         }
1761
1762         frame = gtk_frame_new("Graph font");
1763         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1764         vbox = gtk_vbox_new(FALSE, 6);
1765         gtk_container_add(GTK_CONTAINER(frame), vbox);
1766
1767         font = gtk_font_button_new();
1768         gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5);
1769
1770         gtk_widget_show_all(dialog);
1771
1772         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1773                 gtk_widget_destroy(dialog);
1774                 return;
1775         }
1776
1777         for (i = 0; i < FD_DEBUG_MAX; i++) {
1778                 int set;
1779
1780                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1781                 if (set)
1782                         fio_debug |= (1UL << i);
1783         }
1784
1785         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1786         gtk_widget_destroy(dialog);
1787 }
1788
1789 static void about_dialog(GtkWidget *w, gpointer data)
1790 {
1791         const char *authors[] = {
1792                 "Jens Axboe <axboe@kernel.dk>",
1793                 "Stephen Carmeron <stephenmcameron@gmail.com>",
1794                 NULL
1795         };
1796         const char *license[] = {
1797                 "Fio is free software; you can redistribute it and/or modify "
1798                 "it under the terms of the GNU General Public License as published by "
1799                 "the Free Software Foundation; either version 2 of the License, or "
1800                 "(at your option) any later version.\n",
1801                 "Fio is distributed in the hope that it will be useful, "
1802                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1803                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
1804                 "GNU General Public License for more details.\n",
1805                 "You should have received a copy of the GNU General Public License "
1806                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1807                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
1808         };
1809         char *license_trans;
1810
1811         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1812                                      license[2], "\n", NULL);
1813
1814         gtk_show_about_dialog(NULL,
1815                 "program-name", "gfio",
1816                 "comments", "Gtk2 UI for fio",
1817                 "license", license_trans,
1818                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1819                 "authors", authors,
1820                 "version", fio_version_string,
1821                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1822                 "logo-icon-name", "fio",
1823                 /* Must be last: */
1824                 "wrap-license", TRUE,
1825                 NULL);
1826
1827         g_free (license_trans);
1828 }
1829
1830 static GtkActionEntry menu_items[] = {
1831         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1832         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1833         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1834         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1835         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1836         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1837         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1838         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1839         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1840 };
1841 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1842
1843 static const gchar *ui_string = " \
1844         <ui> \
1845                 <menubar name=\"MainMenu\"> \
1846                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1847                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1848                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1849                                 <separator name=\"Separator\"/> \
1850                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1851                                 <separator name=\"Separator2\"/> \
1852                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1853                         </menu> \
1854                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1855                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1856                         </menu>\
1857                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1858                                 <menuitem name=\"About\" action=\"About\" /> \
1859                         </menu> \
1860                 </menubar> \
1861         </ui> \
1862 ";
1863
1864 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1865                                    struct gui *ui)
1866 {
1867         GtkActionGroup *action_group = gtk_action_group_new("Menu");
1868         GError *error = 0;
1869
1870         action_group = gtk_action_group_new("Menu");
1871         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1872
1873         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1874         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1875
1876         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1877         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1878 }
1879
1880 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1881                    GtkWidget *vbox, GtkUIManager *ui_manager)
1882 {
1883         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1884 }
1885
1886 static void init_ui(int *argc, char **argv[], struct gui *ui)
1887 {
1888         GtkSettings *settings;
1889         GtkUIManager *uimanager;
1890         GtkWidget *menu, *probe, *probe_frame, *probe_box;
1891         GdkColor white;
1892
1893         memset(ui, 0, sizeof(*ui));
1894
1895         /* Magical g*thread incantation, you just need this thread stuff.
1896          * Without it, the update that happens in gfio_update_thread_status
1897          * doesn't really happen in a timely fashion, you need expose events
1898          */
1899         if (!g_thread_supported())
1900                 g_thread_init(NULL);
1901         gdk_threads_init();
1902
1903         gtk_init(argc, argv);
1904         settings = gtk_settings_get_default();
1905         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1906         g_type_init();
1907         
1908         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1909         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1910         gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 700);
1911
1912         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1913         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1914
1915         ui->vbox = gtk_vbox_new(FALSE, 0);
1916         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1917
1918         uimanager = gtk_ui_manager_new();
1919         menu = get_menubar_menu(ui->window, uimanager, ui);
1920         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1921
1922         /*
1923          * Set up alignments for widgets at the top of ui, 
1924          * align top left, expand horizontally but not vertically
1925          */
1926         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
1927         ui->topvbox = gtk_vbox_new(FALSE, 3);
1928         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1929         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1930
1931         probe = gtk_frame_new("Job");
1932         gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1933         probe_frame = gtk_vbox_new(FALSE, 3);
1934         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1935
1936         probe_box = gtk_hbox_new(FALSE, 3);
1937         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1938         ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1939         ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1940         ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1941         ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1942
1943         probe_box = gtk_hbox_new(FALSE, 3);
1944         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1945
1946         ui->eta.name = new_info_entry_in_frame(probe_box, "Name");
1947         ui->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
1948         ui->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
1949         ui->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
1950         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1951         ui->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1952
1953         probe_box = gtk_hbox_new(FALSE, 3);
1954         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1955         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1956         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1957         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1958         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1959
1960         /*
1961          * Only add this if we have a commit rate
1962          */
1963 #if 0
1964         probe_box = gtk_hbox_new(FALSE, 3);
1965         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1966
1967         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1968         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1969
1970         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1971         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1972 #endif
1973
1974         /*
1975          * Set up a drawing area and IOPS and bandwidth graphs
1976          */
1977         gdk_color_parse("white", &white);
1978         ui->drawing_area = gtk_drawing_area_new();
1979         ui->drawing_area_xdim = DRAWING_AREA_XDIM;
1980         ui->drawing_area_ydim = DRAWING_AREA_YDIM;
1981         gtk_widget_set_size_request(GTK_WIDGET(ui->drawing_area), 
1982                         ui->drawing_area_xdim, ui->drawing_area_ydim);
1983         gtk_widget_modify_bg(ui->drawing_area, GTK_STATE_NORMAL, &white);
1984         g_signal_connect(G_OBJECT(ui->drawing_area), "expose_event",
1985                                 G_CALLBACK (on_expose_drawing_area), ui);
1986         g_signal_connect(G_OBJECT(ui->drawing_area), "configure_event",
1987                                 G_CALLBACK (on_config_drawing_area), ui);
1988         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1989         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1990                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1991         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1992                                         ui->drawing_area);
1993         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1994                         TRUE, TRUE, 0);
1995
1996         setup_iops_graph(ui);
1997         setup_bandwidth_graph(ui);
1998
1999         /*
2000          * Set up alignments for widgets at the bottom of ui, 
2001          * align bottom left, expand horizontally but not vertically
2002          */
2003         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
2004         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2005         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
2006         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
2007                                         FALSE, FALSE, 0);
2008
2009         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
2010
2011         /*
2012          * Set up thread status progress bar
2013          */
2014         ui->thread_status_pb = gtk_progress_bar_new();
2015         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2016         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2017         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2018
2019         gfio_ui_setup_log(ui);
2020
2021         gtk_widget_show_all(ui->window);
2022 }
2023
2024 int main(int argc, char *argv[], char *envp[])
2025 {
2026         if (initialize_fio(envp))
2027                 return 1;
2028         if (fio_init_options())
2029                 return 1;
2030
2031         init_ui(&argc, &argv, &ui);
2032
2033         gdk_threads_enter();
2034         gtk_main();
2035         gdk_threads_leave();
2036         return 0;
2037 }