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