Merge branch 'master' into client-server

Conflicts:
	init.c

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/Makefile b/Makefile
index 85942a0..b361603 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
 DEBUGFLAGS = -D_FORTIFY_SOURCE=2 -DFIO_INC_DEBUG
 CPPFLAGS= -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \
 	$(DEBUGFLAGS)
-OPTFLAGS= -O2 -fno-omit-frame-pointer -g $(EXTFLAGS)
+OPTFLAGS= -fno-omit-frame-pointer -g $(EXTFLAGS)
 CFLAGS	= -std=gnu99 -Wwrite-strings -Wall $(OPTFLAGS)
 LIBS	= -lm $(EXTLIBS)
 PROGS	= fio
@@ -14,7 +14,7 @@
 		rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \
 		lib/num2str.c $(wildcard crc/*.c) engines/cpu.c \
 		engines/mmap.c engines/sync.c engines/null.c engines/net.c \
-		memalign.c
+		memalign.c server.c client.c iolog.c ieee754.c
 
 ifeq ($(UNAME), Linux)
   SOURCE += diskutil.c fifo.c blktrace.c helpers.c cgroup.c trim.c \
diff --git a/SERVER-TODO b/SERVER-TODO
new file mode 100644
index 0000000..929d482
--- /dev/null
+++ b/SERVER-TODO
@@ -0,0 +1,2 @@
+- Passing of float/doubles
+- Add -L listen option, to not bind to all devices
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..d538e8a
--- /dev/null
+++ b/client.c
@@ -0,0 +1,587 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <signal.h>
+
+#include "fio.h"
+#include "server.h"
+#include "crc/crc32.h"
+#include "flist.h"
+
+struct fio_client {
+	struct flist_head list;
+	struct sockaddr_in addr;
+	char *hostname;
+	int fd;
+
+	int state;
+	int skip_newline;
+
+	uint16_t argc;
+	char **argv;
+};
+
+enum {
+	Client_created		= 0,
+	Client_connected	= 1,
+	Client_started		= 2,
+	Client_stopped		= 3,
+	Client_exited		= 4,
+};
+
+static FLIST_HEAD(client_list);
+
+static int handle_client(struct fio_client *client, int one);
+
+static struct fio_client *find_client_by_fd(int fd)
+{
+	struct fio_client *client;
+	struct flist_head *entry;
+
+	flist_for_each(entry, &client_list) {
+		client = flist_entry(entry, struct fio_client, list);
+
+		if (client->fd == fd)
+			return client;
+	}
+
+	return NULL;
+}
+
+static struct fio_client *find_client_by_name(const char *name)
+{
+	struct fio_client *client;
+	struct flist_head *entry;
+
+	flist_for_each(entry, &client_list) {
+		client = flist_entry(entry, struct fio_client, list);
+
+		if (!strcmp(name, client->hostname))
+			return client;
+	}
+
+	return NULL;
+}
+
+static void remove_client(struct fio_client *client)
+{
+	dprint(FD_NET, "client: removed <%s>\n", client->hostname);
+	flist_del(&client->list);
+	nr_clients--;
+
+	free(client->hostname);
+	if (client->argv)
+		free(client->argv);
+
+	free(client);
+}
+
+static void __fio_client_add_cmd_option(struct fio_client *client,
+					const char *opt)
+{
+	int index;
+
+	index = client->argc++;
+	client->argv = realloc(client->argv, sizeof(char *) * client->argc);
+	client->argv[index] = strdup(opt);
+	dprint(FD_NET, "client: add cmd %d: %s\n", index, opt);
+}
+
+void fio_client_add_cmd_option(const char *hostname, const char *opt)
+{
+	struct fio_client *client;
+
+	if (!hostname || !opt)
+		return;
+
+	client = find_client_by_name(hostname);
+	if (!client) {
+		log_err("fio: unknown client %s\n", hostname);
+		return;
+	}
+
+	__fio_client_add_cmd_option(client, opt);
+}
+
+void fio_client_add(const char *hostname)
+{
+	struct fio_client *client;
+
+	dprint(FD_NET, "client: added  <%s>\n", hostname);
+	client = malloc(sizeof(*client));
+	memset(client, 0, sizeof(*client));
+
+	client->hostname = strdup(hostname);
+	client->fd = -1;
+
+	__fio_client_add_cmd_option(client, "fio");
+
+	flist_add(&client->list, &client_list);
+	nr_clients++;
+}
+
+static int fio_client_connect(struct fio_client *client)
+{
+	int fd;
+
+	dprint(FD_NET, "client: connect to host %s\n", client->hostname);
+
+	memset(&client->addr, 0, sizeof(client->addr));
+	client->addr.sin_family = AF_INET;
+	client->addr.sin_port = htons(fio_net_port);
+
+	if (inet_aton(client->hostname, &client->addr.sin_addr) != 1) {
+		struct hostent *hent;
+
+		hent = gethostbyname(client->hostname);
+		if (!hent) {
+			log_err("fio: gethostbyname: %s\n", strerror(errno));
+			return 1;
+		}
+
+		memcpy(&client->addr.sin_addr, hent->h_addr, 4);
+	}
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		log_err("fio: socket: %s\n", strerror(errno));
+		return 1;
+	}
+
+	if (connect(fd, (struct sockaddr *) &client->addr, sizeof(client->addr)) < 0) {
+		log_err("fio: connect: %s\n", strerror(errno));
+		log_err("fio: failed to connect to %s\n", client->hostname);
+		return 1;
+	}
+
+	client->fd = fd;
+	client->state = Client_connected;
+	return 0;
+}
+
+void fio_clients_terminate(void)
+{
+	struct flist_head *entry;
+	struct fio_client *client;
+
+	dprint(FD_NET, "client: terminate clients\n");
+
+	flist_for_each(entry, &client_list) {
+		client = flist_entry(entry, struct fio_client, list);
+
+		fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_QUIT, 0);
+	}
+}
+
+static void sig_int(int sig)
+{
+	dprint(FD_NET, "client: got sign %d\n", sig);
+	fio_clients_terminate();
+}
+
+static void client_signal_handler(void)
+{
+	struct sigaction act;
+
+	memset(&act, 0, sizeof(act));
+	act.sa_handler = sig_int;
+	act.sa_flags = SA_RESTART;
+	sigaction(SIGINT, &act, NULL);
+
+	memset(&act, 0, sizeof(act));
+	act.sa_handler = sig_int;
+	act.sa_flags = SA_RESTART;
+	sigaction(SIGTERM, &act, NULL);
+}
+
+static void probe_client(struct fio_client *client)
+{
+	dprint(FD_NET, "client: send probe\n");
+
+	fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_PROBE, 0);
+	handle_client(client, 1);
+}
+
+static int send_client_cmd_line(struct fio_client *client)
+{
+	struct cmd_line_pdu *pdu;
+	int i, ret;
+
+	dprint(FD_NET, "client: send cmdline %d\n", client->argc);
+
+	pdu = malloc(sizeof(*pdu));
+	for (i = 0; i < client->argc; i++)
+		strcpy((char *) pdu->argv[i], client->argv[i]);
+
+	pdu->argc = cpu_to_le16(client->argc);
+	ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOBLINE, pdu, sizeof(*pdu));
+	free(pdu);
+	return ret;
+}
+
+int fio_clients_connect(void)
+{
+	struct fio_client *client;
+	struct flist_head *entry, *tmp;
+	int ret;
+
+	dprint(FD_NET, "client: connect all\n");
+
+	client_signal_handler();
+
+	flist_for_each_safe(entry, tmp, &client_list) {
+		client = flist_entry(entry, struct fio_client, list);
+
+		ret = fio_client_connect(client);
+		if (ret) {
+			remove_client(client);
+			continue;
+		}
+
+		probe_client(client);
+
+		if (client->argc > 1)
+			send_client_cmd_line(client);
+	}
+
+	return !nr_clients;
+}
+
+/*
+ * Send file contents to server backend. We could use sendfile(), but to remain
+ * more portable lets just read/write the darn thing.
+ */
+static int fio_client_send_ini(struct fio_client *client, const char *filename)
+{
+	struct stat sb;
+	char *p, *buf;
+	off_t len;
+	int fd, ret;
+
+	dprint(FD_NET, "send ini %s to %s\n", filename, client->hostname);
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		log_err("fio: job file open: %s\n", strerror(errno));
+		return 1;
+	}
+
+	if (fstat(fd, &sb) < 0) {
+		log_err("fio: job file stat: %s\n", strerror(errno));
+		return 1;
+	}
+
+	buf = malloc(sb.st_size);
+
+	len = sb.st_size;
+	p = buf;
+	do {
+		ret = read(fd, p, len);
+		if (ret > 0) {
+			len -= ret;
+			if (!len)
+				break;
+			p += ret;
+			continue;
+		} else if (!ret)
+			break;
+		else if (errno == EAGAIN || errno == EINTR)
+			continue;
+	} while (1);
+
+	if (len) {
+		log_err("fio: failed reading job file %s\n", filename);
+		return 1;
+	}
+
+	ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOB, buf, sb.st_size);
+	free(buf);
+	return ret;
+}
+
+int fio_clients_send_ini(const char *filename)
+{
+	struct fio_client *client;
+	struct flist_head *entry, *tmp;
+
+	flist_for_each_safe(entry, tmp, &client_list) {
+		client = flist_entry(entry, struct fio_client, list);
+
+		if (fio_client_send_ini(client, filename))
+			remove_client(client);
+	}
+
+	return !nr_clients;
+}
+
+static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
+{
+	dst->max_val	= le64_to_cpu(src->max_val);
+	dst->min_val	= le64_to_cpu(src->min_val);
+	dst->samples	= le64_to_cpu(src->samples);
+
+	/*
+	 * Floats arrive as IEEE 754 encoded uint64_t, convert back to double
+	 */
+	dst->mean.u.f	= fio_uint64_to_double(le64_to_cpu(dst->mean.u.i));
+	dst->S.u.f	= fio_uint64_to_double(le64_to_cpu(dst->S.u.i));
+}
+
+static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
+{
+	int i, j;
+
+	dst->error	= le32_to_cpu(src->error);
+	dst->groupid	= le32_to_cpu(src->groupid);
+	dst->pid	= le32_to_cpu(src->pid);
+	dst->members	= le32_to_cpu(src->members);
+
+	for (i = 0; i < 2; i++) {
+		convert_io_stat(&dst->clat_stat[i], &src->clat_stat[i]);
+		convert_io_stat(&dst->slat_stat[i], &src->slat_stat[i]);
+		convert_io_stat(&dst->lat_stat[i], &src->lat_stat[i]);
+		convert_io_stat(&dst->bw_stat[i], &src->bw_stat[i]);
+	}
+
+	dst->usr_time		= le64_to_cpu(src->usr_time);
+	dst->sys_time		= le64_to_cpu(src->sys_time);
+	dst->ctx		= le64_to_cpu(src->ctx);
+	dst->minf		= le64_to_cpu(src->minf);
+	dst->majf		= le64_to_cpu(src->majf);
+	dst->clat_percentiles	= le64_to_cpu(src->clat_percentiles);
+
+	for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
+		fio_fp64_t *fps = &src->percentile_list[i];
+		fio_fp64_t *fpd = &dst->percentile_list[i];
+
+		fpd->u.f = fio_uint64_to_double(le64_to_cpu(fps->u.i));
+	}
+
+	for (i = 0; i < FIO_IO_U_MAP_NR; i++) {
+		dst->io_u_map[i]	= le32_to_cpu(src->io_u_map[i]);
+		dst->io_u_submit[i]	= le32_to_cpu(src->io_u_submit[i]);
+		dst->io_u_complete[i]	= le32_to_cpu(src->io_u_complete[i]);
+	}
+
+	for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) {
+		dst->io_u_lat_u[i]	= le32_to_cpu(src->io_u_lat_u[i]);
+		dst->io_u_lat_m[i]	= le32_to_cpu(src->io_u_lat_m[i]);
+	}
+
+	for (i = 0; i < 2; i++)
+		for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
+			dst->io_u_plat[i][j] = le32_to_cpu(src->io_u_plat[i][j]);
+
+	for (i = 0; i < 3; i++) {
+		dst->total_io_u[i]	= le64_to_cpu(src->total_io_u[i]);
+		dst->short_io_u[i]	= le64_to_cpu(src->short_io_u[i]);
+	}
+
+	dst->total_submit	= le64_to_cpu(src->total_submit);
+	dst->total_complete	= le64_to_cpu(src->total_complete);
+
+	for (i = 0; i < 2; i++) {
+		dst->io_bytes[i]	= le64_to_cpu(src->io_bytes[i]);
+		dst->runtime[i]		= le64_to_cpu(src->runtime[i]);
+	}
+
+	dst->total_run_time	= le64_to_cpu(src->total_run_time);
+	dst->continue_on_error	= le16_to_cpu(src->continue_on_error);
+	dst->total_err_count	= le64_to_cpu(src->total_err_count);
+	dst->first_error	= le32_to_cpu(src->first_error);
+	dst->kb_base		= le32_to_cpu(src->kb_base);
+}
+
+static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		dst->max_run[i]		= le64_to_cpu(src->max_run[i]);
+		dst->min_run[i]		= le64_to_cpu(src->min_run[i]);
+		dst->max_bw[i]		= le64_to_cpu(src->max_bw[i]);
+		dst->min_bw[i]		= le64_to_cpu(src->min_bw[i]);
+		dst->io_kb[i]		= le64_to_cpu(src->io_kb[i]);
+		dst->agg[i]		= le64_to_cpu(src->agg[i]);
+	}
+
+	dst->kb_base	= le32_to_cpu(src->kb_base);
+	dst->groupid	= le32_to_cpu(src->groupid);
+}
+
+static void handle_ts(struct fio_net_cmd *cmd)
+{
+	struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+
+	convert_ts(&p->ts, &p->ts);
+	convert_gs(&p->rs, &p->rs);
+
+	show_thread_status(&p->ts, &p->rs);
+}
+
+static void handle_gs(struct fio_net_cmd *cmd)
+{
+	struct group_run_stats *gs = (struct group_run_stats *) cmd->payload;
+
+	convert_gs(gs, gs);
+	show_group_stats(gs);
+}
+
+static void handle_eta(struct fio_net_cmd *cmd)
+{
+	struct jobs_eta *je = (struct jobs_eta *) cmd->payload;
+	int i;
+
+	je->nr_running		= le32_to_cpu(je->nr_running);
+	je->nr_ramp		= le32_to_cpu(je->nr_ramp);
+	je->nr_pending		= le32_to_cpu(je->nr_pending);
+	je->files_open		= le32_to_cpu(je->files_open);
+	je->m_rate		= le32_to_cpu(je->m_rate);
+	je->t_rate		= le32_to_cpu(je->t_rate);
+	je->m_iops		= le32_to_cpu(je->m_iops);
+	je->t_iops		= le32_to_cpu(je->t_iops);
+
+	for (i = 0; i < 2; i++) {
+		je->rate[i]	= le32_to_cpu(je->rate[i]);
+		je->iops[i]	= le32_to_cpu(je->iops[i]);
+	}
+
+	je->elapsed_sec		= le32_to_cpu(je->nr_running);
+	je->eta_sec		= le64_to_cpu(je->eta_sec);
+
+	display_thread_status(je);
+}
+
+static void handle_probe(struct fio_net_cmd *cmd)
+{
+	struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
+
+	log_info("Probe: hostname=%s, be=%u, fio ver %u.%u.%u\n",
+		probe->hostname, probe->bigendian, probe->fio_major,
+		probe->fio_minor, probe->fio_patch);
+}
+
+static int handle_client(struct fio_client *client, int one)
+{
+	struct fio_net_cmd *cmd;
+	int done = 0;
+
+	dprint(FD_NET, "client: handle %s\n", client->hostname);
+
+	while ((cmd = fio_net_recv_cmd(client->fd, 1)) != NULL) {
+		dprint(FD_NET, "%s: got cmd op %d\n", client->hostname,
+							cmd->opcode);
+
+		switch (cmd->opcode) {
+		case FIO_NET_CMD_QUIT:
+			remove_client(client);
+			free(cmd);
+			done = 1;
+			break;
+		case FIO_NET_CMD_TEXT: {
+			const char *buf = (const char *) cmd->payload;
+
+			if (!client->skip_newline)
+				fprintf(f_out, "Client <%s>: ", client->hostname);
+			fwrite(buf, cmd->pdu_len, 1, f_out);
+			fflush(f_out);
+			client->skip_newline = strchr(buf, '\n') == NULL;
+			free(cmd);
+			break;
+			}
+		case FIO_NET_CMD_TS:
+			handle_ts(cmd);
+			free(cmd);
+			break;
+		case FIO_NET_CMD_GS:
+			handle_gs(cmd);
+			free(cmd);
+			break;
+		case FIO_NET_CMD_ETA:
+			handle_eta(cmd);
+			free(cmd);
+			break;
+		case FIO_NET_CMD_PROBE:
+			handle_probe(cmd);
+			free(cmd);
+			break;
+		case FIO_NET_CMD_START:
+			client->state = Client_started;
+			free(cmd);
+			break;
+		case FIO_NET_CMD_STOP:
+			client->state = Client_stopped;
+			free(cmd);
+			break;
+		default:
+			log_err("fio: unknown client op: %d\n", cmd->opcode);
+			free(cmd);
+			break;
+		}
+
+		if (done || one)
+			break;
+	}
+
+	return 0;
+}
+
+int fio_handle_clients(void)
+{
+	struct fio_client *client;
+	struct flist_head *entry;
+	struct pollfd *pfds;
+	int i, ret = 0;
+
+	pfds = malloc(nr_clients * sizeof(struct pollfd));
+
+	while (!exit_backend && nr_clients) {
+		i = 0;
+		flist_for_each(entry, &client_list) {
+			client = flist_entry(entry, struct fio_client, list);
+
+			pfds[i].fd = client->fd;
+			pfds[i].events = POLLIN;
+			i++;
+		}
+
+		assert(i == nr_clients);
+
+		do {
+			ret = poll(pfds, nr_clients, 100);
+			if (ret < 0) {
+				if (errno == EINTR)
+					continue;
+				log_err("fio: poll clients: %s\n", strerror(errno));
+				break;
+			} else if (!ret)
+				continue;
+		} while (ret <= 0);
+
+		for (i = 0; i < nr_clients; i++) {
+			if (!(pfds[i].revents & POLLIN))
+				continue;
+
+			client = find_client_by_fd(pfds[i].fd);
+			if (!client) {
+				log_err("fio: unknown client\n");
+				continue;
+			}
+			handle_client(client, 0);
+		}
+	}
+
+	free(pfds);
+	return 0;
+}
diff --git a/crc/crc16.c b/crc/crc16.c
index ac7983a..d9c4e49 100644
--- a/crc/crc16.c
+++ b/crc/crc16.c
@@ -43,11 +43,12 @@
 	0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
 };
 
-unsigned short crc16(unsigned char const *buffer, unsigned int len)
+unsigned short crc16(const void *buffer, unsigned int len)
 {
+	const unsigned char *cp = (const unsigned char *) buffer;
 	unsigned short crc = 0;
 
 	while (len--)
-		crc = crc16_byte(crc, *buffer++);
+		crc = crc16_byte(crc, *cp++);
 	return crc;
 }
diff --git a/crc/crc16.h b/crc/crc16.h
index 841378d..6c078a4 100644
--- a/crc/crc16.h
+++ b/crc/crc16.h
@@ -17,7 +17,7 @@
 
 extern unsigned short const crc16_table[256];
 
-extern unsigned short crc16(const unsigned char *buffer, unsigned int len);
+extern unsigned short crc16(const void *buffer, unsigned int len);
 
 static inline unsigned short crc16_byte(unsigned short crc,
 					const unsigned char data)
diff --git a/debug.c b/debug.c
index 013cd53..5e98063 100644
--- a/debug.c
+++ b/debug.c
@@ -16,8 +16,8 @@
 	    && pid != *fio_debug_jobp)
 		return;
 
-	log_info("%-8s ", debug_levels[type].name);
-	log_info("%-5u ", (int) pid);
+	log_local("%-8s ", debug_levels[type].name);
+	log_local("%-5u ", (int) pid);
 
 	va_start(args, str);
 	log_valist(str, args);
diff --git a/debug.h b/debug.h
index 3473d6f..af71d62 100644
--- a/debug.h
+++ b/debug.h
@@ -18,6 +18,7 @@
 	FD_MUTEX,
 	FD_PROFILE,
 	FD_TIME,
+	FD_NET,
 	FD_DEBUG_MAX,
 };
 
diff --git a/engines/net.c b/engines/net.c
index 6866ba2..781dd10 100644
--- a/engines/net.c
+++ b/engines/net.c
@@ -280,11 +280,7 @@
 
 	do {
 		if (nd->net_protocol == IPPROTO_UDP) {
-#ifdef __hpux
-			int len = sizeof(nd->addr);
-#else
-			socklen_t len = sizeof(nd->addr);
-#endif
+			fio_socklen_t len = sizeof(nd->addr);
 			struct sockaddr *from = (struct sockaddr *) &nd->addr;
 
 			ret = recvfrom(io_u->file->fd, io_u->xfer_buf,
@@ -381,11 +377,7 @@
 static int fio_netio_accept(struct thread_data *td, struct fio_file *f)
 {
 	struct netio_data *nd = td->io_ops->data;
-#ifdef __hpux
-	int socklen = sizeof(nd->addr);
-#else
-	socklen_t socklen = sizeof(nd->addr);
-#endif
+	fio_socklen_t socklen = sizeof(nd->addr);
 
 	if (nd->net_protocol == IPPROTO_UDP) {
 		f->fd = nd->listenfd;
diff --git a/eta.c b/eta.c
index d93bf1a..5b912eb 100644
--- a/eta.c
+++ b/eta.c
@@ -230,32 +230,26 @@
  * Print status of the jobs we know about. This includes rate estimates,
  * ETA, thread state, etc.
  */
-void print_thread_status(void)
+int calc_thread_status(struct jobs_eta *je)
 {
-	unsigned long elapsed = (mtime_since_genesis() + 999) / 1000;
-	int i, nr_ramp, nr_running, nr_pending, t_rate, m_rate;
-	int t_iops, m_iops, files_open;
 	struct thread_data *td;
-	char eta_str[128];
-	double perc = 0.0;
-	unsigned long long io_bytes[2], io_iops[2];
-	unsigned long rate_time, disp_time, bw_avg_time, *eta_secs, eta_sec;
+	int i;
+	unsigned long rate_time, disp_time, bw_avg_time, *eta_secs;
+	unsigned long long io_bytes[2];
+	unsigned long long io_iops[2];
 	struct timeval now;
 
 	static unsigned long long rate_io_bytes[2];
 	static unsigned long long disp_io_bytes[2];
 	static unsigned long long disp_io_iops[2];
 	static struct timeval rate_prev_time, disp_prev_time;
-	static unsigned int rate[2], iops[2];
-	static int linelen_last;
-	static int eta_good;
 	int i2p = 0;
 
 	if (temp_stall_ts || terse_output || eta_print == FIO_ETA_NEVER)
-		return;
+		return 0;
 
 	if (!isatty(STDOUT_FILENO) && (eta_print != FIO_ETA_ALWAYS))
-		return;
+		return 0;
 
 	if (!rate_io_bytes[0] && !rate_io_bytes[1])
 		fill_start_time(&rate_prev_time);
@@ -265,32 +259,31 @@
 	eta_secs = malloc(thread_number * sizeof(unsigned long));
 	memset(eta_secs, 0, thread_number * sizeof(unsigned long));
 
+	je->elapsed_sec = (mtime_since_genesis() + 999) / 1000;
+
 	io_bytes[0] = io_bytes[1] = 0;
 	io_iops[0] = io_iops[1] = 0;
-	nr_pending = nr_running = t_rate = m_rate = t_iops = m_iops = 0;
-	nr_ramp = 0;
 	bw_avg_time = ULONG_MAX;
-	files_open = 0;
 	for_each_td(td, i) {
 		if (td->o.bw_avg_time < bw_avg_time)
 			bw_avg_time = td->o.bw_avg_time;
 		if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING
 		    || td->runstate == TD_FSYNCING
 		    || td->runstate == TD_PRE_READING) {
-			nr_running++;
-			t_rate += td->o.rate[0] + td->o.rate[1];
-			m_rate += td->o.ratemin[0] + td->o.ratemin[1];
-			t_iops += td->o.rate_iops[0] + td->o.rate_iops[1];
-			m_iops += td->o.rate_iops_min[0] +
+			je->nr_running++;
+			je->t_rate += td->o.rate[0] + td->o.rate[1];
+			je->m_rate += td->o.ratemin[0] + td->o.ratemin[1];
+			je->t_iops += td->o.rate_iops[0] + td->o.rate_iops[1];
+			je->m_iops += td->o.rate_iops_min[0] +
 					td->o.rate_iops_min[1];
-			files_open += td->nr_open_files;
+			je->files_open += td->nr_open_files;
 		} else if (td->runstate == TD_RAMP) {
-			nr_running++;
-			nr_ramp++;
+			je->nr_running++;
+			je->nr_ramp++;
 		} else if (td->runstate < TD_RUNNING)
-			nr_pending++;
+			je->nr_pending++;
 
-		if (elapsed >= 3)
+		if (je->elapsed_sec >= 3)
 			eta_secs[i] = thread_eta(td);
 		else
 			eta_secs[i] = INT_MAX;
@@ -306,37 +299,32 @@
 	}
 
 	if (exitall_on_terminate)
-		eta_sec = INT_MAX;
+		je->eta_sec = INT_MAX;
 	else
-		eta_sec = 0;
+		je->eta_sec = 0;
 
 	for_each_td(td, i) {
 		if (!i2p && is_power_of_2(td->o.kb_base))
 			i2p = 1;
 		if (exitall_on_terminate) {
-			if (eta_secs[i] < eta_sec)
-				eta_sec = eta_secs[i];
+			if (eta_secs[i] < je->eta_sec)
+				je->eta_sec = eta_secs[i];
 		} else {
-			if (eta_secs[i] > eta_sec)
-				eta_sec = eta_secs[i];
+			if (eta_secs[i] > je->eta_sec)
+				je->eta_sec = eta_secs[i];
 		}
 	}
 
 	free(eta_secs);
 
-	if (eta_sec != INT_MAX && elapsed) {
-		perc = (double) elapsed / (double) (elapsed + eta_sec);
-		eta_to_str(eta_str, eta_sec);
-	}
-
 	fio_gettime(&now, NULL);
 	rate_time = mtime_since(&rate_prev_time, &now);
 
 	if (write_bw_log && rate_time > bw_avg_time && !in_ramp_time(td)) {
-		calc_rate(rate_time, io_bytes, rate_io_bytes, rate);
+		calc_rate(rate_time, io_bytes, rate_io_bytes, je->rate);
 		memcpy(&rate_prev_time, &now, sizeof(now));
-		add_agg_sample(rate[DDIR_READ], DDIR_READ, 0);
-		add_agg_sample(rate[DDIR_WRITE], DDIR_WRITE, 0);
+		add_agg_sample(je->rate[DDIR_READ], DDIR_READ, 0);
+		add_agg_sample(je->rate[DDIR_WRITE], DDIR_WRITE, 0);
 	}
 
 	disp_time = mtime_since(&disp_prev_time, &now);
@@ -345,34 +333,54 @@
 	 * Allow a little slack, the target is to print it every 1000 msecs
 	 */
 	if (disp_time < 900)
-		return;
+		return 0;
 
-	calc_rate(disp_time, io_bytes, disp_io_bytes, rate);
-	calc_iops(disp_time, io_iops, disp_io_iops, iops);
+	calc_rate(disp_time, io_bytes, disp_io_bytes, je->rate);
+	calc_iops(disp_time, io_iops, disp_io_iops, je->iops);
 
 	memcpy(&disp_prev_time, &now, sizeof(now));
 
-	if (!nr_running && !nr_pending)
-		return;
+	if (!je->nr_running && !je->nr_pending)
+		return 0;
 
-	printf("Jobs: %d (f=%d)", nr_running, files_open);
-	if (m_rate || t_rate) {
+	je->nr_threads = thread_number;
+	memcpy(je->run_str, run_str, thread_number * sizeof(char));
+
+	return 1;
+}
+
+void display_thread_status(struct jobs_eta *je)
+{
+	static int linelen_last;
+	static int eta_good;
+	char output[512], *p = output;
+	char eta_str[128];
+	double perc = 0.0;
+	int i2p = 0;
+
+	if (je->eta_sec != INT_MAX && je->elapsed_sec) {
+		perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
+		eta_to_str(eta_str, je->eta_sec);
+	}
+
+	p += sprintf(p, "Jobs: %d (f=%d)", je->nr_running, je->files_open);
+	if (je->m_rate || je->t_rate) {
 		char *tr, *mr;
 
-		mr = num2str(m_rate, 4, 0, i2p);
-		tr = num2str(t_rate, 4, 0, i2p);
-		printf(", CR=%s/%s KB/s", tr, mr);
+		mr = num2str(je->m_rate, 4, 0, i2p);
+		tr = num2str(je->t_rate, 4, 0, i2p);
+		p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
 		free(tr);
 		free(mr);
-	} else if (m_iops || t_iops)
-		printf(", CR=%d/%d IOPS", t_iops, m_iops);
-	if (eta_sec != INT_MAX && nr_running) {
+	} else if (je->m_iops || je->t_iops)
+		p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
+	if (je->eta_sec != INT_MAX && je->nr_running) {
 		char perc_str[32];
 		char *iops_str[2];
 		char *rate_str[2];
 		int l;
 
-		if ((!eta_sec && !eta_good) || nr_ramp == nr_running)
+		if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
 			strcpy(perc_str, "-.-% done");
 		else {
 			eta_good = 1;
@@ -380,17 +388,18 @@
 			sprintf(perc_str, "%3.1f%% done", perc);
 		}
 
-		rate_str[0] = num2str(rate[0], 5, 10, i2p);
-		rate_str[1] = num2str(rate[1], 5, 10, i2p);
+		rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
+		rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
 
-		iops_str[0] = num2str(iops[0], 4, 1, 0);
-		iops_str[1] = num2str(iops[1], 4, 1, 0);
+		iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+		iops_str[1] = num2str(je->iops[1], 4, 1, 0);
 
-		l = printf(": [%s] [%s] [%s/%s /s] [%s/%s iops] [eta %s]",
-				 run_str, perc_str, rate_str[0], rate_str[1],
-				 iops_str[0], iops_str[1], eta_str);
+		l = sprintf(p, ": [%s] [%s] [%s/%s /s] [%s/%s iops] [eta %s]",
+				je->run_str, perc_str, rate_str[0],
+				rate_str[1], iops_str[0], iops_str[1], eta_str);
+		p += l;
 		if (l >= 0 && l < linelen_last)
-			printf("%*s", linelen_last - l, "");
+			p += sprintf(p, "%*s", linelen_last - l, "");
 		linelen_last = l;
 
 		free(rate_str[0]);
@@ -398,10 +407,26 @@
 		free(iops_str[0]);
 		free(iops_str[1]);
 	}
-	printf("\r");
+	p += sprintf(p, "\r");
+
+	printf("%s", output);
 	fflush(stdout);
 }
 
+void print_thread_status(void)
+{
+	struct jobs_eta *je;
+
+	je = malloc(sizeof(*je) + thread_number * sizeof(char));
+
+	memset(je, 0, sizeof(*je) + thread_number * sizeof(char));
+
+	if (calc_thread_status(je))
+		display_thread_status(je);
+
+	free(je);
+}
+
 void print_status_init(int thr_number)
 {
 	run_str[thr_number] = 'P';
diff --git a/fio.c b/fio.c
index b492889..3e4dbb7 100644
--- a/fio.c
+++ b/fio.c
@@ -46,6 +46,7 @@
 #include "profile.h"
 #include "lib/rand.h"
 #include "memalign.h"
+#include "server.h"
 
 unsigned long page_mask;
 unsigned long page_size;
@@ -74,7 +75,6 @@
 
 struct io_log *agg_io_log[2];
 
-#define TERMINATE_ALL		(-1)
 #define JOB_START_TIMEOUT	(5 * 1000)
 
 void td_set_runstate(struct thread_data *td, int runstate)
@@ -87,7 +87,7 @@
 	td->runstate = runstate;
 }
 
-static void terminate_threads(int group_id)
+void fio_terminate_threads(int group_id)
 {
 	struct thread_data *td;
 	int i;
@@ -122,9 +122,10 @@
 {
 	if (threads) {
 		log_info("\nfio: terminating on signal %d\n", sig);
+		exit_backend = 1;
 		fflush(stdout);
 		exit_value = 128;
-		terminate_threads(TERMINATE_ALL);
+		fio_terminate_threads(TERMINATE_ALL);
 	}
 }
 
@@ -137,7 +138,11 @@
 		if (!threads)
 			break;
 		update_io_ticks();
-		print_thread_status();
+
+		if (is_backend)
+			fio_server_send_status();
+		else
+			print_thread_status();
 	}
 
 	return NULL;
@@ -178,6 +183,13 @@
 	act.sa_handler = sig_int;
 	act.sa_flags = SA_RESTART;
 	sigaction(SIGTERM, &act, NULL);
+
+	if (is_backend) {
+		memset(&act, 0, sizeof(act));
+		act.sa_handler = sig_int;
+		act.sa_flags = SA_RESTART;
+		sigaction(SIGPIPE, &act, NULL);
+	}
 }
 
 /*
@@ -744,7 +756,7 @@
 		if (!in_ramp_time(td) && should_check_rate(td, bytes_done)) {
 			if (check_min_rate(td, &comp_time, bytes_done)) {
 				if (exitall_on_terminate)
-					terminate_threads(td->groupid);
+					fio_terminate_threads(td->groupid);
 				td_verror(td, EIO, "check_min_rate");
 				break;
 			}
@@ -972,7 +984,7 @@
 
 static void reset_io_counters(struct thread_data *td)
 {
-	td->ts.stat_io_bytes[0] = td->ts.stat_io_bytes[1] = 0;
+	td->stat_io_bytes[0] = td->stat_io_bytes[1] = 0;
 	td->this_io_bytes[0] = td->this_io_bytes[1] = 0;
 	td->zone_bytes = 0;
 	td->rate_bytes[0] = td->rate_bytes[1] = 0;
@@ -1167,19 +1179,17 @@
 	}
 
 	fio_gettime(&td->epoch, NULL);
-	getrusage(RUSAGE_SELF, &td->ts.ru_start);
+	getrusage(RUSAGE_SELF, &td->ru_start);
 
 	clear_state = 0;
 	while (keep_running(td)) {
 		fio_gettime(&td->start, NULL);
-		memcpy(&td->ts.stat_sample_time[0], &td->start,
-				sizeof(td->start));
-		memcpy(&td->ts.stat_sample_time[1], &td->start,
-				sizeof(td->start));
+		memcpy(&td->stat_sample_time[0], &td->start, sizeof(td->start));
+		memcpy(&td->stat_sample_time[1], &td->start, sizeof(td->start));
 		memcpy(&td->tv_cache, &td->start, sizeof(td->start));
 
 		if (td->o.ratemin[0] || td->o.ratemin[1])
-			memcpy(&td->lastrate, &td->ts.stat_sample_time,
+			memcpy(&td->lastrate, &td->stat_sample_time,
 							sizeof(td->lastrate));
 
 		if (clear_state)
@@ -1228,40 +1238,40 @@
 	td->ts.io_bytes[1] = td->io_bytes[1];
 
 	fio_mutex_down(writeout_mutex);
-	if (td->ts.bw_log) {
+	if (td->bw_log) {
 		if (td->o.bw_log_file) {
-			finish_log_named(td, td->ts.bw_log,
+			finish_log_named(td, td->bw_log,
 						td->o.bw_log_file, "bw");
 		} else
-			finish_log(td, td->ts.bw_log, "bw");
+			finish_log(td, td->bw_log, "bw");
 	}
-	if (td->ts.lat_log) {
+	if (td->lat_log) {
 		if (td->o.lat_log_file) {
-			finish_log_named(td, td->ts.lat_log,
+			finish_log_named(td, td->lat_log,
 						td->o.lat_log_file, "lat");
 		} else
-			finish_log(td, td->ts.lat_log, "lat");
+			finish_log(td, td->lat_log, "lat");
 	}
-	if (td->ts.slat_log) {
+	if (td->slat_log) {
 		if (td->o.lat_log_file) {
-			finish_log_named(td, td->ts.slat_log,
+			finish_log_named(td, td->slat_log,
 						td->o.lat_log_file, "slat");
 		} else
-			finish_log(td, td->ts.slat_log, "slat");
+			finish_log(td, td->slat_log, "slat");
 	}
-	if (td->ts.clat_log) {
+	if (td->clat_log) {
 		if (td->o.lat_log_file) {
-			finish_log_named(td, td->ts.clat_log,
+			finish_log_named(td, td->clat_log,
 						td->o.lat_log_file, "clat");
 		} else
-			finish_log(td, td->ts.clat_log, "clat");
+			finish_log(td, td->clat_log, "clat");
 	}
 	fio_mutex_up(writeout_mutex);
 	if (td->o.exec_postrun)
 		exec_string(td->o.exec_postrun);
 
 	if (exitall_on_terminate)
-		terminate_threads(td->groupid);
+		fio_terminate_threads(td->groupid);
 
 err:
 	if (td->error)
@@ -1415,7 +1425,7 @@
 	}
 
 	if (*nr_running == cputhreads && !pending && realthreads)
-		terminate_threads(TERMINATE_ALL);
+		fio_terminate_threads(TERMINATE_ALL);
 }
 
 static void *gtod_thread_main(void *data)
@@ -1477,6 +1487,8 @@
 	if (fio_gtod_offload && fio_start_gtod_thread())
 		return;
 
+	set_sig_handlers();
+
 	if (!terse_output) {
 		log_info("Starting ");
 		if (nr_thread)
@@ -1492,8 +1504,6 @@
 		fflush(stdout);
 	}
 
-	set_sig_handlers();
-
 	todo = thread_number;
 	nr_running = 0;
 	nr_started = 0;
@@ -1609,7 +1619,7 @@
 			dprint(FD_MUTEX, "wait on startup_mutex\n");
 			if (fio_mutex_down_timeout(startup_mutex, 10)) {
 				log_err("fio: job startup hung? exiting.\n");
-				terminate_threads(TERMINATE_ALL);
+				fio_terminate_threads(TERMINATE_ALL);
 				fio_abort = 1;
 				nr_started--;
 				break;
@@ -1683,42 +1693,21 @@
 
 	while (nr_running) {
 		reap_threads(&nr_running, &t_rate, &m_rate);
-		usleep(10000);
+
+		if (is_backend)
+			fio_server_idle_loop();
+		else
+			usleep(10000);
 	}
 
 	update_io_ticks();
 	fio_unpin_memory();
 }
 
-int main(int argc, char *argv[], char *envp[])
+int exec_run(void)
 {
-	long ps;
-
-	arch_init(envp);
-
-	sinit();
-
-	/*
-	 * We need locale for number printing, if it isn't set then just
-	 * go with the US format.
-	 */
-	if (!getenv("LC_NUMERIC"))
-		setlocale(LC_NUMERIC, "en_US");
-
-	ps = sysconf(_SC_PAGESIZE);
-	if (ps < 0) {
-		log_err("Failed to get page size\n");
-		return 1;
-	}
-
-	page_size = ps;
-	page_mask = ps - 1;
-
-	fio_keywords_init();
-
-	if (parse_options(argc, argv))
-		return 1;
-
+	if (nr_clients)
+		return fio_handle_clients();
 	if (exec_profile && load_profile(exec_profile))
 		return 1;
 
@@ -1762,3 +1751,44 @@
 	fio_mutex_remove(writeout_mutex);
 	return exit_value;
 }
+
+void reset_fio_state(void)
+{
+	groupid = 0;
+	thread_number = 0;
+	nr_process = 0;
+	nr_thread = 0;
+	done_secs = 0;
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+	long ps;
+
+	arch_init(envp);
+
+	sinit();
+
+	/*
+	 * We need locale for number printing, if it isn't set then just
+	 * go with the US format.
+	 */
+	if (!getenv("LC_NUMERIC"))
+		setlocale(LC_NUMERIC, "en_US");
+
+	ps = sysconf(_SC_PAGESIZE);
+	if (ps < 0) {
+		log_err("Failed to get page size\n");
+		return 1;
+	}
+
+	page_size = ps;
+	page_mask = ps - 1;
+
+	fio_keywords_init();
+
+	if (parse_options(argc, argv))
+		return 1;
+
+	return exec_run();
+}
diff --git a/fio.h b/fio.h
index 022ba57..1fb2118 100644
--- a/fio.h
+++ b/fio.h
@@ -16,6 +16,10 @@
 
 struct thread_data;
 
+#define FIO_MAJOR	1
+#define FIO_MINOR	58
+#define FIO_PATCH	0
+
 #include "compiler/compiler.h"
 #include "flist.h"
 #include "fifo.h"
@@ -35,6 +39,8 @@
 #include "time.h"
 #include "lib/getopt.h"
 #include "lib/rand.h"
+#include "server.h"
+#include "stat.h"
 
 #ifdef FIO_HAVE_GUASI
 #include <guasi.h>
@@ -44,14 +50,6 @@
 #include <sys/asynch.h>
 #endif
 
-struct group_run_stats {
-	unsigned long long max_run[2], min_run[2];
-	unsigned long long max_bw[2], min_bw[2];
-	unsigned long long io_kb[2];
-	unsigned long long agg[2];
-	unsigned int kb_base;
-};
-
 /*
  * What type of allocation to use for io buffers
  */
@@ -71,172 +69,6 @@
 	RW_SEQ_IDENT,
 };
 
-/*
- * How many depth levels to log
- */
-#define FIO_IO_U_MAP_NR	7
-#define FIO_IO_U_LAT_U_NR 10
-#define FIO_IO_U_LAT_M_NR 12
-
-/*
- * Aggregate clat samples to report percentile(s) of them.
- *
- * EXECUTIVE SUMMARY
- *
- * FIO_IO_U_PLAT_BITS determines the maximum statistical error on the
- * value of resulting percentiles. The error will be approximately
- * 1/2^(FIO_IO_U_PLAT_BITS+1) of the value.
- *
- * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the maximum
- * range being tracked for latency samples. The maximum value tracked
- * accurately will be 2^(GROUP_NR + PLAT_BITS -1) microseconds.
- *
- * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the memory
- * requirement of storing those aggregate counts. The memory used will
- * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(int)
- * bytes.
- *
- * FIO_IO_U_PLAT_NR is the total number of buckets.
- *
- * DETAILS
- *
- * Suppose the clat varies from 0 to 999 (usec), the straightforward
- * method is to keep an array of (999 + 1) buckets, in which a counter
- * keeps the count of samples which fall in the bucket, e.g.,
- * {[0],[1],...,[999]}. However this consumes a huge amount of space,
- * and can be avoided if an approximation is acceptable.
- *
- * One such method is to let the range of the bucket to be greater
- * than one. This method has low accuracy when the value is small. For
- * example, let the buckets be {[0,99],[100,199],...,[900,999]}, and
- * the represented value of each bucket be the mean of the range. Then
- * a value 0 has an round-off error of 49.5. To improve on this, we
- * use buckets with non-uniform ranges, while bounding the error of
- * each bucket within a ratio of the sample value. A simple example
- * would be when error_bound = 0.005, buckets are {
- * {[0],[1],...,[99]}, {[100,101],[102,103],...,[198,199]},..,
- * {[900,909],[910,919]...}  }. The total range is partitioned into
- * groups with different ranges, then buckets with uniform ranges. An
- * upper bound of the error is (range_of_bucket/2)/value_of_bucket
- *
- * For better efficiency, we implement this using base two. We group
- * samples by their Most Significant Bit (MSB), extract the next M bit
- * of them as an index within the group, and discard the rest of the
- * bits.
- *
- * E.g., assume a sample 'x' whose MSB is bit n (starting from bit 0),
- * and use M bit for indexing
- *
- *        | n |    M bits   | bit (n-M-1) ... bit 0 |
- *
- * Because x is at least 2^n, and bit 0 to bit (n-M-1) is at most
- * (2^(n-M) - 1), discarding bit 0 to (n-M-1) makes the round-off
- * error
- *
- *           2^(n-M)-1    2^(n-M)    1
- *      e <= --------- <= ------- = ---
- *             2^n          2^n     2^M
- *
- * Furthermore, we use "mean" of the range to represent the bucket,
- * the error e can be lowered by half to 1 / 2^(M+1). By using M bits
- * as the index, each group must contains 2^M buckets.
- *
- * E.g. Let M (FIO_IO_U_PLAT_BITS) be 6
- *      Error bound is 1/2^(6+1) = 0.0078125 (< 1%)
- *
- *	Group	MSB	#discarded	range of		#buckets
- *			error_bits	value
- *	----------------------------------------------------------------
- *	0*	0~5	0		[0,63]			64
- *	1*	6	0		[64,127]		64
- *	2	7	1		[128,255]		64
- *	3	8	2		[256,511]		64
- *	4	9	3		[512,1023]		64
- *	...	...	...		[...,...]		...
- *	18	23	17		[8838608,+inf]**	64
- *
- *  * Special cases: when n < (M-1) or when n == (M-1), in both cases,
- *    the value cannot be rounded off. Use all bits of the sample as
- *    index.
- *
- *  ** If a sample's MSB is greater than 23, it will be counted as 23.
- */
-
-#define FIO_IO_U_PLAT_BITS 6
-#define FIO_IO_U_PLAT_VAL (1 << FIO_IO_U_PLAT_BITS)
-#define FIO_IO_U_PLAT_GROUP_NR 19
-#define FIO_IO_U_PLAT_NR (FIO_IO_U_PLAT_GROUP_NR * FIO_IO_U_PLAT_VAL)
-#define FIO_IO_U_LIST_MAX_LEN 20 /* The size of the default and user-specified
-					list of percentiles */
-
-#define MAX_PATTERN_SIZE 512
-
-struct thread_stat {
-	char *name;
-	char *verror;
-	int error;
-	int groupid;
-	pid_t pid;
-	char *description;
-	int members;
-
-	struct io_log *slat_log;
-	struct io_log *clat_log;
-	struct io_log *lat_log;
-	struct io_log *bw_log;
-
-	/*
-	 * bandwidth and latency stats
-	 */
-	struct io_stat clat_stat[2];		/* completion latency */
-	struct io_stat slat_stat[2];		/* submission latency */
-	struct io_stat lat_stat[2];		/* total latency */
-	struct io_stat bw_stat[2];		/* bandwidth stats */
-
-	unsigned long long stat_io_bytes[2];
-	struct timeval stat_sample_time[2];
-
-	/*
-	 * fio system usage accounting
-	 */
-	struct rusage ru_start;
-	struct rusage ru_end;
-	unsigned long usr_time;
-	unsigned long sys_time;
-	unsigned long ctx;
-	unsigned long minf, majf;
-
-	/*
-	 * IO depth and latency stats
-	 */
-	unsigned int clat_percentiles;
-	double* percentile_list;
-
-	unsigned int io_u_map[FIO_IO_U_MAP_NR];
-	unsigned int io_u_submit[FIO_IO_U_MAP_NR];
-	unsigned int io_u_complete[FIO_IO_U_MAP_NR];
-	unsigned int io_u_lat_u[FIO_IO_U_LAT_U_NR];
-	unsigned int io_u_lat_m[FIO_IO_U_LAT_M_NR];
-	unsigned int io_u_plat[2][FIO_IO_U_PLAT_NR];
-	unsigned long total_io_u[3];
-	unsigned long short_io_u[3];
-	unsigned long total_submit;
-	unsigned long total_complete;
-
-	unsigned long long io_bytes[2];
-	unsigned long long runtime[2];
-	unsigned long total_run_time;
-
-	/*
-	 * IO Error related stats
-	 */
-	unsigned continue_on_error;
-	unsigned long total_err_count;
-	int first_error;
-
-	unsigned int kb_base;
-};
-
 struct bssplit {
 	unsigned int bs;
 	unsigned char perc;
@@ -365,7 +197,7 @@
 	unsigned long long trim_backlog;
 	unsigned int clat_percentiles;
 	unsigned int overwrite_plist;
-	double percentile_list[FIO_IO_U_LIST_MAX_LEN];
+	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
 	char *read_iolog_file;
 	char *write_iolog_file;
@@ -418,8 +250,6 @@
 	unsigned int userspace_libaio_reap;
 };
 
-#define FIO_VERROR_SIZE	128
-
 /*
  * This describes a single thread/process executing a fio job.
  */
@@ -430,6 +260,18 @@
 	int thread_number;
 	int groupid;
 	struct thread_stat ts;
+
+	struct io_log *slat_log;
+	struct io_log *clat_log;
+	struct io_log *lat_log;
+	struct io_log *bw_log;
+
+	uint64_t stat_io_bytes[2];
+	struct timeval stat_sample_time[2];
+
+	struct rusage ru_start;
+	struct rusage ru_end;
+
 	struct fio_file **files;
 	unsigned int files_size;
 	unsigned int files_index;
@@ -632,6 +474,9 @@
 #define td_vmsg(td, err, msg, func)	\
 	__td_verror((td), (err), (msg), (func))
 
+#define __fio_stringify_1(x)	#x
+#define __fio_stringify(x)	__fio_stringify_1(x)
+
 extern int exitall_on_terminate;
 extern int thread_number;
 extern int nr_process, nr_thread;
@@ -650,6 +495,10 @@
 extern enum fio_cs fio_clock_source;
 extern int warnings_fatal;
 extern int terse_version;
+extern int is_backend;
+extern int nr_clients;
+extern int log_syslog;
+extern const fio_fp64_t def_percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
 extern struct thread_data *threads;
 
@@ -690,6 +539,10 @@
  * Init/option functions
  */
 extern int __must_check parse_options(int, char **);
+extern int parse_jobs_ini(char *, int, int);
+extern int parse_cmd_line(int, char **);
+extern int exec_run(void);
+extern void reset_fio_state(void);
 extern int fio_options_parse(struct thread_data *, char **, int);
 extern void fio_keywords_init(void);
 extern int fio_cmd_option_parse(struct thread_data *, const char *, char *);
@@ -731,6 +584,8 @@
 };
 
 extern void td_set_runstate(struct thread_data *, int);
+#define TERMINATE_ALL		(-1)
+extern void fio_terminate_threads(int);
 
 /*
  * Memory helpers
diff --git a/ieee754.c b/ieee754.c
new file mode 100644
index 0000000..c7742a2
--- /dev/null
+++ b/ieee754.c
@@ -0,0 +1,84 @@
+/*
+ * Shamelessly lifted from Beej's Guide to Network Programming, found here:
+ *
+ * http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#serialization
+ *
+ * Below code was granted to the public domain.
+ */
+#include <inttypes.h>
+#include "ieee754.h"
+
+uint64_t pack754(long double f, unsigned bits, unsigned expbits)
+{
+	long double fnorm;
+	int shift;
+	long long sign, exp, significand;
+	unsigned significandbits = bits - expbits - 1; // -1 for sign bit
+
+	// get this special case out of the way
+	if (f == 0.0)
+		return 0;
+
+	// check sign and begin normalization
+	if (f < 0) {
+		sign = 1;
+		fnorm = -f;
+	} else {
+		sign = 0;
+		fnorm = f;
+	}
+
+	// get the normalized form of f and track the exponent
+	shift = 0;
+	while (fnorm >= 2.0) {
+		fnorm /= 2.0;
+		shift++;
+	}
+	while (fnorm < 1.0) {
+		fnorm *= 2.0;
+		shift--;
+	}
+	fnorm = fnorm - 1.0;
+
+	// calculate the binary form (non-float) of the significand data
+	significand = fnorm * ((1LL << significandbits) + 0.5f);
+
+	// get the biased exponent
+	exp = shift + ((1 << (expbits - 1)) - 1); // shift + bias
+
+	// return the final answer
+	return (sign << (bits - 1)) | (exp << (bits-expbits - 1)) | significand;
+}
+
+long double unpack754(uint64_t i, unsigned bits, unsigned expbits)
+{
+	long double result;
+	long long shift;
+	unsigned bias;
+	unsigned significandbits = bits - expbits - 1; // -1 for sign bit
+
+	if (i == 0)
+		return 0.0;
+
+	// pull the significand
+	result = (i & ((1LL << significandbits) - 1)); // mask
+	result /= (1LL << significandbits); // convert back to float
+	result += 1.0f; // add the one back on
+
+	// deal with the exponent
+	bias = (1 << (expbits - 1)) - 1;
+	shift = ((i >> significandbits) & ((1LL << expbits) - 1)) - bias;
+	while (shift > 0) {
+		result *= 2.0;
+		shift--;
+	}
+	while (shift < 0) {
+		result /= 2.0;
+		shift++;
+	}
+
+	// sign it
+	result *= (i >> (bits - 1)) & 1 ? -1.0 : 1.0;
+
+	return result;
+}
diff --git a/ieee754.h b/ieee754.h
new file mode 100644
index 0000000..5af9518
--- /dev/null
+++ b/ieee754.h
@@ -0,0 +1,20 @@
+#ifndef FIO_IEEE754_H
+#define FIO_IEEE754_H
+
+#include <inttypes.h>
+
+extern uint64_t pack754(long double f, unsigned bits, unsigned expbits);
+extern long double unpack754(uint64_t i, unsigned bits, unsigned expbits);
+
+#define fio_double_to_uint64(val)	pack754((val), 64, 11)
+#define fio_uint64_to_double(val)	unpack754((val), 64, 11)
+
+typedef struct fio_fp64 {
+	union {
+		uint64_t i;
+		double f;
+		uint8_t filler[16];
+	} u;
+} fio_fp64_t;
+
+#endif
diff --git a/init.c b/init.c
index 4eb0df2..142a163 100644
--- a/init.c
+++ b/init.c
@@ -19,10 +19,18 @@
 #include "filehash.h"
 #include "verify.h"
 #include "profile.h"
+#include "server.h"
 
 #include "lib/getopt.h"
 
-static char fio_version_string[] = "fio 1.58";
+#if FIO_PATCH > 0
+static char fio_version_string[] =	__fio_stringify(FIO_MAJOR) "."	\
+					__fio_stringify(FIO_MINOR) "."	\
+					__fio_stringify(FIO_PATCH);
+#else
+static char fio_version_string[] =	__fio_stringify(FIO_MAJOR) "."	\
+					__fio_stringify(FIO_MINOR);
+#endif
 
 #define FIO_RANDSEED		(0xb1899bedUL)
 
@@ -45,6 +53,9 @@
 char *exec_profile = NULL;
 int warnings_fatal = 0;
 int terse_version = 2;
+int is_backend = 0;
+int nr_clients = 0;
+int log_syslog = 0;
 
 int write_bw_log = 0;
 int read_only = 0;
@@ -59,6 +70,24 @@
 
 static char cmd_optstr[256];
 
+const fio_fp64_t def_percentile_list[FIO_IO_U_LIST_MAX_LEN] = {
+	{ .u.f	=  1.0 },
+	{ .u.f	=  5.0 },
+	{ .u.f	= 10.0 },
+	{ .u.f	= 20.0 },
+	{ .u.f	= 30.0 },
+	{ .u.f	= 40.0 },
+	{ .u.f	= 50.0 },
+	{ .u.f	= 60.0 },
+	{ .u.f	= 70.0 },
+	{ .u.f	= 80.0 },
+	{ .u.f	= 90.0 },
+	{ .u.f	= 95.0 },
+	{ .u.f	= 99.0 },
+	{ .u.f	= 99.5 },
+	{ .u.f	= 99.9 },
+};
+
 /*
  * Command line options. These will contain the above, plus a few
  * extra that only pertain to fio itself and not jobs.
@@ -155,18 +184,95 @@
 		.val		= 'V',
 	},
 	{
+		.name		= (char *) "server",
+		.has_arg	= no_argument,
+		.val		= 'S',
+	},
+	{	.name		= (char *) "daemonize",
+		.has_arg	= no_argument,
+		.val		= 'D',
+	},
+	{
+		.name		= (char *) "net-port",
+		.has_arg	= required_argument,
+		.val		= 'P',
+	},
+	{
+		.name		= (char *) "client",
+		.has_arg	= required_argument,
+		.val		= 'C',
+	},
+	{
 		.name		= NULL,
 	},
 };
 
-FILE *get_f_out()
+static void free_shm(void)
 {
-	return f_out;
+	struct shmid_ds sbuf;
+
+	if (threads) {
+		void *tp = threads;
+
+		threads = NULL;
+		file_hash_exit();
+		fio_debug_jobp = NULL;
+		shmdt(tp);
+		shmctl(shm_id, IPC_RMID, &sbuf);
+	}
+
+	scleanup();
 }
 
-FILE *get_f_err()
+/*
+ * The thread area is shared between the main process and the job
+ * threads/processes. So setup a shared memory segment that will hold
+ * all the job info. We use the end of the region for keeping track of
+ * open files across jobs, for file sharing.
+ */
+static int setup_thread_area(void)
 {
-	return f_err;
+	void *hash;
+
+	if (threads)
+		return 0;
+
+	/*
+	 * 1024 is too much on some machines, scale max_jobs if
+	 * we get a failure that looks like too large a shm segment
+	 */
+	do {
+		size_t size = max_jobs * sizeof(struct thread_data);
+
+		size += file_hash_size;
+		size += sizeof(unsigned int);
+
+		shm_id = shmget(0, size, IPC_CREAT | 0600);
+		if (shm_id != -1)
+			break;
+		if (errno != EINVAL) {
+			perror("shmget");
+			break;
+		}
+
+		max_jobs >>= 1;
+	} while (max_jobs);
+
+	if (shm_id == -1)
+		return 1;
+
+	threads = shmat(shm_id, NULL, 0);
+	if (threads == (void *) -1) {
+		perror("shmat");
+		return 1;
+	}
+
+	memset(threads, 0, max_jobs * sizeof(struct thread_data));
+	hash = (void *) threads + max_jobs * sizeof(struct thread_data);
+	fio_debug_jobp = (void *) hash + file_hash_size;
+	*fio_debug_jobp = -1;
+	file_hash_init(hash);
+	return 0;
 }
 
 /*
@@ -178,6 +284,10 @@
 
 	if (global)
 		return &def_thread;
+	if (setup_thread_area()) {
+		log_err("error: failed to setup shm segment\n");
+		return NULL;
+	}
 	if (thread_number >= max_jobs) {
 		log_err("error: maximum number of jobs (%d) reached.\n",
 				max_jobs);
@@ -627,9 +737,9 @@
 
 	td->ts.clat_percentiles = td->o.clat_percentiles;
 	if (td->o.overwrite_plist)
-		td->ts.percentile_list = td->o.percentile_list;
+		memcpy(td->ts.percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
 	else
-		td->ts.percentile_list = NULL;
+		memcpy(td->ts.percentile_list, def_percentile_list, sizeof(def_percentile_list));
 
 	td->ts.clat_stat[0].min_val = td->ts.clat_stat[1].min_val = ULONG_MAX;
 	td->ts.slat_stat[0].min_val = td->ts.slat_stat[1].min_val = ULONG_MAX;
@@ -652,12 +762,12 @@
 		goto err;
 
 	if (td->o.write_lat_log) {
-		setup_log(&td->ts.lat_log);
-		setup_log(&td->ts.slat_log);
-		setup_log(&td->ts.clat_log);
+		setup_log(&td->lat_log);
+		setup_log(&td->slat_log);
+		setup_log(&td->clat_log);
 	}
 	if (td->o.write_bw_log)
-		setup_log(&td->ts.bw_log);
+		setup_log(&td->bw_log);
 
 	if (!td->o.name)
 		td->o.name = strdup(jobname);
@@ -800,7 +910,7 @@
 /*
  * This is our [ini] type file parser.
  */
-static int parse_jobs_ini(char *file, int stonewall_flag)
+int parse_jobs_ini(char *file, int is_buf, int stonewall_flag)
 {
 	unsigned int global;
 	struct thread_data *td;
@@ -814,14 +924,18 @@
 	char **opts;
 	int i, alloc_opts, num_opts;
 
-	if (!strcmp(file, "-"))
-		f = stdin;
-	else
-		f = fopen(file, "r");
+	if (is_buf)
+		f = NULL;
+	else {
+		if (!strcmp(file, "-"))
+			f = stdin;
+		else
+			f = fopen(file, "r");
 
-	if (!f) {
-		perror("fopen job file");
-		return 1;
+		if (!f) {
+			perror("fopen job file");
+			return 1;
+		}
 	}
 
 	string = malloc(4096);
@@ -843,7 +957,10 @@
 		 * haven't handled.
 		 */
 		if (!skip_fgets) {
-			p = fgets(string, 4095, f);
+			if (is_buf)
+				p = strsep(&file, "\n");
+			else
+				p = fgets(string, 4095, f);
 			if (!p)
 				break;
 		}
@@ -897,7 +1014,14 @@
 		num_opts = 0;
 		memset(opts, 0, alloc_opts * sizeof(char *));
 
-		while ((p = fgets(string, 4096, f)) != NULL) {
+		while (1) {
+			if (is_buf)
+				p = strsep(&file, "\n");
+			else
+				p = fgets(string, 4096, f);
+			if (!p)
+				break;
+
 			if (is_empty_or_comment(p))
 				continue;
 
@@ -950,7 +1074,7 @@
 	free(string);
 	free(name);
 	free(opts);
-	if (f != stdin)
+	if (!is_buf && f != stdin)
 		fclose(f);
 	return ret;
 }
@@ -969,72 +1093,6 @@
 	return 0;
 }
 
-static void free_shm(void)
-{
-	struct shmid_ds sbuf;
-
-	if (threads) {
-		void *tp = threads;
-
-		threads = NULL;
-		file_hash_exit();
-		fio_debug_jobp = NULL;
-		shmdt(tp);
-		shmctl(shm_id, IPC_RMID, &sbuf);
-	}
-
-	scleanup();
-}
-
-/*
- * The thread area is shared between the main process and the job
- * threads/processes. So setup a shared memory segment that will hold
- * all the job info. We use the end of the region for keeping track of
- * open files across jobs, for file sharing.
- */
-static int setup_thread_area(void)
-{
-	void *hash;
-
-	/*
-	 * 1024 is too much on some machines, scale max_jobs if
-	 * we get a failure that looks like too large a shm segment
-	 */
-	do {
-		size_t size = max_jobs * sizeof(struct thread_data);
-
-		size += file_hash_size;
-		size += sizeof(unsigned int);
-
-		shm_id = shmget(0, size, IPC_CREAT | 0600);
-		if (shm_id != -1)
-			break;
-		if (errno != EINVAL) {
-			perror("shmget");
-			break;
-		}
-
-		max_jobs >>= 1;
-	} while (max_jobs);
-
-	if (shm_id == -1)
-		return 1;
-
-	threads = shmat(shm_id, NULL, 0);
-	if (threads == (void *) -1) {
-		perror("shmat");
-		return 1;
-	}
-
-	memset(threads, 0, max_jobs * sizeof(struct thread_data));
-	hash = (void *) threads + max_jobs * sizeof(struct thread_data);
-	fio_debug_jobp = (void *) hash + file_hash_size;
-	*fio_debug_jobp = -1;
-	file_hash_init(hash);
-	atexit(free_shm);
-	return 0;
-}
-
 static void usage(const char *name)
 {
 	printf("%s\n", fio_version_string);
@@ -1060,6 +1118,9 @@
 		" (def 1024)\n");
 	printf("\t--warnings-fatal Fio parser warnings are fatal\n");
 	printf("\t--max-jobs\tMaximum number of threads/processes to support\n");
+	printf("\t--server\tStart a backend fio server\n");
+	printf("\t--client=hostname Talk to remove backend fio server at hostname\n");
+	printf("\t--net-port=port\tUse specified port for client/server connection\n");
 	printf("\nFio was written by Jens Axboe <jens.axboe@oracle.com>");
 	printf("\n                   Jens Axboe <jaxboe@fusionio.com>\n");
 }
@@ -1079,6 +1140,7 @@
 	{ .name = "mutex",	.shift = FD_MUTEX },
 	{ .name	= "profile",	.shift = FD_PROFILE },
 	{ .name = "time",	.shift = FD_TIME },
+	{ .name = "net",	.shift = FD_NET },
 	{ .name = NULL, },
 };
 
@@ -1163,27 +1225,35 @@
 	ostr[c] = '\0';
 }
 
-static int parse_cmd_line(int argc, char *argv[])
+int parse_cmd_line(int argc, char *argv[])
 {
 	struct thread_data *td = NULL;
 	int c, ini_idx = 0, lidx, ret = 0, do_exit = 0, exit_val = 0;
 	char *ostr = cmd_optstr;
+	int daemonize_server = 0;
+	char *cur_client = NULL;
+	int backend = 0;
 
 	while ((c = getopt_long_only(argc, argv, ostr, l_opts, &lidx)) != -1) {
 		switch (c) {
 		case 'a':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			smalloc_pool_size = atoi(optarg);
 			break;
 		case 't':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			def_timeout = atoi(optarg);
 			break;
 		case 'l':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			write_lat_log = 1;
 			break;
 		case 'b':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			write_bw_log = 1;
 			break;
 		case 'o':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			f_out = fopen(optarg, "w+");
 			if (!f_out) {
 				perror("fopen output");
@@ -1192,6 +1262,7 @@
 			f_err = f_out;
 			break;
 		case 'm':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			terse_output = 1;
 			break;
 		case 'h':
@@ -1200,15 +1271,18 @@
 		case 'c':
 			exit(fio_show_option_help(optarg));
 		case 's':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			dump_cmdline = 1;
 			break;
 		case 'r':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			read_only = 1;
 			break;
 		case 'v':
 			log_info("%s\n", fio_version_string);
 			exit(0);
 		case 'V':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			terse_version = atoi(optarg);
 			if (terse_version != 2) {
 				log_err("fio: bad terse version format\n");
@@ -1217,18 +1291,22 @@
 			}
 			break;
 		case 'e':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			if (!strcmp("always", optarg))
 				eta_print = FIO_ETA_ALWAYS;
 			else if (!strcmp("never", optarg))
 				eta_print = FIO_ETA_NEVER;
 			break;
 		case 'd':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			if (set_debug(optarg))
 				do_exit++;
 			break;
 		case 'x': {
 			size_t new_size;
 
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
+
 			if (!strcmp(optarg, "global")) {
 				log_err("fio: can't use global as only "
 					"section\n");
@@ -1243,12 +1321,15 @@
 			break;
 			}
 		case 'p':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			exec_profile = strdup(optarg);
 			break;
 		case FIO_GETOPT_JOB: {
 			const char *opt = l_opts[lidx].name;
 			char *val = optarg;
 
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
+
 			if (!strncmp(opt, "name", 4) && td) {
 				ret = add_job(td, td->o.name ?: "fio", 0);
 				if (ret)
@@ -1274,9 +1355,11 @@
 			break;
 		}
 		case 'w':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			warnings_fatal = 1;
 			break;
 		case 'j':
+			fio_client_add_cmd_option(cur_client, argv[optind-1]);
 			max_jobs = atoi(optarg);
 			if (!max_jobs || max_jobs > REAL_MAX_JOBS) {
 				log_err("fio: invalid max jobs: %d\n", max_jobs);
@@ -1284,16 +1367,53 @@
 				exit_val = 1;
 			}
 			break;
+		case 'S':
+			if (nr_clients) {
+				log_err("fio: can't be both client and server\n");
+				do_exit++;
+				exit_val = 1;
+				break;
+			}
+			is_backend = 1;
+			backend = 1;
+			break;
+		case 'D':
+			daemonize_server = 1;
+			break;
+		case 'P':
+			fio_net_port = atoi(optarg);
+			break;
+		case 'C':
+			if (is_backend) {
+				log_err("fio: can't be both client and server\n");
+				do_exit++;
+				exit_val = 1;
+				break;
+			}
+			fio_client_add(optarg);
+			cur_client = optarg;
+			break;
 		default:
 			do_exit++;
 			exit_val = 1;
 			break;
 		}
+		if (do_exit)
+			break;
 	}
 
 	if (do_exit)
 		exit(exit_val);
 
+	if (nr_clients && fio_clients_connect()) {
+		do_exit++;
+		exit_val = 1;
+		return -1;
+	}
+
+	if (is_backend && backend)
+		return fio_start_server(daemonize_server);
+
 	if (td) {
 		if (!ret)
 			ret = add_job(td, td->o.name ?: "fio", 0);
@@ -1319,19 +1439,27 @@
 	fio_options_fill_optstring();
 	fio_options_dup_and_init(l_opts);
 
-	if (setup_thread_area())
-		return 1;
+	atexit(free_shm);
+
 	if (fill_def_thread())
 		return 1;
 
 	job_files = parse_cmd_line(argc, argv);
 
-	for (i = 0; i < job_files; i++) {
-		if (fill_def_thread())
-			return 1;
-		if (parse_jobs_ini(ini_file[i], i))
-			return 1;
-		free(ini_file[i]);
+	if (job_files > 0) {
+		for (i = 0; i < job_files; i++) {
+			if (fill_def_thread())
+				return 1;
+			if (nr_clients) {
+				if (fio_clients_send_ini(ini_file[i]))
+					return 1;
+				free(ini_file[i]);
+			} else if (!is_backend) {
+				if (parse_jobs_ini(ini_file[i], 0, i))
+					return 1;
+				free(ini_file[i]);
+			}
+		}
 	}
 
 	free(ini_file);
@@ -1342,9 +1470,13 @@
 			return 0;
 		if (exec_profile)
 			return 0;
+		if (is_backend || nr_clients)
+			return 0;
 
-		log_err("No jobs(s) defined\n\n");
-		usage(argv[0]);
+		if (job_files > 0) {
+			log_err("No jobs(s) defined\n\n");
+			usage(argv[0]);
+		}
 		return 1;
 	}
 
diff --git a/iolog.c b/iolog.c
new file mode 100644
index 0000000..f962864
--- /dev/null
+++ b/iolog.c
@@ -0,0 +1,541 @@
+/*
+ * Code related to writing an iolog of what a thread is doing, and to
+ * later read that back and replay
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <assert.h>
+#include "flist.h"
+#include "fio.h"
+#include "verify.h"
+#include "trim.h"
+
+static const char iolog_ver2[] = "fio version 2 iolog";
+
+void queue_io_piece(struct thread_data *td, struct io_piece *ipo)
+{
+	flist_add_tail(&ipo->list, &td->io_log_list);
+	td->total_io_size += ipo->len;
+}
+
+void log_io_u(struct thread_data *td, struct io_u *io_u)
+{
+	const char *act[] = { "read", "write", "sync", "datasync",
+				"sync_file_range", "wait", "trim" };
+
+	assert(io_u->ddir <= 6);
+
+	if (!td->o.write_iolog_file)
+		return;
+
+	fprintf(td->iolog_f, "%s %s %llu %lu\n", io_u->file->file_name,
+						act[io_u->ddir], io_u->offset,
+						io_u->buflen);
+}
+
+void log_file(struct thread_data *td, struct fio_file *f,
+	      enum file_log_act what)
+{
+	const char *act[] = { "add", "open", "close" };
+
+	assert(what < 3);
+
+	if (!td->o.write_iolog_file)
+		return;
+
+
+	/*
+	 * this happens on the pre-open/close done before the job starts
+	 */
+	if (!td->iolog_f)
+		return;
+
+	fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]);
+}
+
+static void iolog_delay(struct thread_data *td, unsigned long delay)
+{
+	unsigned long usec = utime_since_now(&td->last_issue);
+
+	if (delay < usec)
+		return;
+
+	delay -= usec;
+
+	/*
+	 * less than 100 usec delay, just regard it as noise
+	 */
+	if (delay < 100)
+		return;
+
+	usec_sleep(td, delay);
+}
+
+static int ipo_special(struct thread_data *td, struct io_piece *ipo)
+{
+	struct fio_file *f;
+	int ret;
+
+	/*
+	 * Not a special ipo
+	 */
+	if (ipo->ddir != DDIR_INVAL)
+		return 0;
+
+	f = td->files[ipo->fileno];
+
+	switch (ipo->file_action) {
+	case FIO_LOG_OPEN_FILE:
+		ret = td_io_open_file(td, f);
+		if (!ret)
+			break;
+		td_verror(td, ret, "iolog open file");
+		return -1;
+	case FIO_LOG_CLOSE_FILE:
+		td_io_close_file(td, f);
+		break;
+	case FIO_LOG_UNLINK_FILE:
+		unlink(f->file_name);
+		break;
+	default:
+		log_err("fio: bad file action %d\n", ipo->file_action);
+		break;
+	}
+
+	return 1;
+}
+
+int read_iolog_get(struct thread_data *td, struct io_u *io_u)
+{
+	struct io_piece *ipo;
+	unsigned long elapsed;
+	
+	while (!flist_empty(&td->io_log_list)) {
+		int ret;
+
+		ipo = flist_entry(td->io_log_list.next, struct io_piece, list);
+		flist_del(&ipo->list);
+		remove_trim_entry(td, ipo);
+
+		ret = ipo_special(td, ipo);
+		if (ret < 0) {
+			free(ipo);
+			break;
+		} else if (ret > 0) {
+			free(ipo);
+			continue;
+		}
+
+		io_u->ddir = ipo->ddir;
+		if (ipo->ddir != DDIR_WAIT) {
+			io_u->offset = ipo->offset;
+			io_u->buflen = ipo->len;
+			io_u->file = td->files[ipo->fileno];
+			get_file(io_u->file);
+			dprint(FD_IO, "iolog: get %llu/%lu/%s\n", io_u->offset,
+						io_u->buflen, io_u->file->file_name);
+			if (ipo->delay)
+				iolog_delay(td, ipo->delay);
+		} else {
+			elapsed = mtime_since_genesis();
+			if (ipo->delay > elapsed)
+				usec_sleep(td, (ipo->delay - elapsed) * 1000);
+				
+		}
+
+		free(ipo);
+		
+		if (io_u->ddir != DDIR_WAIT)
+			return 0;
+	}
+
+	td->done = 1;
+	return 1;
+}
+
+void prune_io_piece_log(struct thread_data *td)
+{
+	struct io_piece *ipo;
+	struct rb_node *n;
+
+	while ((n = rb_first(&td->io_hist_tree)) != NULL) {
+		ipo = rb_entry(n, struct io_piece, rb_node);
+		rb_erase(n, &td->io_hist_tree);
+		remove_trim_entry(td, ipo);
+		td->io_hist_len--;
+		free(ipo);
+	}
+
+	while (!flist_empty(&td->io_hist_list)) {
+		ipo = flist_entry(td->io_hist_list.next, struct io_piece, list);
+		flist_del(&ipo->list);
+		remove_trim_entry(td, ipo);
+		td->io_hist_len--;
+		free(ipo);
+	}
+}
+
+/*
+ * log a successful write, so we can unwind the log for verify
+ */
+void log_io_piece(struct thread_data *td, struct io_u *io_u)
+{
+	struct rb_node **p, *parent;
+	struct io_piece *ipo, *__ipo;
+
+	ipo = malloc(sizeof(struct io_piece));
+	init_ipo(ipo);
+	ipo->file = io_u->file;
+	ipo->offset = io_u->offset;
+	ipo->len = io_u->buflen;
+
+	if (io_u_should_trim(td, io_u)) {
+		flist_add_tail(&ipo->trim_list, &td->trim_list);
+		td->trim_entries++;
+	}
+
+	/*
+	 * We don't need to sort the entries, if:
+	 *
+	 *	Sequential writes, or
+	 *	Random writes that lay out the file as it goes along
+	 *
+	 * For both these cases, just reading back data in the order we
+	 * wrote it out is the fastest.
+	 *
+	 * One exception is if we don't have a random map AND we are doing
+	 * verifies, in that case we need to check for duplicate blocks and
+	 * drop the old one, which we rely on the rb insert/lookup for
+	 * handling.
+	 */
+	if ((!td_random(td) || !td->o.overwrite) &&
+	      (file_randommap(td, ipo->file) || td->o.verify == VERIFY_NONE)) {
+		INIT_FLIST_HEAD(&ipo->list);
+		flist_add_tail(&ipo->list, &td->io_hist_list);
+		ipo->flags |= IP_F_ONLIST;
+		td->io_hist_len++;
+		return;
+	}
+
+	RB_CLEAR_NODE(&ipo->rb_node);
+
+	/*
+	 * Sort the entry into the verification list
+	 */
+restart:
+	p = &td->io_hist_tree.rb_node;
+	parent = NULL;
+	while (*p) {
+		parent = *p;
+
+		__ipo = rb_entry(parent, struct io_piece, rb_node);
+		if (ipo->file < __ipo->file)
+			p = &(*p)->rb_left;
+		else if (ipo->file > __ipo->file)
+			p = &(*p)->rb_right;
+		else if (ipo->offset < __ipo->offset)
+			p = &(*p)->rb_left;
+		else if (ipo->offset > __ipo->offset)
+			p = &(*p)->rb_right;
+		else {
+			assert(ipo->len == __ipo->len);
+			td->io_hist_len--;
+			rb_erase(parent, &td->io_hist_tree);
+			remove_trim_entry(td, __ipo);
+			free(__ipo);
+			goto restart;
+		}
+	}
+
+	rb_link_node(&ipo->rb_node, parent, p);
+	rb_insert_color(&ipo->rb_node, &td->io_hist_tree);
+	ipo->flags |= IP_F_ONRB;
+	td->io_hist_len++;
+}
+
+void write_iolog_close(struct thread_data *td)
+{
+	fflush(td->iolog_f);
+	fclose(td->iolog_f);
+	free(td->iolog_buf);
+	td->iolog_f = NULL;
+	td->iolog_buf = NULL;
+}
+
+/*
+ * Read version 2 iolog data. It is enhanced to include per-file logging,
+ * syncs, etc.
+ */
+static int read_iolog2(struct thread_data *td, FILE *f)
+{
+	unsigned long long offset;
+	unsigned int bytes;
+	int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */
+	char *fname, *act;
+	char *str, *p;
+	enum fio_ddir rw;
+
+	free_release_files(td);
+
+	/*
+	 * Read in the read iolog and store it, reuse the infrastructure
+	 * for doing verifications.
+	 */
+	str = malloc(4096);
+	fname = malloc(256+16);
+	act = malloc(256+16);
+
+	reads = writes = waits = 0;
+	while ((p = fgets(str, 4096, f)) != NULL) {
+		struct io_piece *ipo;
+		int r;
+
+		r = sscanf(p, "%256s %256s %llu %u", fname, act, &offset,
+									&bytes);
+		if (r == 4) {
+			/*
+			 * Check action first
+			 */
+			if (!strcmp(act, "wait"))
+				rw = DDIR_WAIT;
+			else if (!strcmp(act, "read"))
+				rw = DDIR_READ;
+			else if (!strcmp(act, "write"))
+				rw = DDIR_WRITE;
+			else if (!strcmp(act, "sync"))
+				rw = DDIR_SYNC;
+			else if (!strcmp(act, "datasync"))
+				rw = DDIR_DATASYNC;
+			else if (!strcmp(act, "trim"))
+				rw = DDIR_TRIM;
+			else {
+				log_err("fio: bad iolog file action: %s\n",
+									act);
+				continue;
+			}
+		} else if (r == 2) {
+			rw = DDIR_INVAL;
+			if (!strcmp(act, "add")) {
+				td->o.nr_files++;
+				fileno = add_file(td, fname);
+				file_action = FIO_LOG_ADD_FILE;
+				continue;
+			} else if (!strcmp(act, "open")) {
+				fileno = get_fileno(td, fname);
+				file_action = FIO_LOG_OPEN_FILE;
+			} else if (!strcmp(act, "close")) {
+				fileno = get_fileno(td, fname);
+				file_action = FIO_LOG_CLOSE_FILE;
+			} else {
+				log_err("fio: bad iolog file action: %s\n",
+									act);
+				continue;
+			}
+		} else {
+			log_err("bad iolog2: %s", p);
+			continue;
+		}
+
+		if (rw == DDIR_READ)
+			reads++;
+		else if (rw == DDIR_WRITE) {
+			/*
+			 * Don't add a write for ro mode
+			 */
+			if (read_only)
+				continue;
+			writes++;
+		} else if (rw == DDIR_WAIT) {
+			waits++;
+		} else if (rw == DDIR_INVAL) {
+		} else if (!ddir_sync(rw)) {
+			log_err("bad ddir: %d\n", rw);
+			continue;
+		}
+
+		/*
+		 * Make note of file
+		 */
+		ipo = malloc(sizeof(*ipo));
+		init_ipo(ipo);
+		ipo->ddir = rw;
+		if (rw == DDIR_WAIT) {
+			ipo->delay = offset;
+		} else {
+			ipo->offset = offset;
+			ipo->len = bytes;
+			if (bytes > td->o.max_bs[rw])
+				td->o.max_bs[rw] = bytes;
+			ipo->fileno = fileno;
+			ipo->file_action = file_action;
+		}
+			
+		queue_io_piece(td, ipo);
+	}
+
+	free(str);
+	free(act);
+	free(fname);
+
+	if (writes && read_only) {
+		log_err("fio: <%s> skips replay of %d writes due to"
+			" read-only\n", td->o.name, writes);
+		writes = 0;
+	}
+
+	if (!reads && !writes && !waits)
+		return 1;
+	else if (reads && !writes)
+		td->o.td_ddir = TD_DDIR_READ;
+	else if (!reads && writes)
+		td->o.td_ddir = TD_DDIR_WRITE;
+	else
+		td->o.td_ddir = TD_DDIR_RW;
+
+	return 0;
+}
+
+/*
+ * open iolog, check version, and call appropriate parser
+ */
+static int init_iolog_read(struct thread_data *td)
+{
+	char buffer[256], *p;
+	FILE *f;
+	int ret;
+
+	f = fopen(td->o.read_iolog_file, "r");
+	if (!f) {
+		perror("fopen read iolog");
+		return 1;
+	}
+
+	p = fgets(buffer, sizeof(buffer), f);
+	if (!p) {
+		td_verror(td, errno, "iolog read");
+		log_err("fio: unable to read iolog\n");
+		fclose(f);
+		return 1;
+	}
+
+	/*
+	 * version 2 of the iolog stores a specific string as the
+	 * first line, check for that
+	 */
+	if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2)))
+		ret = read_iolog2(td, f);
+	else {
+		log_err("fio: iolog version 1 is no longer supported\n");
+		ret = 1;
+	}
+
+	fclose(f);
+	return ret;
+}
+
+/*
+ * Set up a log for storing io patterns.
+ */
+static int init_iolog_write(struct thread_data *td)
+{
+	struct fio_file *ff;
+	FILE *f;
+	unsigned int i;
+
+	f = fopen(td->o.write_iolog_file, "a");
+	if (!f) {
+		perror("fopen write iolog");
+		return 1;
+	}
+
+	/*
+	 * That's it for writing, setup a log buffer and we're done.
+	  */
+	td->iolog_f = f;
+	td->iolog_buf = malloc(8192);
+	setvbuf(f, td->iolog_buf, _IOFBF, 8192);
+
+	/*
+	 * write our version line
+	 */
+	if (fprintf(f, "%s\n", iolog_ver2) < 0) {
+		perror("iolog init\n");
+		return 1;
+	}
+
+	/*
+	 * add all known files
+	 */
+	for_each_file(td, ff, i)
+		log_file(td, ff, FIO_LOG_ADD_FILE);
+
+	return 0;
+}
+
+int init_iolog(struct thread_data *td)
+{
+	int ret = 0;
+
+	if (td->o.read_iolog_file) {
+		/*
+		 * Check if it's a blktrace file and load that if possible.
+		 * Otherwise assume it's a normal log file and load that.
+		 */
+		if (is_blktrace(td->o.read_iolog_file))
+			ret = load_blktrace(td, td->o.read_iolog_file);
+		else
+			ret = init_iolog_read(td);
+	} else if (td->o.write_iolog_file)
+		ret = init_iolog_write(td);
+
+	return ret;
+}
+
+void setup_log(struct io_log **log)
+{
+	struct io_log *l = malloc(sizeof(*l));
+
+	l->nr_samples = 0;
+	l->max_samples = 1024;
+	l->log = malloc(l->max_samples * sizeof(struct io_sample));
+	*log = l;
+}
+
+void __finish_log(struct io_log *log, const char *name)
+{
+	unsigned int i;
+	FILE *f;
+
+	f = fopen(name, "a");
+	if (!f) {
+		perror("fopen log");
+		return;
+	}
+
+	for (i = 0; i < log->nr_samples; i++) {
+		fprintf(f, "%lu, %lu, %u, %u\n", log->log[i].time,
+						log->log[i].val,
+						log->log[i].ddir,
+						log->log[i].bs);
+	}
+
+	fclose(f);
+	free(log->log);
+	free(log);
+}
+
+void finish_log_named(struct thread_data *td, struct io_log *log,
+		       const char *prefix, const char *postfix)
+{
+	char file_name[256], *p;
+
+	snprintf(file_name, 200, "%s_%s.log", prefix, postfix);
+	p = basename(file_name);
+	__finish_log(log, p);
+}
+
+void finish_log(struct thread_data *td, struct io_log *log, const char *name)
+{
+	finish_log_named(td, log, td->o.name, name);
+}
diff --git a/iolog.h b/iolog.h
index c35ce1e..dd4d647 100644
--- a/iolog.h
+++ b/iolog.h
@@ -1,16 +1,18 @@
 #ifndef FIO_IOLOG_H
 #define FIO_IOLOG_H
 
+#include "ieee754.h"
+
 /*
  * Use for maintaining statistics
  */
 struct io_stat {
-	unsigned long max_val;
-	unsigned long min_val;
-	unsigned long samples;
+	uint64_t max_val;
+	uint64_t min_val;
+	uint64_t samples;
 
-	double mean;
-	double S;
+	fio_fp64_t mean;
+	fio_fp64_t S;
 };
 
 /*
diff --git a/log.c b/log.c
index f962864..ec3ebce 100644
--- a/log.c
+++ b/log.c
@@ -1,541 +1,76 @@
-/*
- * Code related to writing an iolog of what a thread is doing, and to
- * later read that back and replay
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <libgen.h>
-#include <assert.h>
-#include "flist.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <syslog.h>
+
 #include "fio.h"
-#include "verify.h"
-#include "trim.h"
 
-static const char iolog_ver2[] = "fio version 2 iolog";
-
-void queue_io_piece(struct thread_data *td, struct io_piece *ipo)
+int log_valist(const char *str, va_list args)
 {
-	flist_add_tail(&ipo->list, &td->io_log_list);
-	td->total_io_size += ipo->len;
-}
+	char buffer[1024];
+	size_t len;
 
-void log_io_u(struct thread_data *td, struct io_u *io_u)
-{
-	const char *act[] = { "read", "write", "sync", "datasync",
-				"sync_file_range", "wait", "trim" };
+	len = vsnprintf(buffer, sizeof(buffer), str, args);
 
-	assert(io_u->ddir <= 6);
-
-	if (!td->o.write_iolog_file)
-		return;
-
-	fprintf(td->iolog_f, "%s %s %llu %lu\n", io_u->file->file_name,
-						act[io_u->ddir], io_u->offset,
-						io_u->buflen);
-}
-
-void log_file(struct thread_data *td, struct fio_file *f,
-	      enum file_log_act what)
-{
-	const char *act[] = { "add", "open", "close" };
-
-	assert(what < 3);
-
-	if (!td->o.write_iolog_file)
-		return;
-
-
-	/*
-	 * this happens on the pre-open/close done before the job starts
-	 */
-	if (!td->iolog_f)
-		return;
-
-	fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]);
-}
-
-static void iolog_delay(struct thread_data *td, unsigned long delay)
-{
-	unsigned long usec = utime_since_now(&td->last_issue);
-
-	if (delay < usec)
-		return;
-
-	delay -= usec;
-
-	/*
-	 * less than 100 usec delay, just regard it as noise
-	 */
-	if (delay < 100)
-		return;
-
-	usec_sleep(td, delay);
-}
-
-static int ipo_special(struct thread_data *td, struct io_piece *ipo)
-{
-	struct fio_file *f;
-	int ret;
-
-	/*
-	 * Not a special ipo
-	 */
-	if (ipo->ddir != DDIR_INVAL)
-		return 0;
-
-	f = td->files[ipo->fileno];
-
-	switch (ipo->file_action) {
-	case FIO_LOG_OPEN_FILE:
-		ret = td_io_open_file(td, f);
-		if (!ret)
-			break;
-		td_verror(td, ret, "iolog open file");
-		return -1;
-	case FIO_LOG_CLOSE_FILE:
-		td_io_close_file(td, f);
-		break;
-	case FIO_LOG_UNLINK_FILE:
-		unlink(f->file_name);
-		break;
-	default:
-		log_err("fio: bad file action %d\n", ipo->file_action);
-		break;
-	}
-
-	return 1;
-}
-
-int read_iolog_get(struct thread_data *td, struct io_u *io_u)
-{
-	struct io_piece *ipo;
-	unsigned long elapsed;
-	
-	while (!flist_empty(&td->io_log_list)) {
-		int ret;
-
-		ipo = flist_entry(td->io_log_list.next, struct io_piece, list);
-		flist_del(&ipo->list);
-		remove_trim_entry(td, ipo);
-
-		ret = ipo_special(td, ipo);
-		if (ret < 0) {
-			free(ipo);
-			break;
-		} else if (ret > 0) {
-			free(ipo);
-			continue;
-		}
-
-		io_u->ddir = ipo->ddir;
-		if (ipo->ddir != DDIR_WAIT) {
-			io_u->offset = ipo->offset;
-			io_u->buflen = ipo->len;
-			io_u->file = td->files[ipo->fileno];
-			get_file(io_u->file);
-			dprint(FD_IO, "iolog: get %llu/%lu/%s\n", io_u->offset,
-						io_u->buflen, io_u->file->file_name);
-			if (ipo->delay)
-				iolog_delay(td, ipo->delay);
-		} else {
-			elapsed = mtime_since_genesis();
-			if (ipo->delay > elapsed)
-				usec_sleep(td, (ipo->delay - elapsed) * 1000);
-				
-		}
-
-		free(ipo);
-		
-		if (io_u->ddir != DDIR_WAIT)
-			return 0;
-	}
-
-	td->done = 1;
-	return 1;
-}
-
-void prune_io_piece_log(struct thread_data *td)
-{
-	struct io_piece *ipo;
-	struct rb_node *n;
-
-	while ((n = rb_first(&td->io_hist_tree)) != NULL) {
-		ipo = rb_entry(n, struct io_piece, rb_node);
-		rb_erase(n, &td->io_hist_tree);
-		remove_trim_entry(td, ipo);
-		td->io_hist_len--;
-		free(ipo);
-	}
-
-	while (!flist_empty(&td->io_hist_list)) {
-		ipo = flist_entry(td->io_hist_list.next, struct io_piece, list);
-		flist_del(&ipo->list);
-		remove_trim_entry(td, ipo);
-		td->io_hist_len--;
-		free(ipo);
-	}
-}
-
-/*
- * log a successful write, so we can unwind the log for verify
- */
-void log_io_piece(struct thread_data *td, struct io_u *io_u)
-{
-	struct rb_node **p, *parent;
-	struct io_piece *ipo, *__ipo;
-
-	ipo = malloc(sizeof(struct io_piece));
-	init_ipo(ipo);
-	ipo->file = io_u->file;
-	ipo->offset = io_u->offset;
-	ipo->len = io_u->buflen;
-
-	if (io_u_should_trim(td, io_u)) {
-		flist_add_tail(&ipo->trim_list, &td->trim_list);
-		td->trim_entries++;
-	}
-
-	/*
-	 * We don't need to sort the entries, if:
-	 *
-	 *	Sequential writes, or
-	 *	Random writes that lay out the file as it goes along
-	 *
-	 * For both these cases, just reading back data in the order we
-	 * wrote it out is the fastest.
-	 *
-	 * One exception is if we don't have a random map AND we are doing
-	 * verifies, in that case we need to check for duplicate blocks and
-	 * drop the old one, which we rely on the rb insert/lookup for
-	 * handling.
-	 */
-	if ((!td_random(td) || !td->o.overwrite) &&
-	      (file_randommap(td, ipo->file) || td->o.verify == VERIFY_NONE)) {
-		INIT_FLIST_HEAD(&ipo->list);
-		flist_add_tail(&ipo->list, &td->io_hist_list);
-		ipo->flags |= IP_F_ONLIST;
-		td->io_hist_len++;
-		return;
-	}
-
-	RB_CLEAR_NODE(&ipo->rb_node);
-
-	/*
-	 * Sort the entry into the verification list
-	 */
-restart:
-	p = &td->io_hist_tree.rb_node;
-	parent = NULL;
-	while (*p) {
-		parent = *p;
-
-		__ipo = rb_entry(parent, struct io_piece, rb_node);
-		if (ipo->file < __ipo->file)
-			p = &(*p)->rb_left;
-		else if (ipo->file > __ipo->file)
-			p = &(*p)->rb_right;
-		else if (ipo->offset < __ipo->offset)
-			p = &(*p)->rb_left;
-		else if (ipo->offset > __ipo->offset)
-			p = &(*p)->rb_right;
-		else {
-			assert(ipo->len == __ipo->len);
-			td->io_hist_len--;
-			rb_erase(parent, &td->io_hist_tree);
-			remove_trim_entry(td, __ipo);
-			free(__ipo);
-			goto restart;
-		}
-	}
-
-	rb_link_node(&ipo->rb_node, parent, p);
-	rb_insert_color(&ipo->rb_node, &td->io_hist_tree);
-	ipo->flags |= IP_F_ONRB;
-	td->io_hist_len++;
-}
-
-void write_iolog_close(struct thread_data *td)
-{
-	fflush(td->iolog_f);
-	fclose(td->iolog_f);
-	free(td->iolog_buf);
-	td->iolog_f = NULL;
-	td->iolog_buf = NULL;
-}
-
-/*
- * Read version 2 iolog data. It is enhanced to include per-file logging,
- * syncs, etc.
- */
-static int read_iolog2(struct thread_data *td, FILE *f)
-{
-	unsigned long long offset;
-	unsigned int bytes;
-	int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */
-	char *fname, *act;
-	char *str, *p;
-	enum fio_ddir rw;
-
-	free_release_files(td);
-
-	/*
-	 * Read in the read iolog and store it, reuse the infrastructure
-	 * for doing verifications.
-	 */
-	str = malloc(4096);
-	fname = malloc(256+16);
-	act = malloc(256+16);
-
-	reads = writes = waits = 0;
-	while ((p = fgets(str, 4096, f)) != NULL) {
-		struct io_piece *ipo;
-		int r;
-
-		r = sscanf(p, "%256s %256s %llu %u", fname, act, &offset,
-									&bytes);
-		if (r == 4) {
-			/*
-			 * Check action first
-			 */
-			if (!strcmp(act, "wait"))
-				rw = DDIR_WAIT;
-			else if (!strcmp(act, "read"))
-				rw = DDIR_READ;
-			else if (!strcmp(act, "write"))
-				rw = DDIR_WRITE;
-			else if (!strcmp(act, "sync"))
-				rw = DDIR_SYNC;
-			else if (!strcmp(act, "datasync"))
-				rw = DDIR_DATASYNC;
-			else if (!strcmp(act, "trim"))
-				rw = DDIR_TRIM;
-			else {
-				log_err("fio: bad iolog file action: %s\n",
-									act);
-				continue;
-			}
-		} else if (r == 2) {
-			rw = DDIR_INVAL;
-			if (!strcmp(act, "add")) {
-				td->o.nr_files++;
-				fileno = add_file(td, fname);
-				file_action = FIO_LOG_ADD_FILE;
-				continue;
-			} else if (!strcmp(act, "open")) {
-				fileno = get_fileno(td, fname);
-				file_action = FIO_LOG_OPEN_FILE;
-			} else if (!strcmp(act, "close")) {
-				fileno = get_fileno(td, fname);
-				file_action = FIO_LOG_CLOSE_FILE;
-			} else {
-				log_err("fio: bad iolog file action: %s\n",
-									act);
-				continue;
-			}
-		} else {
-			log_err("bad iolog2: %s", p);
-			continue;
-		}
-
-		if (rw == DDIR_READ)
-			reads++;
-		else if (rw == DDIR_WRITE) {
-			/*
-			 * Don't add a write for ro mode
-			 */
-			if (read_only)
-				continue;
-			writes++;
-		} else if (rw == DDIR_WAIT) {
-			waits++;
-		} else if (rw == DDIR_INVAL) {
-		} else if (!ddir_sync(rw)) {
-			log_err("bad ddir: %d\n", rw);
-			continue;
-		}
-
-		/*
-		 * Make note of file
-		 */
-		ipo = malloc(sizeof(*ipo));
-		init_ipo(ipo);
-		ipo->ddir = rw;
-		if (rw == DDIR_WAIT) {
-			ipo->delay = offset;
-		} else {
-			ipo->offset = offset;
-			ipo->len = bytes;
-			if (bytes > td->o.max_bs[rw])
-				td->o.max_bs[rw] = bytes;
-			ipo->fileno = fileno;
-			ipo->file_action = file_action;
-		}
-			
-		queue_io_piece(td, ipo);
-	}
-
-	free(str);
-	free(act);
-	free(fname);
-
-	if (writes && read_only) {
-		log_err("fio: <%s> skips replay of %d writes due to"
-			" read-only\n", td->o.name, writes);
-		writes = 0;
-	}
-
-	if (!reads && !writes && !waits)
-		return 1;
-	else if (reads && !writes)
-		td->o.td_ddir = TD_DDIR_READ;
-	else if (!reads && writes)
-		td->o.td_ddir = TD_DDIR_WRITE;
+	if (log_syslog)
+		syslog(LOG_INFO, "%s", buffer);
 	else
-		td->o.td_ddir = TD_DDIR_RW;
+		len = fwrite(buffer, len, 1, f_out);
 
-	return 0;
+	return len;
 }
 
-/*
- * open iolog, check version, and call appropriate parser
- */
-static int init_iolog_read(struct thread_data *td)
+int log_local(const char *format, ...)
 {
-	char buffer[256], *p;
-	FILE *f;
-	int ret;
+	char buffer[1024];
+	va_list args;
+	size_t len;
 
-	f = fopen(td->o.read_iolog_file, "r");
-	if (!f) {
-		perror("fopen read iolog");
-		return 1;
-	}
+	va_start(args, format);
+	len = vsnprintf(buffer, sizeof(buffer), format, args);
+	va_end(args);
 
-	p = fgets(buffer, sizeof(buffer), f);
-	if (!p) {
-		td_verror(td, errno, "iolog read");
-		log_err("fio: unable to read iolog\n");
-		fclose(f);
-		return 1;
-	}
+	if (log_syslog)
+		syslog(LOG_INFO, "%s", buffer);
+	else
+		len = fwrite(buffer, len, 1, f_out);
 
-	/*
-	 * version 2 of the iolog stores a specific string as the
-	 * first line, check for that
-	 */
-	if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2)))
-		ret = read_iolog2(td, f);
+	return len;
+}
+
+int log_info(const char *format, ...)
+{
+	char buffer[1024];
+	va_list args;
+	size_t len;
+
+	va_start(args, format);
+	len = vsnprintf(buffer, sizeof(buffer), format, args);
+	va_end(args);
+
+	if (is_backend)
+		return fio_server_text_output(buffer, len);
+	else
+		return fwrite(buffer, len, 1, f_out);
+}
+
+int log_err(const char *format, ...)
+{
+	char buffer[1024];
+	va_list args;
+	size_t len;
+
+	va_start(args, format);
+	len = vsnprintf(buffer, sizeof(buffer), format, args);
+	va_end(args);
+
+	if (is_backend)
+		return fio_server_text_output(buffer, len);
 	else {
-		log_err("fio: iolog version 1 is no longer supported\n");
-		ret = 1;
+		if (f_err != stderr)
+			fwrite(buffer, len, 1, stderr);
+
+		return fwrite(buffer, len, 1, f_err);
 	}
-
-	fclose(f);
-	return ret;
-}
-
-/*
- * Set up a log for storing io patterns.
- */
-static int init_iolog_write(struct thread_data *td)
-{
-	struct fio_file *ff;
-	FILE *f;
-	unsigned int i;
-
-	f = fopen(td->o.write_iolog_file, "a");
-	if (!f) {
-		perror("fopen write iolog");
-		return 1;
-	}
-
-	/*
-	 * That's it for writing, setup a log buffer and we're done.
-	  */
-	td->iolog_f = f;
-	td->iolog_buf = malloc(8192);
-	setvbuf(f, td->iolog_buf, _IOFBF, 8192);
-
-	/*
-	 * write our version line
-	 */
-	if (fprintf(f, "%s\n", iolog_ver2) < 0) {
-		perror("iolog init\n");
-		return 1;
-	}
-
-	/*
-	 * add all known files
-	 */
-	for_each_file(td, ff, i)
-		log_file(td, ff, FIO_LOG_ADD_FILE);
-
-	return 0;
-}
-
-int init_iolog(struct thread_data *td)
-{
-	int ret = 0;
-
-	if (td->o.read_iolog_file) {
-		/*
-		 * Check if it's a blktrace file and load that if possible.
-		 * Otherwise assume it's a normal log file and load that.
-		 */
-		if (is_blktrace(td->o.read_iolog_file))
-			ret = load_blktrace(td, td->o.read_iolog_file);
-		else
-			ret = init_iolog_read(td);
-	} else if (td->o.write_iolog_file)
-		ret = init_iolog_write(td);
-
-	return ret;
-}
-
-void setup_log(struct io_log **log)
-{
-	struct io_log *l = malloc(sizeof(*l));
-
-	l->nr_samples = 0;
-	l->max_samples = 1024;
-	l->log = malloc(l->max_samples * sizeof(struct io_sample));
-	*log = l;
-}
-
-void __finish_log(struct io_log *log, const char *name)
-{
-	unsigned int i;
-	FILE *f;
-
-	f = fopen(name, "a");
-	if (!f) {
-		perror("fopen log");
-		return;
-	}
-
-	for (i = 0; i < log->nr_samples; i++) {
-		fprintf(f, "%lu, %lu, %u, %u\n", log->log[i].time,
-						log->log[i].val,
-						log->log[i].ddir,
-						log->log[i].bs);
-	}
-
-	fclose(f);
-	free(log->log);
-	free(log);
-}
-
-void finish_log_named(struct thread_data *td, struct io_log *log,
-		       const char *prefix, const char *postfix)
-{
-	char file_name[256], *p;
-
-	snprintf(file_name, 200, "%s_%s.log", prefix, postfix);
-	p = basename(file_name);
-	__finish_log(log, p);
-}
-
-void finish_log(struct thread_data *td, struct io_log *log, const char *name)
-{
-	finish_log_named(td, log, td->o.name, name);
 }
diff --git a/log.h b/log.h
index eea1129..a69846e 100644
--- a/log.h
+++ b/log.h
@@ -2,23 +2,14 @@
 #define FIO_LOG_H
 
 #include <stdio.h>
+#include <stdarg.h>
 
 extern FILE *f_out;
 extern FILE *f_err;
 
-/*
- * If logging output to a file, stderr should go to both stderr and f_err
- */
-#define log_err(args, ...)	do {				\
-	fprintf(f_err, args,  ##__VA_ARGS__);		\
-	if (f_err != stderr)						\
-		fprintf(stderr, args,  ##__VA_ARGS__);	\
-	} while (0)
-
-#define log_info(args, ...)	fprintf(f_out, args, ##__VA_ARGS__)
-#define log_valist(str, args)	vfprintf(f_out, (str), (args))
-
-FILE *get_f_out(void);
-FILE *get_f_err(void);
+extern int log_err(const char *format, ...);
+extern int log_info(const char *format, ...);
+extern int log_local(const char *format, ...);
+extern int log_valist(const char *str, va_list);
 
 #endif
diff --git a/options.c b/options.c
index 5252477..1914037 100644
--- a/options.c
+++ b/options.c
@@ -829,9 +829,6 @@
 	return 0;
 }
 
-#define __stringify_1(x)	#x
-#define __stringify(x)		__stringify_1(x)
-
 /*
  * Map of job/command line options
  */
@@ -1946,7 +1943,7 @@
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(hugepage_size),
 		.help	= "When using hugepages, specify size of each page",
-		.def	= __stringify(FIO_HUGE_PAGE),
+		.def	= __fio_stringify(FIO_HUGE_PAGE),
 	},
 	{
 		.name	= "group_reporting",
diff --git a/os/os-aix.h b/os/os-aix.h
index 91c8bcd..d21e573 100644
--- a/os/os-aix.h
+++ b/os/os-aix.h
@@ -25,6 +25,17 @@
 #define OS_MAP_ANON		MAP_ANON
 #define OS_MSG_DONTWAIT		0
 
+#if BYTE_ORDER == BIG_ENDIAN
+#define FIO_BIG_ENDIAN
+#else
+#define FIO_LITTLE_ENDIAN
+#endif
+
+#define FIO_USE_GENERIC_SWAP
+
+#define FIO_OS_HAVE_SOCKLEN_T
+#define fio_socklen_t socklen_t
+
 static inline int blockdev_invalidate_cache(struct fio_file *f)
 {
 	return EINVAL;
diff --git a/os/os-freebsd.h b/os/os-freebsd.h
index 317d403..1a4f113 100644
--- a/os/os-freebsd.h
+++ b/os/os-freebsd.h
@@ -5,6 +5,7 @@
 #include <sys/sysctl.h>
 #include <sys/disk.h>
 #include <sys/thr.h>
+#include <sys/endian.h>
 
 #include "../file.h"
 
@@ -18,6 +19,16 @@
 
 #define OS_MAP_ANON		MAP_ANON
 
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#else
+#define FIO_BIG_ENDIAN
+#endif
+
+#define fio_swap16(x)	bswap16(x)
+#define fio_swap32(x)	bswap32(x)
+#define fio_swap64(x)	bswap64(x)
+
 typedef off_t off64_t;
 
 static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes)
diff --git a/os/os-hpux.h b/os/os-hpux.h
index 4353a01..93240b3 100644
--- a/os/os-hpux.h
+++ b/os/os-hpux.h
@@ -13,6 +13,7 @@
 #include <sys/pstat.h>
 #include <time.h>
 #include <aio.h>
+#include <arm.h>
 
 #include "../file.h"
 
@@ -43,9 +44,20 @@
 #define MSG_WAITALL	0x40
 #endif
 
+#ifdef LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#else
+#define FIO_BIG_ENDIAN
+#endif
+
+#define FIO_USE_GENERIC_SWAP
+
 #define FIO_OS_HAVE_AIOCB_TYPEDEF
 typedef struct aiocb64 os_aiocb_t;
 
+#define FIO_OS_HAVE_SOCKLEN_T
+typedef int fio_socklen_t;
+
 static inline int blockdev_invalidate_cache(struct fio_file *f)
 {
 	return EINVAL;
diff --git a/os/os-linux.h b/os/os-linux.h
index a36552b..2599091 100644
--- a/os/os-linux.h
+++ b/os/os-linux.h
@@ -12,6 +12,7 @@
 #include <linux/unistd.h>
 #include <linux/raw.h>
 #include <linux/major.h>
+#include <endian.h>
 
 #include "indirect.h"
 #include "binject.h"
@@ -286,6 +287,18 @@
 #define FIO_MADV_FREE	MADV_REMOVE
 #endif
 
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define FIO_BIG_ENDIAN
+#else
+#error "Unknown endianness"
+#endif
+
+#define fio_swap16(x)	__bswap_16(x)
+#define fio_swap32(x)	__bswap_32(x)
+#define fio_swap64(x)	__bswap_64(x)
+
 #define CACHE_LINE_FILE	\
 	"/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size"
 
diff --git a/os/os-mac.h b/os/os-mac.h
index eb55cd7..24e0eb5 100644
--- a/os/os-mac.h
+++ b/os/os-mac.h
@@ -9,6 +9,8 @@
 #include <unistd.h>
 #include <signal.h>
 #include <mach/mach_init.h>
+#include <machine/endian.h>
+#include <libkern/OSByteOrder.h>
 
 #include "../file.h"
 
@@ -28,6 +30,18 @@
 
 #define OS_MAP_ANON		MAP_ANON
 
+#if defined(__LITTLE_ENDIAN__)
+#define FIO_LITTLE_ENDIAN
+#elif defined(__BIG_ENDIAN__)
+#define FIO_BIG_ENDIAN
+#else
+#error "Undefined byte order"
+#endif
+
+#define fio_swap16(x)	OSSwapInt16(x)
+#define fio_swap32(x)	OSSwapInt32(x)
+#define fio_swap64(x)	OSSwapInt64(x)
+
 /*
  * OSX has a pitifully small shared memory segment by default,
  * so default to a lower number of max jobs supported
diff --git a/os/os-netbsd.h b/os/os-netbsd.h
index e03866d..5d8258b 100644
--- a/os/os-netbsd.h
+++ b/os/os-netbsd.h
@@ -4,6 +4,7 @@
 #include <errno.h>
 #include <sys/param.h>
 #include <sys/thr.h>
+#include <sys/endian.h>
 /* XXX hack to avoid confilcts between rbtree.h and <sys/rb.h> */
 #define	rb_node	_rb_node
 #include <sys/sysctl.h>
@@ -30,6 +31,16 @@
 #define PTHREAD_STACK_MIN 4096
 #endif
 
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#else
+#define FIO_BIG_ENDIAN
+#endif
+
+#define fio_swap16(x)	bswap16(x)
+#define fio_swap32(x)	bswap32(x)
+#define fio_swap64(x)	bswap64(x)
+
 typedef off_t off64_t;
 
 static inline int blockdev_invalidate_cache(struct fio_file *f)
diff --git a/os/os-solaris.h b/os/os-solaris.h
index c0d3c30..ef37e49 100644
--- a/os/os-solaris.h
+++ b/os/os-solaris.h
@@ -8,6 +8,7 @@
 #include <sys/pset.h>
 #include <sys/mman.h>
 #include <sys/dkio.h>
+#include <sys/byteorder.h>
 
 #include "../file.h"
 
@@ -24,6 +25,16 @@
 #define OS_MAP_ANON		MAP_ANON
 #define OS_RAND_MAX		2147483648UL
 
+#if defined(_BIG_ENDIAN)
+#define FIO_BIG_ENDIAN
+#else
+#define FIO_LITTLE_ENDIAN
+#endif
+
+#define fio_swap16(x)	BSWAP_16(x)
+#define fio_swap32(x)	BSWAP_32(x)
+#define fio_swap64(x)	BSWAP_64(x)
+
 struct solaris_rand_seed {
 	unsigned short r[3];
 };
diff --git a/os/os-windows.h b/os/os-windows.h
index db4127b..4af212d 100644
--- a/os/os-windows.h
+++ b/os/os-windows.h
@@ -5,6 +5,7 @@
 #include <errno.h>

 #include <windows.h>

 #include <psapi.h>

+#include <stdlib.h>

 

 #include "../smalloc.h"

 #include "../file.h"

@@ -26,6 +27,11 @@
 

 #define FIO_PREFERRED_ENGINE	"windowsaio"

 

+#define FIO_LITTLE_ENDIAN

+#define fio_swap16(x)	_byteswap_ushort(x)

+#define fio_swap32(x)	_byteswap_ulong(x)

+#define fio_swap64(x)	_byteswap_uint64(x)

+

 typedef off_t off64_t;

 

 typedef struct {

diff --git a/os/os.h b/os/os.h
index 2eb38e8..189041f 100644
--- a/os/os.h
+++ b/os/os.h
@@ -115,6 +115,34 @@
 #define FIO_MAX_JOBS		2048
 #endif
 
+#ifndef FIO_OS_HAVE_SOCKLEN_T
+typedef socklen_t fio_socklen_t;
+#endif
+
+#ifdef FIO_USE_GENERIC_SWAP
+static inline uint16_t fio_swap16(uint16_t val)
+{
+	return (val << 8) | (val >> 8);
+}
+
+static inline uint32_t fio_swap32(uint32_t val)
+{
+	val = ((val & 0xff00ff00UL) >> 8) | ((val & 0x00ff00ffUL) << 8);
+
+	return (val >> 16) | (val << 16);
+}
+
+static inline uint64_t fio_swap64(uint64_t val)
+{
+	val = ((val & 0xff00ff00ff00ff00ULL) >> 8) |
+	      ((val & 0x00ff00ff00ff00ffULL) << 8);
+	val = ((val & 0xffff0000ffff0000ULL) >> 16) |
+	      ((val & 0x0000ffff0000ffffULL) << 16);
+
+	return (val >> 32) | (val << 32);
+}
+#endif
+
 #ifndef FIO_HAVE_BLKTRACE
 static inline int is_blktrace(const char *fname)
 {
diff --git a/parse.c b/parse.c
index 239e371..70cfa0b 100644
--- a/parse.c
+++ b/parse.c
@@ -44,24 +44,25 @@
 	qsort(vpmap, entries, sizeof(struct value_pair), vp_cmp);
 }
 
-static void show_option_range(struct fio_option *o, FILE *out)
+static void show_option_range(struct fio_option *o,
+				int (*logger)(const char *format, ...))
 {
 	if (o->type == FIO_OPT_FLOAT_LIST){
 		if (isnan(o->minfp) && isnan(o->maxfp))
 			return;
 
-		fprintf(out, "%20s: min=%f", "range", o->minfp);
+		logger("%20s: min=%f", "range", o->minfp);
 		if (!isnan(o->maxfp))
-			fprintf(out, ", max=%f", o->maxfp);
-		fprintf(out, "\n");
+			logger(", max=%f", o->maxfp);
+		logger("\n");
 	} else {
 		if (!o->minval && !o->maxval)
 			return;
 
-		fprintf(out, "%20s: min=%d", "range", o->minval);
+		logger("%20s: min=%d", "range", o->minval);
 		if (o->maxval)
-			fprintf(out, ", max=%d", o->maxval);
-		fprintf(out, "\n");
+			logger(", max=%d", o->maxval);
+		logger("\n");
 	}
 }
 
@@ -75,17 +76,17 @@
 		if (!vp->ival)
 			continue;
 
-		printf("%20s: %-10s", i == 0 ? "valid values" : "", vp->ival);
+		log_info("%20s: %-10s", i == 0 ? "valid values" : "", vp->ival);
 		if (vp->help)
-			printf(" %s", vp->help);
-		printf("\n");
+			log_info(" %s", vp->help);
+		log_info("\n");
 	}
 
 	if (i)
-		printf("\n");
+		log_info("\n");
 }
 
-static void show_option_help(struct fio_option *o, FILE *out)
+static void show_option_help(struct fio_option *o, int is_err)
 {
 	const char *typehelp[] = {
 		"invalid",
@@ -101,15 +102,21 @@
 		"no argument (opt)",
 		"deprecated",
 	};
+	int (*logger)(const char *format, ...);
+
+	if (is_err)
+		logger = log_err;
+	else
+		logger = log_info;
 
 	if (o->alias)
-		fprintf(out, "%20s: %s\n", "alias", o->alias);
+		logger("%20s: %s\n", "alias", o->alias);
 
-	fprintf(out, "%20s: %s\n", "type", typehelp[o->type]);
-	fprintf(out, "%20s: %s\n", "default", o->def ? o->def : "no default");
+	logger("%20s: %s\n", "type", typehelp[o->type]);
+	logger("%20s: %s\n", "default", o->def ? o->def : "no default");
 	if (o->prof_name)
-		fprintf(out, "%20s: only for profile '%s'\n", "valid", o->prof_name);
-	show_option_range(o, stdout);
+		logger("%20s: only for profile '%s'\n", "valid", o->prof_name);
+	show_option_range(o, logger);
 	show_option_values(o);
 }
 
@@ -635,10 +642,10 @@
 		break;
 	}
 	case FIO_OPT_DEPRECATED:
-		fprintf(stdout, "Option %s is deprecated\n", o->name);
+		log_info("Option %s is deprecated\n", o->name);
 		break;
 	default:
-		fprintf(stderr, "Bad option type %u\n", o->type);
+		log_err("Bad option type %u\n", o->type);
 		ret = 1;
 	}
 
@@ -650,7 +657,7 @@
 		if (ret) {
 			fprintf(stderr,"Correct format for offending option\n");
 			fprintf(stderr, "%20s: %s\n", o->name, o->help);
-			show_option_help(o, stderr);
+			show_option_help(o, 1);
 		}
 	}
 
@@ -776,14 +783,14 @@
 
 	o = find_option(options, opt);
 	if (!o) {
-		fprintf(stderr, "Bad option <%s>\n", opt);
+		log_err("Bad option <%s>\n", opt);
 		return 1;
 	}
 
 	if (!handle_option(o, val, data))
 		return 0;
 
-	fprintf(stderr, "fio: failed parsing %s=%s\n", opt, val);
+	log_err("fio: failed parsing %s=%s\n", opt, val);
 	return 1;
 }
 
@@ -852,7 +859,7 @@
 
 	o = get_option(tmp, options, &post);
 	if (!o) {
-		fprintf(stderr, "Bad option <%s>\n", tmp);
+		log_err("Bad option <%s>\n", tmp);
 		free(tmp);
 		return 1;
 	}
@@ -862,7 +869,7 @@
 		return 0;
 	}
 
-	fprintf(stderr, "fio: failed parsing %s\n", opt);
+	log_err("fio: failed parsing %s\n", opt);
 	free(tmp);
 	return 1;
 }
@@ -1012,7 +1019,7 @@
 		if (!match)
 			continue;
 
-		show_option_help(o, stdout);
+		show_option_help(o, 0);
 	}
 
 	if (found)
@@ -1027,7 +1034,7 @@
 	if (closest && best_dist < 3) {
 		printf(" - showing closest match\n");
 		printf("%20s: %s\n", closest->name, closest->help);
-		show_option_help(closest, stdout);
+		show_option_help(closest, 0);
 	} else
 		printf("\n");
 
@@ -1061,20 +1068,17 @@
 		o->maxfp = NAN;
 	}
 	if (o->type == FIO_OPT_STR_SET && o->def) {
-		fprintf(stderr, "Option %s: string set option with"
+		log_err("Option %s: string set option with"
 				" default will always be true\n", o->name);
 	}
-	if (!o->cb && (!o->off1 && !o->roff1)) {
-		fprintf(stderr, "Option %s: neither cb nor offset given\n",
-							o->name);
-	}
+	if (!o->cb && (!o->off1 && !o->roff1))
+		log_err("Option %s: neither cb nor offset given\n", o->name);
 	if (o->type == FIO_OPT_STR || o->type == FIO_OPT_STR_STORE ||
 	    o->type == FIO_OPT_STR_MULTI)
 		return;
 	if (o->cb && ((o->off1 || o->off2 || o->off3 || o->off4) ||
 		      (o->roff1 || o->roff2 || o->roff3 || o->roff4))) {
-		fprintf(stderr, "Option %s: both cb and offset given\n",
-							 o->name);
+		log_err("Option %s: both cb and offset given\n", o->name);
 	}
 }
 
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..0f64c23
--- /dev/null
+++ b/server.c
@@ -0,0 +1,737 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <signal.h>
+
+#include "fio.h"
+#include "server.h"
+#include "crc/crc16.h"
+#include "ieee754.h"
+
+int fio_net_port = 8765;
+
+int exit_backend = 0;
+
+static int server_fd = -1;
+
+int fio_send_data(int sk, const void *p, unsigned int len)
+{
+	assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_PDU);
+
+	do {
+		int ret = send(sk, p, len, 0);
+
+		if (ret > 0) {
+			len -= ret;
+			if (!len)
+				break;
+			p += ret;
+			continue;
+		} else if (!ret)
+			break;
+		else if (errno == EAGAIN || errno == EINTR)
+			continue;
+	} while (!exit_backend);
+
+	if (!len)
+		return 0;
+
+	return 1;
+}
+
+int fio_recv_data(int sk, void *p, unsigned int len)
+{
+	do {
+		int ret = recv(sk, p, len, MSG_WAITALL);
+
+		if (ret > 0) {
+			len -= ret;
+			if (!len)
+				break;
+			p += ret;
+			continue;
+		} else if (!ret)
+			break;
+		else if (errno == EAGAIN || errno == EINTR)
+			continue;
+	} while (!exit_backend);
+
+	if (!len)
+		return 0;
+
+	return -1;
+}
+
+static int verify_convert_cmd(struct fio_net_cmd *cmd)
+{
+	uint16_t crc;
+
+	cmd->cmd_crc16 = le16_to_cpu(cmd->cmd_crc16);
+	cmd->pdu_crc16 = le16_to_cpu(cmd->pdu_crc16);
+
+	crc = crc16(cmd, FIO_NET_CMD_CRC_SZ);
+	if (crc != cmd->cmd_crc16) {
+		log_err("fio: server bad crc on command (got %x, wanted %x)\n",
+				cmd->cmd_crc16, crc);
+		return 1;
+	}
+
+	cmd->version	= le16_to_cpu(cmd->version);
+	cmd->opcode	= le16_to_cpu(cmd->opcode);
+	cmd->flags	= le32_to_cpu(cmd->flags);
+	cmd->serial	= le64_to_cpu(cmd->serial);
+	cmd->pdu_len	= le32_to_cpu(cmd->pdu_len);
+
+	switch (cmd->version) {
+	case FIO_SERVER_VER1:
+		break;
+	default:
+		log_err("fio: bad server cmd version %d\n", cmd->version);
+		return 1;
+	}
+
+	if (cmd->pdu_len > FIO_SERVER_MAX_PDU) {
+		log_err("fio: command payload too large: %u\n", cmd->pdu_len);
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Read (and defragment, if necessary) incoming commands
+ */
+struct fio_net_cmd *fio_net_recv_cmd(int sk, int block)
+{
+	struct fio_net_cmd cmd, *cmdret = NULL;
+	size_t cmd_size = 0, pdu_offset = 0;
+	uint16_t crc;
+	int ret, first = 1;
+	void *pdu = NULL;
+
+	do {
+		struct pollfd pfd;
+
+		pfd.fd = sk;
+		pfd.events = POLLIN;
+		ret = 0;
+		do {
+			int timeo = block ? 100 : 10;
+
+			ret = poll(&pfd, 1, timeo);
+			if (ret < 0) {
+				if (errno == EINTR)
+					break;
+				log_err("fio: poll: %s\n", strerror(errno));
+				break;
+			} else if (!ret) {
+				if (!block)
+					return NULL;
+				continue;
+			}
+
+			if (pfd.revents & POLLIN)
+				break;
+			if (pfd.revents & (POLLERR|POLLHUP)) {
+				ret = 1;
+				break;
+			}
+		} while (ret >= 0 && block);
+
+		if (ret < 0)
+			break;
+
+		ret = fio_recv_data(sk, &cmd, sizeof(cmd));
+		if (ret)
+			break;
+
+		/* We have a command, verify it and swap if need be */
+		ret = verify_convert_cmd(&cmd);
+		if (ret)
+			break;
+
+		if (first) {
+			/* if this is text, add room for \0 at the end */
+			cmd_size = sizeof(cmd) + cmd.pdu_len + 1;
+			assert(!cmdret);
+		} else
+			cmd_size += cmd.pdu_len;
+
+		cmdret = realloc(cmdret, cmd_size);
+
+		if (first)
+			memcpy(cmdret, &cmd, sizeof(cmd));
+		else
+			assert(cmdret->opcode == cmd.opcode);
+
+		if (!cmd.pdu_len)
+			break;
+
+		/* There's payload, get it */
+		pdu = (void *) cmdret->payload + pdu_offset;
+		ret = fio_recv_data(sk, pdu, cmd.pdu_len);
+		if (ret)
+			break;
+
+		/* Verify payload crc */
+		crc = crc16(pdu, cmd.pdu_len);
+		if (crc != cmd.pdu_crc16) {
+			log_err("fio: server bad crc on payload ");
+			log_err("(got %x, wanted %x)\n", cmd.pdu_crc16, crc);
+			ret = 1;
+			break;
+		}
+
+		pdu_offset += cmd.pdu_len;
+		if (!first)
+			cmdret->pdu_len += cmd.pdu_len;
+		first = 0;
+	} while (cmd.flags & FIO_NET_CMD_F_MORE);
+
+	if (ret) {
+		free(cmdret);
+		cmdret = NULL;
+	} else if (cmdret) {
+		/* zero-terminate text input */
+		if (cmdret->pdu_len && (cmdret->opcode == FIO_NET_CMD_TEXT ||
+		    cmdret->opcode == FIO_NET_CMD_JOB)) {
+			char *buf = (char *) cmdret->payload;
+
+			buf[cmdret->pdu_len ] = '\0';
+		}
+		/* frag flag is internal */
+		cmdret->flags &= ~FIO_NET_CMD_F_MORE;
+	}
+
+	return cmdret;
+}
+
+void fio_net_cmd_crc(struct fio_net_cmd *cmd)
+{
+	uint32_t pdu_len;
+
+	cmd->cmd_crc16 = __cpu_to_le16(crc16(cmd, FIO_NET_CMD_CRC_SZ));
+
+	pdu_len = le32_to_cpu(cmd->pdu_len);
+	if (pdu_len)
+		cmd->pdu_crc16 = __cpu_to_le16(crc16(cmd->payload, pdu_len));
+}
+
+int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size)
+{
+	struct fio_net_cmd *cmd;
+	size_t this_len;
+	int ret;
+
+	do {
+		this_len = size;
+		if (this_len > FIO_SERVER_MAX_PDU)
+			this_len = FIO_SERVER_MAX_PDU;
+
+		cmd = malloc(sizeof(*cmd) + this_len);
+
+		fio_init_net_cmd(cmd, opcode, buf, this_len);
+
+		if (this_len < size)
+			cmd->flags = __cpu_to_le32(FIO_NET_CMD_F_MORE);
+
+		fio_net_cmd_crc(cmd);
+
+		ret = fio_send_data(fd, cmd, sizeof(*cmd) + this_len);
+		free(cmd);
+		size -= this_len;
+		buf += this_len;
+	} while (!ret && size);
+
+	return ret;
+}
+
+int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t serial)
+{
+	struct fio_net_cmd cmd = {
+		.version	= __cpu_to_le16(FIO_SERVER_VER1),
+		.opcode		= cpu_to_le16(opcode),
+		.serial		= cpu_to_le64(serial),
+	};
+
+	fio_net_cmd_crc(&cmd);
+
+	return fio_send_data(sk, &cmd, sizeof(cmd));
+}
+
+static int fio_server_send_quit_cmd(void)
+{
+	dprint(FD_NET, "server: sending quit\n");
+	return fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_QUIT, 0);
+}
+
+static int handle_job_cmd(struct fio_net_cmd *cmd)
+{
+	char *buf = (char *) cmd->payload;
+	int ret;
+
+	if (parse_jobs_ini(buf, 1, 0))
+		return -1;
+
+	fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0);
+
+	ret = exec_run();
+	fio_server_send_quit_cmd();
+	reset_fio_state();
+	return ret;
+}
+
+static int handle_jobline_cmd(struct fio_net_cmd *cmd)
+{
+	struct cmd_line_pdu *pdu = (struct cmd_line_pdu *) cmd->payload;
+	char *argv[FIO_NET_CMD_JOBLINE_ARGV];
+	int ret, i;
+
+	pdu->argc = le16_to_cpu(pdu->argc);
+
+	dprint(FD_NET, "server: %d command line args\n", pdu->argc);
+
+	for (i = 0; i < pdu->argc; i++) {
+		argv[i] = (char *) pdu->argv[i];
+		dprint(FD_NET, "server: %d: %s\n", i, argv[i]);
+	}
+
+	if (parse_cmd_line(pdu->argc, argv))
+		return -1;
+
+	fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0);
+
+	ret = exec_run();
+	fio_server_send_quit_cmd();
+	reset_fio_state();
+	return ret;
+}
+
+static int handle_probe_cmd(struct fio_net_cmd *cmd)
+{
+	struct cmd_probe_pdu probe;
+
+	memset(&probe, 0, sizeof(probe));
+	gethostname((char *) probe.hostname, sizeof(probe.hostname));
+#ifdef FIO_BIG_ENDIAN
+	probe.bigendian = 1;
+#endif
+	probe.fio_major = FIO_MAJOR;
+	probe.fio_minor = FIO_MINOR;
+	probe.fio_patch = FIO_PATCH;
+
+	return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe));
+}
+
+static int handle_command(struct fio_net_cmd *cmd)
+{
+	int ret;
+
+	dprint(FD_NET, "server: got opcode %d\n", cmd->opcode);
+
+	switch (cmd->opcode) {
+	case FIO_NET_CMD_QUIT:
+		fio_terminate_threads(TERMINATE_ALL);
+		return -1;
+	case FIO_NET_CMD_EXIT:
+		exit_backend = 1;
+		return -1;
+	case FIO_NET_CMD_JOB:
+		ret = handle_job_cmd(cmd);
+		break;
+	case FIO_NET_CMD_JOBLINE:
+		ret = handle_jobline_cmd(cmd);
+		break;
+	case FIO_NET_CMD_PROBE:
+		ret = handle_probe_cmd(cmd);
+		break;
+	default:
+		log_err("fio: unknown opcode: %d\n", cmd->opcode);
+		ret = 1;
+	}
+
+	return ret;
+}
+
+static int handle_connection(int sk, int block)
+{
+	struct fio_net_cmd *cmd = NULL;
+	int ret = 0;
+
+	/* read forever */
+	while (!exit_backend) {
+		cmd = fio_net_recv_cmd(sk, block);
+		if (!cmd) {
+			ret = -1;
+			break;
+		}
+
+		ret = handle_command(cmd);
+		if (ret)
+			break;
+
+		free(cmd);
+		cmd = NULL;
+	}
+
+	if (cmd)
+		free(cmd);
+
+	return ret;
+}
+
+void fio_server_idle_loop(void)
+{
+	if (server_fd != -1)
+		handle_connection(server_fd, 0);
+}
+
+static int accept_loop(int listen_sk)
+{
+	struct sockaddr_in addr;
+	fio_socklen_t len = sizeof(addr);
+	struct pollfd pfd;
+	int ret, sk, flags, exitval = 0;
+
+	dprint(FD_NET, "server enter accept loop\n");
+
+	flags = fcntl(listen_sk, F_GETFL);
+	flags |= O_NONBLOCK;
+	fcntl(listen_sk, F_SETFL, flags);
+again:
+	pfd.fd = listen_sk;
+	pfd.events = POLLIN;
+	do {
+		ret = poll(&pfd, 1, 100);
+		if (ret < 0) {
+			if (errno == EINTR)
+				break;
+			log_err("fio: poll: %s\n", strerror(errno));
+			goto out;
+		} else if (!ret)
+			continue;
+
+		if (pfd.revents & POLLIN)
+			break;
+	} while (!exit_backend);
+
+	if (exit_backend)
+		goto out;
+
+	sk = accept(listen_sk, (struct sockaddr *) &addr, &len);
+	if (sk < 0) {
+		log_err("fio: accept: %s\n", strerror(errno));
+		return -1;
+	}
+
+	dprint(FD_NET, "server: connect from %s\n", inet_ntoa(addr.sin_addr));
+
+	server_fd = sk;
+
+	exitval = handle_connection(sk, 1);
+
+	server_fd = -1;
+	close(sk);
+
+	if (!exit_backend)
+		goto again;
+
+out:
+	return exitval;
+}
+
+int fio_server_text_output(const char *buf, unsigned int len)
+{
+	if (server_fd != -1)
+		return fio_net_send_cmd(server_fd, FIO_NET_CMD_TEXT, buf, len);
+
+	return fwrite(buf, len, 1, f_err);
+}
+
+static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
+{
+	dst->max_val	= cpu_to_le64(src->max_val);
+	dst->min_val	= cpu_to_le64(src->min_val);
+	dst->samples	= cpu_to_le64(src->samples);
+
+	/*
+	 * Encode to IEEE 754 for network transfer
+	 */
+	dst->mean.u.i	= __cpu_to_le64(fio_double_to_uint64(src->mean.u.f));
+	dst->S.u.i	= __cpu_to_le64(fio_double_to_uint64(src->S.u.f));
+}
+
+static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		dst->max_run[i]		= cpu_to_le64(src->max_run[i]);
+		dst->min_run[i]		= cpu_to_le64(src->min_run[i]);
+		dst->max_bw[i]		= cpu_to_le64(src->max_bw[i]);
+		dst->min_bw[i]		= cpu_to_le64(src->min_bw[i]);
+		dst->io_kb[i]		= cpu_to_le64(src->io_kb[i]);
+		dst->agg[i]		= cpu_to_le64(src->agg[i]);
+	}
+
+	dst->kb_base	= cpu_to_le32(src->kb_base);
+	dst->groupid	= cpu_to_le32(src->groupid);
+}
+
+/*
+ * Send a CMD_TS, which packs struct thread_stat and group_run_stats
+ * into a single payload.
+ */
+void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
+{
+	struct cmd_ts_pdu p;
+	int i, j;
+
+	dprint(FD_NET, "server sending end stats\n");
+
+	memset(&p, 0, sizeof(p));
+
+	strcpy(p.ts.name, ts->name);
+	strcpy(p.ts.verror, ts->verror);
+	strcpy(p.ts.description, ts->description);
+
+	p.ts.error	= cpu_to_le32(ts->error);
+	p.ts.groupid	= cpu_to_le32(ts->groupid);
+	p.ts.pid	= cpu_to_le32(ts->pid);
+	p.ts.members	= cpu_to_le32(ts->members);
+
+	for (i = 0; i < 2; i++) {
+		convert_io_stat(&p.ts.clat_stat[i], &ts->clat_stat[i]);
+		convert_io_stat(&p.ts.slat_stat[i], &ts->slat_stat[i]);
+		convert_io_stat(&p.ts.lat_stat[i], &ts->lat_stat[i]);
+		convert_io_stat(&p.ts.bw_stat[i], &ts->bw_stat[i]);
+	}
+
+	p.ts.usr_time		= cpu_to_le64(ts->usr_time);
+	p.ts.sys_time		= cpu_to_le64(ts->sys_time);
+	p.ts.ctx		= cpu_to_le64(ts->ctx);
+	p.ts.minf		= cpu_to_le64(ts->minf);
+	p.ts.majf		= cpu_to_le64(ts->majf);
+	p.ts.clat_percentiles	= cpu_to_le64(ts->clat_percentiles);
+
+	for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
+		fio_fp64_t *fp = &p.ts.percentile_list[i];
+
+		fp->u.i = __cpu_to_le64(fio_double_to_uint64(fp->u.f));
+	}
+
+	for (i = 0; i < FIO_IO_U_MAP_NR; i++) {
+		p.ts.io_u_map[i]	= cpu_to_le32(ts->io_u_map[i]);
+		p.ts.io_u_submit[i]	= cpu_to_le32(ts->io_u_submit[i]);
+		p.ts.io_u_complete[i]	= cpu_to_le32(ts->io_u_complete[i]);
+	}
+
+	for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) {
+		p.ts.io_u_lat_u[i]	= cpu_to_le32(ts->io_u_lat_u[i]);
+		p.ts.io_u_lat_m[i]	= cpu_to_le32(ts->io_u_lat_m[i]);
+	}
+
+	for (i = 0; i < 2; i++)
+		for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
+			p.ts.io_u_plat[i][j] = cpu_to_le32(ts->io_u_plat[i][j]);
+
+	for (i = 0; i < 3; i++) {
+		p.ts.total_io_u[i]	= cpu_to_le64(ts->total_io_u[i]);
+		p.ts.short_io_u[i]	= cpu_to_le64(ts->short_io_u[i]);
+	}
+
+	p.ts.total_submit	= cpu_to_le64(ts->total_submit);
+	p.ts.total_complete	= cpu_to_le64(ts->total_complete);
+
+	for (i = 0; i < 2; i++) {
+		p.ts.io_bytes[i]	= cpu_to_le64(ts->io_bytes[i]);
+		p.ts.runtime[i]		= cpu_to_le64(ts->runtime[i]);
+	}
+
+	p.ts.total_run_time	= cpu_to_le64(ts->total_run_time);
+	p.ts.continue_on_error	= cpu_to_le16(ts->continue_on_error);
+	p.ts.total_err_count	= cpu_to_le64(ts->total_err_count);
+	p.ts.first_error	= cpu_to_le32(ts->first_error);
+	p.ts.kb_base		= cpu_to_le32(ts->kb_base);
+
+	convert_gs(&p.rs, rs);
+
+	fio_net_send_cmd(server_fd, FIO_NET_CMD_TS, &p, sizeof(p));
+}
+
+void fio_server_send_gs(struct group_run_stats *rs)
+{
+	struct group_run_stats gs;
+
+	dprint(FD_NET, "server sending group run stats\n");
+
+	convert_gs(&gs, rs);
+	fio_net_send_cmd(server_fd, FIO_NET_CMD_GS, &gs, sizeof(gs));
+}
+
+void fio_server_send_status(void)
+{
+	struct jobs_eta *je;
+	size_t size;
+	void *buf;
+	int i;
+
+	size = sizeof(*je) + thread_number * sizeof(char);
+	buf = malloc(size);
+	memset(buf, 0, size);
+	je = buf;
+
+	if (!calc_thread_status(je)) {
+		free(je);
+		return;
+	}
+
+	dprint(FD_NET, "server sending status\n");
+
+	je->nr_running		= cpu_to_le32(je->nr_running);
+	je->nr_ramp		= cpu_to_le32(je->nr_ramp);
+	je->nr_pending		= cpu_to_le32(je->nr_pending);
+	je->files_open		= cpu_to_le32(je->files_open);
+	je->m_rate		= cpu_to_le32(je->m_rate);
+	je->t_rate		= cpu_to_le32(je->t_rate);
+	je->m_iops		= cpu_to_le32(je->m_iops);
+	je->t_iops		= cpu_to_le32(je->t_iops);
+
+	for (i = 0; i < 2; i++) {
+		je->rate[i]	= cpu_to_le32(je->rate[i]);
+		je->iops[i]	= cpu_to_le32(je->iops[i]);
+	}
+
+	je->elapsed_sec		= cpu_to_le32(je->nr_running);
+	je->eta_sec		= cpu_to_le64(je->eta_sec);
+
+	fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, buf, size);
+	free(je);
+}
+
+int fio_server_log(const char *format, ...)
+{
+	char buffer[1024];
+	va_list args;
+	size_t len;
+
+	dprint(FD_NET, "server log\n");
+
+	va_start(args, format);
+	len = vsnprintf(buffer, sizeof(buffer), format, args);
+	va_end(args);
+
+	return fio_server_text_output(buffer, len);
+}
+
+static int fio_server(void)
+{
+	struct sockaddr_in saddr_in;
+	struct sockaddr addr;
+	fio_socklen_t len;
+	int sk, opt, ret;
+
+	dprint(FD_NET, "starting server\n");
+
+	sk = socket(AF_INET, SOCK_STREAM, 0);
+	if (sk < 0) {
+		log_err("fio: socket: %s\n", strerror(errno));
+		return -1;
+	}
+
+	opt = 1;
+	if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+		log_err("fio: setsockopt: %s\n", strerror(errno));
+		return -1;
+	}
+#ifdef SO_REUSEPORT
+	if (setsockopt(sk, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
+		log_err("fio: setsockopt: %s\n", strerror(errno));
+		return -1;
+	}
+#endif
+
+	saddr_in.sin_family = AF_INET;
+	saddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+	saddr_in.sin_port = htons(fio_net_port);
+
+	if (bind(sk, (struct sockaddr *) &saddr_in, sizeof(saddr_in)) < 0) {
+		log_err("fio: bind: %s\n", strerror(errno));
+		return -1;
+	}
+
+	if (listen(sk, 1) < 0) {
+		log_err("fio: listen: %s\n", strerror(errno));
+		return -1;
+	}
+
+	len = sizeof(addr);
+	if (getsockname(sk, &addr, &len) < 0) {
+		log_err("fio: getsockname: %s\n", strerror(errno));
+		return -1;
+	}
+
+	ret = accept_loop(sk);
+	close(sk);
+	return ret;
+}
+
+static void sig_int(int sig)
+{
+	fio_terminate_threads(TERMINATE_ALL);
+	exit_backend = 1;
+}
+
+static void server_signal_handler(void)
+{
+	struct sigaction act;
+
+	memset(&act, 0, sizeof(act));
+	act.sa_handler = sig_int;
+	act.sa_flags = SA_RESTART;
+	sigaction(SIGINT, &act, NULL);
+
+	memset(&act, 0, sizeof(act));
+	act.sa_handler = sig_int;
+	act.sa_flags = SA_RESTART;
+	sigaction(SIGTERM, &act, NULL);
+}
+
+int fio_start_server(int daemonize)
+{
+	pid_t pid;
+
+	server_signal_handler();
+
+	if (!daemonize)
+		return fio_server();
+
+	openlog("fio", LOG_NDELAY|LOG_NOWAIT|LOG_PID, LOG_USER);
+	pid = fork();
+	if (pid < 0) {
+		syslog(LOG_ERR, "failed server fork");
+		return -1;
+	} else if (pid)
+		exit(0);
+
+	setsid();
+	close(STDIN_FILENO);
+	close(STDOUT_FILENO);
+	close(STDERR_FILENO);
+	f_out = NULL;
+	f_err = NULL;
+	log_syslog = 1;
+	return fio_server();
+}
diff --git a/server.h b/server.h
new file mode 100644
index 0000000..b397617
--- /dev/null
+++ b/server.h
@@ -0,0 +1,155 @@
+#ifndef FIO_SERVER_H
+#define FIO_SERVER_H
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "stat.h"
+#include "os/os.h"
+
+/*
+ * On-wire encoding is little endian
+ */
+struct fio_net_cmd {
+	uint16_t version;	/* protocol version */
+	uint16_t opcode;	/* command opcode */
+	uint32_t flags;		/* modifier flags */
+	uint64_t serial;	/* serial number */
+	uint32_t pdu_len;	/* length of post-cmd layload */
+	/*
+	 * These must be immediately before the payload, anything before
+	 * these fields are checksummed.
+	 */
+	uint16_t cmd_crc16;	/* cmd checksum */
+	uint16_t pdu_crc16;	/* payload checksum */
+	uint8_t payload[0];	/* payload */
+};
+
+enum {
+	FIO_SERVER_VER		= 1,
+	FIO_SERVER_VER1		= 1,
+
+	FIO_SERVER_MAX_PDU	= 64,
+
+	FIO_NET_CMD_QUIT	= 1,
+	FIO_NET_CMD_EXIT	= 2,
+	FIO_NET_CMD_JOB		= 3,
+	FIO_NET_CMD_JOBLINE	= 4,
+	FIO_NET_CMD_TEXT	= 5,
+	FIO_NET_CMD_TS		= 6,
+	FIO_NET_CMD_GS		= 7,
+	FIO_NET_CMD_ETA		= 8,
+	FIO_NET_CMD_PROBE	= 9,
+	FIO_NET_CMD_START	= 10,
+	FIO_NET_CMD_STOP	= 11,
+
+	FIO_NET_CMD_F_MORE	= 1UL << 0,
+
+	/* crc does not include the crc fields */
+	FIO_NET_CMD_CRC_SZ	= sizeof(struct fio_net_cmd) -
+					2 * sizeof(uint16_t),
+
+	FIO_NET_CMD_JOBLINE_ARGV	= 128,
+};
+
+struct cmd_ts_pdu {
+	struct thread_stat ts;
+	struct group_run_stats rs;
+};
+
+struct cmd_probe_pdu {
+	uint8_t hostname[64];
+	uint8_t bigendian;
+	uint8_t fio_major;
+	uint8_t fio_minor;
+	uint8_t fio_patch;
+};
+
+struct cmd_line_pdu {
+	uint16_t argc;
+	uint8_t argv[FIO_NET_CMD_JOBLINE_ARGV][64];
+};
+
+extern int fio_start_server(int);
+extern int fio_server_text_output(const char *, unsigned int len);
+extern int fio_server_log(const char *format, ...);
+extern int fio_net_send_cmd(int, uint16_t, const void *, off_t);
+extern int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t serial);
+
+struct thread_stat;
+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_status(void);
+extern void fio_server_idle_loop(void);
+
+extern int fio_clients_connect(void);
+extern int fio_clients_send_ini(const char *);
+extern int fio_handle_clients(void);
+extern void fio_client_add(const char *);
+extern void fio_client_add_cmd_option(const char *, const char *);
+
+extern int fio_recv_data(int sk, void *p, unsigned int len);
+extern int fio_send_data(int sk, const void *p, unsigned int len);
+extern void fio_net_cmd_crc(struct fio_net_cmd *);
+extern struct fio_net_cmd *fio_net_recv_cmd(int sk, int block);
+
+extern int exit_backend;
+extern int fio_net_port;
+
+#ifdef FIO_LITTLE_ENDIAN
+#define __le16_to_cpu(x)		(x)
+#define __le32_to_cpu(x)		(x)
+#define __le64_to_cpu(x)		(x)
+#define __cpu_to_le16(x)		(x)
+#define __cpu_to_le32(x)		(x)
+#define __cpu_to_le64(x)		(x)
+#else
+#define __le16_to_cpu(x)		fio_swap16(x)
+#define __le32_to_cpu(x)		fio_swap32(x)
+#define __le64_to_cpu(x)		fio_swap64(x)
+#define __cpu_to_le16(x)		fio_swap16(x)
+#define __cpu_to_le32(x)		fio_swap32(x)
+#define __cpu_to_le64(x)		fio_swap64(x)
+#endif
+
+#define le16_to_cpu(val) ({			\
+	uint16_t *__val = &(val);		\
+	__le16_to_cpu(*__val);			\
+})
+#define le32_to_cpu(val) ({			\
+	uint32_t *__val = &(val);		\
+	__le32_to_cpu(*__val);			\
+})
+#define le64_to_cpu(val) ({			\
+	uint64_t *__val = &(val);		\
+	__le64_to_cpu(*__val);			\
+})
+#define cpu_to_le16(val) ({			\
+	uint16_t *__val = &(val);		\
+	__cpu_to_le16(*__val);			\
+})
+#define cpu_to_le32(val) ({			\
+	uint32_t *__val = &(val);		\
+	__cpu_to_le32(*__val);			\
+})
+#define cpu_to_le64(val) ({			\
+	uint64_t *__val = &(val);		\
+	__cpu_to_le64(*__val);			\
+})
+
+static inline void fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode,
+				    const void *pdu, uint32_t pdu_len)
+{
+	memset(cmd, 0, sizeof(*cmd));
+
+	cmd->version	= __cpu_to_le16(FIO_SERVER_VER1);
+	cmd->opcode	= cpu_to_le16(opcode);
+
+	if (pdu) {
+		cmd->pdu_len	= cpu_to_le32(pdu_len);
+		memcpy(&cmd->payload, pdu, pdu_len);
+	}
+}
+
+#endif
diff --git a/stat.c b/stat.c
index 3662fd9..7bcae31 100644
--- a/stat.c
+++ b/stat.c
@@ -9,23 +9,24 @@
 
 #include "fio.h"
 #include "diskutil.h"
+#include "ieee754.h"
 
 void update_rusage_stat(struct thread_data *td)
 {
 	struct thread_stat *ts = &td->ts;
 
-	getrusage(RUSAGE_SELF, &ts->ru_end);
+	getrusage(RUSAGE_SELF, &td->ru_end);
 
-	ts->usr_time += mtime_since(&ts->ru_start.ru_utime,
-					&ts->ru_end.ru_utime);
-	ts->sys_time += mtime_since(&ts->ru_start.ru_stime,
-					&ts->ru_end.ru_stime);
-	ts->ctx += ts->ru_end.ru_nvcsw + ts->ru_end.ru_nivcsw
-			- (ts->ru_start.ru_nvcsw + ts->ru_start.ru_nivcsw);
-	ts->minf += ts->ru_end.ru_minflt - ts->ru_start.ru_minflt;
-	ts->majf += ts->ru_end.ru_majflt - ts->ru_start.ru_majflt;
+	ts->usr_time += mtime_since(&td->ru_start.ru_utime,
+					&td->ru_end.ru_utime);
+	ts->sys_time += mtime_since(&td->ru_start.ru_stime,
+					&td->ru_end.ru_stime);
+	ts->ctx += td->ru_end.ru_nvcsw + td->ru_end.ru_nivcsw
+			- (td->ru_start.ru_nvcsw + td->ru_start.ru_nivcsw);
+	ts->minf += td->ru_end.ru_minflt - td->ru_start.ru_minflt;
+	ts->majf += td->ru_end.ru_majflt - td->ru_start.ru_majflt;
 
-	memcpy(&ts->ru_start, &ts->ru_end, sizeof(ts->ru_end));
+	memcpy(&td->ru_start, &td->ru_end, sizeof(td->ru_end));
 }
 
 /*
@@ -101,13 +102,13 @@
 
 static int double_cmp(const void *a, const void *b)
 {
-	const double fa = *(const double *)a;
-	const double fb = *(const double *)b;
+	const fio_fp64_t fa = *(const fio_fp64_t *) a;
+	const fio_fp64_t fb = *(const fio_fp64_t *) b;
 	int cmp = 0;
 
-	if (fa > fb)
+	if (fa.u.f > fb.u.f)
 		cmp = 1;
-	else if (fa < fb)
+	else if (fa.u.f < fb.u.f)
 		cmp = -1;
 
 	return cmp;
@@ -116,38 +117,31 @@
 /*
  * Find and display the p-th percentile of clat
  */
-static void show_clat_percentiles(unsigned int* io_u_plat, unsigned long nr,
-				 double* user_list)
+static void show_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
+				  fio_fp64_t *plist)
 {
 	unsigned long sum = 0;
 	unsigned int len, i, j = 0;
-	const double *plist;
 	int is_last = 0;
-	static const double def_list[FIO_IO_U_LIST_MAX_LEN] = {
-			1.0, 5.0, 10.0, 20.0, 30.0,
-			40.0, 50.0, 60.0, 70.0, 80.0,
-			90.0, 95.0, 99.0, 99.5, 99.9};
 
-	plist = user_list;
-	if (!plist)
-		plist = def_list;
-
-	for (len = 0; len <FIO_IO_U_LIST_MAX_LEN && plist[len] != 0; len++)
-		;
+	len = 0;
+	while (len < FIO_IO_U_LIST_MAX_LEN && plist[len].u.f != 0.0)
+		len++;
 
 	/*
-	 * Sort the user-specified list. Note that this does not work
-	 * for NaN values
+	 * Sort the percentile list. Note that it may already be sorted if
+	 * we are using the default values, but since it's a short list this
+	 * isn't a worry. Also note that this does not work for NaN values.
 	 */
-	if (user_list && len > 1)
-		qsort((void*)user_list, len, sizeof(user_list[0]), double_cmp);
+	if (len > 1)
+		qsort((void*)plist, len, sizeof(plist[0]), double_cmp);
 
 	log_info("    clat percentiles (usec) :");
 
 	for (i = 0; i < FIO_IO_U_PLAT_NR && !is_last; i++) {
 		sum += io_u_plat[i];
-		while (sum >= (plist[j] / 100 * nr)) {
-			assert(plist[j] <= 100.0);
+		while (sum >= (plist[j].u.f / 100.0 * nr)) {
+			assert(plist[j].u.f <= 100.0);
 
 			/* for formatting */
 			if (j != 0 && (j % 4) == 0)
@@ -181,23 +175,23 @@
 	*max = is->max_val;
 
 	n = (double) is->samples;
-	*mean = is->mean;
+	*mean = is->mean.u.f;
 
 	if (n > 1.0)
-		*dev = sqrt(is->S / (n - 1.0));
+		*dev = sqrt(is->S.u.f / (n - 1.0));
 	else
 		*dev = 0;
 
 	return 1;
 }
 
-static void show_group_stats(struct group_run_stats *rs, int id)
+void show_group_stats(struct group_run_stats *rs)
 {
 	char *p1, *p2, *p3, *p4;
 	const char *ddir_str[] = { "   READ", "  WRITE" };
 	int i;
 
-	log_info("\nRun status group %d (all jobs):\n", id);
+	log_info("\nRun status group %d (all jobs):\n", rs->groupid);
 
 	for (i = 0; i <= DDIR_WRITE; i++) {
 		const int i2p = is_power_of_2(rs->kb_base);
@@ -433,8 +427,7 @@
 	log_info("\n");
 }
 
-static void show_thread_status(struct thread_stat *ts,
-			       struct group_run_stats *rs)
+void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs)
 {
 	double usr_cpu, sys_cpu;
 	unsigned long runtime;
@@ -630,23 +623,23 @@
 	 *  #Parallel_algorithm>
 	 */
 	if (nr == 1) {
-		mean = src->mean;
-		S = src->S;
+		mean = src->mean.u.f;
+		S = src->S.u.f;
 	} else {
-		double delta = src->mean - dst->mean;
+		double delta = src->mean.u.f - dst->mean.u.f;
 
-		mean = ((src->mean * src->samples) +
-			(dst->mean * dst->samples)) /
+		mean = ((src->mean.u.f * src->samples) +
+			(dst->mean.u.f * dst->samples)) /
 			(dst->samples + src->samples);
 
-		S =  src->S + dst->S + pow(delta, 2.0) *
+		S =  src->S.u.f + dst->S.u.f + pow(delta, 2.0) *
 			(dst->samples * src->samples) /
 			(dst->samples + src->samples);
 	}
 
 	dst->samples += src->samples;
-	dst->mean = mean;
-	dst->S = S;
+	dst->mean.u.f = mean;
+	dst->S.u.f = S;
 }
 
 void show_run_stats(void)
@@ -716,9 +709,9 @@
 
 		ts->clat_percentiles = td->o.clat_percentiles;
 		if (td->o.overwrite_plist)
-			ts->percentile_list = td->o.percentile_list;
+			memcpy(ts->percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
 		else
-			ts->percentile_list = NULL;
+			memcpy(ts->percentile_list, def_percentile_list, sizeof(def_percentile_list));
 
 		idx++;
 		ts->members++;
@@ -727,8 +720,13 @@
 			/*
 			 * These are per-group shared already
 			 */
-			ts->name = td->o.name;
-			ts->description = td->o.description;
+			strncpy(ts->name, td->o.name, FIO_JOBNAME_SIZE);
+			if (td->o.description)
+				strncpy(ts->description, td->o.description,
+						FIO_JOBNAME_SIZE);
+			else
+				memset(ts->description, 0, FIO_JOBNAME_SIZE);
+
 			ts->groupid = td->groupid;
 
 			/*
@@ -750,10 +748,10 @@
 			if (!td->error && td->o.continue_on_error &&
 			    td->first_error) {
 				ts->error = td->first_error;
-				ts->verror = td->verror;
+				strcpy(ts->verror, td->verror);
 			} else  if (td->error) {
 				ts->error = td->error;
-				ts->verror = td->verror;
+				strcpy(ts->verror, td->verror);
 			}
 		}
 
@@ -763,7 +761,6 @@
 			sum_stat(&ts->lat_stat[l], &td->ts.lat_stat[l], idx);
 			sum_stat(&ts->bw_stat[l], &td->ts.bw_stat[l], idx);
 
-			ts->stat_io_bytes[l] += td->ts.stat_io_bytes[l];
 			ts->io_bytes[l] += td->ts.io_bytes[l];
 
 			if (ts->runtime[l] < td->ts.runtime[l])
@@ -858,15 +855,24 @@
 		ts = &threadstats[i];
 		rs = &runstats[ts->groupid];
 
-		if (terse_output)
+		if (is_backend)
+			fio_server_send_ts(ts, rs);
+		else if (terse_output)
 			show_thread_status_terse(ts, rs);
 		else
 			show_thread_status(ts, rs);
 	}
 
 	if (!terse_output) {
-		for (i = 0; i < groupid + 1; i++)
-			show_group_stats(&runstats[i], i);
+		for (i = 0; i < groupid + 1; i++) {
+			rs = &runstats[i];
+
+			rs->groupid = i;
+			if (is_backend)
+				fio_server_send_gs(rs);
+			else
+				show_group_stats(rs);
+		}
 
 		show_disk_util();
 	}
@@ -885,10 +891,10 @@
 	if (data < is->min_val)
 		is->min_val = data;
 
-	delta = val - is->mean;
+	delta = val - is->mean.u.f;
 	if (delta) {
-		is->mean += delta / (is->samples + 1.0);
-		is->S += delta * (val - is->mean);
+		is->mean.u.f += delta / (is->samples + 1.0);
+		is->S.u.f += delta * (val - is->mean.u.f);
 	}
 
 	is->samples++;
@@ -954,8 +960,8 @@
 
 	add_stat_sample(&ts->clat_stat[ddir], usec);
 
-	if (ts->clat_log)
-		add_log_sample(td, ts->clat_log, usec, ddir, bs);
+	if (td->clat_log)
+		add_log_sample(td, td->clat_log, usec, ddir, bs);
 
 	if (ts->clat_percentiles)
 		add_clat_percentile_sample(ts, usec, ddir);
@@ -971,8 +977,8 @@
 
 	add_stat_sample(&ts->slat_stat[ddir], usec);
 
-	if (ts->slat_log)
-		add_log_sample(td, ts->slat_log, usec, ddir, bs);
+	if (td->slat_log)
+		add_log_sample(td, td->slat_log, usec, ddir, bs);
 }
 
 void add_lat_sample(struct thread_data *td, enum fio_ddir ddir,
@@ -985,8 +991,8 @@
 
 	add_stat_sample(&ts->lat_stat[ddir], usec);
 
-	if (ts->lat_log)
-		add_log_sample(td, ts->lat_log, usec, ddir, bs);
+	if (td->lat_log)
+		add_log_sample(td, td->lat_log, usec, ddir, bs);
 }
 
 void add_bw_sample(struct thread_data *td, enum fio_ddir ddir, unsigned int bs,
@@ -998,17 +1004,17 @@
 	if (!ddir_rw(ddir))
 		return;
 
-	spent = mtime_since(&ts->stat_sample_time[ddir], t);
+	spent = mtime_since(&td->stat_sample_time[ddir], t);
 	if (spent < td->o.bw_avg_time)
 		return;
 
-	rate = (td->this_io_bytes[ddir] - ts->stat_io_bytes[ddir]) *
+	rate = (td->this_io_bytes[ddir] - td->stat_io_bytes[ddir]) *
 			1000 / spent / 1024;
 	add_stat_sample(&ts->bw_stat[ddir], rate);
 
-	if (ts->bw_log)
-		add_log_sample(td, ts->bw_log, rate, ddir, bs);
+	if (td->bw_log)
+		add_log_sample(td, td->bw_log, rate, ddir, bs);
 
-	fio_gettime(&ts->stat_sample_time[ddir], NULL);
-	ts->stat_io_bytes[ddir] = td->this_io_bytes[ddir];
+	fio_gettime(&td->stat_sample_time[ddir], NULL);
+	td->stat_io_bytes[ddir] = td->this_io_bytes[ddir];
 }
diff --git a/stat.h b/stat.h
new file mode 100644
index 0000000..89766fc
--- /dev/null
+++ b/stat.h
@@ -0,0 +1,195 @@
+#ifndef FIO_STAT_H
+#define FIO_STAT_H
+
+struct group_run_stats {
+	uint64_t max_run[2], min_run[2];
+	uint64_t max_bw[2], min_bw[2];
+	uint64_t io_kb[2];
+	uint64_t agg[2];
+	uint32_t kb_base;
+	uint32_t groupid;
+};
+
+/*
+ * How many depth levels to log
+ */
+#define FIO_IO_U_MAP_NR	7
+#define FIO_IO_U_LAT_U_NR 10
+#define FIO_IO_U_LAT_M_NR 12
+
+/*
+ * Aggregate clat samples to report percentile(s) of them.
+ *
+ * EXECUTIVE SUMMARY
+ *
+ * FIO_IO_U_PLAT_BITS determines the maximum statistical error on the
+ * value of resulting percentiles. The error will be approximately
+ * 1/2^(FIO_IO_U_PLAT_BITS+1) of the value.
+ *
+ * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the maximum
+ * range being tracked for latency samples. The maximum value tracked
+ * accurately will be 2^(GROUP_NR + PLAT_BITS -1) microseconds.
+ *
+ * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the memory
+ * requirement of storing those aggregate counts. The memory used will
+ * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(int)
+ * bytes.
+ *
+ * FIO_IO_U_PLAT_NR is the total number of buckets.
+ *
+ * DETAILS
+ *
+ * Suppose the clat varies from 0 to 999 (usec), the straightforward
+ * method is to keep an array of (999 + 1) buckets, in which a counter
+ * keeps the count of samples which fall in the bucket, e.g.,
+ * {[0],[1],...,[999]}. However this consumes a huge amount of space,
+ * and can be avoided if an approximation is acceptable.
+ *
+ * One such method is to let the range of the bucket to be greater
+ * than one. This method has low accuracy when the value is small. For
+ * example, let the buckets be {[0,99],[100,199],...,[900,999]}, and
+ * the represented value of each bucket be the mean of the range. Then
+ * a value 0 has an round-off error of 49.5. To improve on this, we
+ * use buckets with non-uniform ranges, while bounding the error of
+ * each bucket within a ratio of the sample value. A simple example
+ * would be when error_bound = 0.005, buckets are {
+ * {[0],[1],...,[99]}, {[100,101],[102,103],...,[198,199]},..,
+ * {[900,909],[910,919]...}  }. The total range is partitioned into
+ * groups with different ranges, then buckets with uniform ranges. An
+ * upper bound of the error is (range_of_bucket/2)/value_of_bucket
+ *
+ * For better efficiency, we implement this using base two. We group
+ * samples by their Most Significant Bit (MSB), extract the next M bit
+ * of them as an index within the group, and discard the rest of the
+ * bits.
+ *
+ * E.g., assume a sample 'x' whose MSB is bit n (starting from bit 0),
+ * and use M bit for indexing
+ *
+ *        | n |    M bits   | bit (n-M-1) ... bit 0 |
+ *
+ * Because x is at least 2^n, and bit 0 to bit (n-M-1) is at most
+ * (2^(n-M) - 1), discarding bit 0 to (n-M-1) makes the round-off
+ * error
+ *
+ *           2^(n-M)-1    2^(n-M)    1
+ *      e <= --------- <= ------- = ---
+ *             2^n          2^n     2^M
+ *
+ * Furthermore, we use "mean" of the range to represent the bucket,
+ * the error e can be lowered by half to 1 / 2^(M+1). By using M bits
+ * as the index, each group must contains 2^M buckets.
+ *
+ * E.g. Let M (FIO_IO_U_PLAT_BITS) be 6
+ *      Error bound is 1/2^(6+1) = 0.0078125 (< 1%)
+ *
+ *	Group	MSB	#discarded	range of		#buckets
+ *			error_bits	value
+ *	----------------------------------------------------------------
+ *	0*	0~5	0		[0,63]			64
+ *	1*	6	0		[64,127]		64
+ *	2	7	1		[128,255]		64
+ *	3	8	2		[256,511]		64
+ *	4	9	3		[512,1023]		64
+ *	...	...	...		[...,...]		...
+ *	18	23	17		[8838608,+inf]**	64
+ *
+ *  * Special cases: when n < (M-1) or when n == (M-1), in both cases,
+ *    the value cannot be rounded off. Use all bits of the sample as
+ *    index.
+ *
+ *  ** If a sample's MSB is greater than 23, it will be counted as 23.
+ */
+
+#define FIO_IO_U_PLAT_BITS 6
+#define FIO_IO_U_PLAT_VAL (1 << FIO_IO_U_PLAT_BITS)
+#define FIO_IO_U_PLAT_GROUP_NR 19
+#define FIO_IO_U_PLAT_NR (FIO_IO_U_PLAT_GROUP_NR * FIO_IO_U_PLAT_VAL)
+#define FIO_IO_U_LIST_MAX_LEN 20 /* The size of the default and user-specified
+					list of percentiles */
+
+#define MAX_PATTERN_SIZE	512
+#define FIO_JOBNAME_SIZE	128
+#define FIO_VERROR_SIZE		128
+
+struct thread_stat {
+	char name[FIO_JOBNAME_SIZE];
+	char verror[FIO_VERROR_SIZE];
+	uint32_t error;
+	uint32_t groupid;
+	uint32_t pid;
+	char description[FIO_JOBNAME_SIZE];
+	uint32_t members;
+
+	/*
+	 * bandwidth and latency stats
+	 */
+	struct io_stat clat_stat[2];		/* completion latency */
+	struct io_stat slat_stat[2];		/* submission latency */
+	struct io_stat lat_stat[2];		/* total latency */
+	struct io_stat bw_stat[2];		/* bandwidth stats */
+
+	/*
+	 * fio system usage accounting
+	 */
+	uint64_t usr_time;
+	uint64_t sys_time;
+	uint64_t ctx;
+	uint64_t minf, majf;
+
+	/*
+	 * IO depth and latency stats
+	 */
+	uint64_t clat_percentiles;
+	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
+
+	uint32_t io_u_map[FIO_IO_U_MAP_NR];
+	uint32_t io_u_submit[FIO_IO_U_MAP_NR];
+	uint32_t io_u_complete[FIO_IO_U_MAP_NR];
+	uint32_t io_u_lat_u[FIO_IO_U_LAT_U_NR];
+	uint32_t io_u_lat_m[FIO_IO_U_LAT_M_NR];
+	uint32_t io_u_plat[2][FIO_IO_U_PLAT_NR];
+	uint64_t total_io_u[3];
+	uint64_t short_io_u[3];
+	uint64_t total_submit;
+	uint64_t total_complete;
+
+	uint64_t io_bytes[2];
+	uint64_t runtime[2];
+	uint64_t total_run_time;
+
+	/*
+	 * IO Error related stats
+	 */
+	uint16_t continue_on_error;
+	uint64_t total_err_count;
+	uint32_t first_error;
+
+	uint32_t kb_base;
+};
+
+struct jobs_eta {
+	uint32_t nr_running;
+	uint32_t nr_ramp;
+	uint32_t nr_pending;
+	uint32_t files_open;
+	uint32_t m_rate, t_rate;
+	uint32_t m_iops, t_iops;
+	uint32_t rate[2];
+	uint32_t iops[2];
+	uint64_t elapsed_sec;
+	uint64_t eta_sec;
+
+	/*
+	 * Network 'copy' of run_str[]
+	 */
+	uint32_t nr_threads;
+	uint8_t run_str[0];
+};
+
+extern void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs);
+extern void show_group_stats(struct group_run_stats *rs);
+extern int calc_thread_status(struct jobs_eta *je);
+extern void display_thread_status(struct jobs_eta *je);
+
+#endif