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