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_label_set_text(GTK_LABEL(ui->eta.name), "");
109 gtk_label_set_text(GTK_LABEL(ui->eta.iotype), "");
110 gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), "");
111 gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), "");
112 gtk_label_set_text(GTK_LABEL(ui->eta.jobs), "");
113 gtk_label_set_text(GTK_LABEL(ui->eta.files), "");
114 gtk_label_set_text(GTK_LABEL(ui->eta.read_bw), "");
115 gtk_label_set_text(GTK_LABEL(ui->eta.read_iops), "");
116 gtk_label_set_text(GTK_LABEL(ui->eta.write_bw), "");
117 gtk_label_set_text(GTK_LABEL(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);
354 static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
355 struct thread_stat *ts, int ddir)
357 const char *ddir_label[2] = { "Read", "Write" };
358 GtkWidget *frame, *label, *box, *vbox;
359 unsigned long min, max, runt;
360 unsigned long long bw, iops;
362 char *io_p, *bw_p, *iops_p;
365 if (!ts->runtime[ddir])
368 i2p = is_power_of_2(rs->kb_base);
369 runt = ts->runtime[ddir];
371 bw = (1000 * ts->io_bytes[ddir]) / runt;
372 io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
373 bw_p = num2str(bw, 6, 1, i2p);
375 iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
376 iops_p = num2str(iops, 6, 1, 0);
378 box = gtk_hbox_new(FALSE, 3);
379 gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
381 frame = gtk_frame_new(ddir_label[ddir]);
382 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
384 vbox = gtk_vbox_new(FALSE, 3);
385 gtk_container_add(GTK_CONTAINER(frame), vbox);
387 box = gtk_hbox_new(FALSE, 3);
388 gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 3);
390 label = new_info_label_in_frame(box, "IO");
391 gtk_label_set_text(GTK_LABEL(label), io_p);
392 label = new_info_label_in_frame(box, "Bandwidth");
393 gtk_label_set_text(GTK_LABEL(label), bw_p);
394 label = new_info_label_in_frame(box, "IOPS");
395 gtk_label_set_text(GTK_LABEL(label), iops_p);
396 label = new_info_label_in_frame(box, "Runtime (msec)");
397 label_set_int_value(label, ts->runtime[ddir]);
399 frame = gtk_frame_new("Latency");
400 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
402 vbox = gtk_vbox_new(FALSE, 3);
403 gtk_container_add(GTK_CONTAINER(frame), vbox);
405 if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
406 gfio_show_lat(vbox, "Submission latency", min, max, mean, dev);
407 if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
408 gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
409 if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
410 gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
411 if (ts->clat_percentiles)
412 gfio_show_clat_percentiles(vbox, ts, ddir);
419 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
420 struct group_run_stats *rs)
422 GtkWidget *dialog, *box, *vbox, *entry, *content;
423 struct gui *ui = client->client_data;
427 dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
428 GTK_DIALOG_DESTROY_WITH_PARENT,
429 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
431 g_signal_connect_swapped(dialog, "response",
432 G_CALLBACK(gtk_widget_destroy),
435 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
437 vbox = gtk_vbox_new(FALSE, 3);
438 gtk_container_add(GTK_CONTAINER(content), vbox);
440 box = gtk_hbox_new(TRUE, 3);
441 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
443 entry = new_info_entry_in_frame(box, "Name");
444 gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
445 if (strlen(ts->description)) {
446 entry = new_info_entry_in_frame(box, "Description");
447 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
449 entry = new_info_entry_in_frame(box, "Group ID");
450 entry_set_int_value(entry, ts->groupid);
451 entry = new_info_entry_in_frame(box, "Jobs");
452 entry_set_int_value(entry, ts->members);
453 entry = new_info_entry_in_frame(box, "Error");
454 entry_set_int_value(entry, ts->error);
455 entry = new_info_entry_in_frame(box, "PID");
456 entry_set_int_value(entry, ts->pid);
458 if (ts->io_bytes[DDIR_READ])
459 gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
460 if (ts->io_bytes[DDIR_WRITE])
461 gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
463 gtk_widget_show_all(dialog);
468 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
471 GtkTextBuffer *buffer;
474 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
476 gtk_text_buffer_get_end_iter(buffer, &end);
477 gtk_text_buffer_insert(buffer, &end, buf, -1);
479 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
480 &end, 0.0, FALSE, 0.0,0.0);
482 fio_client_ops.text_op(client, cmd);
486 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
488 printf("gfio_disk_util_op called\n");
489 fio_client_ops.disk_util(client, cmd);
492 extern int sum_stat_clients;
493 extern struct thread_stat client_ts;
494 extern struct group_run_stats client_gs;
496 static int sum_stat_nr;
498 static void gfio_thread_status_op(struct fio_client *client,
499 struct fio_net_cmd *cmd)
501 struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
503 gfio_display_ts(client, &p->ts, &p->rs);
505 if (sum_stat_clients == 1)
508 sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
509 sum_group_stats(&client_gs, &p->rs);
512 client_ts.groupid = p->ts.groupid;
514 if (++sum_stat_nr == sum_stat_clients) {
515 strcpy(client_ts.name, "All clients");
516 gfio_display_ts(client, &client_ts, &client_gs);
520 static void gfio_group_stats_op(struct fio_client *client,
521 struct fio_net_cmd *cmd)
523 printf("gfio_group_stats_op called\n");
524 fio_client_ops.group_stats(client, cmd);
527 static void gfio_update_eta(struct jobs_eta *je)
539 if (je->eta_sec != INT_MAX && je->elapsed_sec) {
540 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
541 eta_to_str(eta_str, je->eta_sec);
544 sprintf(tmp, "%u", je->nr_running);
545 gtk_label_set_text(GTK_LABEL(ui.eta.jobs), tmp);
546 sprintf(tmp, "%u", je->files_open);
547 gtk_label_set_text(GTK_LABEL(ui.eta.files), tmp);
550 if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
551 if (je->m_rate || je->t_rate) {
554 mr = num2str(je->m_rate, 4, 0, i2p);
555 tr = num2str(je->t_rate, 4, 0, i2p);
556 gtk_label_set_text(GTK_LABEL(ui.eta.
557 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
560 } else if (je->m_iops || je->t_iops)
561 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
563 gtk_label_set_text(GTK_LABEL(ui.eta.cr_bw), "---");
564 gtk_label_set_text(GTK_LABEL(ui.eta.cr_iops), "---");
565 gtk_label_set_text(GTK_LABEL(ui.eta.cw_bw), "---");
566 gtk_label_set_text(GTK_LABEL(ui.eta.cw_iops), "---");
569 if (je->eta_sec != INT_MAX && je->nr_running) {
573 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
574 strcpy(output, "-.-% done");
578 sprintf(output, "%3.1f%% done", perc);
581 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
582 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
584 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
585 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
587 gtk_label_set_text(GTK_LABEL(ui.eta.read_bw), rate_str[0]);
588 gtk_label_set_text(GTK_LABEL(ui.eta.read_iops), iops_str[0]);
589 gtk_label_set_text(GTK_LABEL(ui.eta.write_bw), rate_str[1]);
590 gtk_label_set_text(GTK_LABEL(ui.eta.write_iops), iops_str[1]);
599 char *dst = output + strlen(output);
601 sprintf(dst, " - %s", eta_str);
604 gfio_update_thread_status(output, perc);
607 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
609 struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
610 const char *os, *arch;
613 os = fio_get_os_string(probe->os);
617 arch = fio_get_arch_string(probe->arch);
622 client->name = strdup((char *) probe->hostname);
624 gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
625 gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
626 gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
627 sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
628 gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
631 static void gfio_update_thread_status(char *status_message, double perc)
633 static char message[100];
634 const char *m = message;
636 strncpy(message, status_message, sizeof(message) - 1);
637 gtk_progress_bar_set_text(
638 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
639 gtk_progress_bar_set_fraction(
640 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
642 gtk_widget_queue_draw(ui.window);
646 static void gfio_quit_op(struct fio_client *client)
648 struct gui *ui = client->client_data;
650 gfio_set_connected(ui, 0);
653 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
655 struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
656 struct gui *ui = client->client_data;
660 p->iodepth = le32_to_cpu(p->iodepth);
661 p->rw = le32_to_cpu(p->rw);
663 for (i = 0; i < 2; i++) {
664 p->min_bs[i] = le32_to_cpu(p->min_bs[i]);
665 p->max_bs[i] = le32_to_cpu(p->max_bs[i]);
668 p->numjobs = le32_to_cpu(p->numjobs);
669 p->group_reporting = le32_to_cpu(p->group_reporting);
671 gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
672 gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
673 gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
675 sprintf(tmp, "%u", p->iodepth);
676 gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
679 static void gfio_client_timed_out(struct fio_client *client)
681 struct gui *ui = client->client_data;
682 GtkWidget *dialog, *label, *content;
687 gfio_set_connected(ui, 0);
690 sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
692 dialog = gtk_dialog_new_with_buttons("Timed out!",
693 GTK_WINDOW(ui->window),
694 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
695 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
697 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
698 label = gtk_label_new((const gchar *) buf);
699 gtk_container_add(GTK_CONTAINER(content), label);
700 gtk_widget_show_all(dialog);
701 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
703 gtk_dialog_run(GTK_DIALOG(dialog));
704 gtk_widget_destroy(dialog);
709 struct client_ops gfio_client_ops = {
710 .text_op = gfio_text_op,
711 .disk_util = gfio_disk_util_op,
712 .thread_status = gfio_thread_status_op,
713 .group_stats = gfio_group_stats_op,
714 .eta = gfio_update_eta,
715 .probe = gfio_probe_op,
716 .quit = gfio_quit_op,
717 .add_job = gfio_add_job_op,
718 .timed_out = gfio_client_timed_out,
722 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
723 __attribute__((unused)) gpointer data)
728 static void *job_thread(void *arg)
730 fio_handle_clients(&gfio_client_ops);
734 static int send_job_files(struct gui *ui)
738 for (i = 0; i < ui->nr_job_files; i++) {
739 ret = fio_clients_send_ini(ui->job_files[i]);
743 free(ui->job_files[i]);
744 ui->job_files[i] = NULL;
746 while (i < ui->nr_job_files) {
747 free(ui->job_files[i]);
748 ui->job_files[i] = NULL;
755 static void start_job_thread(struct gui *ui)
757 if (send_job_files(ui)) {
758 printf("Yeah, I didn't really like those options too much.\n");
759 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
764 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
767 struct gui *ui = data;
769 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
770 start_job_thread(ui);
773 static void file_open(GtkWidget *w, gpointer data);
775 static void connect_clicked(GtkWidget *widget, gpointer data)
777 struct gui *ui = data;
779 if (!ui->connected) {
780 if (!ui->nr_job_files)
781 file_open(widget, data);
782 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
783 fio_clients_connect();
784 pthread_create(&ui->t, NULL, job_thread, NULL);
785 gfio_set_connected(ui, 1);
787 fio_clients_terminate();
788 gfio_set_connected(ui, 0);
793 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
794 struct button_spec *buttonspec)
796 ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
797 g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
798 gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
799 gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
800 gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
803 static void add_buttons(struct gui *ui,
804 struct button_spec *buttonlist,
809 for (i = 0; i < nbuttons; i++)
810 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
813 static void on_info_bar_response(GtkWidget *widget, gint response,
816 if (response == GTK_RESPONSE_OK) {
817 gtk_widget_destroy(widget);
818 ui.error_info_bar = NULL;
822 void report_error(GError *error)
824 if (ui.error_info_bar == NULL) {
825 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
828 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
829 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
832 ui.error_label = gtk_label_new(error->message);
833 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
834 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
836 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
837 gtk_widget_show_all(ui.vbox);
840 snprintf(buffer, sizeof(buffer), "Failed to open file.");
841 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
845 static int get_connection_details(char **host, int *port, int *type,
848 GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
852 dialog = gtk_dialog_new_with_buttons("Connection details",
853 GTK_WINDOW(ui.window),
854 GTK_DIALOG_DESTROY_WITH_PARENT,
855 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
856 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
858 frame = gtk_frame_new("Hostname / socket name");
859 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
860 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
862 box = gtk_vbox_new(FALSE, 6);
863 gtk_container_add(GTK_CONTAINER(frame), box);
865 hbox = gtk_hbox_new(TRUE, 10);
866 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
867 hentry = gtk_entry_new();
868 gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
869 gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
871 frame = gtk_frame_new("Port");
872 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
873 box = gtk_vbox_new(FALSE, 10);
874 gtk_container_add(GTK_CONTAINER(frame), box);
876 hbox = gtk_hbox_new(TRUE, 4);
877 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
878 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
880 frame = gtk_frame_new("Type");
881 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
882 box = gtk_vbox_new(FALSE, 10);
883 gtk_container_add(GTK_CONTAINER(frame), box);
885 hbox = gtk_hbox_new(TRUE, 4);
886 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
888 combo = gtk_combo_box_text_new();
889 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
890 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
891 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
892 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
894 gtk_container_add(GTK_CONTAINER(hbox), combo);
896 frame = gtk_frame_new("Options");
897 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
898 box = gtk_vbox_new(FALSE, 10);
899 gtk_container_add(GTK_CONTAINER(frame), box);
901 hbox = gtk_hbox_new(TRUE, 4);
902 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
904 button = gtk_check_button_new_with_label("Auto-spawn fio backend");
905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
906 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.");
907 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
909 gtk_widget_show_all(dialog);
911 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
912 gtk_widget_destroy(dialog);
916 *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
917 *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
919 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
920 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
921 *type = Fio_client_ipv4;
922 else if (!strncmp(typeentry, "IPv6", 4))
923 *type = Fio_client_ipv6;
925 *type = Fio_client_socket;
928 *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
930 gtk_widget_destroy(dialog);
934 static void file_open(GtkWidget *w, gpointer data)
937 GSList *filenames, *fn_glist;
938 GtkFileFilter *filter;
940 int port, type, server_start;
942 dialog = gtk_file_chooser_dialog_new("Open File",
943 GTK_WINDOW(ui.window),
944 GTK_FILE_CHOOSER_ACTION_OPEN,
945 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
946 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
948 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
950 filter = gtk_file_filter_new();
951 gtk_file_filter_add_pattern(filter, "*.fio");
952 gtk_file_filter_add_pattern(filter, "*.job");
953 gtk_file_filter_add_mime_type(filter, "text/fio");
954 gtk_file_filter_set_name(filter, "Fio job file");
955 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
957 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
958 gtk_widget_destroy(dialog);
962 fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
964 gtk_widget_destroy(dialog);
966 if (get_connection_details(&host, &port, &type, &server_start))
969 filenames = fn_glist;
970 while (filenames != NULL) {
971 ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
972 ui.job_files[ui.nr_job_files] = strdup(filenames->data);
975 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
979 error = g_error_new(g_quark_from_string("fio"), 1,
980 "Failed to add client %s", host);
984 ui.client->client_data = &ui;
986 g_free(filenames->data);
987 filenames = g_slist_next(filenames);
991 g_slist_free(fn_glist);
994 static void file_save(GtkWidget *w, gpointer data)
998 dialog = gtk_file_chooser_dialog_new("Save File",
999 GTK_WINDOW(ui.window),
1000 GTK_FILE_CHOOSER_ACTION_SAVE,
1001 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1002 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1005 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1006 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1008 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1011 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1012 // save_job_file(filename);
1015 gtk_widget_destroy(dialog);
1018 static void preferences(GtkWidget *w, gpointer data)
1020 GtkWidget *dialog, *frame, *box, **buttons;
1023 dialog = gtk_dialog_new_with_buttons("Preferences",
1024 GTK_WINDOW(ui.window),
1025 GTK_DIALOG_DESTROY_WITH_PARENT,
1026 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1027 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1030 frame = gtk_frame_new("Debug logging");
1031 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1032 box = gtk_hbox_new(FALSE, 6);
1033 gtk_container_add(GTK_CONTAINER(frame), box);
1035 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1037 for (i = 0; i < FD_DEBUG_MAX; i++) {
1038 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1039 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1040 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1043 gtk_widget_show_all(dialog);
1045 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1046 gtk_widget_destroy(dialog);
1050 for (i = 0; i < FD_DEBUG_MAX; i++) {
1053 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1055 fio_debug |= (1UL << i);
1058 gtk_widget_destroy(dialog);
1061 static void about_dialog(GtkWidget *w, gpointer data)
1063 gtk_show_about_dialog(NULL,
1064 "program-name", "gfio",
1065 "comments", "Gtk2 UI for fio",
1067 "version", fio_version_string,
1068 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
1069 "logo-icon-name", "fio",
1075 static GtkActionEntry menu_items[] = {
1076 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1077 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1078 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1079 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1080 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1081 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1082 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
1084 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1086 static const gchar *ui_string = " \
1088 <menubar name=\"MainMenu\"> \
1089 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1090 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1091 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1092 <separator name=\"Separator\"/> \
1093 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1094 <separator name=\"Separator2\"/> \
1095 <menuitem name=\"Quit\" action=\"Quit\" /> \
1097 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1098 <menuitem name=\"About\" action=\"About\" /> \
1104 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
1106 GtkActionGroup *action_group = gtk_action_group_new("Menu");
1109 action_group = gtk_action_group_new("Menu");
1110 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
1112 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1113 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1115 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1116 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1119 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1120 GtkWidget *vbox, GtkUIManager *ui_manager)
1122 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1125 static void init_ui(int *argc, char **argv[], struct gui *ui)
1127 GtkSettings *settings;
1128 GtkUIManager *uimanager;
1129 GtkWidget *menu, *probe, *probe_frame, *probe_box;
1131 memset(ui, 0, sizeof(*ui));
1133 /* Magical g*thread incantation, you just need this thread stuff.
1134 * Without it, the update that happens in gfio_update_thread_status
1135 * doesn't really happen in a timely fashion, you need expose events
1137 if (!g_thread_supported())
1138 g_thread_init(NULL);
1141 gtk_init(argc, argv);
1142 settings = gtk_settings_get_default();
1143 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1146 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1147 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1148 gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
1150 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1151 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1153 ui->vbox = gtk_vbox_new(FALSE, 0);
1154 gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1156 uimanager = gtk_ui_manager_new();
1157 menu = get_menubar_menu(ui->window, uimanager);
1158 gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1161 * Set up alignments for widgets at the top of ui,
1162 * align top left, expand horizontally but not vertically
1164 ui->topalign = gtk_alignment_new(0, 0, 1, 0);
1165 ui->topvbox = gtk_vbox_new(FALSE, 3);
1166 gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1167 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1169 probe = gtk_frame_new("Job");
1170 gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1171 probe_frame = gtk_vbox_new(FALSE, 3);
1172 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1174 probe_box = gtk_hbox_new(FALSE, 3);
1175 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1176 ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1177 ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1178 ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1179 ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1181 probe_box = gtk_hbox_new(FALSE, 3);
1182 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1184 ui->eta.name = new_info_label_in_frame(probe_box, "Name");
1185 ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
1186 ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
1187 ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
1188 ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
1189 ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
1191 probe_box = gtk_hbox_new(FALSE, 3);
1192 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1193 ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
1194 ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
1195 ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
1196 ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
1199 * Only add this if we have a commit rate
1202 probe_box = gtk_hbox_new(FALSE, 3);
1203 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1205 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1206 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1208 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1209 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1213 * Add a text box for text op messages
1215 ui->textview = gtk_text_view_new();
1216 ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
1217 gtk_text_buffer_set_text(ui->text, "", -1);
1218 gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
1219 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
1220 ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1221 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1222 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1223 gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
1224 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1228 * Set up alignments for widgets at the bottom of ui,
1229 * align bottom left, expand horizontally but not vertically
1231 ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
1232 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1233 gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
1234 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1237 add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
1240 * Set up thread status progress bar
1242 ui->thread_status_pb = gtk_progress_bar_new();
1243 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1244 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1245 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1248 gtk_widget_show_all(ui->window);
1251 int main(int argc, char *argv[], char *envp[])
1253 if (initialize_fio(envp))
1255 if (fio_init_options())
1258 init_ui(&argc, &argv, &ui);
1260 gdk_threads_enter();
1262 gdk_threads_leave();