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