Home | History | Annotate | Download | only in fio
      1 #include <malloc.h>
      2 #include <string.h>
      3 
      4 #include <glib.h>
      5 #include <cairo.h>
      6 #include <gtk/gtk.h>
      7 
      8 #include "fio.h"
      9 #include "gfio.h"
     10 #include "ghelpers.h"
     11 #include "goptions.h"
     12 #include "gerror.h"
     13 #include "graph.h"
     14 #include "gclient.h"
     15 #include "printing.h"
     16 
     17 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
     18 			    struct group_run_stats *rs);
     19 
     20 static gboolean results_window_delete(GtkWidget *w, gpointer data)
     21 {
     22 	struct gui_entry *ge = (struct gui_entry *) data;
     23 
     24 	gtk_widget_destroy(w);
     25 	ge->results_window = NULL;
     26 	ge->results_notebook = NULL;
     27 	return TRUE;
     28 }
     29 
     30 static void results_close(GtkWidget *w, gpointer *data)
     31 {
     32 	struct gui_entry *ge = (struct gui_entry *) data;
     33 
     34 	gtk_widget_destroy(ge->results_window);
     35 }
     36 
     37 static void results_print(GtkWidget *w, gpointer *data)
     38 {
     39 	struct gui_entry *ge = (struct gui_entry *) data;
     40 
     41 	gfio_print_results(ge);
     42 }
     43 
     44 static GtkActionEntry results_menu_items[] = {
     45 	{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
     46 	{ "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL},
     47 	{ "PrintFile", GTK_STOCK_PRINT, "Print", "<Control>P", NULL, G_CALLBACK(results_print) },
     48 	{ "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(results_close) },
     49 };
     50 static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]);
     51 
     52 static const gchar *results_ui_string = " \
     53 	<ui> \
     54 		<menubar name=\"MainMenu\"> \
     55 			<menu name=\"FileMenu\" action=\"FileMenuAction\"> \
     56 				<menuitem name=\"Print\" action=\"PrintFile\" /> \
     57 				<menuitem name=\"Close\" action=\"CloseFile\" /> \
     58 			</menu> \
     59 			<menu name=\"GraphMenu\" action=\"GraphMenuAction\"> \
     60 			</menu>\
     61 		</menubar> \
     62 	</ui> \
     63 ";
     64 
     65 static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge)
     66 {
     67 	GtkActionGroup *action_group;
     68 	GtkWidget *widget;
     69 	GError *error = 0;
     70 
     71 	ge->results_uimanager = gtk_ui_manager_new();
     72 
     73 	action_group = gtk_action_group_new("ResultsMenu");
     74 	gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge);
     75 
     76 	gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0);
     77 	gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error);
     78 
     79 	gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager));
     80 
     81 	widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu");
     82 	return widget;
     83 }
     84 
     85 static GtkWidget *get_results_window(struct gui_entry *ge)
     86 {
     87 	GtkWidget *win, *notebook, *vbox;
     88 
     89 	if (ge->results_window)
     90 		return ge->results_notebook;
     91 
     92 	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     93 	gtk_window_set_title(GTK_WINDOW(win), "Results");
     94 	gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
     95 	g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge);
     96 	g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge);
     97 
     98 	vbox = gtk_vbox_new(FALSE, 0);
     99 	gtk_container_add(GTK_CONTAINER(win), vbox);
    100 
    101 	ge->results_menu = get_results_menubar(win, ge);
    102 	gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0);
    103 
    104 	notebook = gtk_notebook_new();
    105 	gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1);
    106 	gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
    107 	gtk_container_add(GTK_CONTAINER(vbox), notebook);
    108 
    109 	ge->results_window = win;
    110 	ge->results_notebook = notebook;
    111 	return ge->results_notebook;
    112 }
    113 
    114 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
    115 {
    116 	struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload;
    117 	struct gfio_client *gc = client->client_data;
    118 	struct gui_entry *ge = gc->ge;
    119 	struct gui *ui = ge->ui;
    120 	GtkTreeIter iter;
    121 	struct tm *tm;
    122 	time_t sec;
    123 	char tmp[64], timebuf[80];
    124 
    125 	sec = p->log_sec;
    126 	tm = localtime(&sec);
    127 	strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm);
    128 	sprintf(timebuf, "%s.%03ld", tmp, (long) p->log_usec / 1000);
    129 
    130 	gdk_threads_enter();
    131 
    132 	gtk_list_store_append(ui->log_model, &iter);
    133 	gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1);
    134 	gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1);
    135 	gtk_list_store_set(ui->log_model, &iter, 2, log_get_level(p->level), -1);
    136 	gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1);
    137 
    138 	if (p->level == FIO_LOG_ERR)
    139 		gfio_view_log(ui);
    140 
    141 	gdk_threads_leave();
    142 }
    143 
    144 static void disk_util_destroy(GtkWidget *w, gpointer data)
    145 {
    146 	struct gui_entry *ge = (struct gui_entry *) data;
    147 
    148 	ge->disk_util_vbox = NULL;
    149 	gtk_widget_destroy(w);
    150 }
    151 
    152 static GtkWidget *gfio_disk_util_get_vbox(struct gui_entry *ge)
    153 {
    154 	GtkWidget *vbox, *box, *scroll, *res_notebook;
    155 
    156 	if (ge->disk_util_vbox)
    157 		return ge->disk_util_vbox;
    158 
    159 	scroll = get_scrolled_window(5);
    160 	vbox = gtk_vbox_new(FALSE, 3);
    161 	box = gtk_hbox_new(FALSE, 0);
    162 	gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
    163 
    164 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
    165 	res_notebook = get_results_window(ge);
    166 
    167 	gtk_notebook_append_page(GTK_NOTEBOOK(res_notebook), scroll, gtk_label_new("Disk utilization"));
    168 	ge->disk_util_vbox = box;
    169 	g_signal_connect(vbox, "destroy", G_CALLBACK(disk_util_destroy), ge);
    170 
    171 	return ge->disk_util_vbox;
    172 }
    173 
    174 static int __gfio_disk_util_show(GtkWidget *res_notebook,
    175 				 struct gfio_client *gc, struct cmd_du_pdu *p)
    176 {
    177 	GtkWidget *box, *frame, *entry, *vbox, *util_vbox;
    178 	struct gui_entry *ge = gc->ge;
    179 	double util;
    180 	char tmp[16];
    181 
    182 	util_vbox = gfio_disk_util_get_vbox(ge);
    183 
    184 	vbox = gtk_vbox_new(FALSE, 3);
    185 	gtk_container_add(GTK_CONTAINER(util_vbox), vbox);
    186 
    187 	frame = gtk_frame_new((char *) p->dus.name);
    188 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2);
    189 
    190 	box = gtk_vbox_new(FALSE, 3);
    191 	gtk_container_add(GTK_CONTAINER(frame), box);
    192 
    193 	frame = gtk_frame_new("Read");
    194 	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
    195 	vbox = gtk_hbox_new(TRUE, 3);
    196 	gtk_container_add(GTK_CONTAINER(frame), vbox);
    197 	entry = new_info_entry_in_frame(vbox, "IOs");
    198 	entry_set_int_value(entry, p->dus.s.ios[0]);
    199 	entry = new_info_entry_in_frame(vbox, "Merges");
    200 	entry_set_int_value(entry, p->dus.s.merges[0]);
    201 	entry = new_info_entry_in_frame(vbox, "Sectors");
    202 	entry_set_int_value(entry, p->dus.s.sectors[0]);
    203 	entry = new_info_entry_in_frame(vbox, "Ticks");
    204 	entry_set_int_value(entry, p->dus.s.ticks[0]);
    205 
    206 	frame = gtk_frame_new("Write");
    207 	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
    208 	vbox = gtk_hbox_new(TRUE, 3);
    209 	gtk_container_add(GTK_CONTAINER(frame), vbox);
    210 	entry = new_info_entry_in_frame(vbox, "IOs");
    211 	entry_set_int_value(entry, p->dus.s.ios[1]);
    212 	entry = new_info_entry_in_frame(vbox, "Merges");
    213 	entry_set_int_value(entry, p->dus.s.merges[1]);
    214 	entry = new_info_entry_in_frame(vbox, "Sectors");
    215 	entry_set_int_value(entry, p->dus.s.sectors[1]);
    216 	entry = new_info_entry_in_frame(vbox, "Ticks");
    217 	entry_set_int_value(entry, p->dus.s.ticks[1]);
    218 
    219 	frame = gtk_frame_new("Shared");
    220 	gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2);
    221 	vbox = gtk_hbox_new(TRUE, 3);
    222 	gtk_container_add(GTK_CONTAINER(frame), vbox);
    223 	entry = new_info_entry_in_frame(vbox, "IO ticks");
    224 	entry_set_int_value(entry, p->dus.s.io_ticks);
    225 	entry = new_info_entry_in_frame(vbox, "Time in queue");
    226 	entry_set_int_value(entry, p->dus.s.time_in_queue);
    227 
    228 	util = 0.0;
    229 	if (p->dus.s.msec)
    230 		util = (double) 100 * p->dus.s.io_ticks / (double) p->dus.s.msec;
    231 	if (util > 100.0)
    232 		util = 100.0;
    233 
    234 	sprintf(tmp, "%3.2f%%", util);
    235 	entry = new_info_entry_in_frame(vbox, "Disk utilization");
    236 	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
    237 
    238 	gtk_widget_show_all(ge->results_window);
    239 	return 0;
    240 }
    241 
    242 static int gfio_disk_util_show(struct gfio_client *gc)
    243 {
    244 	struct gui_entry *ge = gc->ge;
    245 	GtkWidget *res_notebook;
    246 	int i;
    247 
    248 	if (!gc->nr_du)
    249 		return 1;
    250 
    251 	res_notebook = get_results_window(ge);
    252 
    253 	for (i = 0; i < gc->nr_du; i++) {
    254 		struct cmd_du_pdu *p = &gc->du[i];
    255 
    256 		__gfio_disk_util_show(res_notebook, gc, p);
    257 	}
    258 
    259 	gtk_widget_show_all(ge->results_window);
    260 	return 0;
    261 }
    262 
    263 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
    264 {
    265 	struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload;
    266 	struct gfio_client *gc = client->client_data;
    267 	struct gui_entry *ge = gc->ge;
    268 	unsigned int nr = gc->nr_du;
    269 
    270 	gc->du = realloc(gc->du, (nr + 1) * sizeof(struct cmd_du_pdu));
    271 	memcpy(&gc->du[nr], p, sizeof(*p));
    272 	gc->nr_du++;
    273 
    274 	gdk_threads_enter();
    275 	if (ge->results_window)
    276 		__gfio_disk_util_show(ge->results_notebook, gc, p);
    277 	else
    278 		gfio_disk_util_show(gc);
    279 	gdk_threads_leave();
    280 }
    281 
    282 extern int sum_stat_clients;
    283 extern struct thread_stat client_ts;
    284 extern struct group_run_stats client_gs;
    285 
    286 static int sum_stat_nr;
    287 
    288 static void gfio_thread_status_op(struct fio_client *client,
    289 				  struct fio_net_cmd *cmd)
    290 {
    291 	struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
    292 
    293 	gfio_display_ts(client, &p->ts, &p->rs);
    294 
    295 	if (sum_stat_clients == 1)
    296 		return;
    297 
    298 	sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
    299 	sum_group_stats(&client_gs, &p->rs);
    300 
    301 	client_ts.members++;
    302 	client_ts.thread_number = p->ts.thread_number;
    303 	client_ts.groupid = p->ts.groupid;
    304 
    305 	if (++sum_stat_nr == sum_stat_clients) {
    306 		strcpy(client_ts.name, "All clients");
    307 		gfio_display_ts(client, &client_ts, &client_gs);
    308 	}
    309 }
    310 
    311 static void gfio_group_stats_op(struct fio_client *client,
    312 				struct fio_net_cmd *cmd)
    313 {
    314 	/* We're ignoring group stats for now */
    315 }
    316 
    317 static void gfio_update_thread_status(struct gui_entry *ge,
    318 				      char *status_message, double perc)
    319 {
    320 	static char message[100];
    321 	const char *m = message;
    322 
    323 	strncpy(message, status_message, sizeof(message) - 1);
    324 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m);
    325 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0);
    326 	gtk_widget_queue_draw(ge->ui->window);
    327 }
    328 
    329 static void gfio_update_thread_status_all(struct gui *ui, char *status_message,
    330 					  double perc)
    331 {
    332 	static char message[100];
    333 	const char *m = message;
    334 
    335 	strncpy(message, status_message, sizeof(message) - 1);
    336 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m);
    337 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0);
    338 	gtk_widget_queue_draw(ui->window);
    339 }
    340 
    341 /*
    342  * Client specific ETA
    343  */
    344 static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je)
    345 {
    346 	struct gfio_client *gc = client->client_data;
    347 	struct gui_entry *ge = gc->ge;
    348 	static int eta_good;
    349 	char eta_str[128];
    350 	char output[256];
    351 	char tmp[32];
    352 	double perc = 0.0;
    353 	int i2p = 0;
    354 
    355 	gdk_threads_enter();
    356 
    357 	eta_str[0] = '\0';
    358 	output[0] = '\0';
    359 
    360 	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
    361 		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
    362 		eta_to_str(eta_str, je->eta_sec);
    363 	}
    364 
    365 	sprintf(tmp, "%u", je->nr_running);
    366 	gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp);
    367 	sprintf(tmp, "%u", je->files_open);
    368 	gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp);
    369 
    370 #if 0
    371 	if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
    372 	if (je->m_rate || je->t_rate) {
    373 		char *tr, *mr;
    374 
    375 		mr = num2str(je->m_rate, 4, 0, i2p);
    376 		tr = num2str(je->t_rate, 4, 0, i2p);
    377 		gtk_entry_set_text(GTK_ENTRY(ge->eta);
    378 		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
    379 		free(tr);
    380 		free(mr);
    381 	} else if (je->m_iops || je->t_iops)
    382 		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
    383 
    384 	gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---");
    385 	gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---");
    386 	gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---");
    387 	gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---");
    388 #endif
    389 
    390 	if (je->eta_sec != INT_MAX && je->nr_running) {
    391 		char *iops_str[DDIR_RWDIR_CNT];
    392 		char *rate_str[DDIR_RWDIR_CNT];
    393 		int i;
    394 
    395 		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
    396 			strcpy(output, "-.-% done");
    397 		else {
    398 			eta_good = 1;
    399 			perc *= 100.0;
    400 			sprintf(output, "%3.1f%% done", perc);
    401 		}
    402 
    403 		rate_str[0] = num2str(je->rate[0], 5, 10, i2p, 0);
    404 		rate_str[1] = num2str(je->rate[1], 5, 10, i2p, 0);
    405 		rate_str[2] = num2str(je->rate[2], 5, 10, i2p, 0);
    406 
    407 		iops_str[0] = num2str(je->iops[0], 4, 1, 0, 0);
    408 		iops_str[1] = num2str(je->iops[1], 4, 1, 0, 0);
    409 		iops_str[2] = num2str(je->iops[2], 4, 1, 0, 0);
    410 
    411 		gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]);
    412 		gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]);
    413 		gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]);
    414 		gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]);
    415 		gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_bw), rate_str[2]);
    416 		gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_iops), iops_str[2]);
    417 
    418 		graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]);
    419 		graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]);
    420 		graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.trim_iops, je->elapsed_sec, je->iops[2], iops_str[2]);
    421 		graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]);
    422 		graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]);
    423 		graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.trim_bw, je->elapsed_sec, je->rate[2], rate_str[2]);
    424 
    425 		for (i = 0; i < DDIR_RWDIR_CNT; i++) {
    426 			free(rate_str[i]);
    427 			free(iops_str[i]);
    428 		}
    429 	}
    430 
    431 	if (eta_str[0]) {
    432 		char *dst = output + strlen(output);
    433 
    434 		sprintf(dst, " - %s", eta_str);
    435 	}
    436 
    437 	gfio_update_thread_status(ge, output, perc);
    438 	gdk_threads_leave();
    439 }
    440 
    441 /*
    442  * Update ETA in main window for all clients
    443  */
    444 static void gfio_update_all_eta(struct jobs_eta *je)
    445 {
    446 	struct gui *ui = &main_ui;
    447 	static int eta_good;
    448 	char eta_str[128];
    449 	char output[256];
    450 	double perc = 0.0;
    451 	int i, i2p = 0;
    452 
    453 	gdk_threads_enter();
    454 
    455 	eta_str[0] = '\0';
    456 	output[0] = '\0';
    457 
    458 	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
    459 		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
    460 		eta_to_str(eta_str, je->eta_sec);
    461 	}
    462 
    463 #if 0
    464 	if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
    465 	if (je->m_rate || je->t_rate) {
    466 		char *tr, *mr;
    467 
    468 		mr = num2str(je->m_rate, 4, 0, i2p);
    469 		tr = num2str(je->t_rate, 4, 0, i2p);
    470 		gtk_entry_set_text(GTK_ENTRY(ui->eta);
    471 		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
    472 		free(tr);
    473 		free(mr);
    474 	} else if (je->m_iops || je->t_iops)
    475 		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
    476 
    477 	gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---");
    478 	gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---");
    479 	gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---");
    480 	gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---");
    481 #endif
    482 
    483 	entry_set_int_value(ui->eta.jobs, je->nr_running);
    484 
    485 	if (je->eta_sec != INT_MAX && je->nr_running) {
    486 		char *iops_str[3];
    487 		char *rate_str[3];
    488 
    489 		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
    490 			strcpy(output, "-.-% done");
    491 		else {
    492 			eta_good = 1;
    493 			perc *= 100.0;
    494 			sprintf(output, "%3.1f%% done", perc);
    495 		}
    496 
    497 		rate_str[0] = num2str(je->rate[0], 5, 10, i2p, 0);
    498 		rate_str[1] = num2str(je->rate[1], 5, 10, i2p, 0);
    499 		rate_str[2] = num2str(je->rate[2], 5, 10, i2p, 0);
    500 
    501 		iops_str[0] = num2str(je->iops[0], 4, 1, 0, 0);
    502 		iops_str[1] = num2str(je->iops[1], 4, 1, 0, 0);
    503 		iops_str[2] = num2str(je->iops[2], 4, 1, 0, 0);
    504 
    505 		gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]);
    506 		gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]);
    507 		gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]);
    508 		gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]);
    509 		gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_bw), rate_str[2]);
    510 		gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_iops), iops_str[2]);
    511 
    512 		graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]);
    513 		graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]);
    514 		graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.trim_iops, je->elapsed_sec, je->iops[2], iops_str[2]);
    515 		graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]);
    516 		graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]);
    517 		graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.trim_bw, je->elapsed_sec, je->rate[2], rate_str[2]);
    518 
    519 		for (i = 0; i < DDIR_RWDIR_CNT; i++) {
    520 			free(rate_str[i]);
    521 			free(iops_str[i]);
    522 		}
    523 	}
    524 
    525 	if (eta_str[0]) {
    526 		char *dst = output + strlen(output);
    527 
    528 		sprintf(dst, " - %s", eta_str);
    529 	}
    530 
    531 	gfio_update_thread_status_all(ui, output, perc);
    532 	gdk_threads_leave();
    533 }
    534 
    535 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
    536 {
    537 	struct cmd_probe_reply_pdu *probe = (struct cmd_probe_reply_pdu *) cmd->payload;
    538 	struct gfio_client *gc = client->client_data;
    539 	struct gui_entry *ge = gc->ge;
    540 	const char *os, *arch;
    541 
    542 	os = fio_get_os_string(probe->os);
    543 	if (!os)
    544 		os = "unknown";
    545 
    546 	arch = fio_get_arch_string(probe->arch);
    547 	if (!arch)
    548 		os = "unknown";
    549 
    550 	if (!client->name)
    551 		client->name = strdup((char *) probe->hostname);
    552 
    553 	gc->client_cpus = le32_to_cpu(probe->cpus);
    554 	gc->client_flags = le64_to_cpu(probe->flags);
    555 
    556 	gdk_threads_enter();
    557 
    558 	gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname);
    559 	gtk_label_set_text(GTK_LABEL(ge->probe.os), os);
    560 	gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch);
    561 	gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), (char *) probe->fio_version);
    562 
    563 	gfio_set_state(ge, GE_STATE_CONNECTED);
    564 
    565 	gdk_threads_leave();
    566 }
    567 
    568 static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd)
    569 {
    570 	struct gfio_client *gc = client->client_data;
    571 
    572 	gdk_threads_enter();
    573 	gfio_set_state(gc->ge, GE_STATE_NEW);
    574 	gdk_threads_leave();
    575 }
    576 
    577 static struct thread_options *gfio_client_add_job(struct gfio_client *gc,
    578 			struct thread_options_pack *top)
    579 {
    580 	struct gfio_client_options *gco;
    581 
    582 	gco = calloc(1, sizeof(*gco));
    583 	convert_thread_options_to_cpu(&gco->o, top);
    584 	INIT_FLIST_HEAD(&gco->list);
    585 	flist_add_tail(&gco->list, &gc->o_list);
    586 	gc->o_list_nr = 1;
    587 	return &gco->o;
    588 }
    589 
    590 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
    591 {
    592 	struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
    593 	struct gfio_client *gc = client->client_data;
    594 	struct gui_entry *ge = gc->ge;
    595 	struct thread_options *o;
    596 	char *c1, *c2, *c3, *c4;
    597 	char tmp[80];
    598 
    599 	p->thread_number = le32_to_cpu(p->thread_number);
    600 	p->groupid = le32_to_cpu(p->groupid);
    601 	o = gfio_client_add_job(gc, &p->top);
    602 
    603 	gdk_threads_enter();
    604 
    605 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ge->eta.names), (gchar *) o->name);
    606 	gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0);
    607 
    608 	sprintf(tmp, "%s %s", o->odirect ? "direct" : "buffered", ddir_str(o->td_ddir));
    609 	multitext_add_entry(&ge->eta.iotype, tmp);
    610 
    611 	c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
    612 	c2 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
    613 	c3 = fio_uint_to_kmg(o->min_bs[DDIR_READ]);
    614 	c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]);
    615 	sprintf(tmp, "%s-%s/%s-%s", c1, c2, c3, c4);
    616 	free(c1);
    617 	free(c2);
    618 	free(c3);
    619 	free(c4);
    620 	multitext_add_entry(&ge->eta.bs, tmp);
    621 
    622 	multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine);
    623 
    624 	sprintf(tmp, "%u", o->iodepth);
    625 	multitext_add_entry(&ge->eta.iodepth, tmp);
    626 
    627 	multitext_set_entry(&ge->eta.iotype, 0);
    628 	multitext_set_entry(&ge->eta.bs, 0);
    629 	multitext_set_entry(&ge->eta.ioengine, 0);
    630 	multitext_set_entry(&ge->eta.iodepth, 0);
    631 
    632 	gfio_set_state(ge, GE_STATE_JOB_SENT);
    633 
    634 	gdk_threads_leave();
    635 }
    636 
    637 static void gfio_update_job_op(struct fio_client *client,
    638 			       struct fio_net_cmd *cmd)
    639 {
    640 	uint32_t *pdu_error = (uint32_t *) cmd->payload;
    641 	struct gfio_client *gc = client->client_data;
    642 
    643 	gc->update_job_status = le32_to_cpu(*pdu_error);
    644 	gc->update_job_done = 1;
    645 }
    646 
    647 static void gfio_client_timed_out(struct fio_client *client)
    648 {
    649 	struct gfio_client *gc = client->client_data;
    650 	char buf[256];
    651 
    652 	gdk_threads_enter();
    653 
    654 	gfio_set_state(gc->ge, GE_STATE_NEW);
    655 	clear_ge_ui_info(gc->ge);
    656 
    657 	sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
    658 	gfio_report_info(gc->ge->ui, "Network timeout", buf);
    659 
    660 	gdk_threads_leave();
    661 }
    662 
    663 static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd)
    664 {
    665 	struct gfio_client *gc = client->client_data;
    666 
    667 	gdk_threads_enter();
    668 
    669 	gfio_set_state(gc->ge, GE_STATE_JOB_DONE);
    670 
    671 	if (gc->err_entry)
    672 		entry_set_int_value(gc->err_entry, client->error);
    673 
    674 	gdk_threads_leave();
    675 }
    676 
    677 static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd)
    678 {
    679 	struct gfio_client *gc = client->client_data;
    680 
    681 	gdk_threads_enter();
    682 	gfio_set_state(gc->ge, GE_STATE_JOB_STARTED);
    683 	gdk_threads_leave();
    684 }
    685 
    686 static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd)
    687 {
    688 	struct gfio_client *gc = client->client_data;
    689 
    690 	gdk_threads_enter();
    691 	gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING);
    692 	gdk_threads_leave();
    693 }
    694 
    695 static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu)
    696 {
    697 	printf("got iolog: name=%s, type=%u, entries=%lu\n", pdu->name, pdu->log_type, (unsigned long) pdu->nr_samples);
    698 	free(pdu);
    699 }
    700 
    701 static void gfio_add_total_depths_tree(GtkListStore *model,
    702 				       struct thread_stat *ts, unsigned int len)
    703 {
    704 	double io_u_dist[FIO_IO_U_MAP_NR];
    705 	GtkTreeIter iter;
    706 	/* Bits 1-6, and 8 */
    707 	const int add_mask = 0x17e;
    708 	int i, j;
    709 
    710 	stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist);
    711 
    712 	gtk_list_store_append(model, &iter);
    713 
    714 	gtk_list_store_set(model, &iter, 0, "Total", -1);
    715 
    716 	for (i = 1, j = 0; i < len; i++) {
    717 		char fbuf[32];
    718 
    719 		if (!(add_mask & (1UL << (i - 1))))
    720 			sprintf(fbuf, "0.0%%");
    721 		else {
    722 			sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
    723 			j++;
    724 		}
    725 
    726 		gtk_list_store_set(model, &iter, i, fbuf, -1);
    727 	}
    728 
    729 }
    730 
    731 static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts,
    732 				 struct group_run_stats *rs)
    733 {
    734 	unsigned int nr = gc->nr_results;
    735 
    736 	gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results));
    737 	memcpy(&gc->results[nr].ts, ts, sizeof(*ts));
    738 	memcpy(&gc->results[nr].gs, rs, sizeof(*rs));
    739 	gc->nr_results++;
    740 }
    741 
    742 static void gfio_add_sc_depths_tree(GtkListStore *model,
    743 				    struct thread_stat *ts, unsigned int len,
    744 				    int submit)
    745 {
    746 	double io_u_dist[FIO_IO_U_MAP_NR];
    747 	GtkTreeIter iter;
    748 	/* Bits 0, and 3-8 */
    749 	const int add_mask = 0x1f9;
    750 	int i, j;
    751 
    752 	if (submit)
    753 		stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
    754 	else
    755 		stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
    756 
    757 	gtk_list_store_append(model, &iter);
    758 
    759 	gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
    760 
    761 	for (i = 1, j = 0; i < len; i++) {
    762 		char fbuf[32];
    763 
    764 		if (!(add_mask & (1UL << (i - 1))))
    765 			sprintf(fbuf, "0.0%%");
    766 		else {
    767 			sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
    768 			j++;
    769 		}
    770 
    771 		gtk_list_store_set(model, &iter, i, fbuf, -1);
    772 	}
    773 
    774 }
    775 
    776 static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
    777 {
    778 	GtkWidget *frame, *box, *tree_view = NULL;
    779 	GtkTreeSelection *selection;
    780 	GtkListStore *model;
    781 	int i;
    782 	const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
    783 	const int nr_labels = ARRAY_SIZE(labels);
    784 	GType types[nr_labels];
    785 
    786 	frame = gtk_frame_new("IO depths");
    787 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    788 
    789 	box = gtk_hbox_new(FALSE, 3);
    790 	gtk_container_add(GTK_CONTAINER(frame), box);
    791 
    792 	for (i = 0; i < nr_labels; i++)
    793 		types[i] = G_TYPE_STRING;
    794 
    795 	model = gtk_list_store_newv(nr_labels, types);
    796 
    797 	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
    798 	gtk_widget_set_can_focus(tree_view, FALSE);
    799 
    800 	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
    801 		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
    802 
    803 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
    804 	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
    805 
    806 	for (i = 0; i < nr_labels; i++)
    807 		tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
    808 
    809 	gfio_add_total_depths_tree(model, ts, nr_labels);
    810 	gfio_add_sc_depths_tree(model, ts, nr_labels, 1);
    811 	gfio_add_sc_depths_tree(model, ts, nr_labels, 0);
    812 
    813 	gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, TRUE, 3);
    814 }
    815 
    816 static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
    817 {
    818 	GtkWidget *box, *frame, *entry;
    819 	double usr_cpu, sys_cpu;
    820 	unsigned long runtime;
    821 	char tmp[32];
    822 
    823 	runtime = ts->total_run_time;
    824 	if (runtime) {
    825 		double runt = (double) runtime;
    826 
    827 		usr_cpu = (double) ts->usr_time * 100 / runt;
    828 		sys_cpu = (double) ts->sys_time * 100 / runt;
    829 	} else {
    830 		usr_cpu = 0;
    831 		sys_cpu = 0;
    832 	}
    833 
    834 	frame = gtk_frame_new("OS resources");
    835 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    836 
    837 	box = gtk_hbox_new(FALSE, 3);
    838 	gtk_container_add(GTK_CONTAINER(frame), box);
    839 
    840 	entry = new_info_entry_in_frame(box, "User CPU");
    841 	sprintf(tmp, "%3.2f%%", usr_cpu);
    842 	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
    843 	entry = new_info_entry_in_frame(box, "System CPU");
    844 	sprintf(tmp, "%3.2f%%", sys_cpu);
    845 	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
    846 	entry = new_info_entry_in_frame(box, "Context switches");
    847 	entry_set_int_value(entry, ts->ctx);
    848 	entry = new_info_entry_in_frame(box, "Major faults");
    849 	entry_set_int_value(entry, ts->majf);
    850 	entry = new_info_entry_in_frame(box, "Minor faults");
    851 	entry_set_int_value(entry, ts->minf);
    852 }
    853 
    854 static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels,
    855 					  int num)
    856 {
    857 	GtkWidget *tree_view;
    858 	GtkTreeSelection *selection;
    859 	GtkListStore *model;
    860 	GtkTreeIter iter;
    861 	GType *types;
    862 	int i;
    863 
    864 	types = malloc(num * sizeof(GType));
    865 
    866 	for (i = 0; i < num; i++)
    867 		types[i] = G_TYPE_STRING;
    868 
    869 	model = gtk_list_store_newv(num, types);
    870 	free(types);
    871 	types = NULL;
    872 
    873 	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
    874 	gtk_widget_set_can_focus(tree_view, FALSE);
    875 
    876 	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
    877 		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
    878 
    879 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
    880 	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
    881 
    882 	for (i = 0; i < num; i++)
    883 		tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
    884 
    885 	gtk_list_store_append(model, &iter);
    886 
    887 	for (i = 0; i < num; i++) {
    888 		char fbuf[32];
    889 
    890 		if (lat[i] <= 0.0)
    891 			sprintf(fbuf, "0.00");
    892 		else
    893 			sprintf(fbuf, "%3.2f%%", lat[i]);
    894 
    895 		gtk_list_store_set(model, &iter, i, fbuf, -1);
    896 	}
    897 
    898 	return tree_view;
    899 }
    900 
    901 static struct graph *setup_lat_bucket_graph(const char *title, double *lat,
    902 					    const char **labels,
    903 					    unsigned int len,
    904 					    double xdim, double ydim)
    905 {
    906 	struct graph *g;
    907 	int i;
    908 
    909 	g = graph_new(xdim, ydim, gfio_graph_font);
    910 	graph_title(g, title);
    911 	graph_x_title(g, "Buckets");
    912 	graph_y_title(g, "Percent");
    913 
    914 	for (i = 0; i < len; i++) {
    915 		graph_label_t l;
    916 
    917 		l = graph_add_label(g, labels[i]);
    918 		graph_add_data(g, l, lat[i]);
    919 	}
    920 
    921 	return g;
    922 }
    923 
    924 static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
    925 {
    926 	struct graph *g = p;
    927 	cairo_t *cr;
    928 
    929 	cr = gdk_cairo_create(gtk_widget_get_window(w));
    930 #if 0
    931 	if (graph_has_tooltips(g)) {
    932 		g_object_set(w, "has-tooltip", TRUE, NULL);
    933 		g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g);
    934 	}
    935 #endif
    936 	cairo_set_source_rgb(cr, 0, 0, 0);
    937 	bar_graph_draw(g, cr);
    938 	cairo_destroy(cr);
    939 
    940 	return FALSE;
    941 }
    942 
    943 static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event,
    944 				       gpointer data)
    945 {
    946 	guint width = gtk_widget_get_allocated_width(w);
    947 	guint height = gtk_widget_get_allocated_height(w);
    948 	struct graph *g = data;
    949 
    950 	graph_set_size(g, width, height);
    951 	graph_set_size(g, width, height);
    952 	graph_set_position(g, 0, 0);
    953 	return TRUE;
    954 }
    955 
    956 static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox,
    957 				      struct thread_stat *ts)
    958 {
    959 	double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR];
    960 	const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u",
    961 				 "250u", "500u", "750u", "1m", "2m",
    962 				 "4m", "10m", "20m", "50m", "100m",
    963 				 "250m", "500m", "750m", "1s", "2s", ">= 2s" };
    964 	int start, end, i;
    965 	const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR;
    966 	GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area;
    967 	struct gui_entry *ge = gc->ge;
    968 
    969 	stat_calc_lat_u(ts, io_u_lat);
    970 	stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]);
    971 
    972 	/*
    973 	 * Found out which first bucket has entries, and which last bucket
    974 	 */
    975 	start = end = -1U;
    976 	for (i = 0; i < total; i++) {
    977 		if (io_u_lat[i] == 0.00)
    978 			continue;
    979 
    980 		if (start == -1U)
    981 			start = i;
    982 		end = i;
    983 	}
    984 
    985 	/*
    986 	 * No entries...
    987 	 */
    988 	if (start == -1U)
    989 		return;
    990 
    991 	tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1);
    992 	ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0);
    993 
    994 	frame = gtk_frame_new("Latency buckets");
    995 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
    996 
    997 	completion_vbox = gtk_vbox_new(FALSE, 3);
    998 	gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
    999 	hbox = gtk_hbox_new(FALSE, 3);
   1000 	gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
   1001 
   1002 	drawing_area = gtk_drawing_area_new();
   1003 	gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
   1004 	gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
   1005 	gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
   1006 	g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph);
   1007 	g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph);
   1008 
   1009 	gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3);
   1010 }
   1011 
   1012 static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
   1013 			  unsigned long max, double mean, double dev)
   1014 {
   1015 	const char *base = "(usec)";
   1016 	GtkWidget *hbox, *label, *frame;
   1017 	char *minp, *maxp;
   1018 	char tmp[64];
   1019 
   1020 	if (!usec_to_msec(&min, &max, &mean, &dev))
   1021 		base = "(msec)";
   1022 
   1023 	minp = num2str(min, 6, 1, 0, 0);
   1024 	maxp = num2str(max, 6, 1, 0, 0);
   1025 
   1026 	sprintf(tmp, "%s %s", name, base);
   1027 	frame = gtk_frame_new(tmp);
   1028 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
   1029 
   1030 	hbox = gtk_hbox_new(FALSE, 3);
   1031 	gtk_container_add(GTK_CONTAINER(frame), hbox);
   1032 
   1033 	label = new_info_label_in_frame(hbox, "Minimum");
   1034 	gtk_label_set_text(GTK_LABEL(label), minp);
   1035 	label = new_info_label_in_frame(hbox, "Maximum");
   1036 	gtk_label_set_text(GTK_LABEL(label), maxp);
   1037 	label = new_info_label_in_frame(hbox, "Average");
   1038 	sprintf(tmp, "%5.02f", mean);
   1039 	gtk_label_set_text(GTK_LABEL(label), tmp);
   1040 	label = new_info_label_in_frame(hbox, "Standard deviation");
   1041 	sprintf(tmp, "%5.02f", dev);
   1042 	gtk_label_set_text(GTK_LABEL(label), tmp);
   1043 
   1044 	free(minp);
   1045 	free(maxp);
   1046 }
   1047 
   1048 static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
   1049 					       fio_fp64_t *plist,
   1050 					       unsigned int len,
   1051 					       const char *base,
   1052 					       unsigned int scale)
   1053 {
   1054 	GType types[FIO_IO_U_LIST_MAX_LEN];
   1055 	GtkWidget *tree_view;
   1056 	GtkTreeSelection *selection;
   1057 	GtkListStore *model;
   1058 	GtkTreeIter iter;
   1059 	int i;
   1060 
   1061 	for (i = 0; i < len; i++)
   1062 		types[i] = G_TYPE_INT;
   1063 
   1064 	model = gtk_list_store_newv(len, types);
   1065 
   1066 	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
   1067 	gtk_widget_set_can_focus(tree_view, FALSE);
   1068 
   1069 	g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
   1070 		"enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
   1071 
   1072 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
   1073 	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
   1074 
   1075 	for (i = 0; i < len; i++) {
   1076 		char fbuf[8];
   1077 
   1078 		sprintf(fbuf, "%2.2f%%", plist[i].u.f);
   1079 		tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
   1080 	}
   1081 
   1082 	gtk_list_store_append(model, &iter);
   1083 
   1084 	for (i = 0; i < len; i++) {
   1085 		if (scale)
   1086 			ovals[i] = (ovals[i] + 999) / 1000;
   1087 		gtk_list_store_set(model, &iter, i, ovals[i], -1);
   1088 	}
   1089 
   1090 	return tree_view;
   1091 }
   1092 
   1093 static struct graph *setup_clat_graph(char *title, unsigned int *ovals,
   1094 				      fio_fp64_t *plist,
   1095 				      unsigned int len,
   1096 				      double xdim, double ydim)
   1097 {
   1098 	struct graph *g;
   1099 	int i;
   1100 
   1101 	g = graph_new(xdim, ydim, gfio_graph_font);
   1102 	graph_title(g, title);
   1103 	graph_x_title(g, "Percentile");
   1104 	graph_y_title(g, "Time");
   1105 
   1106 	for (i = 0; i < len; i++) {
   1107 		graph_label_t l;
   1108 		char fbuf[8];
   1109 
   1110 		sprintf(fbuf, "%2.2f%%", plist[i].u.f);
   1111 		l = graph_add_label(g, fbuf);
   1112 		graph_add_data(g, l, (double) ovals[i]);
   1113 	}
   1114 
   1115 	return g;
   1116 }
   1117 
   1118 static void gfio_show_clat_percentiles(struct gfio_client *gc,
   1119 				       GtkWidget *vbox, struct thread_stat *ts,
   1120 				       int ddir)
   1121 {
   1122 	unsigned int *io_u_plat = ts->io_u_plat[ddir];
   1123 	unsigned long nr = ts->clat_stat[ddir].samples;
   1124 	fio_fp64_t *plist = ts->percentile_list;
   1125 	unsigned int *ovals, len, minv, maxv, scale_down;
   1126 	const char *base;
   1127 	GtkWidget *tree_view, *frame, *hbox, *drawing_area, *completion_vbox;
   1128 	struct gui_entry *ge = gc->ge;
   1129 	char tmp[64];
   1130 
   1131 	len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
   1132 	if (!len)
   1133 		goto out;
   1134 
   1135 	/*
   1136 	 * We default to usecs, but if the value range is such that we
   1137 	 * should scale down to msecs, do that.
   1138 	 */
   1139 	if (minv > 2000 && maxv > 99999) {
   1140 		scale_down = 1;
   1141 		base = "msec";
   1142 	} else {
   1143 		scale_down = 0;
   1144 		base = "usec";
   1145 	}
   1146 
   1147 	sprintf(tmp, "Completion percentiles (%s)", base);
   1148 	tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
   1149 	ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0);
   1150 
   1151 	frame = gtk_frame_new(tmp);
   1152 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
   1153 
   1154 	completion_vbox = gtk_vbox_new(FALSE, 3);
   1155 	gtk_container_add(GTK_CONTAINER(frame), completion_vbox);
   1156 	hbox = gtk_hbox_new(FALSE, 3);
   1157 	gtk_container_add(GTK_CONTAINER(completion_vbox), hbox);
   1158 	drawing_area = gtk_drawing_area_new();
   1159 	gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300);
   1160 	gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
   1161 	gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area);
   1162 	g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->clat_graph);
   1163 	g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->clat_graph);
   1164 
   1165 	gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3);
   1166 out:
   1167 	if (ovals)
   1168 		free(ovals);
   1169 }
   1170 
   1171 #define GFIO_CLAT	1
   1172 #define GFIO_SLAT	2
   1173 #define GFIO_LAT	4
   1174 
   1175 static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
   1176 				  struct group_run_stats *rs,
   1177 				  struct thread_stat *ts, int ddir)
   1178 {
   1179 	const char *ddir_label[3] = { "Read", "Write", "Trim" };
   1180 	GtkWidget *frame, *label, *box, *vbox, *main_vbox;
   1181 	unsigned long min[3], max[3], runt;
   1182 	unsigned long long bw, iops;
   1183 	unsigned int flags = 0;
   1184 	double mean[3], dev[3];
   1185 	char *io_p, *bw_p, *iops_p;
   1186 	int i2p;
   1187 
   1188 	if (!ts->runtime[ddir])
   1189 		return;
   1190 
   1191 	i2p = is_power_of_2(rs->kb_base);
   1192 	runt = ts->runtime[ddir];
   1193 
   1194 	bw = (1000 * ts->io_bytes[ddir]) / runt;
   1195 	io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p, 8);
   1196 	bw_p = num2str(bw, 6, 1, i2p, ts->unit_base);
   1197 
   1198 	iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
   1199 	iops_p = num2str(iops, 6, 1, 0, 0);
   1200 
   1201 	box = gtk_hbox_new(FALSE, 3);
   1202 	gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
   1203 
   1204 	frame = gtk_frame_new(ddir_label[ddir]);
   1205 	gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 5);
   1206 
   1207 	main_vbox = gtk_vbox_new(FALSE, 3);
   1208 	gtk_container_add(GTK_CONTAINER(frame), main_vbox);
   1209 
   1210 	box = gtk_hbox_new(FALSE, 3);
   1211 	gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
   1212 
   1213 	label = new_info_label_in_frame(box, "IO");
   1214 	gtk_label_set_text(GTK_LABEL(label), io_p);
   1215 	label = new_info_label_in_frame(box, "Bandwidth");
   1216 	gtk_label_set_text(GTK_LABEL(label), bw_p);
   1217 	label = new_info_label_in_frame(box, "IOPS");
   1218 	gtk_label_set_text(GTK_LABEL(label), iops_p);
   1219 	label = new_info_label_in_frame(box, "Runtime (msec)");
   1220 	label_set_int_value(label, ts->runtime[ddir]);
   1221 
   1222 	if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) {
   1223 		double p_of_agg = 100.0;
   1224 		const char *bw_str = "KB";
   1225 		char tmp[32];
   1226 
   1227 		if (rs->agg[ddir]) {
   1228 			p_of_agg = mean[0] * 100 / (double) rs->agg[ddir];
   1229 			if (p_of_agg > 100.0)
   1230 				p_of_agg = 100.0;
   1231 		}
   1232 
   1233 		if (mean[0] > 999999.9) {
   1234 			min[0] /= 1000.0;
   1235 			max[0] /= 1000.0;
   1236 			mean[0] /= 1000.0;
   1237 			dev[0] /= 1000.0;
   1238 			bw_str = "MB";
   1239 		}
   1240 
   1241 		sprintf(tmp, "Bandwidth (%s)", bw_str);
   1242 		frame = gtk_frame_new(tmp);
   1243 		gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
   1244 
   1245 		box = gtk_hbox_new(FALSE, 3);
   1246 		gtk_container_add(GTK_CONTAINER(frame), box);
   1247 
   1248 		label = new_info_label_in_frame(box, "Minimum");
   1249 		label_set_int_value(label, min[0]);
   1250 		label = new_info_label_in_frame(box, "Maximum");
   1251 		label_set_int_value(label, max[0]);
   1252 		label = new_info_label_in_frame(box, "Percentage of jobs");
   1253 		sprintf(tmp, "%3.2f%%", p_of_agg);
   1254 		gtk_label_set_text(GTK_LABEL(label), tmp);
   1255 		label = new_info_label_in_frame(box, "Average");
   1256 		sprintf(tmp, "%5.02f", mean[0]);
   1257 		gtk_label_set_text(GTK_LABEL(label), tmp);
   1258 		label = new_info_label_in_frame(box, "Standard deviation");
   1259 		sprintf(tmp, "%5.02f", dev[0]);
   1260 		gtk_label_set_text(GTK_LABEL(label), tmp);
   1261 	}
   1262 
   1263 	if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0]))
   1264 		flags |= GFIO_SLAT;
   1265 	if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1]))
   1266 		flags |= GFIO_CLAT;
   1267 	if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2]))
   1268 		flags |= GFIO_LAT;
   1269 
   1270 	if (flags) {
   1271 		frame = gtk_frame_new("Latency");
   1272 		gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
   1273 
   1274 		vbox = gtk_vbox_new(FALSE, 3);
   1275 		gtk_container_add(GTK_CONTAINER(frame), vbox);
   1276 
   1277 		if (flags & GFIO_SLAT)
   1278 			gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]);
   1279 		if (flags & GFIO_CLAT)
   1280 			gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]);
   1281 		if (flags & GFIO_LAT)
   1282 			gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
   1283 	}
   1284 
   1285 	if (ts->clat_percentiles)
   1286 		gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
   1287 
   1288 	free(io_p);
   1289 	free(bw_p);
   1290 	free(iops_p);
   1291 }
   1292 
   1293 static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc,
   1294 				       struct thread_stat *ts,
   1295 				       struct group_run_stats *rs)
   1296 {
   1297 	GtkWidget *box, *vbox, *entry, *scroll;
   1298 	int i;
   1299 
   1300 	scroll = gtk_scrolled_window_new(NULL, NULL);
   1301 	gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
   1302 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
   1303 
   1304 	vbox = gtk_vbox_new(FALSE, 3);
   1305 
   1306 	box = gtk_hbox_new(FALSE, 0);
   1307 	gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5);
   1308 
   1309 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox);
   1310 
   1311 	gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name));
   1312 
   1313 	entry = new_info_entry_in_frame(box, "Name");
   1314 	gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
   1315 	if (strlen(ts->description)) {
   1316 		entry = new_info_entry_in_frame(box, "Description");
   1317 		gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
   1318 	}
   1319 	entry = new_info_entry_in_frame(box, "Group ID");
   1320 	entry_set_int_value(entry, ts->groupid);
   1321 	entry = new_info_entry_in_frame(box, "Jobs");
   1322 	entry_set_int_value(entry, ts->members);
   1323 	gc->err_entry = entry = new_info_entry_in_frame(box, "Error");
   1324 	entry_set_int_value(entry, ts->error);
   1325 	entry = new_info_entry_in_frame(box, "PID");
   1326 	entry_set_int_value(entry, ts->pid);
   1327 
   1328 	for (i = 0; i < DDIR_RWDIR_CNT; i++) {
   1329 		if (ts->io_bytes[i])
   1330 			gfio_show_ddir_status(gc, vbox, rs, ts, i);
   1331 	}
   1332 
   1333 	gfio_show_latency_buckets(gc, vbox, ts);
   1334 	gfio_show_cpu_usage(vbox, ts);
   1335 	gfio_show_io_depths(vbox, ts);
   1336 }
   1337 
   1338 void gfio_display_end_results(struct gfio_client *gc)
   1339 {
   1340 	struct gui_entry *ge = gc->ge;
   1341 	GtkWidget *res_notebook;
   1342 	int i;
   1343 
   1344 	res_notebook = get_results_window(ge);
   1345 
   1346 	for (i = 0; i < gc->nr_results; i++) {
   1347 		struct end_results *e = &gc->results[i];
   1348 
   1349 		__gfio_display_end_results(res_notebook, gc, &e->ts, &e->gs);
   1350 	}
   1351 
   1352 	if (gfio_disk_util_show(gc))
   1353 		gtk_widget_show_all(ge->results_window);
   1354 }
   1355 
   1356 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
   1357 			    struct group_run_stats *rs)
   1358 {
   1359 	struct gfio_client *gc = client->client_data;
   1360 	struct gui_entry *ge = gc->ge;
   1361 
   1362 	gfio_add_end_results(gc, ts, rs);
   1363 
   1364 	gdk_threads_enter();
   1365 	if (ge->results_window)
   1366 		__gfio_display_end_results(ge->results_notebook, gc, ts, rs);
   1367 	else
   1368 		gfio_display_end_results(gc);
   1369 	gdk_threads_leave();
   1370 }
   1371 
   1372 static void gfio_client_removed(struct fio_client *client)
   1373 {
   1374 	struct gfio_client *gc = client->client_data;
   1375 
   1376 	assert(gc->client == client);
   1377 	fio_put_client(gc->client);
   1378 	gc->client = NULL;
   1379 }
   1380 
   1381 struct client_ops gfio_client_ops = {
   1382 	.text			= gfio_text_op,
   1383 	.disk_util		= gfio_disk_util_op,
   1384 	.thread_status		= gfio_thread_status_op,
   1385 	.group_stats		= gfio_group_stats_op,
   1386 	.jobs_eta		= gfio_update_client_eta,
   1387 	.eta			= gfio_update_all_eta,
   1388 	.probe			= gfio_probe_op,
   1389 	.quit			= gfio_quit_op,
   1390 	.add_job		= gfio_add_job_op,
   1391 	.update_job		= gfio_update_job_op,
   1392 	.timed_out		= gfio_client_timed_out,
   1393 	.stop			= gfio_client_stop,
   1394 	.start			= gfio_client_start,
   1395 	.job_start		= gfio_client_job_start,
   1396 	.iolog			= gfio_client_iolog,
   1397 	.removed		= gfio_client_removed,
   1398 	.eta_msec		= FIO_CLIENT_DEF_ETA_MSEC,
   1399 	.stay_connected		= 1,
   1400 	.client_type		= FIO_CLIENT_TYPE_GUI,
   1401 };
   1402