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