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