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