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