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