2 * gfio - gui front end for fio - the flexible io tester
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
6 * The license below covers all files distributed with fio unless otherwise
7 * noted in the file itself.
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 static void gfio_update_thread_status(char *status_message, double perc);
33 #define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
35 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
37 static void connect_clicked(GtkWidget *widget, gpointer data);
38 static void start_job_clicked(GtkWidget *widget, gpointer data);
40 static struct button_spec {
41 const char *buttontext;
43 const char *tooltiptext;
44 const int start_insensitive;
45 } buttonspeclist[] = {
46 #define CONNECT_BUTTON 0
47 #define START_JOB_BUTTON 1
48 { "Connect", connect_clicked, "Connect to host", 0 },
51 "Send current fio job to fio server to be executed", 1 },
73 GtkWidget *write_iops;
83 GtkWidget *bottomalign;
84 GtkWidget *thread_status_pb;
86 GtkWidget *button[ARRAYSIZE(buttonspeclist)];
87 GtkWidget *scrolled_window;
89 GtkWidget *error_info_bar;
90 GtkWidget *error_label;
92 struct probe_widget probe;
93 struct eta_widget eta;
97 struct fio_client *client;
102 static void clear_ui_info(struct gui *ui)
104 gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
105 gtk_label_set_text(GTK_LABEL(ui->probe.os), "");
106 gtk_label_set_text(GTK_LABEL(ui->probe.arch), "");
107 gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), "");
108 gtk_entry_set_text(GTK_ENTRY(ui->eta.name), "");
109 gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), "");
110 gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), "");
111 gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), "");
112 gtk_entry_set_text(GTK_ENTRY(ui->eta.jobs), "");
113 gtk_entry_set_text(GTK_ENTRY(ui->eta.files), "");
114 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), "");
115 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), "");
116 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), "");
117 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), "");
120 static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
122 GtkWidget *entry, *frame;
124 frame = gtk_frame_new(label);
125 entry = gtk_entry_new();
126 gtk_entry_set_editable(GTK_ENTRY(entry), 0);
127 gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
128 gtk_container_add(GTK_CONTAINER(frame), entry);
133 static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
135 GtkWidget *label_widget;
138 frame = gtk_frame_new(label);
139 label_widget = gtk_label_new(NULL);
140 gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
141 gtk_container_add(GTK_CONTAINER(frame), label_widget);
146 static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
148 GtkWidget *button, *box;
150 box = gtk_hbox_new(FALSE, 3);
151 gtk_container_add(GTK_CONTAINER(hbox), box);
153 button = gtk_spin_button_new_with_range(min, max, 1.0);
154 gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
156 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
157 gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
162 static void gfio_set_connected(struct gui *ui, int connected)
165 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
167 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
170 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
171 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
175 static void label_set_int_value(GtkWidget *entry, unsigned int val)
179 sprintf(tmp, "%u", val);
180 gtk_label_set_text(GTK_LABEL(entry), tmp);
183 static void entry_set_int_value(GtkWidget *entry, unsigned int val)
187 sprintf(tmp, "%u", val);
188 gtk_entry_set_text(GTK_ENTRY(entry), tmp);
192 #define ALIGN_RIGHT 2
196 GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags)
198 GtkCellRenderer *renderer;
199 GtkTreeViewColumn *col;
200 double xalign = 0.0; /* left as default */
201 PangoAlignment align;
204 align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
205 (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
207 visible = !(flags & INVISIBLE);
209 renderer = gtk_cell_renderer_text_new();
210 col = gtk_tree_view_column_new();
212 gtk_tree_view_column_set_title(col, title);
213 if (!(flags & UNSORTABLE))
214 gtk_tree_view_column_set_sort_column_id(col, index);
215 gtk_tree_view_column_set_resizable(col, TRUE);
216 gtk_tree_view_column_pack_start(col, renderer, TRUE);
217 gtk_tree_view_column_add_attribute(col, renderer, "text", index);
218 gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
220 case PANGO_ALIGN_LEFT:
223 case PANGO_ALIGN_CENTER:
226 case PANGO_ALIGN_RIGHT:
230 gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
231 gtk_tree_view_column_set_visible(col, visible);
232 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
236 static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
242 GType types[FIO_IO_U_LIST_MAX_LEN];
243 GtkWidget *tree_view;
244 GtkTreeSelection *selection;
249 for (i = 0; i < len; i++)
250 types[i] = G_TYPE_INT;
252 model = gtk_list_store_newv(len, types);
254 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
255 gtk_widget_set_can_focus(tree_view, FALSE);
257 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
258 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
260 for (i = 0; i < len; i++) {
263 sprintf(fbuf, "%2.2f%%", plist[i].u.f);
264 tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
267 gtk_list_store_append(model, &iter);
269 for (i = 0; i < len; i++)
270 gtk_list_store_set(model, &iter, i, ovals[i], -1);
275 static void gfio_show_clat_percentiles(GtkWidget *vbox, struct thread_stat *ts,
278 unsigned int *io_u_plat = ts->io_u_plat[ddir];
279 unsigned long nr = ts->clat_stat[ddir].samples;
280 fio_fp64_t *plist = ts->percentile_list;
281 unsigned int *ovals, len, minv, maxv, scale_down;
283 GtkWidget *tree_view, *frame, *hbox;
286 len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
291 * We default to usecs, but if the value range is such that we
292 * should scale down to msecs, do that.
294 if (minv > 2000 && maxv > 99999) {
302 tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
304 sprintf(tmp, "Completion percentiles (%s)", base);
305 frame = gtk_frame_new(tmp);
306 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
308 hbox = gtk_hbox_new(FALSE, 3);
309 gtk_container_add(GTK_CONTAINER(frame), hbox);
311 gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
317 static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
318 unsigned long max, double mean, double dev)
320 const char *base = "(usec)";
321 GtkWidget *hbox, *label, *frame;
325 if (!usec_to_msec(&min, &max, &mean, &dev))
328 minp = num2str(min, 6, 1, 0);
329 maxp = num2str(max, 6, 1, 0);
331 sprintf(tmp, "%s %s", name, base);
332 frame = gtk_frame_new(tmp);
333 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
335 hbox = gtk_hbox_new(FALSE, 3);
336 gtk_container_add(GTK_CONTAINER(frame), hbox);
338 label = new_info_label_in_frame(hbox, "Minimum");
339 gtk_label_set_text(GTK_LABEL(label), minp);
340 label = new_info_label_in_frame(hbox, "Maximum");
341 gtk_label_set_text(GTK_LABEL(label), maxp);
342 label = new_info_label_in_frame(hbox, "Average");
343 sprintf(tmp, "%5.02f", mean);
344 gtk_label_set_text(GTK_LABEL(label), tmp);
345 label = new_info_label_in_frame(hbox, "Standard deviation");
346 sprintf(tmp, "%5.02f", dev);
347 gtk_label_set_text(GTK_LABEL(label), tmp);
358 static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
359 struct thread_stat *ts, int ddir)
361 const char *ddir_label[2] = { "Read", "Write" };
362 GtkWidget *frame, *label, *box, *vbox;
363 unsigned long min, max, runt;
364 unsigned long long bw, iops;
365 unsigned int flags = 0;
367 char *io_p, *bw_p, *iops_p;
370 if (!ts->runtime[ddir])
373 i2p = is_power_of_2(rs->kb_base);
374 runt = ts->runtime[ddir];
376 bw = (1000 * ts->io_bytes[ddir]) / runt;
377 io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
378 bw_p = num2str(bw, 6, 1, i2p);
380 iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
381 iops_p = num2str(iops, 6, 1, 0);
383 box = gtk_hbox_new(FALSE, 3);
384 gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
386 frame = gtk_frame_new(ddir_label[ddir]);
387 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
389 vbox = gtk_vbox_new(FALSE, 3);
390 gtk_container_add(GTK_CONTAINER(frame), vbox);
392 box = gtk_hbox_new(FALSE, 3);
393 gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 3);
395 label = new_info_label_in_frame(box, "IO");
396 gtk_label_set_text(GTK_LABEL(label), io_p);
397 label = new_info_label_in_frame(box, "Bandwidth");
398 gtk_label_set_text(GTK_LABEL(label), bw_p);
399 label = new_info_label_in_frame(box, "IOPS");
400 gtk_label_set_text(GTK_LABEL(label), iops_p);
401 label = new_info_label_in_frame(box, "Runtime (msec)");
402 label_set_int_value(label, ts->runtime[ddir]);
404 if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
406 if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
408 if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
412 frame = gtk_frame_new("Latency");
413 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
415 vbox = gtk_vbox_new(FALSE, 3);
416 gtk_container_add(GTK_CONTAINER(frame), vbox);
418 if (flags & GFIO_SLAT)
419 gfio_show_lat(vbox, "Submission latency", min, max, mean, dev);
420 if (flags & GFIO_CLAT)
421 gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
422 if (flags & GFIO_LAT)
423 gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
426 if (ts->clat_percentiles)
427 gfio_show_clat_percentiles(vbox, ts, ddir);
429 if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
430 double p_of_agg = 100.0;
431 const char *bw_str = "KB";
435 p_of_agg = mean * 100 / (double) rs->agg[ddir];
436 if (p_of_agg > 100.0)
440 if (mean > 999999.9) {
448 frame = gtk_frame_new("Bandwidth");
449 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
451 vbox = gtk_vbox_new(FALSE, 3);
452 gtk_container_add(GTK_CONTAINER(frame), vbox);
454 label = new_info_label_in_frame(vbox, "Bandwidth");
455 gtk_label_set_text(GTK_LABEL(label), bw_str);
456 label = new_info_label_in_frame(vbox, "Minimum");
457 label_set_int_value(label, min);
458 label = new_info_label_in_frame(vbox, "Maximum");
459 label_set_int_value(label, max);
460 label = new_info_label_in_frame(vbox, "Percentage of jobs");
461 sprintf(tmp, "%3.2f%%", p_of_agg);
462 gtk_label_set_text(GTK_LABEL(label), tmp);
463 label = new_info_label_in_frame(vbox, "Average");
464 sprintf(tmp, "%5.02f", mean);
465 gtk_label_set_text(GTK_LABEL(label), tmp);
466 label = new_info_label_in_frame(vbox, "Standard deviation");
467 sprintf(tmp, "%5.02f", dev);
468 gtk_label_set_text(GTK_LABEL(label), tmp);
476 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
477 struct group_run_stats *rs)
479 GtkWidget *dialog, *box, *vbox, *entry, *content;
480 struct gui *ui = client->client_data;
484 dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
485 GTK_DIALOG_DESTROY_WITH_PARENT,
486 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
488 g_signal_connect_swapped(dialog, "response",
489 G_CALLBACK(gtk_widget_destroy),
492 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
494 vbox = gtk_vbox_new(FALSE, 3);
495 gtk_container_add(GTK_CONTAINER(content), vbox);
497 box = gtk_hbox_new(TRUE, 3);
498 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
500 entry = new_info_entry_in_frame(box, "Name");
501 gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
502 if (strlen(ts->description)) {
503 entry = new_info_entry_in_frame(box, "Description");
504 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
506 entry = new_info_entry_in_frame(box, "Group ID");
507 entry_set_int_value(entry, ts->groupid);
508 entry = new_info_entry_in_frame(box, "Jobs");
509 entry_set_int_value(entry, ts->members);
510 entry = new_info_entry_in_frame(box, "Error");
511 entry_set_int_value(entry, ts->error);
512 entry = new_info_entry_in_frame(box, "PID");
513 entry_set_int_value(entry, ts->pid);
515 if (ts->io_bytes[DDIR_READ])
516 gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
517 if (ts->io_bytes[DDIR_WRITE])
518 gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
520 gtk_widget_show_all(dialog);
525 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
528 GtkTextBuffer *buffer;
531 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
533 gtk_text_buffer_get_end_iter(buffer, &end);
534 gtk_text_buffer_insert(buffer, &end, buf, -1);
536 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
537 &end, 0.0, FALSE, 0.0,0.0);
539 fio_client_ops.text_op(client, cmd);
543 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
545 printf("gfio_disk_util_op called\n");
546 fio_client_ops.disk_util(client, cmd);
549 extern int sum_stat_clients;
550 extern struct thread_stat client_ts;
551 extern struct group_run_stats client_gs;
553 static int sum_stat_nr;
555 static void gfio_thread_status_op(struct fio_client *client,
556 struct fio_net_cmd *cmd)
558 struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
560 gfio_display_ts(client, &p->ts, &p->rs);
562 if (sum_stat_clients == 1)
565 sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
566 sum_group_stats(&client_gs, &p->rs);
569 client_ts.groupid = p->ts.groupid;
571 if (++sum_stat_nr == sum_stat_clients) {
572 strcpy(client_ts.name, "All clients");
573 gfio_display_ts(client, &client_ts, &client_gs);
577 static void gfio_group_stats_op(struct fio_client *client,
578 struct fio_net_cmd *cmd)
580 printf("gfio_group_stats_op called\n");
581 fio_client_ops.group_stats(client, cmd);
584 static void gfio_update_eta(struct jobs_eta *je)
596 if (je->eta_sec != INT_MAX && je->elapsed_sec) {
597 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
598 eta_to_str(eta_str, je->eta_sec);
601 sprintf(tmp, "%u", je->nr_running);
602 gtk_entry_set_text(GTK_ENTRY(ui.eta.jobs), tmp);
603 sprintf(tmp, "%u", je->files_open);
604 gtk_entry_set_text(GTK_ENTRY(ui.eta.files), tmp);
607 if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
608 if (je->m_rate || je->t_rate) {
611 mr = num2str(je->m_rate, 4, 0, i2p);
612 tr = num2str(je->t_rate, 4, 0, i2p);
613 gtk_entry_set_text(GTK_ENTRY(ui.eta);
614 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
617 } else if (je->m_iops || je->t_iops)
618 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
620 gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_bw), "---");
621 gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_iops), "---");
622 gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_bw), "---");
623 gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_iops), "---");
626 if (je->eta_sec != INT_MAX && je->nr_running) {
630 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
631 strcpy(output, "-.-% done");
635 sprintf(output, "%3.1f%% done", perc);
638 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
639 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
641 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
642 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
644 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_bw), rate_str[0]);
645 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_iops), iops_str[0]);
646 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_bw), rate_str[1]);
647 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_iops), iops_str[1]);
656 char *dst = output + strlen(output);
658 sprintf(dst, " - %s", eta_str);
661 gfio_update_thread_status(output, perc);
664 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
666 struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
667 const char *os, *arch;
670 os = fio_get_os_string(probe->os);
674 arch = fio_get_arch_string(probe->arch);
679 client->name = strdup((char *) probe->hostname);
681 gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
682 gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
683 gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
684 sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
685 gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
688 static void gfio_update_thread_status(char *status_message, double perc)
690 static char message[100];
691 const char *m = message;
693 strncpy(message, status_message, sizeof(message) - 1);
694 gtk_progress_bar_set_text(
695 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
696 gtk_progress_bar_set_fraction(
697 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
699 gtk_widget_queue_draw(ui.window);
703 static void gfio_quit_op(struct fio_client *client)
705 struct gui *ui = client->client_data;
707 gfio_set_connected(ui, 0);
710 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
712 struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
713 struct gui *ui = client->client_data;
717 p->iodepth = le32_to_cpu(p->iodepth);
718 p->rw = le32_to_cpu(p->rw);
720 for (i = 0; i < 2; i++) {
721 p->min_bs[i] = le32_to_cpu(p->min_bs[i]);
722 p->max_bs[i] = le32_to_cpu(p->max_bs[i]);
725 p->numjobs = le32_to_cpu(p->numjobs);
726 p->group_reporting = le32_to_cpu(p->group_reporting);
728 gtk_entry_set_text(GTK_ENTRY(ui->eta.name), (gchar *) p->jobname);
729 gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), ddir_str(p->rw));
730 gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), (gchar *) p->ioengine);
732 sprintf(tmp, "%u", p->iodepth);
733 gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
736 static void gfio_client_timed_out(struct fio_client *client)
738 struct gui *ui = client->client_data;
739 GtkWidget *dialog, *label, *content;
744 gfio_set_connected(ui, 0);
747 sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
749 dialog = gtk_dialog_new_with_buttons("Timed out!",
750 GTK_WINDOW(ui->window),
751 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
752 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
754 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
755 label = gtk_label_new((const gchar *) buf);
756 gtk_container_add(GTK_CONTAINER(content), label);
757 gtk_widget_show_all(dialog);
758 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
760 gtk_dialog_run(GTK_DIALOG(dialog));
761 gtk_widget_destroy(dialog);
766 struct client_ops gfio_client_ops = {
767 .text_op = gfio_text_op,
768 .disk_util = gfio_disk_util_op,
769 .thread_status = gfio_thread_status_op,
770 .group_stats = gfio_group_stats_op,
771 .eta = gfio_update_eta,
772 .probe = gfio_probe_op,
773 .quit = gfio_quit_op,
774 .add_job = gfio_add_job_op,
775 .timed_out = gfio_client_timed_out,
779 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
780 __attribute__((unused)) gpointer data)
785 static void *job_thread(void *arg)
787 fio_handle_clients(&gfio_client_ops);
791 static int send_job_files(struct gui *ui)
795 for (i = 0; i < ui->nr_job_files; i++) {
796 ret = fio_clients_send_ini(ui->job_files[i]);
800 free(ui->job_files[i]);
801 ui->job_files[i] = NULL;
803 while (i < ui->nr_job_files) {
804 free(ui->job_files[i]);
805 ui->job_files[i] = NULL;
812 static void start_job_thread(struct gui *ui)
814 if (send_job_files(ui)) {
815 printf("Yeah, I didn't really like those options too much.\n");
816 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
821 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
824 struct gui *ui = data;
826 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
827 start_job_thread(ui);
830 static void file_open(GtkWidget *w, gpointer data);
832 static void connect_clicked(GtkWidget *widget, gpointer data)
834 struct gui *ui = data;
836 if (!ui->connected) {
837 if (!ui->nr_job_files)
838 file_open(widget, data);
839 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
840 fio_clients_connect();
841 pthread_create(&ui->t, NULL, job_thread, NULL);
842 gfio_set_connected(ui, 1);
844 fio_clients_terminate();
845 gfio_set_connected(ui, 0);
850 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
851 struct button_spec *buttonspec)
853 ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
854 g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
855 gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
856 gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
857 gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
860 static void add_buttons(struct gui *ui,
861 struct button_spec *buttonlist,
866 for (i = 0; i < nbuttons; i++)
867 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
870 static void on_info_bar_response(GtkWidget *widget, gint response,
873 if (response == GTK_RESPONSE_OK) {
874 gtk_widget_destroy(widget);
875 ui.error_info_bar = NULL;
879 void report_error(GError *error)
881 if (ui.error_info_bar == NULL) {
882 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
885 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
886 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
889 ui.error_label = gtk_label_new(error->message);
890 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
891 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
893 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
894 gtk_widget_show_all(ui.vbox);
897 snprintf(buffer, sizeof(buffer), "Failed to open file.");
898 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
902 static int get_connection_details(char **host, int *port, int *type,
905 GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
909 dialog = gtk_dialog_new_with_buttons("Connection details",
910 GTK_WINDOW(ui.window),
911 GTK_DIALOG_DESTROY_WITH_PARENT,
912 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
913 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
915 frame = gtk_frame_new("Hostname / socket name");
916 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
917 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
919 box = gtk_vbox_new(FALSE, 6);
920 gtk_container_add(GTK_CONTAINER(frame), box);
922 hbox = gtk_hbox_new(TRUE, 10);
923 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
924 hentry = gtk_entry_new();
925 gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
926 gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
928 frame = gtk_frame_new("Port");
929 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
930 box = gtk_vbox_new(FALSE, 10);
931 gtk_container_add(GTK_CONTAINER(frame), box);
933 hbox = gtk_hbox_new(TRUE, 4);
934 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
935 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
937 frame = gtk_frame_new("Type");
938 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
939 box = gtk_vbox_new(FALSE, 10);
940 gtk_container_add(GTK_CONTAINER(frame), box);
942 hbox = gtk_hbox_new(TRUE, 4);
943 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
945 combo = gtk_combo_box_text_new();
946 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
947 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
948 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
949 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
951 gtk_container_add(GTK_CONTAINER(hbox), combo);
953 frame = gtk_frame_new("Options");
954 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
955 box = gtk_vbox_new(FALSE, 10);
956 gtk_container_add(GTK_CONTAINER(frame), box);
958 hbox = gtk_hbox_new(TRUE, 4);
959 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
961 button = gtk_check_button_new_with_label("Auto-spawn fio backend");
962 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
963 gtk_widget_set_tooltip_text(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.");
964 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
966 gtk_widget_show_all(dialog);
968 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
969 gtk_widget_destroy(dialog);
973 *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
974 *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
976 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
977 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
978 *type = Fio_client_ipv4;
979 else if (!strncmp(typeentry, "IPv6", 4))
980 *type = Fio_client_ipv6;
982 *type = Fio_client_socket;
985 *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
987 gtk_widget_destroy(dialog);
991 static void file_open(GtkWidget *w, gpointer data)
994 GSList *filenames, *fn_glist;
995 GtkFileFilter *filter;
997 int port, type, server_start;
999 dialog = gtk_file_chooser_dialog_new("Open File",
1000 GTK_WINDOW(ui.window),
1001 GTK_FILE_CHOOSER_ACTION_OPEN,
1002 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1003 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1005 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1007 filter = gtk_file_filter_new();
1008 gtk_file_filter_add_pattern(filter, "*.fio");
1009 gtk_file_filter_add_pattern(filter, "*.job");
1010 gtk_file_filter_add_mime_type(filter, "text/fio");
1011 gtk_file_filter_set_name(filter, "Fio job file");
1012 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1014 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1015 gtk_widget_destroy(dialog);
1019 fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1021 gtk_widget_destroy(dialog);
1023 if (get_connection_details(&host, &port, &type, &server_start))
1026 filenames = fn_glist;
1027 while (filenames != NULL) {
1028 ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
1029 ui.job_files[ui.nr_job_files] = strdup(filenames->data);
1032 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1036 error = g_error_new(g_quark_from_string("fio"), 1,
1037 "Failed to add client %s", host);
1038 report_error(error);
1039 g_error_free(error);
1041 ui.client->client_data = &ui;
1043 g_free(filenames->data);
1044 filenames = g_slist_next(filenames);
1048 g_slist_free(fn_glist);
1051 static void file_save(GtkWidget *w, gpointer data)
1055 dialog = gtk_file_chooser_dialog_new("Save File",
1056 GTK_WINDOW(ui.window),
1057 GTK_FILE_CHOOSER_ACTION_SAVE,
1058 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1059 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1062 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1063 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1065 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1068 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1069 // save_job_file(filename);
1072 gtk_widget_destroy(dialog);
1075 static void preferences(GtkWidget *w, gpointer data)
1077 GtkWidget *dialog, *frame, *box, **buttons;
1080 dialog = gtk_dialog_new_with_buttons("Preferences",
1081 GTK_WINDOW(ui.window),
1082 GTK_DIALOG_DESTROY_WITH_PARENT,
1083 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1084 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1087 frame = gtk_frame_new("Debug logging");
1088 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1089 box = gtk_hbox_new(FALSE, 6);
1090 gtk_container_add(GTK_CONTAINER(frame), box);
1092 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1094 for (i = 0; i < FD_DEBUG_MAX; i++) {
1095 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1096 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1097 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1100 gtk_widget_show_all(dialog);
1102 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1103 gtk_widget_destroy(dialog);
1107 for (i = 0; i < FD_DEBUG_MAX; i++) {
1110 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1112 fio_debug |= (1UL << i);
1115 gtk_widget_destroy(dialog);
1118 static void about_dialog(GtkWidget *w, gpointer data)
1120 gtk_show_about_dialog(NULL,
1121 "program-name", "gfio",
1122 "comments", "Gtk2 UI for fio",
1124 "version", fio_version_string,
1125 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
1126 "logo-icon-name", "fio",
1132 static GtkActionEntry menu_items[] = {
1133 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1134 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1135 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1136 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1137 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1138 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1139 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1141 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1143 static const gchar *ui_string = " \
1145 <menubar name=\"MainMenu\"> \
1146 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1147 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1148 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1149 <separator name=\"Separator\"/> \
1150 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1151 <separator name=\"Separator2\"/> \
1152 <menuitem name=\"Quit\" action=\"Quit\" /> \
1154 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1155 <menuitem name=\"About\" action=\"About\" /> \
1161 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
1163 GtkActionGroup *action_group = gtk_action_group_new("Menu");
1166 action_group = gtk_action_group_new("Menu");
1167 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
1169 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1170 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1172 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1173 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1176 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1177 GtkWidget *vbox, GtkUIManager *ui_manager)
1179 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1182 static void init_ui(int *argc, char **argv[], struct gui *ui)
1184 GtkSettings *settings;
1185 GtkUIManager *uimanager;
1186 GtkWidget *menu, *probe, *probe_frame, *probe_box;
1188 memset(ui, 0, sizeof(*ui));
1190 /* Magical g*thread incantation, you just need this thread stuff.
1191 * Without it, the update that happens in gfio_update_thread_status
1192 * doesn't really happen in a timely fashion, you need expose events
1194 if (!g_thread_supported())
1195 g_thread_init(NULL);
1198 gtk_init(argc, argv);
1199 settings = gtk_settings_get_default();
1200 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1203 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1204 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1205 gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
1207 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1208 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1210 ui->vbox = gtk_vbox_new(FALSE, 0);
1211 gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1213 uimanager = gtk_ui_manager_new();
1214 menu = get_menubar_menu(ui->window, uimanager);
1215 gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1218 * Set up alignments for widgets at the top of ui,
1219 * align top left, expand horizontally but not vertically
1221 ui->topalign = gtk_alignment_new(0, 0, 1, 0);
1222 ui->topvbox = gtk_vbox_new(FALSE, 3);
1223 gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1224 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1226 probe = gtk_frame_new("Job");
1227 gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1228 probe_frame = gtk_vbox_new(FALSE, 3);
1229 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1231 probe_box = gtk_hbox_new(FALSE, 3);
1232 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1233 ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1234 ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1235 ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1236 ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1238 probe_box = gtk_hbox_new(FALSE, 3);
1239 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1241 ui->eta.name = new_info_entry_in_frame(probe_box, "Name");
1242 ui->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
1243 ui->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
1244 ui->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
1245 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1246 ui->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1248 probe_box = gtk_hbox_new(FALSE, 3);
1249 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1250 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1251 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1252 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1253 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1256 * Only add this if we have a commit rate
1259 probe_box = gtk_hbox_new(FALSE, 3);
1260 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1262 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1263 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1265 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1266 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1270 * Add a text box for text op messages
1272 ui->textview = gtk_text_view_new();
1273 ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
1274 gtk_text_buffer_set_text(ui->text, "", -1);
1275 gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
1276 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
1277 ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1278 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1279 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1280 gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
1281 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1285 * Set up alignments for widgets at the bottom of ui,
1286 * align bottom left, expand horizontally but not vertically
1288 ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
1289 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1290 gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
1291 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1294 add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
1297 * Set up thread status progress bar
1299 ui->thread_status_pb = gtk_progress_bar_new();
1300 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1301 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1302 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1305 gtk_widget_show_all(ui->window);
1308 int main(int argc, char *argv[], char *envp[])
1310 if (initialize_fio(envp))
1312 if (fio_init_options())
1315 init_ui(&argc, &argv, &ui);
1317 gdk_threads_enter();
1319 gdk_threads_leave();