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