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