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