client/server: add support for passing disk_util structures

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/client.c b/client.c
index e443dc3..56accf0 100644
--- a/client.c
+++ b/client.c
@@ -41,6 +41,7 @@
 
 	int skip_newline;
 	int is_sock;
+	int disk_stats_shown;
 
 	struct flist_head eta_list;
 	struct client_eta *eta_in_flight;
@@ -601,6 +602,54 @@
 	show_group_stats(gs);
 }
 
+static void convert_agg(struct disk_util_agg *agg)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		agg->ios[i]	= le32_to_cpu(agg->ios[i]);
+		agg->merges[i]	= le32_to_cpu(agg->merges[i]);
+		agg->sectors[i]	= le64_to_cpu(agg->sectors[i]);
+		agg->ticks[i]	= le32_to_cpu(agg->ticks[i]);
+	}
+
+	agg->io_ticks		= le32_to_cpu(agg->io_ticks);
+	agg->time_in_queue	= le32_to_cpu(agg->time_in_queue);
+	agg->slavecount		= le32_to_cpu(agg->slavecount);
+	agg->max_util.u.f	= __le64_to_cpu(fio_uint64_to_double(agg->max_util.u.i));
+}
+
+static void convert_dus(struct disk_util_stat *dus)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		dus->ios[i]	= le32_to_cpu(dus->ios[i]);
+		dus->merges[i]	= le32_to_cpu(dus->merges[i]);
+		dus->sectors[i]	= le64_to_cpu(dus->sectors[i]);
+		dus->ticks[i]	= le32_to_cpu(dus->ticks[i]);
+	}
+
+	dus->io_ticks		= le32_to_cpu(dus->io_ticks);
+	dus->time_in_queue	= le32_to_cpu(dus->time_in_queue);
+	dus->msec		= le64_to_cpu(dus->msec);
+}
+
+static void handle_du(struct fio_client *client, struct fio_net_cmd *cmd)
+{
+	struct cmd_du_pdu *du = (struct cmd_du_pdu *) cmd->payload;
+
+	convert_dus(&du->dus);
+	convert_agg(&du->agg);
+
+	if (!client->disk_stats_shown) {
+		client->disk_stats_shown = 1;
+		log_info("\nDisk stats (read/write):\n");
+	}
+
+	print_disk_util(&du->dus, &du->agg);
+}
+
 static void convert_jobs_eta(struct jobs_eta *je)
 {
 	int i;
@@ -753,6 +802,10 @@
 		free(cmd);
 		break;
 		}
+	case FIO_NET_CMD_DU:
+		handle_du(client, cmd);
+		free(cmd);
+		break;
 	case FIO_NET_CMD_TS:
 		handle_ts(cmd);
 		free(cmd);
diff --git a/diskutil.c b/diskutil.c
index 9fc3095..67c47bc 100644
--- a/diskutil.c
+++ b/diskutil.c
@@ -14,7 +14,7 @@
 static int last_majdev, last_mindev;
 static struct disk_util *last_du;
 
-static struct flist_head disk_list = FLIST_HEAD_INIT(disk_list);
+FLIST_HEAD(disk_list);
 
 static struct disk_util *__init_per_file_disk_util(struct thread_data *td,
 		int majdev, int mindev, char *path);
@@ -33,13 +33,13 @@
 	}
 	
 	fio_mutex_remove(du->lock);
-	sfree(du->name);
 	sfree(du);
 }
 
 static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus)
 {
 	unsigned in_flight;
+	unsigned long long sectors[2];
 	char line[256];
 	FILE *f;
 	char *p;
@@ -60,13 +60,15 @@
 	dprint(FD_DISKUTIL, "%s: %s", du->path, p);
 
 	ret = sscanf(p, "%u %u %llu %u %u %u %llu %u %u %u %u\n", &dus->ios[0],
-					&dus->merges[0], &dus->sectors[0],
+					&dus->merges[0], &sectors[0],
 					&dus->ticks[0], &dus->ios[1],
-					&dus->merges[1], &dus->sectors[1],
+					&dus->merges[1], &sectors[1],
 					&dus->ticks[1], &in_flight,
 					&dus->io_ticks, &dus->time_in_queue);
 	fclose(f);
 	dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 1);
+	dus->sectors[0] = sectors[0];
+	dus->sectors[1] = sectors[1];
 	return ret != 11;
 }
 
@@ -95,7 +97,7 @@
 	dus->time_in_queue += (__dus.time_in_queue - ldus->time_in_queue);
 
 	fio_gettime(&t, NULL);
-	du->msec += mtime_since(&du->time, &t);
+	dus->msec += mtime_since(&du->time, &t);
 	memcpy(&du->time, &t, sizeof(t));
 	memcpy(ldus, &__dus, sizeof(__dus));
 }
@@ -265,7 +267,7 @@
 	memset(du, 0, sizeof(*du));
 	INIT_FLIST_HEAD(&du->list);
 	sprintf(du->path, "%s/stat", path);
-	du->name = smalloc_strdup(basename(path));
+	strncpy((char *) du->dus.name, basename(path), FIO_DU_NAME_SZ);
 	du->sysfs_root = path;
 	du->major = majdev;
 	du->minor = mindev;
@@ -277,15 +279,15 @@
 	flist_for_each(entry, &disk_list) {
 		__du = flist_entry(entry, struct disk_util, list);
 
-		dprint(FD_DISKUTIL, "found %s in list\n", __du->name);
+		dprint(FD_DISKUTIL, "found %s in list\n", __du->dus.name);
 
-		if (!strcmp(du->name, __du->name)) {
+		if (!strcmp((char *) du->dus.name, (char *) __du->dus.name)) {
 			disk_util_free(du);
 			return __du;
 		}
 	}
 
-	dprint(FD_DISKUTIL, "add %s to list\n", du->name);
+	dprint(FD_DISKUTIL, "add %s to list\n", du->dus.name);
 
 	fio_gettime(&du->time, NULL);
 	get_io_ticks(du, &du->last_dus);
@@ -451,61 +453,103 @@
 		f->du = __init_disk_util(td, f);
 }
 
+static void show_agg_stats(struct disk_util_agg *agg)
+{
+	if (!agg->slavecount)
+		return;
+
+	log_info(", aggrios=%u/%u, aggrmerge=%u/%u, aggrticks=%u/%u,"
+			" aggrin_queue=%u, aggrutil=%3.2f%%",
+			agg->ios[0] / agg->slavecount,
+			agg->ios[1] / agg->slavecount,
+			agg->merges[0] / agg->slavecount,
+			agg->merges[1] / agg->slavecount,
+			agg->ticks[0] / agg->slavecount,
+			agg->ticks[1] / agg->slavecount,
+			agg->time_in_queue / agg->slavecount,
+			agg->max_util.u.f);
+}
+
 static void aggregate_slaves_stats(struct disk_util *masterdu)
 {
+	struct disk_util_agg *agg = &masterdu->agg;
 	struct disk_util_stat *dus;
 	struct flist_head *entry;
 	struct disk_util *slavedu;
-	double util, max_util = 0;
-	int slavecount = 0;
-
-	unsigned merges[2] = { 0, };
-	unsigned ticks[2] = { 0, };
-	unsigned time_in_queue = { 0, };
-	unsigned long long sectors[2] = { 0, };
-	unsigned ios[2] = { 0, };
+	double util;
 
 	flist_for_each(entry, &masterdu->slaves) {
 		slavedu = flist_entry(entry, struct disk_util, slavelist);
 		dus = &slavedu->dus;
-		ios[0] += dus->ios[0];
-		ios[1] += dus->ios[1];
-		merges[0] += dus->merges[0];
-		merges[1] += dus->merges[1];
-		sectors[0] += dus->sectors[0];
-		sectors[1] += dus->sectors[1];
-		ticks[0] += dus->ticks[0];
-		ticks[1] += dus->ticks[1];
-		time_in_queue += dus->time_in_queue;
-		++slavecount;
+		agg->ios[0] += dus->ios[0];
+		agg->ios[1] += dus->ios[1];
+		agg->merges[0] += dus->merges[0];
+		agg->merges[1] += dus->merges[1];
+		agg->sectors[0] += dus->sectors[0];
+		agg->sectors[1] += dus->sectors[1];
+		agg->ticks[0] += dus->ticks[0];
+		agg->ticks[1] += dus->ticks[1];
+		agg->time_in_queue += dus->time_in_queue;
+		agg->slavecount++;
 
-		util = (double) (100 * dus->io_ticks / (double) slavedu->msec);
+		util = (double) (100 * dus->io_ticks / (double) slavedu->dus.msec);
 		/* System utilization is the utilization of the
 		 * component with the highest utilization.
 		 */
-		if (util > max_util)
-			max_util = util;
+		if (util > agg->max_util.u.f)
+			agg->max_util.u.f = util;
 
 	}
 
-	if (max_util > 100.0)
-		max_util = 100.0;
+	if (agg->max_util.u.f > 100.0)
+		agg->max_util.u.f = 100.0;
+}
 
-	log_info(", aggrios=%u/%u, aggrmerge=%u/%u, aggrticks=%u/%u,"
-			" aggrin_queue=%u, aggrutil=%3.2f%%",
-			ios[0]/slavecount, ios[1]/slavecount,
-			merges[0]/slavecount, merges[1]/slavecount,
-			ticks[0]/slavecount, ticks[1]/slavecount,
-			time_in_queue/slavecount, max_util);
+void free_disk_util(void)
+{
+	struct disk_util *du;
 
+	while (!flist_empty(&disk_list)) {
+		du = flist_entry(disk_list.next, struct disk_util, list);
+		flist_del(&du->list);
+		disk_util_free(du);
+	}
+
+	last_majdev = last_mindev = -1;
+}
+
+void print_disk_util(struct disk_util_stat *dus, struct disk_util_agg *agg)
+{
+	double util;
+
+	util = (double) 100 * dus->io_ticks / (double) dus->msec;
+	if (util > 100.0)
+		util = 100.0;
+
+	if (agg->slavecount)
+		log_info("  ");
+
+	log_info("  %s: ios=%u/%u, merge=%u/%u, ticks=%u/%u, "
+		 "in_queue=%u, util=%3.2f%%", dus->name,
+					dus->ios[0], dus->ios[1],
+					dus->merges[0], dus->merges[1],
+					dus->ticks[0], dus->ticks[1],
+					dus->time_in_queue, util);
+
+	/*
+	 * If the device has slaves, aggregate the stats for
+	 * those slave devices also.
+	 */
+	if (agg->slavecount)
+		show_agg_stats(agg);
+
+	log_info("\n");
 }
 
 void show_disk_util(void)
 {
-	struct disk_util_stat *dus;
-	struct flist_head *entry, *next;
+	struct flist_head *entry;
 	struct disk_util *du;
-	double util;
 
 	if (flist_empty(&disk_list))
 		return;
@@ -514,45 +558,8 @@
 
 	flist_for_each(entry, &disk_list) {
 		du = flist_entry(entry, struct disk_util, list);
-		dus = &du->dus;
 
-		util = (double) 100 * du->dus.io_ticks / (double) du->msec;
-		if (util > 100.0)
-			util = 100.0;
-
-		/* If this node is the slave of a master device, as
-		 * happens in case of software RAIDs, inward-indent
-		 * this stats line to reflect a master-slave
-		 * relationship. Because the master device gets added
-		 * before the slave devices, we can safely assume that
-		 * the master's stats line has been displayed in a
-		 * previous iteration of this loop.
-		 */
-		if (!flist_empty(&du->slavelist))
-			log_info("  ");
-
-		log_info("  %s: ios=%u/%u, merge=%u/%u, ticks=%u/%u, "
-			 "in_queue=%u, util=%3.2f%%", du->name,
-						dus->ios[0], dus->ios[1],
-						dus->merges[0], dus->merges[1],
-						dus->ticks[0], dus->ticks[1],
-						dus->time_in_queue, util);
-
-		/* If the device has slaves, aggregate the stats for
-		 * those slave devices also.
-		 */
-		if (!flist_empty(&du->slaves))
-			aggregate_slaves_stats(du);
-
-		log_info("\n");
-	}
-
-	/*
-	 * now free the list
-	 */
-	flist_for_each_safe(entry, next, &disk_list) {
-		flist_del(entry);
-		du = flist_entry(entry, struct disk_util, list);
-		disk_util_free(du);
+		aggregate_slaves_stats(du);
+		print_disk_util(&du->dus, &du->agg);
 	}
 }
diff --git a/diskutil.h b/diskutil.h
index dc89cc5..49c56ae 100644
--- a/diskutil.h
+++ b/diskutil.h
@@ -1,16 +1,31 @@
 #ifndef FIO_DISKUTIL_H
 #define FIO_DISKUTIL_H
 
+#define FIO_DU_NAME_SZ		64
+
 /*
  * Disk utils as read in /sys/block/<dev>/stat
  */
 struct disk_util_stat {
-	unsigned ios[2];
-	unsigned merges[2];
-	unsigned long long sectors[2];
-	unsigned ticks[2];
-	unsigned io_ticks;
-	unsigned time_in_queue;
+	uint8_t name[FIO_DU_NAME_SZ];
+	uint32_t ios[2];
+	uint32_t merges[2];
+	uint64_t sectors[2];
+	uint32_t ticks[2];
+	uint32_t io_ticks;
+	uint32_t time_in_queue;
+	uint64_t msec;
+};
+
+struct disk_util_agg {
+	uint32_t ios[2];
+	uint32_t merges[2];
+	uint64_t sectors[2];
+	uint32_t ticks[2];
+	uint32_t io_ticks;
+	uint32_t time_in_queue;
+	uint32_t slavecount;
+	fio_fp64_t max_util;
 };
 
 /*
@@ -31,6 +46,8 @@
 	struct disk_util_stat dus;
 	struct disk_util_stat last_dus;
 
+	struct disk_util_agg agg;
+
 	/* For software raids, this entry maintains pointers to the
 	 * entries for the slave devices. The disk_util entries for
 	 * the slaves devices should primarily be maintained through
@@ -40,7 +57,6 @@
 	 */
 	struct flist_head slaves;
 
-	unsigned long msec;
 	struct timeval time;
 
 	struct fio_mutex *lock;
@@ -76,15 +92,21 @@
 
 #define DISK_UTIL_MSEC	(250)
 
+extern struct flist_head disk_list;
+
 /*
  * disk util stuff
  */
 #ifdef FIO_HAVE_DISK_UTIL
+extern void print_disk_util(struct disk_util_stat *, struct disk_util_agg *);
 extern void show_disk_util(void);
+extern void free_disk_util(void);
 extern void init_disk_util(struct thread_data *);
 extern void update_io_ticks(void);
 #else
+#define print_disk_util(dus, agg)
 #define show_disk_util()
+#define free_disk_util()
 #define init_disk_util(td)
 #define update_io_ticks()
 #endif
diff --git a/fio.c b/fio.c
index b242f94..e5d3bbf 100644
--- a/fio.c
+++ b/fio.c
@@ -62,6 +62,13 @@
 int temp_stall_ts;
 unsigned long done_secs = 0;
 
+/*
+ * Just expose an empty list, if the OS does not support disk util stats
+ */
+#ifndef FIO_HAVE_DISK_UTIL
+FLIST_HEAD(disk_list);
+#endif
+
 static struct fio_mutex *startup_mutex;
 static struct fio_mutex *writeout_mutex;
 static volatile int fio_abort;
diff --git a/server.c b/server.c
index dcce37e..e4793f7 100644
--- a/server.c
+++ b/server.c
@@ -46,7 +46,8 @@
 	"ETA",
 	"PROBE",
 	"START",
-	"STOP"
+	"STOP",
+	"DISK_UTIL",
 };
 
 const char *fio_server_op(unsigned int op)
@@ -726,6 +727,59 @@
 	fio_net_send_cmd(server_fd, FIO_NET_CMD_GS, &gs, sizeof(gs), 0);
 }
 
+static void convert_agg(struct disk_util_agg *dst, struct disk_util_agg *src)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		dst->ios[i]	= cpu_to_le32(src->ios[i]);
+		dst->merges[i]	= cpu_to_le32(src->merges[i]);
+		dst->sectors[i]	= cpu_to_le64(src->sectors[i]);
+		dst->ticks[i]	= cpu_to_le32(src->ticks[i]);
+	}
+
+	dst->io_ticks		= cpu_to_le32(src->io_ticks);
+	dst->time_in_queue	= cpu_to_le32(src->time_in_queue);
+	dst->slavecount		= cpu_to_le32(src->slavecount);
+	dst->max_util.u.i	= __cpu_to_le64(fio_double_to_uint64(src->max_util.u.f));
+}
+
+static void convert_dus(struct disk_util_stat *dst, struct disk_util_stat *src)
+{
+	int i;
+
+	strcpy((char *) dst->name, (char *) src->name);
+
+	for (i = 0; i < 2; i++) {
+		dst->ios[i]	= cpu_to_le32(src->ios[i]);
+		dst->merges[i]	= cpu_to_le32(src->merges[i]);
+		dst->sectors[i]	= cpu_to_le64(src->sectors[i]);
+		dst->ticks[i]	= cpu_to_le32(src->ticks[i]);
+	}
+
+	dst->io_ticks		= cpu_to_le32(src->io_ticks);
+	dst->time_in_queue	= cpu_to_le32(src->time_in_queue);
+	dst->msec		= cpu_to_le64(src->msec);
+}
+
+void fio_server_send_du(void)
+{
+	struct disk_util *du;
+	struct flist_head *entry;
+	struct cmd_du_pdu pdu;
+
+	dprint(FD_NET, "server: sending disk_util %d\n", !flist_empty(&disk_list));
+
+	flist_for_each(entry, &disk_list) {
+		du = flist_entry(entry, struct disk_util, list);
+
+		convert_dus(&pdu.dus, &du->dus);
+		convert_agg(&pdu.agg, &du->agg);
+
+		fio_net_send_cmd(server_fd, FIO_NET_CMD_DU, &pdu, sizeof(pdu), 0);
+	}
+}
+
 int fio_server_log(const char *format, ...)
 {
 	char buffer[1024];
diff --git a/server.h b/server.h
index 46555ce..d709e98 100644
--- a/server.h
+++ b/server.h
@@ -7,6 +7,7 @@
 
 #include "stat.h"
 #include "os/os.h"
+#include "diskutil.h"
 
 /*
  * On-wire encoding is little endian
@@ -50,7 +51,8 @@
 	FIO_NET_CMD_PROBE	= 10,
 	FIO_NET_CMD_START	= 11,
 	FIO_NET_CMD_STOP	= 12,
-	FIO_NET_CMD_NR		= 13,
+	FIO_NET_CMD_DU		= 13,
+	FIO_NET_CMD_NR		= 14,
 
 	FIO_NET_CMD_F_MORE	= 1UL << 0,
 
@@ -66,6 +68,11 @@
 	struct group_run_stats rs;
 };
 
+struct cmd_du_pdu {
+	struct disk_util_stat dus;
+	struct disk_util_agg agg;
+};
+
 struct cmd_probe_pdu {
 	uint8_t hostname[64];
 	uint8_t bigendian;
@@ -101,6 +108,7 @@
 struct group_run_stats;
 extern void fio_server_send_ts(struct thread_stat *, struct group_run_stats *);
 extern void fio_server_send_gs(struct group_run_stats *);
+extern void fio_server_send_du(void);
 extern void fio_server_idle_loop(void);
 
 extern int fio_clients_connect(void);
diff --git a/stat.c b/stat.c
index f77e7b6..6377869 100644
--- a/stat.c
+++ b/stat.c
@@ -949,7 +949,12 @@
 				show_group_stats(rs);
 		}
 
-		show_disk_util();
+		if (is_backend)
+			fio_server_send_du();
+		else
+			show_disk_util();
+
+		free_disk_util();
 	}
 
 	free(runstats);