1dfe3a23ef73d9a54c658334a070ec0b9023ab63
[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         .eta_msec               = FIO_CLIENT_DEF_ETA_MSEC,
1468         .stay_connected         = 1,
1469 };
1470
1471 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1472                 __attribute__((unused)) gpointer data)
1473 {
1474         gtk_main_quit();
1475 }
1476
1477 static void *job_thread(void *arg)
1478 {
1479         struct gui *ui = arg;
1480
1481         ui->handler_running = 1;
1482         fio_handle_clients(&gfio_client_ops);
1483         ui->handler_running = 0;
1484         return NULL;
1485 }
1486
1487 static int send_job_files(struct gui_entry *ge)
1488 {
1489         struct gfio_client *gc = ge->client;
1490         int i, ret = 0;
1491
1492         for (i = 0; i < ge->nr_job_files; i++) {
1493                 ret = fio_client_send_ini(gc->client, ge->job_files[i]);
1494                 if (ret < 0) {
1495                         GError *error;
1496
1497                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to send file %s: %s\n", ge->job_files[i], strerror(-ret));
1498                         report_error(error);
1499                         g_error_free(error);
1500                         break;
1501                 } else if (ret)
1502                         break;
1503
1504                 free(ge->job_files[i]);
1505                 ge->job_files[i] = NULL;
1506         }
1507         while (i < ge->nr_job_files) {
1508                 free(ge->job_files[i]);
1509                 ge->job_files[i] = NULL;
1510                 i++;
1511         }
1512
1513         return ret;
1514 }
1515
1516 static void *server_thread(void *arg)
1517 {
1518         is_backend = 1;
1519         gfio_server_running = 1;
1520         fio_start_server(NULL);
1521         gfio_server_running = 0;
1522         return NULL;
1523 }
1524
1525 static void gfio_start_server(void)
1526 {
1527         struct gui *ui = &main_ui;
1528
1529         if (!gfio_server_running) {
1530                 gfio_server_running = 1;
1531                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
1532                 pthread_detach(ui->server_t);
1533         }
1534 }
1535
1536 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1537                 gpointer data)
1538 {
1539         struct gui_entry *ge = data;
1540         struct gfio_client *gc = ge->client;
1541
1542         gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 0);
1543         fio_start_client(gc->client);
1544 }
1545
1546 static void file_open(GtkWidget *w, gpointer data);
1547
1548 static void connect_clicked(GtkWidget *widget, gpointer data)
1549 {
1550         struct gui_entry *ge = data;
1551         struct gfio_client *gc = ge->client;
1552
1553         if (!ge->connected) {
1554                 int ret;
1555
1556                 if (!ge->nr_job_files)
1557                         file_open(widget, data);
1558                 if (!ge->nr_job_files)
1559                         return;
1560
1561                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
1562                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1563                 ret = fio_client_connect(gc->client);
1564                 if (!ret) {
1565                         if (!ge->ui->handler_running)
1566                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
1567                         gtk_widget_set_sensitive(ge->button[CONNECT_BUTTON], 0);
1568                         gtk_widget_set_sensitive(ge->button[SEND_BUTTON], 1);
1569                 } else {
1570                         GError *error;
1571
1572                         error = g_error_new(g_quark_from_string("fio"), 1, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
1573                         report_error(error);
1574                         g_error_free(error);
1575                 }
1576         } else {
1577                 fio_client_terminate(gc->client);
1578                 gfio_set_connected(ge, 0);
1579                 clear_ge_ui_info(ge);
1580         }
1581 }
1582
1583 static void send_clicked(GtkWidget *widget, gpointer data)
1584 {
1585         struct gui_entry *ge = data;
1586
1587         if (send_job_files(ge)) {
1588                 GError *error;
1589
1590                 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);
1591                 report_error(error);
1592                 g_error_free(error);
1593
1594                 gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
1595         }
1596
1597         gtk_widget_set_sensitive(ge->button[SEND_BUTTON], 0);
1598         gtk_widget_set_sensitive(ge->button[START_JOB_BUTTON], 1);
1599 }
1600
1601 static GtkWidget *add_button(GtkWidget *buttonbox,
1602                              struct button_spec *buttonspec, gpointer data)
1603 {
1604         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
1605
1606         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
1607         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
1608         gtk_widget_set_tooltip_text(button, buttonspec->tooltiptext);
1609         gtk_widget_set_sensitive(button, !buttonspec->start_insensitive);
1610
1611         return button;
1612 }
1613
1614 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
1615                         int nbuttons)
1616 {
1617         int i;
1618
1619         for (i = 0; i < nbuttons; i++)
1620                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
1621 }
1622
1623 static void on_info_bar_response(GtkWidget *widget, gint response,
1624                                  gpointer data)
1625 {
1626         struct gui *ui = &main_ui;
1627
1628         if (response == GTK_RESPONSE_OK) {
1629                 gtk_widget_destroy(widget);
1630                 ui->error_info_bar = NULL;
1631         }
1632 }
1633
1634 void report_error(GError *error)
1635 {
1636         struct gui *ui = &main_ui;
1637
1638         if (ui->error_info_bar == NULL) {
1639                 ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1640                                                                GTK_RESPONSE_OK,
1641                                                                NULL);
1642                 g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1643                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar),
1644                                               GTK_MESSAGE_ERROR);
1645                 
1646                 ui->error_label = gtk_label_new(error->message);
1647                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar));
1648                 gtk_container_add(GTK_CONTAINER(container), ui->error_label);
1649                 
1650                 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0);
1651                 gtk_widget_show_all(ui->vbox);
1652         } else {
1653                 char buffer[256];
1654                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1655                 gtk_label_set(GTK_LABEL(ui->error_label), buffer);
1656         }
1657 }
1658
1659 struct connection_widgets
1660 {
1661         GtkWidget *hentry;
1662         GtkWidget *combo;
1663         GtkWidget *button;
1664 };
1665
1666 static void hostname_cb(GtkEntry *entry, gpointer data)
1667 {
1668         struct connection_widgets *cw = data;
1669         int uses_net = 0, is_localhost = 0;
1670         const gchar *text;
1671         gchar *ctext;
1672
1673         /*
1674          * Check whether to display the 'auto start backend' box
1675          * or not. Show it if we are a localhost and using network,
1676          * or using a socket.
1677          */
1678         ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
1679         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
1680                 uses_net = 1;
1681         g_free(ctext);
1682
1683         if (uses_net) {
1684                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
1685                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
1686                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
1687                     !strcmp(text, "ip6-loopback"))
1688                         is_localhost = 1;
1689         }
1690
1691         if (!uses_net || is_localhost) {
1692                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
1693                 gtk_widget_set_sensitive(cw->button, 1);
1694         } else {
1695                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
1696                 gtk_widget_set_sensitive(cw->button, 0);
1697         }
1698 }
1699
1700 static int get_connection_details(char **host, int *port, int *type,
1701                                   int *server_start)
1702 {
1703         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
1704         struct connection_widgets cw;
1705         char *typeentry;
1706
1707         dialog = gtk_dialog_new_with_buttons("Connection details",
1708                         GTK_WINDOW(main_ui.window),
1709                         GTK_DIALOG_DESTROY_WITH_PARENT,
1710                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1711                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1712
1713         frame = gtk_frame_new("Hostname / socket name");
1714         /* gtk_dialog_get_content_area() is 2.14 and newer */
1715         vbox = GTK_DIALOG(dialog)->vbox;
1716         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1717
1718         box = gtk_vbox_new(FALSE, 6);
1719         gtk_container_add(GTK_CONTAINER(frame), box);
1720
1721         hbox = gtk_hbox_new(TRUE, 10);
1722         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1723         cw.hentry = gtk_entry_new();
1724         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
1725         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
1726
1727         frame = gtk_frame_new("Port");
1728         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1729         box = gtk_vbox_new(FALSE, 10);
1730         gtk_container_add(GTK_CONTAINER(frame), box);
1731
1732         hbox = gtk_hbox_new(TRUE, 4);
1733         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1734         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
1735
1736         frame = gtk_frame_new("Type");
1737         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1738         box = gtk_vbox_new(FALSE, 10);
1739         gtk_container_add(GTK_CONTAINER(frame), box);
1740
1741         hbox = gtk_hbox_new(TRUE, 4);
1742         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1743
1744         cw.combo = gtk_combo_box_new_text();
1745         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
1746         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
1747         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
1748         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
1749
1750         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
1751
1752         frame = gtk_frame_new("Options");
1753         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1754         box = gtk_vbox_new(FALSE, 10);
1755         gtk_container_add(GTK_CONTAINER(frame), box);
1756
1757         hbox = gtk_hbox_new(TRUE, 4);
1758         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1759
1760         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
1761         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
1762         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.");
1763         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
1764
1765         /*
1766          * Connect edit signal, so we can show/not-show the auto start button
1767          */
1768         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
1769         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
1770
1771         gtk_widget_show_all(dialog);
1772
1773         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1774                 gtk_widget_destroy(dialog);
1775                 return 1;
1776         }
1777
1778         *host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
1779         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
1780
1781         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
1782         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
1783                 *type = Fio_client_ipv4;
1784         else if (!strncmp(typeentry, "IPv6", 4))
1785                 *type = Fio_client_ipv6;
1786         else
1787                 *type = Fio_client_socket;
1788         g_free(typeentry);
1789
1790         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
1791
1792         gtk_widget_destroy(dialog);
1793         return 0;
1794 }
1795
1796 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
1797 {
1798         struct gfio_client *gc;
1799
1800         gc = malloc(sizeof(*gc));
1801         memset(gc, 0, sizeof(*gc));
1802         gc->ge = ge;
1803         gc->client = fio_get_client(client);
1804
1805         ge->client = gc;
1806
1807         client->client_data = gc;
1808 }
1809
1810 static GtkWidget *new_client_page(struct gui_entry *ge);
1811
1812 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
1813 {
1814         struct gui_entry *ge;
1815
1816         ge = malloc(sizeof(*ge));
1817         memset(ge, 0, sizeof(*ge));
1818         INIT_FLIST_HEAD(&ge->list);
1819         flist_add_tail(&ge->list, &ui->list);
1820         ge->ui = ui;
1821         return ge;
1822 }
1823
1824 /*
1825  * FIXME: need more handling here
1826  */
1827 static void ge_destroy(GtkWidget *w, gpointer data)
1828 {
1829         struct gui_entry *ge = data;
1830         struct gfio_client *gc = ge->client;
1831
1832         if (gc->client)
1833                 fio_put_client(gc->client);
1834
1835         flist_del(&ge->list);
1836         free(ge);
1837 }
1838
1839 static struct gui_entry *get_new_ge_with_tab(const char *name)
1840 {
1841         struct gui_entry *ge;
1842
1843         ge = alloc_new_gui_entry(&main_ui);
1844
1845         ge->vbox = new_client_page(ge);
1846         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_destroy), ge);
1847
1848         ge->page_label = gtk_label_new(name);
1849         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(main_ui.notebook), ge->vbox, ge->page_label);
1850
1851         gtk_widget_show_all(main_ui.window);
1852         return ge;
1853 }
1854
1855 static void file_new(GtkWidget *w, gpointer data)
1856 {
1857         get_new_ge_with_tab("Untitled");
1858 }
1859
1860 /*
1861  * Return the 'ge' corresponding to the tab. If the active tab is the
1862  * main tab, open a new tab.
1863  */
1864 static struct gui_entry *get_ge_from_page(unsigned int cur_page)
1865 {
1866         struct flist_head *entry;
1867         struct gui_entry *ge;
1868
1869         if (!cur_page)
1870                 return get_new_ge_with_tab("Untitled");
1871
1872         flist_for_each(entry, &main_ui.list) {
1873                 ge = flist_entry(entry, struct gui_entry, list);
1874                 if (ge->page_num == cur_page)
1875                         return ge;
1876         }
1877
1878         return NULL;
1879 }
1880
1881 static void file_open(GtkWidget *w, gpointer data)
1882 {
1883         struct gui *ui = data;
1884         GtkWidget *dialog;
1885         GSList *filenames, *fn_glist;
1886         GtkFileFilter *filter;
1887         char *host;
1888         int port, type, server_start;
1889         struct gui_entry *ge;
1890         gint cur_page;
1891
1892         /*
1893          * Creates new tab if current tab is the main window, or the
1894          * current tab already has a client.
1895          */
1896         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
1897         ge = get_ge_from_page(cur_page);
1898         if (ge->client)
1899                 ge = get_new_ge_with_tab("Untitled");
1900
1901         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
1902
1903         dialog = gtk_file_chooser_dialog_new("Open File",
1904                 GTK_WINDOW(ui->window),
1905                 GTK_FILE_CHOOSER_ACTION_OPEN,
1906                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1907                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1908                 NULL);
1909         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1910
1911         filter = gtk_file_filter_new();
1912         gtk_file_filter_add_pattern(filter, "*.fio");
1913         gtk_file_filter_add_pattern(filter, "*.job");
1914         gtk_file_filter_add_pattern(filter, "*.ini");
1915         gtk_file_filter_add_mime_type(filter, "text/fio");
1916         gtk_file_filter_set_name(filter, "Fio job file");
1917         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1918
1919         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1920                 gtk_widget_destroy(dialog);
1921                 return;
1922         }
1923
1924         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1925
1926         gtk_widget_destroy(dialog);
1927
1928         if (get_connection_details(&host, &port, &type, &server_start))
1929                 goto err;
1930
1931         filenames = fn_glist;
1932         while (filenames != NULL) {
1933                 struct fio_client *client;
1934
1935                 ge->job_files = realloc(ge->job_files, (ge->nr_job_files + 1) * sizeof(char *));
1936                 ge->job_files[ge->nr_job_files] = strdup(filenames->data);
1937                 ge->nr_job_files++;
1938
1939                 client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1940                 if (!client) {
1941                         GError *error;
1942
1943                         error = g_error_new(g_quark_from_string("fio"), 1,
1944                                         "Failed to add client %s", host);
1945                         report_error(error);
1946                         g_error_free(error);
1947                 }
1948                 gfio_client_added(ge, client);
1949                         
1950                 g_free(filenames->data);
1951                 filenames = g_slist_next(filenames);
1952         }
1953         free(host);
1954
1955         if (server_start)
1956                 gfio_start_server();
1957 err:
1958         g_slist_free(fn_glist);
1959 }
1960
1961 static void file_save(GtkWidget *w, gpointer data)
1962 {
1963         struct gui *ui = data;
1964         GtkWidget *dialog;
1965
1966         dialog = gtk_file_chooser_dialog_new("Save File",
1967                 GTK_WINDOW(ui->window),
1968                 GTK_FILE_CHOOSER_ACTION_SAVE,
1969                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1970                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1971                 NULL);
1972
1973         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1974         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1975
1976         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1977                 char *filename;
1978
1979                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1980                 // save_job_file(filename);
1981                 g_free(filename);
1982         }
1983         gtk_widget_destroy(dialog);
1984 }
1985
1986 static void view_log_destroy(GtkWidget *w, gpointer data)
1987 {
1988         struct gui *ui = (struct gui *) data;
1989
1990         gtk_widget_ref(ui->log_tree);
1991         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
1992         gtk_widget_destroy(w);
1993         ui->log_view = NULL;
1994 }
1995
1996 static void view_log(GtkWidget *w, gpointer data)
1997 {
1998         GtkWidget *win, *scroll, *vbox, *box;
1999         struct gui *ui = (struct gui *) data;
2000
2001         if (ui->log_view)
2002                 return;
2003
2004         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2005         gtk_window_set_title(GTK_WINDOW(win), "Log");
2006         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
2007
2008         scroll = gtk_scrolled_window_new(NULL, NULL);
2009
2010         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
2011
2012         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2013
2014         box = gtk_hbox_new(TRUE, 0);
2015         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
2016         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
2017         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
2018
2019         vbox = gtk_vbox_new(TRUE, 5);
2020         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
2021
2022         gtk_container_add(GTK_CONTAINER(win), vbox);
2023         gtk_widget_show_all(win);
2024 }
2025
2026 static void __update_graph_limits(struct gfio_graphs *g)
2027 {
2028         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
2029         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
2030 }
2031
2032 static void update_graph_limits(void)
2033 {
2034         struct flist_head *entry;
2035         struct gui_entry *ge;
2036
2037         __update_graph_limits(&main_ui.graphs);
2038
2039         flist_for_each(entry, &main_ui.list) {
2040                 ge = flist_entry(entry, struct gui_entry, list);
2041                 __update_graph_limits(&ge->graphs);
2042         }
2043 }
2044
2045 static void preferences(GtkWidget *w, gpointer data)
2046 {
2047         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
2048         GtkWidget *hbox, *spin, *entry, *spin_int;
2049         int i;
2050
2051         dialog = gtk_dialog_new_with_buttons("Preferences",
2052                 GTK_WINDOW(main_ui.window),
2053                 GTK_DIALOG_DESTROY_WITH_PARENT,
2054                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2055                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2056                 NULL);
2057
2058         frame = gtk_frame_new("Graphing");
2059         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2060         vbox = gtk_vbox_new(FALSE, 6);
2061         gtk_container_add(GTK_CONTAINER(frame), vbox);
2062
2063         hbox = gtk_hbox_new(FALSE, 5);
2064         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
2065         entry = gtk_label_new("Font face to use for graph labels");
2066         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
2067
2068         font = gtk_font_button_new();
2069         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
2070
2071         box = gtk_vbox_new(FALSE, 6);
2072         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2073
2074         hbox = gtk_hbox_new(FALSE, 5);
2075         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2076         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
2077         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2078
2079         spin = create_spinbutton(hbox, 10, 1000000, 100);
2080
2081         box = gtk_vbox_new(FALSE, 6);
2082         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
2083
2084         hbox = gtk_hbox_new(FALSE, 5);
2085         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
2086         entry = gtk_label_new("Client ETA request interval (msec)");
2087         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2088
2089         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
2090         frame = gtk_frame_new("Debug logging");
2091         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
2092         vbox = gtk_vbox_new(FALSE, 6);
2093         gtk_container_add(GTK_CONTAINER(frame), vbox);
2094
2095         box = gtk_hbox_new(FALSE, 6);
2096         gtk_container_add(GTK_CONTAINER(vbox), box);
2097
2098         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
2099
2100         for (i = 0; i < FD_DEBUG_MAX; i++) {
2101                 if (i == 7) {
2102                         box = gtk_hbox_new(FALSE, 6);
2103                         gtk_container_add(GTK_CONTAINER(vbox), box);
2104                 }
2105
2106
2107                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
2108                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
2109                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
2110         }
2111
2112         gtk_widget_show_all(dialog);
2113
2114         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
2115                 gtk_widget_destroy(dialog);
2116                 return;
2117         }
2118
2119         for (i = 0; i < FD_DEBUG_MAX; i++) {
2120                 int set;
2121
2122                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
2123                 if (set)
2124                         fio_debug |= (1UL << i);
2125         }
2126
2127         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
2128         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
2129         update_graph_limits();
2130         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
2131
2132         gtk_widget_destroy(dialog);
2133 }
2134
2135 static void about_dialog(GtkWidget *w, gpointer data)
2136 {
2137         const char *authors[] = {
2138                 "Jens Axboe <axboe@kernel.dk>",
2139                 "Stephen Carmeron <stephenmcameron@gmail.com>",
2140                 NULL
2141         };
2142         const char *license[] = {
2143                 "Fio is free software; you can redistribute it and/or modify "
2144                 "it under the terms of the GNU General Public License as published by "
2145                 "the Free Software Foundation; either version 2 of the License, or "
2146                 "(at your option) any later version.\n",
2147                 "Fio is distributed in the hope that it will be useful, "
2148                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
2149                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
2150                 "GNU General Public License for more details.\n",
2151                 "You should have received a copy of the GNU General Public License "
2152                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
2153                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
2154         };
2155         char *license_trans;
2156
2157         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
2158                                      license[2], "\n", NULL);
2159
2160         gtk_show_about_dialog(NULL,
2161                 "program-name", "gfio",
2162                 "comments", "Gtk2 UI for fio",
2163                 "license", license_trans,
2164                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
2165                 "authors", authors,
2166                 "version", fio_version_string,
2167                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
2168                 "logo-icon-name", "fio",
2169                 /* Must be last: */
2170                 "wrap-license", TRUE,
2171                 NULL);
2172
2173         g_free(license_trans);
2174 }
2175
2176 static GtkActionEntry menu_items[] = {
2177         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
2178         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
2179         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2180         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
2181         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
2182         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
2183         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
2184         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
2185         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
2186         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
2187 };
2188 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
2189
2190 static const gchar *ui_string = " \
2191         <ui> \
2192                 <menubar name=\"MainMenu\"> \
2193                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2194                                 <menuitem name=\"New\" action=\"NewFile\" /> \
2195                                 <separator name=\"Separator1\"/> \
2196                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
2197                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
2198                                 <separator name=\"Separator2\"/> \
2199                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
2200                                 <separator name=\"Separator3\"/> \
2201                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
2202                         </menu> \
2203                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
2204                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
2205                         </menu>\
2206                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
2207                                 <menuitem name=\"About\" action=\"About\" /> \
2208                         </menu> \
2209                 </menubar> \
2210         </ui> \
2211 ";
2212
2213 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
2214                                    struct gui *ui)
2215 {
2216         GtkActionGroup *action_group = gtk_action_group_new("Menu");
2217         GError *error = 0;
2218
2219         action_group = gtk_action_group_new("Menu");
2220         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
2221
2222         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
2223         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
2224
2225         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
2226         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
2227 }
2228
2229 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
2230                    GtkWidget *vbox, GtkUIManager *ui_manager)
2231 {
2232         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
2233 }
2234
2235 static GtkWidget *new_client_page(struct gui_entry *ge)
2236 {
2237         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2238         GdkColor white;
2239
2240         main_vbox = gtk_vbox_new(FALSE, 3);
2241
2242         ge->topalign = gtk_alignment_new(0, 0, 1, 0);
2243         ge->topvbox = gtk_vbox_new(FALSE, 3);
2244         gtk_container_add(GTK_CONTAINER(ge->topalign), ge->topvbox);
2245         gtk_box_pack_start(GTK_BOX(main_vbox), ge->topalign, FALSE, FALSE, 0);
2246
2247         probe = gtk_frame_new("Job");
2248         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2249         probe_frame = gtk_vbox_new(FALSE, 3);
2250         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2251
2252         probe_box = gtk_hbox_new(FALSE, 3);
2253         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2254         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
2255         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
2256         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
2257         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
2258
2259         probe_box = gtk_hbox_new(FALSE, 3);
2260         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2261
2262         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
2263         ge->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
2264         ge->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
2265         ge->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
2266         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
2267         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
2268
2269         probe_box = gtk_hbox_new(FALSE, 3);
2270         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2271         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2272         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2273         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2274         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2275
2276         /*
2277          * Only add this if we have a commit rate
2278          */
2279 #if 0
2280         probe_box = gtk_hbox_new(FALSE, 3);
2281         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2282
2283         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2284         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2285
2286         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2287         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2288 #endif
2289
2290         /*
2291          * Set up a drawing area and IOPS and bandwidth graphs
2292          */
2293         gdk_color_parse("white", &white);
2294         ge->graphs.drawing_area = gtk_drawing_area_new();
2295         ge->graphs.drawing_area_xdim = DRAWING_AREA_XDIM;
2296         ge->graphs.drawing_area_ydim = DRAWING_AREA_YDIM;
2297         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
2298                 ge->graphs.drawing_area_xdim, ge->graphs.drawing_area_ydim);
2299         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2300         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
2301                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
2302         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
2303                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
2304         ge->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2305         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ge->scrolled_window),
2306                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2307         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ge->scrolled_window),
2308                                         ge->graphs.drawing_area);
2309         gtk_box_pack_start(GTK_BOX(main_vbox), ge->scrolled_window,
2310                         TRUE, TRUE, 0);
2311
2312         setup_graphs(&ge->graphs);
2313
2314         /*
2315          * Set up alignments for widgets at the bottom of ui, 
2316          * align bottom left, expand horizontally but not vertically
2317          */
2318         ge->bottomalign = gtk_alignment_new(0, 1, 1, 0);
2319         ge->buttonbox = gtk_hbox_new(FALSE, 0);
2320         gtk_container_add(GTK_CONTAINER(ge->bottomalign), ge->buttonbox);
2321         gtk_box_pack_start(GTK_BOX(main_vbox), ge->bottomalign,
2322                                         FALSE, FALSE, 0);
2323
2324         add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
2325
2326         /*
2327          * Set up thread status progress bar
2328          */
2329         ge->thread_status_pb = gtk_progress_bar_new();
2330         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
2331         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
2332         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
2333
2334
2335         return main_vbox;
2336 }
2337
2338 static GtkWidget *new_main_page(struct gui *ui)
2339 {
2340         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
2341         GdkColor white;
2342
2343         main_vbox = gtk_vbox_new(FALSE, 3);
2344
2345         /*
2346          * Set up alignments for widgets at the top of ui,
2347          * align top left, expand horizontally but not vertically
2348          */
2349         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
2350         ui->topvbox = gtk_vbox_new(FALSE, 0);
2351         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
2352         gtk_box_pack_start(GTK_BOX(main_vbox), ui->topalign, FALSE, FALSE, 0);
2353
2354         probe = gtk_frame_new("Run statistics");
2355         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
2356         probe_frame = gtk_vbox_new(FALSE, 3);
2357         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
2358
2359         probe_box = gtk_hbox_new(FALSE, 3);
2360         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
2361         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
2362         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
2363         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
2364         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
2365         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
2366
2367         /*
2368          * Only add this if we have a commit rate
2369          */
2370 #if 0
2371         probe_box = gtk_hbox_new(FALSE, 3);
2372         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2373
2374         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
2375         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2376
2377         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
2378         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
2379 #endif
2380
2381         /*
2382          * Set up a drawing area and IOPS and bandwidth graphs
2383          */
2384         gdk_color_parse("white", &white);
2385         ui->graphs.drawing_area = gtk_drawing_area_new();
2386         ui->graphs.drawing_area_xdim = DRAWING_AREA_XDIM;
2387         ui->graphs.drawing_area_ydim = DRAWING_AREA_YDIM;
2388         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
2389                 ui->graphs.drawing_area_xdim, ui->graphs.drawing_area_ydim);
2390         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &white);
2391         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
2392                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
2393         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
2394                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
2395         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2396         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
2397                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2398         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ui->scrolled_window),
2399                                         ui->graphs.drawing_area);
2400         gtk_box_pack_start(GTK_BOX(main_vbox), ui->scrolled_window,
2401                         TRUE, TRUE, 0);
2402
2403         setup_graphs(&ui->graphs);
2404
2405         /*
2406          * Set up alignments for widgets at the bottom of ui, 
2407          * align bottom left, expand horizontally but not vertically
2408          */
2409         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
2410         ui->buttonbox = gtk_hbox_new(FALSE, 0);
2411         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
2412         gtk_box_pack_start(GTK_BOX(main_vbox), ui->bottomalign,
2413                                         FALSE, FALSE, 0);
2414
2415         /*
2416          * Set up thread status progress bar
2417          */
2418         ui->thread_status_pb = gtk_progress_bar_new();
2419         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
2420         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
2421         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
2422
2423         return main_vbox;
2424 }
2425
2426 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
2427                                      guint page, gpointer data)
2428
2429 {
2430         return TRUE;
2431 }
2432
2433 static void init_ui(int *argc, char **argv[], struct gui *ui)
2434 {
2435         GtkSettings *settings;
2436         GtkUIManager *uimanager;
2437         GtkWidget *menu, *vbox;
2438
2439         /* Magical g*thread incantation, you just need this thread stuff.
2440          * Without it, the update that happens in gfio_update_thread_status
2441          * doesn't really happen in a timely fashion, you need expose events
2442          */
2443         if (!g_thread_supported())
2444                 g_thread_init(NULL);
2445         gdk_threads_init();
2446
2447         gtk_init(argc, argv);
2448         settings = gtk_settings_get_default();
2449         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
2450         g_type_init();
2451         
2452         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2453         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2454         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
2455
2456         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
2457         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
2458
2459         ui->vbox = gtk_vbox_new(FALSE, 0);
2460         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
2461
2462         uimanager = gtk_ui_manager_new();
2463         menu = get_menubar_menu(ui->window, uimanager, ui);
2464         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
2465
2466         ui->notebook = gtk_notebook_new();
2467         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
2468         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
2469         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
2470         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
2471
2472         vbox = new_main_page(ui);
2473
2474         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
2475
2476         gfio_ui_setup_log(ui);
2477
2478         gtk_widget_show_all(ui->window);
2479 }
2480
2481 int main(int argc, char *argv[], char *envp[])
2482 {
2483         if (initialize_fio(envp))
2484                 return 1;
2485         if (fio_init_options())
2486                 return 1;
2487
2488         memset(&main_ui, 0, sizeof(main_ui));
2489         INIT_FLIST_HEAD(&main_ui.list);
2490
2491         init_ui(&argc, &argv, &main_ui);
2492
2493         gdk_threads_enter();
2494         gtk_main();
2495         gdk_threads_leave();
2496         return 0;
2497 }