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