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