Merge branch 'fix-fsync-on-close' of https://github.com/sitsofe/fio
[fio.git] / gfio.c
CommitLineData
ff1f3280
SC
1/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
3c3ed070 4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
c0187f3b 5 * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
ff1f3280
SC
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
fa07eaa6 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
ff1f3280
SC
22 *
23 */
8232e285 24#include <locale.h>
cfe1ed39 25#include <stdlib.h>
6b79c80c 26#include <string.h>
cfe1ed39 27#include <libgen.h>
8232e285 28
5b7573ab 29#include <glib.h>
2fd3bb0e 30#include <cairo.h>
ff1f3280
SC
31#include <gtk/gtk.h>
32
8232e285 33#include "fio.h"
53e0e85d
JA
34#include "gfio.h"
35#include "ghelpers.h"
9af4a244 36#include "goptions.h"
41666588 37#include "gerror.h"
1252d8f2 38#include "gclient.h"
2fd3bb0e 39#include "graph.h"
8232e285 40
f1867a7f 41static bool gfio_server_running;
8577f4fd 42static unsigned int gfio_graph_limit = 100;
63a130b7 43
1252d8f2 44GdkColor gfio_color_white;
2a95f716 45GdkColor gfio_color_lightyellow;
a1e7972d 46const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
3e47bd25 47
f3074008
SC
48typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
49
3e47bd25 50static void connect_clicked(GtkWidget *widget, gpointer data);
f3074008 51static void start_job_clicked(GtkWidget *widget, gpointer data);
b9d2f30a 52static void send_clicked(GtkWidget *widget, gpointer data);
f3074008
SC
53
54static struct button_spec {
55 const char *buttontext;
56 clickfunction f;
014f4024
JA
57 const char *tooltiptext[2];
58 const int start_sensitive;
f3074008 59} buttonspeclist[] = {
53e0e85d
JA
60 {
61 .buttontext = "Connect",
62 .f = connect_clicked,
63 .tooltiptext = { "Disconnect from host", "Connect to host" },
64 .start_sensitive = 1,
65 },
66 {
67 .buttontext = "Send",
68 .f = send_clicked,
69 .tooltiptext = { "Send job description to host", NULL },
70 .start_sensitive = 0,
71 },
72 {
73 .buttontext = "Start Job",
74 .f = start_job_clicked,
75 .tooltiptext = { "Start the current job on the server", NULL },
76 .start_sensitive = 0,
77 },
e0681f3e
JA
78};
79
8dfd6071 80static void setup_iops_graph(struct gfio_graphs *gg)
2f99deb0
JA
81{
82 struct graph *g;
83
84 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
d8fbeefb 85 graph_title(g, "IOPS (IOs/sec)");
2f99deb0 86 graph_x_title(g, "Time (secs)");
8dfd6071
JA
87 gg->read_iops = graph_add_label(g, "Read IOPS");
88 gg->write_iops = graph_add_label(g, "Write IOPS");
5442cfb0 89 gg->trim_iops = graph_add_label(g, "Trim IOPS");
dbc542d4
JA
90 graph_set_color(g, gg->read_iops, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
91 graph_set_color(g, gg->write_iops, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
b78ca651 92 graph_set_color(g, gg->trim_iops, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
8577f4fd 93 line_graph_set_data_count_limit(g, gfio_graph_limit);
d8fbeefb 94 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
01a947f0 95 graph_set_graph_all_zeroes(g, 0);
75450781 96 gg->iops_graph = g;
2f99deb0
JA
97}
98
8dfd6071 99static void setup_bandwidth_graph(struct gfio_graphs *gg)
2f99deb0
JA
100{
101 struct graph *g;
102
103 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
d8fbeefb 104 graph_title(g, "Bandwidth (bytes/sec)");
2f99deb0 105 graph_x_title(g, "Time (secs)");
8dfd6071
JA
106 gg->read_bw = graph_add_label(g, "Read Bandwidth");
107 gg->write_bw = graph_add_label(g, "Write Bandwidth");
5442cfb0 108 gg->trim_bw = graph_add_label(g, "Trim Bandwidth");
dbc542d4
JA
109 graph_set_color(g, gg->read_bw, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
110 graph_set_color(g, gg->write_bw, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
b78ca651 111 graph_set_color(g, gg->trim_bw, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
d8fbeefb 112 graph_set_base_offset(g, 1);
2f99deb0 113 line_graph_set_data_count_limit(g, 100);
d8fbeefb 114 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
01a947f0 115 graph_set_graph_all_zeroes(g, 0);
75450781 116 gg->bandwidth_graph = g;
2f99deb0
JA
117}
118
119static void setup_graphs(struct gfio_graphs *g)
120{
8dfd6071
JA
121 setup_iops_graph(g);
122 setup_bandwidth_graph(g);
2f99deb0
JA
123}
124
1252d8f2 125void clear_ge_ui_info(struct gui_entry *ge)
2f99deb0
JA
126{
127 gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
128 gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
129 gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
130 gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
3863d1ad
JA
131#if 0
132 /* should we empty it... */
2f99deb0 133 gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
3863d1ad 134#endif
c80b74b0 135 multitext_update_entry(&ge->eta.iotype, 0, "");
99d633af 136 multitext_update_entry(&ge->eta.bs, 0, "");
c80b74b0
JA
137 multitext_update_entry(&ge->eta.ioengine, 0, "");
138 multitext_update_entry(&ge->eta.iodepth, 0, "");
2f99deb0
JA
139 gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
140 gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
141 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
142 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
143 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
144 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
8663ea65
JA
145}
146
781ccba6
JA
147static void set_menu_entry_text(struct gui *ui, const char *path,
148 const char *text)
149{
150 GtkWidget *w;
151
152 w = gtk_ui_manager_get_widget(ui->uimanager, path);
153 if (w)
154 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
155 else
156 fprintf(stderr, "gfio: can't find path %s\n", path);
157}
158
159
160static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
161{
162 GtkWidget *w;
163
164 w = gtk_ui_manager_get_widget(ui->uimanager, path);
165 if (w)
166 gtk_widget_set_sensitive(w, show);
167 else
168 fprintf(stderr, "gfio: can't find path %s\n", path);
169}
170
171static void set_job_menu_visible(struct gui *ui, int visible)
172{
173 set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
174}
175
176static void set_view_results_visible(struct gui *ui, int visible)
177{
178 set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
179}
180
014f4024
JA
181static const char *get_button_tooltip(struct button_spec *s, int sensitive)
182{
183 if (s->tooltiptext[sensitive])
184 return s->tooltiptext[sensitive];
185
186 return s->tooltiptext[0];
187}
188
189static GtkWidget *add_button(GtkWidget *buttonbox,
190 struct button_spec *buttonspec, gpointer data)
191{
192 GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
193 gboolean sens = buttonspec->start_sensitive;
194
195 g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
196 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
197
198 sens = buttonspec->start_sensitive;
199 gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
200 gtk_widget_set_sensitive(button, sens);
201
202 return button;
203}
204
205static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
206 int nbuttons)
207{
208 int i;
209
210 for (i = 0; i < nbuttons; i++)
211 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
212}
213
85dd01e7
JA
214/*
215 * Update sensitivity of job buttons and job menu items, based on the
216 * state of the client.
217 */
218static void update_button_states(struct gui *ui, struct gui_entry *ge)
219{
220 unsigned int connect_state, send_state, start_state, edit_state;
221 const char *connect_str = NULL;
85dd01e7
JA
222
223 switch (ge->state) {
1252d8f2
JA
224 default:
225 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
85dd01e7 226 /* fall through to new state */
85dd01e7
JA
227 case GE_STATE_NEW:
228 connect_state = 1;
9af4a244 229 edit_state = 1;
85dd01e7
JA
230 connect_str = "Connect";
231 send_state = 0;
232 start_state = 0;
233 break;
234 case GE_STATE_CONNECTED:
235 connect_state = 1;
9af4a244 236 edit_state = 1;
85dd01e7
JA
237 connect_str = "Disconnect";
238 send_state = 1;
239 start_state = 0;
240 break;
241 case GE_STATE_JOB_SENT:
242 connect_state = 1;
9af4a244 243 edit_state = 1;
85dd01e7
JA
244 connect_str = "Disconnect";
245 send_state = 0;
246 start_state = 1;
247 break;
248 case GE_STATE_JOB_STARTED:
249 connect_state = 1;
250 edit_state = 1;
251 connect_str = "Disconnect";
252 send_state = 0;
253 start_state = 1;
254 break;
255 case GE_STATE_JOB_RUNNING:
256 connect_state = 1;
257 edit_state = 0;
258 connect_str = "Disconnect";
259 send_state = 0;
260 start_state = 0;
261 break;
262 case GE_STATE_JOB_DONE:
263 connect_state = 1;
264 edit_state = 0;
265 connect_str = "Connect";
266 send_state = 0;
267 start_state = 0;
268 break;
269 }
270
53e0e85d
JA
271 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
272 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
273 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
274 gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
275 gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
85dd01e7 276
781ccba6
JA
277 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
278 set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
85dd01e7 279
781ccba6
JA
280 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
281 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
282 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
85dd01e7 283
781ccba6
JA
284 if (ge->client && ge->client->nr_results)
285 set_view_results_visible(ui, 1);
286 else
287 set_view_results_visible(ui, 0);
85dd01e7
JA
288}
289
1252d8f2 290void gfio_set_state(struct gui_entry *ge, unsigned int state)
85dd01e7
JA
291{
292 ge->state = state;
293 update_button_states(ge->ui, ge);
294}
295
9b260bdf
JA
296static void gfio_ui_setup_log(struct gui *ui)
297{
298 GtkTreeSelection *selection;
299 GtkListStore *model;
300 GtkWidget *tree_view;
301
3ad04399 302 model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
9b260bdf
JA
303
304 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
305 gtk_widget_set_can_focus(tree_view, FALSE);
306
307 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
308 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
661f741a
JA
309 g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
310 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
9b260bdf
JA
311
312 tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
313 tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
314 tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
f095d563 315 tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
9b260bdf
JA
316
317 ui->log_model = model;
318 ui->log_tree = tree_view;
319}
320
1252d8f2
JA
321static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
322 gpointer data)
c57f254c 323{
cd0d2db8
JA
324 guint width = gtk_widget_get_allocated_width(w);
325 guint height = gtk_widget_get_allocated_height(w);
1252d8f2 326 struct gfio_graphs *g = data;
c57f254c 327
cd0d2db8
JA
328 graph_set_size(g->iops_graph, width / 2.0, height);
329 graph_set_position(g->iops_graph, width / 2.0, 0.0);
330 graph_set_size(g->bandwidth_graph, width / 2.0, height);
1252d8f2
JA
331 graph_set_position(g->bandwidth_graph, 0, 0);
332 return TRUE;
c57f254c
SC
333}
334
1252d8f2 335static void draw_graph(struct graph *g, cairo_t *cr)
a269790c 336{
1252d8f2
JA
337 line_graph_draw(g, cr);
338 cairo_stroke(cr);
339}
a269790c 340
1252d8f2
JA
341static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
342 gboolean keyboard_mode, GtkTooltip *tooltip,
343 gpointer data)
344{
345 struct gfio_graphs *g = data;
346 const char *text = NULL;
a269790c 347
1252d8f2
JA
348 if (graph_contains_xy(g->iops_graph, x, y))
349 text = graph_find_tooltip(g->iops_graph, x, y);
350 else if (graph_contains_xy(g->bandwidth_graph, x, y))
351 text = graph_find_tooltip(g->bandwidth_graph, x, y);
a269790c 352
1252d8f2
JA
353 if (text) {
354 gtk_tooltip_set_text(tooltip, text);
355 return TRUE;
e0681f3e 356 }
a269790c 357
1252d8f2 358 return FALSE;
a269790c
JA
359}
360
1252d8f2 361static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
c57f254c 362{
1252d8f2 363 struct gfio_graphs *g = p;
c57f254c
SC
364 cairo_t *cr;
365
cd0d2db8 366 cr = gdk_cairo_create(gtk_widget_get_window(w));
1252d8f2
JA
367
368 if (graph_has_tooltips(g->iops_graph) ||
369 graph_has_tooltips(g->bandwidth_graph)) {
c57f254c 370 g_object_set(w, "has-tooltip", TRUE, NULL);
1252d8f2 371 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
c57f254c 372 }
1252d8f2 373
c57f254c 374 cairo_set_source_rgb(cr, 0, 0, 0);
1252d8f2
JA
375 draw_graph(g->iops_graph, cr);
376 draw_graph(g->bandwidth_graph, cr);
c57f254c
SC
377 cairo_destroy(cr);
378
379 return FALSE;
380}
381
1252d8f2
JA
382/*
383 * FIXME: need more handling here
384 */
385static void ge_destroy(struct gui_entry *ge)
c57f254c 386{
1252d8f2 387 struct gfio_client *gc = ge->client;
c57f254c 388
470cdbb7
JA
389 if (gc) {
390 if (gc->client) {
391 if (ge->state >= GE_STATE_CONNECTED)
392 fio_client_terminate(gc->client);
393
394 fio_put_client(gc->client);
395 }
396 free(gc);
a269790c
JA
397 }
398
b98ab718
JA
399 g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
400
1252d8f2
JA
401 free(ge->job_file);
402 free(ge->host);
1252d8f2 403 free(ge);
a269790c
JA
404}
405
1252d8f2 406static void ge_widget_destroy(GtkWidget *w, gpointer data)
3650a3ca 407{
1252d8f2 408 struct gui_entry *ge = (struct gui_entry *) data;
3650a3ca 409
1252d8f2 410 ge_destroy(ge);
3650a3ca
JA
411}
412
1252d8f2 413static void gfio_quit(struct gui *ui)
3650a3ca 414{
3c3ed070 415 gtk_main_quit();
1252d8f2 416}
3650a3ca 417
1252d8f2 418static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
3c3ed070 419 gpointer data)
1252d8f2
JA
420{
421 struct gui *ui = (struct gui *) data;
3650a3ca 422
1252d8f2
JA
423 gfio_quit(ui);
424}
3650a3ca 425
1252d8f2
JA
426static void *job_thread(void *arg)
427{
428 struct gui *ui = arg;
3650a3ca 429
1252d8f2
JA
430 ui->handler_running = 1;
431 fio_handle_clients(&gfio_client_ops);
432 ui->handler_running = 0;
433 return NULL;
434}
ca850992 435
1252d8f2
JA
436static int send_job_file(struct gui_entry *ge)
437{
438 struct gfio_client *gc = ge->client;
439 int ret = 0;
ca850992 440
753e9e64
JA
441 /*
442 * Prune old options, we are expecting the return options
443 * when the job file is parsed remotely and returned to us.
444 */
445 while (!flist_empty(&gc->o_list)) {
446 struct gfio_client_options *gco;
447
9342d5f8 448 gco = flist_first_entry(&gc->o_list, struct gfio_client_options, list);
753e9e64
JA
449 flist_del(&gco->list);
450 free(gco);
451 }
452
bb781643 453 ret = fio_client_send_ini(gc->client, ge->job_file, false);
1252d8f2
JA
454 if (!ret)
455 return 0;
ca850992 456
1252d8f2
JA
457 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
458 return 1;
459}
ca850992 460
1252d8f2
JA
461static void *server_thread(void *arg)
462{
8267ddba 463 fio_server_create_sk_key();
f1867a7f
JA
464 is_backend = true;
465 gfio_server_running = true;
1252d8f2 466 fio_start_server(NULL);
f1867a7f 467 gfio_server_running = false;
8267ddba 468 fio_server_destroy_sk_key();
1252d8f2
JA
469 return NULL;
470}
ca850992 471
1252d8f2
JA
472static void gfio_start_server(struct gui *ui)
473{
474 if (!gfio_server_running) {
f1867a7f 475 gfio_server_running = true;
1252d8f2
JA
476 pthread_create(&ui->server_t, NULL, server_thread, NULL);
477 pthread_detach(ui->server_t);
2b089892 478 }
3650a3ca
JA
479}
480
1252d8f2 481static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
3c3ed070 482 gpointer data)
09d574e3 483{
1252d8f2
JA
484 struct gui_entry *ge = data;
485 struct gfio_client *gc = ge->client;
09d574e3 486
1252d8f2
JA
487 if (gc)
488 fio_start_client(gc->client);
09d574e3
JA
489}
490
1252d8f2 491static void file_open(GtkWidget *w, gpointer data);
e5bd1347 492
1252d8f2
JA
493struct connection_widgets
494{
495 GtkWidget *hentry;
496 GtkWidget *combo;
497 GtkWidget *button;
498};
e5bd1347 499
1252d8f2
JA
500static void hostname_cb(GtkEntry *entry, gpointer data)
501{
502 struct connection_widgets *cw = data;
503 int uses_net = 0, is_localhost = 0;
504 const gchar *text;
505 gchar *ctext;
e5bd1347 506
1252d8f2
JA
507 /*
508 * Check whether to display the 'auto start backend' box
509 * or not. Show it if we are a localhost and using network,
510 * or using a socket.
511 */
f762cef9 512 ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
1252d8f2
JA
513 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
514 uses_net = 1;
515 g_free(ctext);
e5bd1347 516
1252d8f2
JA
517 if (uses_net) {
518 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
519 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
520 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
521 !strcmp(text, "ip6-loopback"))
522 is_localhost = 1;
523 }
e5bd1347 524
1252d8f2
JA
525 if (!uses_net || is_localhost) {
526 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
527 gtk_widget_set_sensitive(cw->button, 1);
528 } else {
529 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
530 gtk_widget_set_sensitive(cw->button, 0);
531 }
b0500ccb 532}
661f741a 533
b0500ccb
JA
534static int get_connection_details(struct gui_entry *ge)
535{
536 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
537 struct connection_widgets cw;
538 struct gui *ui = ge->ui;
539 char *typeentry;
2e33101f 540
b0500ccb
JA
541 if (ge->host)
542 return 0;
543
544 dialog = gtk_dialog_new_with_buttons("Connection details",
545 GTK_WINDOW(ui->window),
546 GTK_DIALOG_DESTROY_WITH_PARENT,
547 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
548 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
549
550 frame = gtk_frame_new("Hostname / socket name");
f762cef9 551 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
b0500ccb
JA
552 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
553
554 box = gtk_vbox_new(FALSE, 6);
555 gtk_container_add(GTK_CONTAINER(frame), box);
556
557 hbox = gtk_hbox_new(TRUE, 10);
558 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
559 cw.hentry = gtk_entry_new();
560 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
561 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
562
563 frame = gtk_frame_new("Port");
564 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
565 box = gtk_vbox_new(FALSE, 10);
566 gtk_container_add(GTK_CONTAINER(frame), box);
567
568 hbox = gtk_hbox_new(TRUE, 4);
569 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
570 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
571
572 frame = gtk_frame_new("Type");
573 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
574 box = gtk_vbox_new(FALSE, 10);
575 gtk_container_add(GTK_CONTAINER(frame), box);
576
577 hbox = gtk_hbox_new(TRUE, 4);
578 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
579
f762cef9
JA
580 cw.combo = gtk_combo_box_text_new();
581 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
582 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
583 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
b0500ccb
JA
584 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
585
586 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
587
588 frame = gtk_frame_new("Options");
589 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
590 box = gtk_vbox_new(FALSE, 10);
591 gtk_container_add(GTK_CONTAINER(frame), box);
592
593 hbox = gtk_hbox_new(TRUE, 4);
594 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
595
596 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
597 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
598 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.");
599 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2e33101f 600
b0500ccb
JA
601 /*
602 * Connect edit signal, so we can show/not-show the auto start button
603 */
cd972fdd
JA
604 g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
605 g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2e33101f 606
b0500ccb 607 gtk_widget_show_all(dialog);
2e33101f 608
b0500ccb 609 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1252d8f2 610 gtk_widget_destroy(dialog);
b0500ccb 611 return 1;
1252d8f2 612 }
2e33101f 613
b0500ccb
JA
614 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
615 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2e33101f 616
f762cef9 617 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
b0500ccb
JA
618 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
619 ge->type = Fio_client_ipv4;
620 else if (!strncmp(typeentry, "IPv6", 4))
621 ge->type = Fio_client_ipv6;
622 else
623 ge->type = Fio_client_socket;
624 g_free(typeentry);
2e33101f 625
b0500ccb 626 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2e33101f 627
b0500ccb
JA
628 gtk_widget_destroy(dialog);
629 return 0;
630}
1252d8f2 631
b0500ccb
JA
632static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
633{
634 gc->client = fio_get_client(client);
635 client->client_data = gc;
636}
637
638static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
639{
cf3d8241 640 struct gfio_client_options *gco;
b0500ccb
JA
641 struct gfio_client *gc;
642
cf3d8241
JA
643 gc = calloc(1, sizeof(*gc));
644 INIT_FLIST_HEAD(&gc->o_list);
b0500ccb
JA
645 gc->ge = ge;
646 ge->client = gc;
647 gfio_set_client(gc, client);
cf3d8241
JA
648
649 /*
650 * Just add a default set of options, need to consider how best
651 * to handle this
652 */
653 gco = calloc(1, sizeof(*gco));
753e9e64 654 INIT_FLIST_HEAD(&gco->list);
cf3d8241
JA
655 options_default_fill(&gco->o);
656 flist_add_tail(&gco->list, &gc->o_list);
753e9e64 657 gc->o_list_nr++;
b0500ccb
JA
658}
659
5721d270
JA
660static void gfio_clear_graph_data(struct gfio_graphs *g)
661{
662 graph_clear_values(g->iops_graph);
663 graph_clear_values(g->bandwidth_graph);
664}
665
b0500ccb
JA
666static void connect_clicked(GtkWidget *widget, gpointer data)
667{
668 struct gui_entry *ge = data;
669 struct gfio_client *gc = ge->client;
670
671 if (ge->state == GE_STATE_NEW) {
672 int ret;
19998dbc 673
b0500ccb
JA
674 if (!ge->job_file)
675 file_open(widget, ge->ui);
676 if (!ge->job_file)
677 return;
2e33101f 678
b0500ccb 679 gc = ge->client;
2e33101f 680
b0500ccb
JA
681 if (!gc->client) {
682 struct fio_client *client;
19998dbc 683
b0500ccb
JA
684 if (get_connection_details(ge)) {
685 gfio_report_error(ge, "Failed to get connection details\n");
686 return;
1252d8f2 687 }
2e33101f 688
b0500ccb
JA
689 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
690 if (!client) {
691 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
692 free(ge->host);
693 ge->host = NULL;
694 return;
1252d8f2 695 }
b0500ccb
JA
696 gfio_set_client(gc, client);
697 }
698
699 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
700 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
701 ret = fio_client_connect(gc->client);
702 if (!ret) {
703 if (!ge->ui->handler_running)
704 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
705 gfio_set_state(ge, GE_STATE_CONNECTED);
5721d270 706 gfio_clear_graph_data(&ge->graphs);
1252d8f2 707 } else {
b0500ccb 708 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
2e33101f 709 }
b0500ccb
JA
710 } else {
711 fio_client_terminate(gc->client);
712 gfio_set_state(ge, GE_STATE_NEW);
713 clear_ge_ui_info(ge);
2e33101f 714 }
b0500ccb 715}
2e33101f 716
b0500ccb
JA
717static void send_clicked(GtkWidget *widget, gpointer data)
718{
719 struct gui_entry *ge = data;
2e33101f 720
b0500ccb
JA
721 if (send_job_file(ge))
722 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
723}
62bc937f 724
b0500ccb 725static GtkWidget *new_client_page(struct gui_entry *ge);
62bc937f 726
b0500ccb
JA
727static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
728{
729 struct gui_entry *ge;
a7a42ce1 730
b0500ccb
JA
731 ge = malloc(sizeof(*ge));
732 memset(ge, 0, sizeof(*ge));
733 ge->state = GE_STATE_NEW;
b0500ccb
JA
734 ge->ui = ui;
735 return ge;
736}
a7a42ce1 737
b0500ccb
JA
738static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
739{
740 struct gui_entry *ge;
a7a42ce1 741
b0500ccb 742 ge = alloc_new_gui_entry(ui);
a7a42ce1 743
b0500ccb
JA
744 ge->vbox = new_client_page(ge);
745 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
a7a42ce1 746
b0500ccb
JA
747 ge->page_label = gtk_label_new(name);
748 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
b9f3c7ed 749
b98ab718
JA
750 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
751
b0500ccb
JA
752 gtk_widget_show_all(ui->window);
753 return ge;
754}
b9f3c7ed 755
b0500ccb
JA
756static void file_new(GtkWidget *w, gpointer data)
757{
758 struct gui *ui = (struct gui *) data;
759 struct gui_entry *ge;
62bc937f 760
b0500ccb
JA
761 ge = get_new_ge_with_tab(ui, "Untitled");
762 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
763}
a7a42ce1 764
b0500ccb
JA
765/*
766 * Return the 'ge' corresponding to the tab. If the active tab is the
767 * main tab, open a new tab.
768 */
769static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
770 int *created)
771{
b0500ccb 772 if (!cur_page) {
1252d8f2 773 if (created)
b0500ccb
JA
774 *created = 1;
775 return get_new_ge_with_tab(ui, "Untitled");
776 }
b9f3c7ed 777
b0500ccb
JA
778 if (created)
779 *created = 0;
a7a42ce1 780
b98ab718 781 return g_hash_table_lookup(ui->ge_hash, &cur_page);
b0500ccb 782}
e0681f3e 783
b0500ccb
JA
784static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
785{
786 gint cur_page;
e0681f3e 787
b0500ccb
JA
788 /*
789 * Main tab is tab 0, so any current page other than 0 holds
790 * a ge entry.
791 */
792 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
793 if (cur_page)
794 return get_ge_from_page(ui, cur_page, NULL);
0cf3ece0 795
b0500ccb
JA
796 return NULL;
797}
0cf3ece0 798
b0500ccb
JA
799static void file_close(GtkWidget *w, gpointer data)
800{
801 struct gui *ui = (struct gui *) data;
802 struct gui_entry *ge;
2f99deb0 803
b0500ccb
JA
804 /*
805 * Can't close the main tab
806 */
807 ge = get_ge_from_cur_tab(ui);
808 if (ge) {
809 gtk_widget_destroy(ge->vbox);
810 return;
1252d8f2 811 }
16ce5adf 812
b98ab718 813 if (g_hash_table_size(ui->ge_hash)) {
b0500ccb
JA
814 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
815 return;
38634cb1
JA
816 }
817
b0500ccb
JA
818 gfio_quit(ui);
819}
2f99deb0 820
b0500ccb
JA
821static void file_add_recent(struct gui *ui, const gchar *uri)
822{
823 GtkRecentData grd;
2f99deb0 824
b0500ccb
JA
825 memset(&grd, 0, sizeof(grd));
826 grd.display_name = strdup("gfio");
827 grd.description = strdup("Fio job file");
828 grd.mime_type = strdup(GFIO_MIME);
829 grd.app_name = strdup(g_get_application_name());
830 grd.app_exec = strdup("gfio %f/%u");
85dd01e7 831
b0500ccb
JA
832 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
833}
85dd01e7 834
b0500ccb
JA
835static gchar *get_filename_from_uri(const gchar *uri)
836{
837 if (strncmp(uri, "file://", 7))
838 return strdup(uri);
16ce5adf 839
b0500ccb
JA
840 return strdup(uri + 7);
841}
16ce5adf 842
b0500ccb
JA
843static int do_file_open(struct gui_entry *ge, const gchar *uri)
844{
845 struct fio_client *client;
f5c6726e 846
b0500ccb 847 assert(!ge->job_file);
38634cb1 848
b0500ccb 849 ge->job_file = get_filename_from_uri(uri);
38634cb1 850
b0500ccb
JA
851 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
852 if (client) {
832fde79
JA
853 char *label = strdup(uri);
854
855 basename(label);
856 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
857 free(label);
858
b0500ccb
JA
859 gfio_client_added(ge, client);
860 file_add_recent(ge->ui, uri);
861 return 0;
862 }
38634cb1 863
b0500ccb
JA
864 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
865 free(ge->host);
866 ge->host = NULL;
b98ab718
JA
867 free(ge->job_file);
868 ge->job_file = NULL;
b0500ccb
JA
869 return 1;
870}
38634cb1 871
b0500ccb
JA
872static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
873{
874 struct gui_entry *ge;
875 gint cur_page;
876 int ret, ge_is_new = 0;
2f99deb0 877
b0500ccb
JA
878 /*
879 * Creates new tab if current tab is the main window, or the
880 * current tab already has a client.
881 */
882 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
883 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
884 if (ge->client) {
885 ge = get_new_ge_with_tab(ui, "Untitled");
886 ge_is_new = 1;
38634cb1 887 }
2f99deb0 888
b0500ccb
JA
889 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
890
891 if (get_connection_details(ge)) {
892 if (ge_is_new)
893 gtk_widget_destroy(ge->vbox);
3c3ed070 894
b0500ccb
JA
895 return 1;
896 }
a6790906 897
b0500ccb 898 ret = do_file_open(ge, uri);
a6790906 899
b0500ccb
JA
900 if (!ret) {
901 if (ge->server_start)
902 gfio_start_server(ui);
903 } else {
904 if (ge_is_new)
905 gtk_widget_destroy(ge->vbox);
a6790906
JA
906 }
907
b0500ccb
JA
908 return ret;
909}
a6790906 910
b0500ccb
JA
911static void recent_open(GtkAction *action, gpointer data)
912{
913 struct gui *ui = (struct gui *) data;
914 GtkRecentInfo *info;
915 const gchar *uri;
0420ba6a 916
b0500ccb
JA
917 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
918 uri = gtk_recent_info_get_uri(info);
0420ba6a 919
b0500ccb
JA
920 do_file_open_with_tab(ui, uri);
921}
a7a42ce1 922
b0500ccb
JA
923static void file_open(GtkWidget *w, gpointer data)
924{
925 struct gui *ui = data;
926 GtkWidget *dialog;
927 GtkFileFilter *filter;
928 gchar *filename;
929
930 dialog = gtk_file_chooser_dialog_new("Open File",
931 GTK_WINDOW(ui->window),
932 GTK_FILE_CHOOSER_ACTION_OPEN,
933 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
934 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
935 NULL);
936 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
937
938 filter = gtk_file_filter_new();
939 gtk_file_filter_add_pattern(filter, "*.fio");
940 gtk_file_filter_add_pattern(filter, "*.job");
941 gtk_file_filter_add_pattern(filter, "*.ini");
942 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
943 gtk_file_filter_set_name(filter, "Fio job file");
944 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
945
946 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
947 gtk_widget_destroy(dialog);
948 return;
1252d8f2 949 }
0420ba6a 950
b0500ccb 951 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
0420ba6a 952
b0500ccb 953 gtk_widget_destroy(dialog);
0420ba6a 954
b0500ccb
JA
955 do_file_open_with_tab(ui, filename);
956 g_free(filename);
957}
0420ba6a 958
b0500ccb
JA
959static void file_save(GtkWidget *w, gpointer data)
960{
961 struct gui *ui = data;
962 GtkWidget *dialog;
0420ba6a 963
b0500ccb
JA
964 dialog = gtk_file_chooser_dialog_new("Save File",
965 GTK_WINDOW(ui->window),
966 GTK_FILE_CHOOSER_ACTION_SAVE,
967 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
968 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
969 NULL);
970
971 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
972 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
973
974 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
975 char *filename;
976
977 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
978 // save_job_file(filename);
979 g_free(filename);
0420ba6a 980 }
b0500ccb
JA
981 gtk_widget_destroy(dialog);
982}
0420ba6a 983
9b260bdf
JA
984static void view_log_destroy(GtkWidget *w, gpointer data)
985{
986 struct gui *ui = (struct gui *) data;
987
f762cef9 988 g_object_ref(G_OBJECT(ui->log_tree));
9b260bdf
JA
989 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
990 gtk_widget_destroy(w);
4cbe7211 991 ui->log_view = NULL;
9b260bdf
JA
992}
993
1252d8f2 994void gfio_view_log(struct gui *ui)
9b260bdf 995{
4cbe7211 996 GtkWidget *win, *scroll, *vbox, *box;
9b260bdf 997
4cbe7211
JA
998 if (ui->log_view)
999 return;
1000
1001 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
9b260bdf 1002 gtk_window_set_title(GTK_WINDOW(win), "Log");
4cbe7211 1003 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
9b260bdf 1004
4cbe7211
JA
1005 scroll = gtk_scrolled_window_new(NULL, NULL);
1006
1007 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
1008
1009 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1010
1011 box = gtk_hbox_new(TRUE, 0);
f762cef9 1012 gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
4cbe7211
JA
1013 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
1014 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
1015
1016 vbox = gtk_vbox_new(TRUE, 5);
f762cef9 1017 gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
9b260bdf 1018
4cbe7211 1019 gtk_container_add(GTK_CONTAINER(win), vbox);
9b260bdf
JA
1020 gtk_widget_show_all(win);
1021}
1022
1252d8f2
JA
1023static void view_log(GtkWidget *w, gpointer data)
1024{
1025 struct gui *ui = (struct gui *) data;
1026
1027 gfio_view_log(ui);
1028}
1029
85dd01e7
JA
1030static void connect_job_entry(GtkWidget *w, gpointer data)
1031{
1032 struct gui *ui = (struct gui *) data;
1033 struct gui_entry *ge;
3c3ed070 1034
85dd01e7
JA
1035 ge = get_ge_from_cur_tab(ui);
1036 if (ge)
1037 connect_clicked(w, ge);
1038}
1039
1040static void send_job_entry(GtkWidget *w, gpointer data)
1041{
1042 struct gui *ui = (struct gui *) data;
1043 struct gui_entry *ge;
1044
1045 ge = get_ge_from_cur_tab(ui);
1046 if (ge)
1047 send_clicked(w, ge);
85dd01e7
JA
1048}
1049
1050static void edit_job_entry(GtkWidget *w, gpointer data)
16ce5adf 1051{
9af4a244 1052 struct gui *ui = (struct gui *) data;
789f4ccd 1053 struct gui_entry *ge;
9af4a244 1054
789f4ccd
JA
1055 ge = get_ge_from_cur_tab(ui);
1056 if (ge && ge->client)
cf3d8241 1057 gopt_get_options_window(ui->window, ge->client);
16ce5adf
JA
1058}
1059
85dd01e7
JA
1060static void start_job_entry(GtkWidget *w, gpointer data)
1061{
1062 struct gui *ui = (struct gui *) data;
1063 struct gui_entry *ge;
1064
1065 ge = get_ge_from_cur_tab(ui);
1066 if (ge)
1067 start_job_clicked(w, ge);
1068}
1069
781ccba6
JA
1070static void view_results(GtkWidget *w, gpointer data)
1071{
1072 struct gui *ui = (struct gui *) data;
1073 struct gfio_client *gc;
1074 struct gui_entry *ge;
1075
1076 ge = get_ge_from_cur_tab(ui);
1077 if (!ge)
1078 return;
1079
1080 if (ge->results_window)
1081 return;
1082
1083 gc = ge->client;
1084 if (gc && gc->nr_results)
1085 gfio_display_end_results(gc);
1086}
1087
a1e7972d 1088static void __update_graph_settings(struct gfio_graphs *g)
8577f4fd
JA
1089{
1090 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
a1e7972d 1091 graph_set_font(g->iops_graph, gfio_graph_font);
8577f4fd 1092 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
a1e7972d 1093 graph_set_font(g->bandwidth_graph, gfio_graph_font);
8577f4fd
JA
1094}
1095
a1e7972d 1096static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
b98ab718
JA
1097{
1098 struct gui_entry *ge = (struct gui_entry *) value;
a1e7972d 1099 GdkEvent *ev;
b98ab718 1100
a1e7972d
JA
1101 __update_graph_settings(&ge->graphs);
1102
1103 ev = gdk_event_new(GDK_EXPOSE);
b6ab6a31 1104 g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
a1e7972d 1105 gdk_event_free(ev);
b98ab718
JA
1106}
1107
8577f4fd
JA
1108static void update_graph_limits(void)
1109{
b98ab718 1110 struct gui *ui = &main_ui;
a1e7972d
JA
1111 GdkEvent *ev;
1112
1113 __update_graph_settings(&ui->graphs);
8577f4fd 1114
a1e7972d 1115 ev = gdk_event_new(GDK_EXPOSE);
b6ab6a31 1116 g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
a1e7972d 1117 gdk_event_free(ev);
8577f4fd 1118
a1e7972d 1119 g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
8577f4fd
JA
1120}
1121
46974a7d
JA
1122static void preferences(GtkWidget *w, gpointer data)
1123{
f3e8440f 1124 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1cf6bca5 1125 GtkWidget *hbox, *spin, *entry, *spin_int;
6e02ad6c 1126 struct gui *ui = (struct gui *) data;
46974a7d
JA
1127 int i;
1128
1129 dialog = gtk_dialog_new_with_buttons("Preferences",
6e02ad6c 1130 GTK_WINDOW(ui->window),
46974a7d
JA
1131 GTK_DIALOG_DESTROY_WITH_PARENT,
1132 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1133 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1134 NULL);
1135
8577f4fd 1136 frame = gtk_frame_new("Graphing");
f762cef9
JA
1137 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1138 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
f3e8440f
JA
1139 vbox = gtk_vbox_new(FALSE, 6);
1140 gtk_container_add(GTK_CONTAINER(frame), vbox);
1141
1cf6bca5
JA
1142 hbox = gtk_hbox_new(FALSE, 5);
1143 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1144 entry = gtk_label_new("Font face to use for graph labels");
1145 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1146
a1e7972d 1147 font = gtk_font_button_new_with_font(gfio_graph_font);
1cf6bca5 1148 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
f3e8440f 1149
8577f4fd
JA
1150 box = gtk_vbox_new(FALSE, 6);
1151 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1152
1153 hbox = gtk_hbox_new(FALSE, 5);
1cf6bca5 1154 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
8577f4fd
JA
1155 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1156 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1157
c05d9056 1158 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
8577f4fd 1159
1cf6bca5
JA
1160 box = gtk_vbox_new(FALSE, 6);
1161 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1162
1163 hbox = gtk_hbox_new(FALSE, 5);
1164 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1165 entry = gtk_label_new("Client ETA request interval (msec)");
1166 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1167
1168 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
a31d9fa4 1169 frame = gtk_frame_new("Debug logging");
f762cef9
JA
1170 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1171 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
a31d9fa4
JA
1172 vbox = gtk_vbox_new(FALSE, 6);
1173 gtk_container_add(GTK_CONTAINER(frame), vbox);
1174
1175 box = gtk_hbox_new(FALSE, 6);
1176 gtk_container_add(GTK_CONTAINER(vbox), box);
1177
1178 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1179
1180 for (i = 0; i < FD_DEBUG_MAX; i++) {
1181 if (i == 7) {
1182 box = gtk_hbox_new(FALSE, 6);
1183 gtk_container_add(GTK_CONTAINER(vbox), box);
1184 }
1185
1186
1187 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1188 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1189 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1190 }
1191
46974a7d
JA
1192 gtk_widget_show_all(dialog);
1193
1194 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1195 gtk_widget_destroy(dialog);
1196 return;
1197 }
1198
1199 for (i = 0; i < FD_DEBUG_MAX; i++) {
1200 int set;
1201
1202 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1203 if (set)
1204 fio_debug |= (1UL << i);
1205 }
1206
f3e8440f 1207 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
8577f4fd
JA
1208 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1209 update_graph_limits();
1cf6bca5 1210 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
8577f4fd 1211
46974a7d
JA
1212 gtk_widget_destroy(dialog);
1213}
1214
0420ba6a
JA
1215static void about_dialog(GtkWidget *w, gpointer data)
1216{
81e4ea6e
JA
1217 const char *authors[] = {
1218 "Jens Axboe <axboe@kernel.dk>",
5921cea2 1219 "Stephen Cameron <stephenmcameron@gmail.com>",
81e4ea6e
JA
1220 NULL
1221 };
84a72ed3
JA
1222 const char *license[] = {
1223 "Fio is free software; you can redistribute it and/or modify "
1224 "it under the terms of the GNU General Public License as published by "
1225 "the Free Software Foundation; either version 2 of the License, or "
1226 "(at your option) any later version.\n",
1227 "Fio is distributed in the hope that it will be useful, "
1228 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1229 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1230 "GNU General Public License for more details.\n",
1231 "You should have received a copy of the GNU General Public License "
1232 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1233 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1234 };
1235 char *license_trans;
1236
1237 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1238 license[2], "\n", NULL);
81e4ea6e 1239
0420ba6a
JA
1240 gtk_show_about_dialog(NULL,
1241 "program-name", "gfio",
1242 "comments", "Gtk2 UI for fio",
84a72ed3 1243 "license", license_trans,
ec9a7e69 1244 "website", "http://git.kernel.dk/cgit/fio/",
81e4ea6e 1245 "authors", authors,
0420ba6a 1246 "version", fio_version_string,
403bb450 1247 "copyright", "© 2012-2017 Jens Axboe <axboe@kernel.dk>",
0420ba6a
JA
1248 "logo-icon-name", "fio",
1249 /* Must be last: */
81e4ea6e 1250 "wrap-license", TRUE,
0420ba6a 1251 NULL);
84a72ed3 1252
2f99deb0 1253 g_free(license_trans);
0420ba6a
JA
1254}
1255
1256static GtkActionEntry menu_items[] = {
46974a7d 1257 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
9b260bdf 1258 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
16ce5adf 1259 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
46974a7d 1260 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2f99deb0 1261 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
16ce5adf 1262 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
46974a7d
JA
1263 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1264 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1265 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
9b260bdf 1266 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
781ccba6 1267 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
bc271d87 1268 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
85dd01e7
JA
1269 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1270 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1271 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
46974a7d
JA
1272 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1273 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
0420ba6a 1274};
b920b29b 1275static gint nmenu_items = ARRAY_SIZE(menu_items);
0420ba6a
JA
1276
1277static const gchar *ui_string = " \
1278 <ui> \
1279 <menubar name=\"MainMenu\"> \
1280 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2f99deb0 1281 <menuitem name=\"New\" action=\"NewFile\" /> \
bf64138b 1282 <menuitem name=\"Open\" action=\"OpenFile\" /> \
16ce5adf 1283 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2f99deb0 1284 <separator name=\"Separator1\"/> \
0420ba6a 1285 <menuitem name=\"Save\" action=\"SaveFile\" /> \
46974a7d 1286 <separator name=\"Separator2\"/> \
2f99deb0
JA
1287 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1288 <separator name=\"Separator3\"/> \
261f21d0
JA
1289 <placeholder name=\"FileRecentFiles\"/> \
1290 <separator name=\"Separator4\"/> \
0420ba6a
JA
1291 <menuitem name=\"Quit\" action=\"Quit\" /> \
1292 </menu> \
16ce5adf 1293 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
85dd01e7 1294 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
261f21d0 1295 <separator name=\"Separator5\"/> \
85dd01e7
JA
1296 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1297 <menuitem name=\"Send job\" action=\"SendJob\" /> \
261f21d0 1298 <separator name=\"Separator6\"/> \
85dd01e7 1299 <menuitem name=\"Start job\" action=\"StartJob\" /> \
16ce5adf 1300 </menu>\
9b260bdf 1301 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
781ccba6
JA
1302 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1303 <separator name=\"Separator7\"/> \
9b260bdf
JA
1304 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1305 </menu>\
0420ba6a
JA
1306 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1307 <menuitem name=\"About\" action=\"About\" /> \
1308 </menu> \
1309 </menubar> \
1310 </ui> \
1311";
1312
4cbe7211
JA
1313static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1314 struct gui *ui)
0420ba6a 1315{
ca664f49 1316 GtkActionGroup *action_group;
0420ba6a
JA
1317 GError *error = 0;
1318
1319 action_group = gtk_action_group_new("Menu");
4cbe7211 1320 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
0420ba6a
JA
1321
1322 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1323 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1324
1325 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
02421e69 1326
0420ba6a
JA
1327 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1328}
1329
1330void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1252d8f2 1331 GtkWidget *vbox, GtkUIManager *ui_manager)
0420ba6a 1332{
1252d8f2 1333 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
0420ba6a
JA
1334}
1335
c80b74b0
JA
1336static void combo_entry_changed(GtkComboBox *box, gpointer data)
1337{
1338 struct gui_entry *ge = (struct gui_entry *) data;
1339 gint index;
1340
1341 index = gtk_combo_box_get_active(box);
1342
1343 multitext_set_entry(&ge->eta.iotype, index);
99d633af 1344 multitext_set_entry(&ge->eta.bs, index);
c80b74b0
JA
1345 multitext_set_entry(&ge->eta.ioengine, index);
1346 multitext_set_entry(&ge->eta.iodepth, index);
1347}
1348
1349static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1350{
1351 struct gui_entry *ge = (struct gui_entry *) data;
1352
1353 multitext_free(&ge->eta.iotype);
99d633af 1354 multitext_free(&ge->eta.bs);
c80b74b0
JA
1355 multitext_free(&ge->eta.ioengine);
1356 multitext_free(&ge->eta.iodepth);
1357}
1358
2f99deb0 1359static GtkWidget *new_client_page(struct gui_entry *ge)
ff1f3280 1360{
2f99deb0 1361 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
65476336 1362 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
0420ba6a 1363
2f99deb0 1364 main_vbox = gtk_vbox_new(FALSE, 3);
45032dd8 1365
65476336
JA
1366 top_align = gtk_alignment_new(0, 0, 1, 0);
1367 top_vbox = gtk_vbox_new(FALSE, 3);
1368 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1369 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2f99deb0
JA
1370
1371 probe = gtk_frame_new("Job");
1372 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1373 probe_frame = gtk_vbox_new(FALSE, 3);
1374 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1375
1376 probe_box = gtk_hbox_new(FALSE, 3);
1377 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1378 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1379 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1380 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1381 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1382
1383 probe_box = gtk_hbox_new(FALSE, 3);
1384 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1385
3863d1ad 1386 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
c80b74b0
JA
1387 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1388 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1389 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
5921cea2 1390 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write/Trim)");
c80b74b0
JA
1391 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1392 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2f99deb0
JA
1393 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1394 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1395
1396 probe_box = gtk_hbox_new(FALSE, 3);
1397 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
b78ca651 1398 ge->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
5921cea2 1399 ge->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "Read IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
b78ca651 1400 ge->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
5921cea2 1401 ge->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "Write IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
b78ca651 1402 ge->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
5921cea2 1403 ge->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "Trim IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
dbc542d4 1404
2f99deb0
JA
1405 /*
1406 * Only add this if we have a commit rate
2839f0c6 1407 */
2f99deb0
JA
1408#if 0
1409 probe_box = gtk_hbox_new(FALSE, 3);
1410 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2839f0c6 1411
2f99deb0
JA
1412 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1413 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
ff1f3280 1414
2f99deb0
JA
1415 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1416 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1417#endif
ff1f3280 1418
2f99deb0
JA
1419 /*
1420 * Set up a drawing area and IOPS and bandwidth graphs
1421 */
2f99deb0 1422 ge->graphs.drawing_area = gtk_drawing_area_new();
2f99deb0 1423 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
57f9d28e 1424 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2a95f716 1425 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
b6ab6a31 1426 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT,
2f99deb0
JA
1427 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1428 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1429 G_CALLBACK(on_config_drawing_area), &ge->graphs);
65476336
JA
1430 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1431 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1432 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
65476336 1433 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1434 ge->graphs.drawing_area);
65476336 1435 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
04cc6b77 1436
2f99deb0
JA
1437 setup_graphs(&ge->graphs);
1438
1439 /*
3c3ed070 1440 * Set up alignments for widgets at the bottom of ui,
2f99deb0
JA
1441 * align bottom left, expand horizontally but not vertically
1442 */
65476336 1443 bottom_align = gtk_alignment_new(0, 1, 1, 0);
2f99deb0 1444 ge->buttonbox = gtk_hbox_new(FALSE, 0);
65476336
JA
1445 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1446 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2f99deb0 1447
05775438 1448 add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
0420ba6a 1449
c36f98d9 1450 /*
2f99deb0
JA
1451 * Set up thread status progress bar
1452 */
1453 ge->thread_status_pb = gtk_progress_bar_new();
1454 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1455 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1456 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1457
1458
1459 return main_vbox;
1460}
1461
1462static GtkWidget *new_main_page(struct gui *ui)
1463{
1464 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
65476336 1465 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2f99deb0
JA
1466
1467 main_vbox = gtk_vbox_new(FALSE, 3);
1468
1469 /*
1470 * Set up alignments for widgets at the top of ui,
c36f98d9
SC
1471 * align top left, expand horizontally but not vertically
1472 */
65476336
JA
1473 top_align = gtk_alignment_new(0, 0, 1, 0);
1474 top_vbox = gtk_vbox_new(FALSE, 0);
1475 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1476 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
c36f98d9 1477
2f99deb0
JA
1478 probe = gtk_frame_new("Run statistics");
1479 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
843ad237
JA
1480 probe_frame = gtk_vbox_new(FALSE, 3);
1481 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1482
1483 probe_box = gtk_hbox_new(FALSE, 3);
2f99deb0 1484 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
3863d1ad 1485 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
b78ca651
JA
1486 ui->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1487 ui->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B);
1488 ui->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1489 ui->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B);
1490 ui->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
1491 ui->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B);
3e47bd25 1492
807f9971
JA
1493 /*
1494 * Only add this if we have a commit rate
1495 */
1496#if 0
3e47bd25
JA
1497 probe_box = gtk_hbox_new(FALSE, 3);
1498 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
807f9971
JA
1499
1500 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1501 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1502
3e47bd25
JA
1503 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1504 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
807f9971 1505#endif
3e47bd25 1506
736f2dff 1507 /*
2fd3bb0e 1508 * Set up a drawing area and IOPS and bandwidth graphs
736f2dff 1509 */
2f99deb0 1510 ui->graphs.drawing_area = gtk_drawing_area_new();
2f99deb0 1511 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
57f9d28e 1512 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
2a95f716 1513 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow);
b6ab6a31 1514 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT,
2f99deb0
JA
1515 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1516 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1517 G_CALLBACK(on_config_drawing_area), &ui->graphs);
65476336
JA
1518 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1519 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
736f2dff 1520 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
65476336 1521 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1522 ui->graphs.drawing_area);
65476336 1523 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
e164534f 1524 TRUE, TRUE, 0);
736f2dff 1525
2f99deb0 1526 setup_graphs(&ui->graphs);
2fd3bb0e 1527
c36f98d9 1528 /*
3c3ed070 1529 * Set up alignments for widgets at the bottom of ui,
c36f98d9
SC
1530 * align bottom left, expand horizontally but not vertically
1531 */
65476336 1532 bottom_align = gtk_alignment_new(0, 1, 1, 0);
c36f98d9 1533 ui->buttonbox = gtk_hbox_new(FALSE, 0);
65476336
JA
1534 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1535 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
c36f98d9 1536
3ec62ec4
JA
1537 /*
1538 * Set up thread status progress bar
1539 */
1540 ui->thread_status_pb = gtk_progress_bar_new();
1541 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
8663ea65 1542 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
3ec62ec4
JA
1543 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1544
2f99deb0
JA
1545 return main_vbox;
1546}
1547
1548static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1549 guint page, gpointer data)
1550
1551{
02421e69 1552 struct gui *ui = (struct gui *) data;
85dd01e7
JA
1553 struct gui_entry *ge;
1554
1555 if (!page) {
1556 set_job_menu_visible(ui, 0);
781ccba6 1557 set_view_results_visible(ui, 0);
85dd01e7
JA
1558 return TRUE;
1559 }
1560
1561 set_job_menu_visible(ui, 1);
6e02ad6c 1562 ge = get_ge_from_page(ui, page, NULL);
85dd01e7
JA
1563 if (ge)
1564 update_button_states(ui, ge);
02421e69 1565
2f99deb0
JA
1566 return TRUE;
1567}
1568
38634cb1
JA
1569static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1570{
1571 time_t time_a = gtk_recent_info_get_visited(a);
1572 time_t time_b = gtk_recent_info_get_visited(b);
1573
1574 return time_b - time_a;
1575}
1576
1577static void add_recent_file_items(struct gui *ui)
1578{
1579 const gchar *gfio = g_get_application_name();
1580 GList *items, *item;
1581 int i = 0;
1582
1583 if (ui->recent_ui_id) {
1584 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1585 gtk_ui_manager_ensure_update(ui->uimanager);
1586 }
1587 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1588
1589 if (ui->actiongroup) {
1590 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1591 g_object_unref(ui->actiongroup);
1592 }
1593 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1594
1595 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1596
1597 items = gtk_recent_manager_get_items(ui->recentmanager);
1598 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1599
1600 for (item = items; item && item->data; item = g_list_next(item)) {
1601 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1602 gchar *action_name;
1603 const gchar *label;
1604 GtkAction *action;
1605
1606 if (!gtk_recent_info_has_application(info, gfio))
1607 continue;
1608
1609 /*
1610 * We only support local files for now
1611 */
1612 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1613 continue;
1614
1615 action_name = g_strdup_printf("RecentFile%u", i++);
1616 label = gtk_recent_info_get_display_name(info);
1617
1618 action = g_object_new(GTK_TYPE_ACTION,
1619 "name", action_name,
1620 "label", label, NULL);
1621
1622 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1623 gtk_recent_info_ref(info),
1624 (GDestroyNotify) gtk_recent_info_unref);
1625
1626
1627 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1628
1629 gtk_action_group_add_action(ui->actiongroup, action);
1630 g_object_unref(action);
1631
1632 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1633 "/MainMenu/FileMenu/FileRecentFiles",
1634 label, action_name,
1635 GTK_UI_MANAGER_MENUITEM, FALSE);
1636
1637 g_free(action_name);
1638
1639 if (i == 8)
1640 break;
1641 }
1642
1643 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1644 g_list_free(items);
1645}
1646
a6790906 1647static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
6e02ad6c
JA
1648 gint x, gint y, GtkSelectionData *seldata,
1649 guint info, guint time, gpointer *data)
a6790906 1650{
6e02ad6c 1651 struct gui *ui = (struct gui *) data;
a6790906
JA
1652 gchar **uris;
1653 GtkWidget *source;
a6790906
JA
1654
1655 source = gtk_drag_get_source_widget(ctx);
1656 if (source && widget == gtk_widget_get_toplevel(source)) {
1657 gtk_drag_finish(ctx, FALSE, FALSE, time);
1658 return;
1659 }
1660
6e02ad6c 1661 uris = gtk_selection_data_get_uris(seldata);
a6790906
JA
1662 if (!uris) {
1663 gtk_drag_finish(ctx, FALSE, FALSE, time);
1664 return;
1665 }
1666
0cf3ece0
JA
1667 if (uris[0])
1668 do_file_open_with_tab(ui, uris[0]);
a6790906
JA
1669
1670 gtk_drag_finish(ctx, TRUE, FALSE, time);
1671 g_strfreev(uris);
1672}
1673
2f99deb0
JA
1674static void init_ui(int *argc, char **argv[], struct gui *ui)
1675{
1676 GtkSettings *settings;
02421e69 1677 GtkWidget *vbox;
2f99deb0
JA
1678
1679 /* Magical g*thread incantation, you just need this thread stuff.
1680 * Without it, the update that happens in gfio_update_thread_status
1681 * doesn't really happen in a timely fashion, you need expose events
1682 */
f1c4b372 1683#if !GLIB_CHECK_VERSION(2, 31, 0)
2f99deb0
JA
1684 if (!g_thread_supported())
1685 g_thread_init(NULL);
b11bd8d8
JA
1686#endif
1687
2f99deb0
JA
1688 gdk_threads_init();
1689
1690 gtk_init(argc, argv);
1691 settings = gtk_settings_get_default();
1692 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
c05da82c 1693#if !GLIB_CHECK_VERSION(2, 36, 0)
2f99deb0 1694 g_type_init();
c05da82c 1695#endif
0950e832 1696 gdk_color_parse("#fffff4", &gfio_color_lightyellow);
1252d8f2 1697 gdk_color_parse("white", &gfio_color_white);
3c3ed070 1698
2f99deb0 1699 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
814479d5 1700 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2f99deb0
JA
1701 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1702
6e02ad6c
JA
1703 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1704 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
2f99deb0
JA
1705
1706 ui->vbox = gtk_vbox_new(FALSE, 0);
1707 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1708
02421e69
JA
1709 ui->uimanager = gtk_ui_manager_new();
1710 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1711 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
2f99deb0 1712
38634cb1
JA
1713 ui->recentmanager = gtk_recent_manager_get_default();
1714 add_recent_file_items(ui);
1715
2f99deb0
JA
1716 ui->notebook = gtk_notebook_new();
1717 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
b870c31b 1718 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
0aa928c4 1719 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
2f99deb0
JA
1720 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1721
1722 vbox = new_main_page(ui);
0cf3ece0 1723 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
a6790906
JA
1724 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1725 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
2f99deb0
JA
1726
1727 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1728
9b260bdf 1729 gfio_ui_setup_log(ui);
3ec62ec4 1730
ff1f3280
SC
1731 gtk_widget_show_all(ui->window);
1732}
1733
8232e285 1734int main(int argc, char *argv[], char *envp[])
ff1f3280 1735{
8232e285
SC
1736 if (initialize_fio(envp))
1737 return 1;
0420ba6a
JA
1738 if (fio_init_options())
1739 return 1;
a1820207 1740
753e9e64
JA
1741 gopt_init();
1742
2f99deb0 1743 memset(&main_ui, 0, sizeof(main_ui));
b98ab718 1744 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
2f99deb0
JA
1745
1746 init_ui(&argc, &argv, &main_ui);
5b7573ab 1747
2839f0c6 1748 gdk_threads_enter();
ff1f3280 1749 gtk_main();
2839f0c6 1750 gdk_threads_leave();
b98ab718
JA
1751
1752 g_hash_table_destroy(main_ui.ge_hash);
753e9e64
JA
1753
1754 gopt_exit();
ff1f3280
SC
1755 return 0;
1756}