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