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