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