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