Recent changes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The following changes since commit 75db6e2c168e895cd93504a7d20ddc853413e3b1:

  Usage typo (2011-11-07 22:07:36 +0100)

are available in the git repository at:
  git://git.kernel.dk/fio.git master

Jens Axboe (4):
      Fio 1.99.11
      Update examples/netio to new option format
      Fix crash when attempting to dupe options
      Fio 1.99.12

Steven Lang (2):
      Improve accuracy of rate= option
      Private parameters for ioengines

 HOWTO                  |   64 +++++++++---
 README                 |    2 +
 engines/libaio.c       |   48 ++++++---
 engines/net.c          |  270 +++++++++++++++++++++++++++++-------------------
 examples/netio         |   11 +-
 fio.1                  |   72 ++++++++++---
 fio.h                  |   15 ++-
 fio_version.h          |    2 +-
 init.c                 |  132 ++++++++++++++++++++----
 io_u.c                 |   18 +++-
 ioengine.h             |    7 +-
 ioengines.c            |   66 +++++++++++-
 options.c              |  181 +++++++++++++++++++++-----------
 os/windows/install.wxs |    2 +-
 os/windows/version.h   |    2 +-
 parse.c                |   58 ++++++-----
 parse.h                |    2 +-
 17 files changed, 664 insertions(+), 288 deletions(-)

---

Diff of recent changes:

diff --git a/HOWTO b/HOWTO
index 41edcf1..2403a5c 100644
--- a/HOWTO
+++ b/HOWTO
@@ -524,16 +524,7 @@ ioengine=str	Defines how the job issues io to the file. The following
 			libaio	Linux native asynchronous io. Note that Linux
 				may only support queued behaviour with
 				non-buffered IO (set direct=1 or buffered=0).
-				This engine also has a sub-option,
-				userspace_reap. To set it, use
-				ioengine=libaio:userspace_reap. Normally, with
-				the libaio engine in use, fio will use the
-				io_getevents system call to reap newly returned
-				events. With this flag turned on, the AIO ring
-				will be read directly from user-space to reap
-				events. The reaping mode is only enabled when
-				polling for a minimum of 0 events (eg when
-				iodepth_batch_complete=0).
+				This engine defines engine specific options.
 
 			posixaio glibc posix asynchronous io.
 
@@ -562,16 +553,16 @@ ioengine=str	Defines how the job issues io to the file. The following
 				itself and for debugging/testing purposes.
 
 			net	Transfer over the network to given host:port.
-				'filename' must be set appropriately to
-				filename=host/port/protocol regardless of send
-				or receive, if the latter only the port
-				argument is used. 'host' may be an IP address
-				or hostname, port is the port number to be used,
-				and protocol may be 'udp' or 'tcp'. If no
-				protocol is given, TCP is used.
+				Depending on the protocol used, the hostname,
+				port, listen and filename options are used to
+				specify what sort of connection to make, while
+				the protocol option determines which protocol
+				will be used.
+				This engine defines engine specific options.
 
 			netsplice Like net, but uses splice/vmsplice to
 				map data and send/receive.
+				This engine defines engine specific options.
 
 			cpuio	Doesn't transfer any data, but burns CPU
 				cycles according to the cpuload= and
@@ -1210,6 +1201,45 @@ uid=int		Instead of running as the invoking user, set the user ID to
 
 gid=int		Set group ID, see uid.
 
+In addition, there are some parameters which are only valid when a specific
+ioengine is in use. These are used identically to normal parameters, with the
+caveat that when used on the command line, they must come after the ioengine
+that defines them is selected.
+
+[libaio] userspace_reap Normally, with the libaio engine in use, fio will use
+		the io_getevents system call to reap newly returned events.
+		With this flag turned on, the AIO ring will be read directly
+		from user-space to reap events. The reaping mode is only
+		enabled when polling for a minimum of 0 events (eg when
+		iodepth_batch_complete=0).
+
+[netsplice] hostname=str
+[net] hostname=str The host name or IP address to use for TCP or UDP based IO.
+		If the job is a TCP listener or UDP reader, the hostname is not
+		used and must be omitted.
+
+[netsplice] port=int
+[net] port=int	The TCP or UDP port to bind to or connect to.
+
+[netsplice] protocol=str
+[netsplice] proto=str
+[net] protocol=str
+[net] proto=str	The network protocol to use. Accepted values are:
+
+			tcp	Transmission control protocol
+			udp	Unreliable datagram protocol
+			unix	UNIX domain socket
+
+		When the protocol is TCP or UDP, the port must also be given,
+		as well as the hostname if the job is a TCP listener or UDP
+		reader. For unix sockets, the normal filename option should be
+		used and the port is invalid.
+
+[net] listen	For TCP network connections, tell fio to listen for incoming
+		connections rather than initiating an outgoing connection. The
+		hostname must be omitted if this option is used.
+
+
 6.0 Interpreting the output
 ---------------------------
 
diff --git a/README b/README
index b2fdd0b..03e6751 100644
--- a/README
+++ b/README
@@ -140,6 +140,8 @@ $ fio
 	--terse-version=type	Terse version output format (default 3, or 2).
 	--help			Print this page
 	--cmdhelp=cmd		Print command help, "all" for all of them
+	--enghelp=engine	Print ioengine help, or list available ioengines
+	--enghelp=engine,cmd	Print help for an ioengine cmd
 	--showcmd		Turn a job file into command line options
 	--readonly		Turn on safety read-only checks, preventing
 				writes
diff --git a/engines/libaio.c b/engines/libaio.c
index ad34d06..e4869aa 100644
--- a/engines/libaio.c
+++ b/engines/libaio.c
@@ -24,6 +24,23 @@ struct libaio_data {
 	int iocbs_nr;
 };
 
+struct libaio_options {
+	struct thread_data *td;
+	unsigned int userspace_reap;
+};
+
+static struct fio_option options[] = {
+	{
+		.name	= "userspace_reap",
+		.type	= FIO_OPT_STR_SET,
+		.off1	= offsetof(struct libaio_options, userspace_reap),
+		.help	= "Use alternative user-space reap implementation",
+	},
+	{
+		.name	= NULL,
+	},
+};
+
 static int fio_libaio_prep(struct thread_data fio_unused *td, struct io_u *io_u)
 {
 	struct fio_file *f = io_u->file;
@@ -103,11 +120,12 @@ static int fio_libaio_getevents(struct thread_data *td, unsigned int min,
 				unsigned int max, struct timespec *t)
 {
 	struct libaio_data *ld = td->io_ops->data;
+	struct libaio_options *o = td->eo;
 	unsigned actual_min = td->o.iodepth_batch_complete == 0 ? 0 : min;
 	int r, events = 0;
 
 	do {
-		if (td->o.userspace_libaio_reap == 1
+		if (o->userspace_reap == 1
 		    && actual_min == 0
 		    && ((struct aio_ring *)(ld->aio_ctx))->magic
 				== AIO_RING_MAGIC) {
@@ -262,19 +280,21 @@ static int fio_libaio_init(struct thread_data *td)
 }
 
 static struct ioengine_ops ioengine = {
-	.name		= "libaio",
-	.version	= FIO_IOOPS_VERSION,
-	.init		= fio_libaio_init,
-	.prep		= fio_libaio_prep,
-	.queue		= fio_libaio_queue,
-	.commit		= fio_libaio_commit,
-	.cancel		= fio_libaio_cancel,
-	.getevents	= fio_libaio_getevents,
-	.event		= fio_libaio_event,
-	.cleanup	= fio_libaio_cleanup,
-	.open_file	= generic_open_file,
-	.close_file	= generic_close_file,
-	.get_file_size	= generic_get_file_size,
+	.name			= "libaio",
+	.version		= FIO_IOOPS_VERSION,
+	.init			= fio_libaio_init,
+	.prep			= fio_libaio_prep,
+	.queue			= fio_libaio_queue,
+	.commit			= fio_libaio_commit,
+	.cancel			= fio_libaio_cancel,
+	.getevents		= fio_libaio_getevents,
+	.event			= fio_libaio_event,
+	.cleanup		= fio_libaio_cleanup,
+	.open_file		= generic_open_file,
+	.close_file		= generic_close_file,
+	.get_file_size		= generic_get_file_size,
+	.options		= options,
+	.option_struct_size	= sizeof(struct libaio_options),
 };
 
 #else /* FIO_HAVE_LIBAIO */
diff --git a/engines/net.c b/engines/net.c
index d6821a4..3401039 100644
--- a/engines/net.c
+++ b/engines/net.c
@@ -22,15 +22,19 @@
 
 struct netio_data {
 	int listenfd;
-	int send_to_net;
 	int use_splice;
-	int type;
 	int pipes[2];
-	char host[64];
 	struct sockaddr_in addr;
 	struct sockaddr_un addr_un;
 };
 
+struct netio_options {
+	struct thread_data *td;
+	unsigned int port;
+	unsigned int proto;
+	unsigned int listen;
+};
+
 struct udp_close_msg {
 	uint32_t magic;
 	uint32_t cmd;
@@ -45,6 +49,55 @@ enum {
 	FIO_TYPE_UNIX	= 3,
 };
 
+static int str_hostname_cb(void *data, const char *input);
+static struct fio_option options[] = {
+	{
+		.name	= "hostname",
+		.type	= FIO_OPT_STR_STORE,
+		.cb	= str_hostname_cb,
+		.help	= "Hostname for net IO engine",
+	},
+	{
+		.name	= "port",
+		.type	= FIO_OPT_INT,
+		.off1	= offsetof(struct netio_options, port),
+		.minval	= 1,
+		.maxval	= 65535,
+		.help	= "Port to use for TCP or UDP net connections",
+	},
+	{
+		.name	= "protocol",
+		.alias	= "proto",
+		.type	= FIO_OPT_STR,
+		.off1	= offsetof(struct netio_options, proto),
+		.help	= "Network protocol to use",
+		.def	= "tcp",
+		.posval = {
+			  { .ival = "tcp",
+			    .oval = FIO_TYPE_TCP,
+			    .help = "Transmission Control Protocol",
+			  },
+			  { .ival = "udp",
+			    .oval = FIO_TYPE_UDP,
+			    .help = "Unreliable Datagram Protocol",
+			  },
+			  { .ival = "unix",
+			    .oval = FIO_TYPE_UNIX,
+			    .help = "UNIX domain socket",
+			  },
+		},
+	},
+	{
+		.name	= "listen",
+		.type	= FIO_OPT_STR_SET,
+		.off1	= offsetof(struct netio_options, listen),
+		.help	= "Listen for incoming TCP connections",
+	},
+	{
+		.name	= NULL,
+	},
+};
+
 /*
  * Return -1 for error and 'nr events' for a positive number
  * of events
@@ -78,13 +131,16 @@ static int poll_wait(struct thread_data *td, int fd, short events)
 
 static int fio_netio_prep(struct thread_data *td, struct io_u *io_u)
 {
-	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 
 	/*
 	 * Make sure we don't see spurious reads to a receiver, and vice versa
 	 */
-	if ((nd->send_to_net && io_u->ddir == DDIR_READ) ||
-	    (!nd->send_to_net && io_u->ddir == DDIR_WRITE)) {
+	if (o->proto == FIO_TYPE_TCP)
+		return 0;
+
+	if ((o->listen && io_u->ddir == DDIR_WRITE) ||
+	    (!o->listen && io_u->ddir == DDIR_READ)) {
 		td_verror(td, EINVAL, "bad direction");
 		return 1;
 	}
@@ -230,10 +286,11 @@ static int fio_netio_splice_out(struct thread_data *td, struct io_u *io_u)
 static int fio_netio_send(struct thread_data *td, struct io_u *io_u)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	int ret, flags = OS_MSG_DONTWAIT;
 
 	do {
-		if (nd->type == FIO_TYPE_UDP) {
+		if (o->proto == FIO_TYPE_UDP) {
 			struct sockaddr *to = (struct sockaddr *) &nd->addr;
 
 			ret = sendto(io_u->file->fd, io_u->xfer_buf,
@@ -283,10 +340,11 @@ static int is_udp_close(struct io_u *io_u, int len)
 static int fio_netio_recv(struct thread_data *td, struct io_u *io_u)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	int ret, flags = OS_MSG_DONTWAIT;
 
 	do {
-		if (nd->type == FIO_TYPE_UDP) {
+		if (o->proto == FIO_TYPE_UDP) {
 			fio_socklen_t len = sizeof(nd->addr);
 			struct sockaddr *from = (struct sockaddr *) &nd->addr;
 
@@ -316,19 +374,20 @@ static int fio_netio_recv(struct thread_data *td, struct io_u *io_u)
 static int fio_netio_queue(struct thread_data *td, struct io_u *io_u)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	int ret;
 
 	fio_ro_check(td, io_u);
 
 	if (io_u->ddir == DDIR_WRITE) {
-		if (!nd->use_splice || nd->type == FIO_TYPE_UDP ||
-		    nd->type == FIO_TYPE_UNIX) 
+		if (!nd->use_splice || o->proto == FIO_TYPE_UDP ||
+		    o->proto == FIO_TYPE_UNIX)
 			ret = fio_netio_send(td, io_u);
 		else
 			ret = fio_netio_splice_out(td, io_u);
 	} else if (io_u->ddir == DDIR_READ) {
-		if (!nd->use_splice || nd->type == FIO_TYPE_UDP ||
-		    nd->type == FIO_TYPE_UDP)
+		if (!nd->use_splice || o->proto == FIO_TYPE_UDP ||
+		    o->proto == FIO_TYPE_UNIX)
 			ret = fio_netio_recv(td, io_u);
 		else
 			ret = fio_netio_splice_in(td, io_u);
@@ -359,19 +418,20 @@ static int fio_netio_queue(struct thread_data *td, struct io_u *io_u)
 static int fio_netio_connect(struct thread_data *td, struct fio_file *f)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	int type, domain;
 
-	if (nd->type == FIO_TYPE_TCP) {
+	if (o->proto == FIO_TYPE_TCP) {
 		domain = AF_INET;
 		type = SOCK_STREAM;
-	} else if (nd->type == FIO_TYPE_UDP) {
+	} else if (o->proto == FIO_TYPE_UDP) {
 		domain = AF_INET;
 		type = SOCK_DGRAM;
-	} else if (nd->type == FIO_TYPE_UNIX) {
+	} else if (o->proto == FIO_TYPE_UNIX) {
 		domain = AF_UNIX;
 		type = SOCK_STREAM;
 	} else {
-		log_err("fio: bad network type %d\n", nd->type);
+		log_err("fio: bad network type %d\n", o->proto);
 		f->fd = -1;
 		return 1;
 	}
@@ -382,9 +442,9 @@ static int fio_netio_connect(struct thread_data *td, struct fio_file *f)
 		return 1;
 	}
 
-	if (nd->type == FIO_TYPE_UDP)
+	if (o->proto == FIO_TYPE_UDP)
 		return 0;
-	else if (nd->type == FIO_TYPE_TCP) {
+	else if (o->proto == FIO_TYPE_TCP) {
 		fio_socklen_t len = sizeof(nd->addr);
 
 		if (connect(f->fd, (struct sockaddr *) &nd->addr, len) < 0) {
@@ -411,9 +471,10 @@ static int fio_netio_connect(struct thread_data *td, struct fio_file *f)
 static int fio_netio_accept(struct thread_data *td, struct fio_file *f)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	fio_socklen_t socklen = sizeof(nd->addr);
 
-	if (nd->type == FIO_TYPE_UDP) {
+	if (o->proto == FIO_TYPE_UDP) {
 		f->fd = nd->listenfd;
 		return 0;
 	}
@@ -464,13 +525,13 @@ static void fio_netio_udp_close(struct thread_data *td, struct fio_file *f)
 
 static int fio_netio_close_file(struct thread_data *td, struct fio_file *f)
 {
-	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 
 	/*
 	 * If this is an UDP connection, notify the receiver that we are
 	 * closing down the link
 	 */
-	if (nd->type == FIO_TYPE_UDP)
+	if (o->proto == FIO_TYPE_UDP)
 		fio_netio_udp_close(td, f);
 
 	return generic_close_file(td, f);
@@ -510,15 +571,14 @@ static int fio_netio_setup_connect_unix(struct thread_data *td,
 	return 0;
 }
 
-static int fio_netio_setup_connect(struct thread_data *td, const char *host,
-				   unsigned short port)
+static int fio_netio_setup_connect(struct thread_data *td)
 {
-	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 
-	if (nd->type == FIO_TYPE_UDP || nd->type == FIO_TYPE_TCP)
-		return fio_netio_setup_connect_inet(td, host, port);
+	if (o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_TCP)
+		return fio_netio_setup_connect_inet(td, td->o.filename,o->port);
 	else
-		return fio_netio_setup_connect_unix(td, host);
+		return fio_netio_setup_connect_unix(td, td->o.filename);
 }
 
 static int fio_netio_setup_listen_unix(struct thread_data *td, const char *path)
@@ -557,9 +617,10 @@ static int fio_netio_setup_listen_unix(struct thread_data *td, const char *path)
 static int fio_netio_setup_listen_inet(struct thread_data *td, short port)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	int fd, opt, type;
 
-	if (nd->type == FIO_TYPE_TCP)
+	if (o->proto == FIO_TYPE_TCP)
 		type = SOCK_STREAM;
 	else
 		type = SOCK_DGRAM;
@@ -595,20 +656,20 @@ static int fio_netio_setup_listen_inet(struct thread_data *td, short port)
 	return 0;
 }
 
-static int fio_netio_setup_listen(struct thread_data *td, const char *path,
-				  short port)
+static int fio_netio_setup_listen(struct thread_data *td)
 {
 	struct netio_data *nd = td->io_ops->data;
+	struct netio_options *o = td->eo;
 	int ret;
 
-	if (nd->type == FIO_TYPE_UDP || nd->type == FIO_TYPE_TCP)
-		ret = fio_netio_setup_listen_inet(td, port);
+	if (o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_TCP)
+		ret = fio_netio_setup_listen_inet(td, o->port);
 	else
-		ret = fio_netio_setup_listen_unix(td, path);
+		ret = fio_netio_setup_listen_unix(td, td->o.filename);
 
 	if (ret)
 		return ret;
-	if (nd->type == FIO_TYPE_UDP)
+	if (o->proto == FIO_TYPE_UDP)
 		return 0;
 
 	if (listen(nd->listenfd, 10) < 0) {
@@ -622,72 +683,46 @@ static int fio_netio_setup_listen(struct thread_data *td, const char *path,
 
 static int fio_netio_init(struct thread_data *td)
 {
-	struct netio_data *nd = td->io_ops->data;
-	unsigned int port;
-	char host[64], buf[128];
-	char *sep, *portp, *modep;
+	struct netio_options *o = td->eo;
 	int ret;
 
-	if (td_rw(td)) {
-		log_err("fio: network connections must be read OR write\n");
-		return 1;
-	}
 	if (td_random(td)) {
 		log_err("fio: network IO can't be random\n");
 		return 1;
 	}
 
-	strcpy(buf, td->o.filename);
-
-	sep = strchr(buf, ',');
-	if (!sep)
-		goto bad_host;
+	if (o->proto == FIO_TYPE_UNIX && o->port) {
+		log_err("fio: network IO port not valid with unix socket\n");
+		return 1;
+	} else if (o->proto != FIO_TYPE_UNIX && !o->port) {
+		log_err("fio: network IO requires port for tcp or udp\n");
+		return 1;
+	}
 
-	*sep = '\0';
-	sep++;
-	strcpy(host, buf);
-	if (!strlen(host))
-		goto bad_host;
+	if (o->proto != FIO_TYPE_TCP) {
+		if (o->listen) {
+			  log_err("fio: listen only valid for TCP proto IO\n");
+			  return 1;
+		}
+		if (td_rw(td)) {
+			  log_err("fio: datagram network connections must be"
+				   " read OR write\n");
+			  return 1;
+		}
+		o->listen = td_read(td);
+	}
 
-	modep = NULL;
-	portp = sep;
-	sep = strchr(portp, ',');
-	if (sep) {
-		*sep = '\0';
-		modep = sep + 1;
+	if (o->proto != FIO_TYPE_UNIX && o->listen && td->o.filename) {
+		log_err("fio: hostname not valid for inbound network IO\n");
+		return 1;
 	}
 
-	if (!strncmp("tcp", modep, strlen(modep)) ||
-	    !strncmp("TCP", modep, strlen(modep)))
-		nd->type = FIO_TYPE_TCP;
-	else if (!strncmp("udp", modep, strlen(modep)) ||
-		 !strncmp("UDP", modep, strlen(modep)))
-		nd->type = FIO_TYPE_UDP;
-	else if (!strncmp("unix", modep, strlen(modep)) ||
-		 !strncmp("UNIX", modep, strlen(modep)))
-		nd->type = FIO_TYPE_UNIX;
+	if (o->listen)
+		ret = fio_netio_setup_listen(td);
 	else
-		goto bad_host;
-
-	if (nd->type != FIO_TYPE_UNIX) {
-		port = strtol(portp, NULL, 10);
-		if (!port || port > 65535)
-			goto bad_host;
-	} else
-		port = 0;
-
-	if (td_read(td)) {
-		nd->send_to_net = 0;
-		ret = fio_netio_setup_listen(td, host, port);
-	} else {
-		nd->send_to_net = 1;
-		ret = fio_netio_setup_connect(td, host, port);
-	}
+		ret = fio_netio_setup_connect(td);
 
 	return ret;
-bad_host:
-	log_err("fio: bad network host/port/protocol: %s\n", td->o.filename);
-	return 1;
 }
 
 static void fio_netio_cleanup(struct thread_data *td)
@@ -710,6 +745,11 @@ static int fio_netio_setup(struct thread_data *td)
 {
 	struct netio_data *nd;
 
+	if (!td->files_index) {
+		add_file(td, td->o.filename ?: "net");
+		td->o.nr_files = td->o.nr_files ?: 1;
+	}
+
 	if (!td->io_ops->data) {
 		nd = malloc(sizeof(*nd));;
 
@@ -742,34 +782,48 @@ static int fio_netio_setup_splice(struct thread_data *td)
 }
 
 static struct ioengine_ops ioengine_splice = {
-	.name		= "netsplice",
-	.version	= FIO_IOOPS_VERSION,
-	.prep		= fio_netio_prep,
-	.queue		= fio_netio_queue,
-	.setup		= fio_netio_setup_splice,
-	.init		= fio_netio_init,
-	.cleanup	= fio_netio_cleanup,
-	.open_file	= fio_netio_open_file,
-	.close_file	= generic_close_file,
-	.flags		= FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
-			  FIO_SIGTERM | FIO_PIPEIO,
+	.name			= "netsplice",
+	.version		= FIO_IOOPS_VERSION,
+	.prep			= fio_netio_prep,
+	.queue			= fio_netio_queue,
+	.setup			= fio_netio_setup_splice,
+	.init			= fio_netio_init,
+	.cleanup		= fio_netio_cleanup,
+	.open_file		= fio_netio_open_file,
+	.close_file		= generic_close_file,
+	.options		= options,
+	.option_struct_size	= sizeof(struct netio_options),
+	.flags			= FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
+				  FIO_SIGTERM | FIO_PIPEIO,
 };
 #endif
 
 static struct ioengine_ops ioengine_rw = {
-	.name		= "net",
-	.version	= FIO_IOOPS_VERSION,
-	.prep		= fio_netio_prep,
-	.queue		= fio_netio_queue,
-	.setup		= fio_netio_setup,
-	.init		= fio_netio_init,
-	.cleanup	= fio_netio_cleanup,
-	.open_file	= fio_netio_open_file,
-	.close_file	= fio_netio_close_file,
-	.flags		= FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
-			  FIO_SIGTERM | FIO_PIPEIO,
+	.name			= "net",
+	.version		= FIO_IOOPS_VERSION,
+	.prep			= fio_netio_prep,
+	.queue			= fio_netio_queue,
+	.setup			= fio_netio_setup,
+	.init			= fio_netio_init,
+	.cleanup		= fio_netio_cleanup,
+	.open_file		= fio_netio_open_file,
+	.close_file		= fio_netio_close_file,
+	.options		= options,
+	.option_struct_size	= sizeof(struct netio_options),
+	.flags			= FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR |
+				  FIO_SIGTERM | FIO_PIPEIO,
 };
 
+static int str_hostname_cb(void *data, const char *input)
+{
+	struct netio_options *o = data;
+
+	if (o->td->o.filename)
+		free(o->td->o.filename);
+	o->td->o.filename = strdup(input);
+	return 0;
+}
+
 static void fio_init fio_netio_register(void)
 {
 	register_ioengine(&ioengine_rw);
diff --git a/examples/netio b/examples/netio
index 5b07468..bd44adb 100644
--- a/examples/netio
+++ b/examples/netio
@@ -1,12 +1,11 @@
 # Example network job, just defines two clients that send/recv data
 [global]
 ioengine=net
-#this would use UDP over localhost, port 8888
-#filename=localhost,8888,udp
-#this would use a local domain socket /tmp/fio.sock
-#filename=/tmp/fio.sock,,unix
-#TCP, port 8888, localhost
-filename=localhost,8888,tcp
+#Use hostname=/tmp.fio.sock for local unix domain sockets
+hostname=localhost
+port=8888
+#Use =udp for UDP, =unix for local unix domain socket
+protocol=tcp
 bs=4k
 size=10g
 #set the below option to enable end-to-end data integrity tests
diff --git a/fio.1 b/fio.1
index aae7657..138208f 100644
--- a/fio.1
+++ b/fio.1
@@ -44,6 +44,9 @@ Display usage information and exit.
 .BI \-\-cmdhelp \fR=\fPcommand
 Print help information for \fIcommand\fR.  May be `all' for all commands.
 .TP
+.BI \-\-enghelp \fR=\fPioengine[,command]
+List all commands defined by \fIioengine\fR, or print help for \fIcommand\fR defined by \fIioengine\fR.
+.TP
 .BI \-\-showcmd \fR=\fPjobfile
 Convert \fIjobfile\fR to a set of command-line options.
 .TP
@@ -142,9 +145,8 @@ than `./'.
 .B fio
 normally makes up a file name based on the job name, thread number, and file
 number. If you want to share files between threads in a job or several jobs,
-specify a \fIfilename\fR for each of them to override the default. If the I/O
-engine used is `net', \fIfilename\fR is the host and port to connect to in the
-format \fIhost\fR/\fIport\fR. If the I/O engine is file-based, you can specify
+specify a \fIfilename\fR for each of them to override the default.
+If the I/O engine is file-based, you can specify
 a number of files by separating the names with a `:' character. `\-' is a
 reserved name, meaning stdin or stdout, depending on the read/write direction
 set.
@@ -398,13 +400,7 @@ Basic \fIreadv\fR\|(2) or \fIwritev\fR\|(2) I/O. Will emulate queuing by
 coalescing adjacents IOs into a single submission.
 .TP
 .B libaio
-Linux native asynchronous I/O.  This engine also has a sub-option,
-\fBuserspace_reap\fR. To set it, use \fBioengine=libaio:userspace_reap\fR.
-Normally, with the libaio engine in use, fio will use the
-\fIio_getevents\fR\|(3) system call to reap newly returned events. With this
-flag turned on, the AIO ring will be read directly from user-space to reap
-events. The reaping mode is only enabled when polling for a minimum of \fB0\fR
-events (eg when \fBiodepth_batch_complete=0\fR).
+Linux native asynchronous I/O. This ioengine defines engine specific options.
 .TP
 .B posixaio
 POSIX asynchronous I/O using \fIaio_read\fR\|(3) and \fIaio_write\fR\|(3).
@@ -436,14 +432,14 @@ Doesn't transfer any data, just pretends to.  Mainly used to exercise \fBfio\fR
 itself and for debugging and testing purposes.
 .TP
 .B net
-Transfer over the network.  \fBfilename\fR must be set appropriately to
-`\fIhost\fR,\fIport\fR,\fItype\fR' regardless of data direction. \fItype\fR
-is one of \fBtcp\fR, \fBudp\fR, or \fBunix\fR. For UNIX domain sockets,
-the \fIhost\fR parameter is a file system path.
+Transfer over the network.  The protocol to be used can be defined with the
+\fBprotocol\fR parameter.  Depending on the protocol, \fBfilename\fR,
+\fBhostname\fR, \fBport\fR, or \fBlisten\fR must be specified.
+This ioengine defines engine specific options.
 .TP
 .B netsplice
 Like \fBnet\fR, but uses \fIsplice\fR\|(2) and \fIvmsplice\fR\|(2) to map data
-and send/receive.
+and send/receive. This ioengine defines engine specific options.
 .TP
 .B cpuio
 Doesn't transfer any data, but burns CPU cycles according to \fBcpuload\fR and
@@ -965,6 +961,52 @@ the maximum length of the list is 20. Use ':' to separate the
 numbers. For example, \-\-percentile_list=99.5:99.9 will cause fio to
 report the values of completion latency below which 99.5% and 99.9% of
 the observed latencies fell, respectively.
+.SS "Ioengine Parameters List"
+Some parameters are only valid when a specific ioengine is in use. These are
+used identically to normal parameters, with the caveat that when used on the
+command line, the must come after the ioengine that defines them is selected.
+.TP
+.BI (libaio)userspace_reap
+Normally, with the libaio engine in use, fio will use
+the io_getevents system call to reap newly returned events.
+With this flag turned on, the AIO ring will be read directly
+from user-space to reap events. The reaping mode is only
+enabled when polling for a minimum of 0 events (eg when
+iodepth_batch_complete=0).
+.TP
+.BI (net,netsplice)hostname \fR=\fPstr
+The host name or IP address to use for TCP or UDP based IO.
+If the job is a TCP listener or UDP reader, the hostname is not
+used and must be omitted.
+.TP
+.BI (net,netsplice)port \fR=\fPint
+The TCP or UDP port to bind to or connect to.
+.TP
+.BI (net,netsplice)protocol \fR=\fPstr "\fR,\fP proto" \fR=\fPstr
+The network protocol to use. Accepted values are:
+.RS
+.RS
+.TP
+.B tcp
+Transmission control protocol
+.TP
+.B udp
+Unreliable datagram protocol
+.TP
+.B unix
+UNIX domain socket
+.RE
+.P
+When the protocol is TCP or UDP, the port must also be given,
+as well as the hostname if the job is a TCP listener or UDP
+reader. For unix sockets, the normal filename option should be
+used and the port is invalid.
+.RE
+.TP
+.BI (net,netsplice)listen
+For TCP network connections, tell fio to listen for incoming
+connections rather than initiating an outgoing connection. The
+hostname must be omitted if this option is used.
 .SH OUTPUT
 While running, \fBfio\fR will display the status of the created jobs.  For
 example:
diff --git a/fio.h b/fio.h
index be684ca..cc1f65f 100644
--- a/fio.h
+++ b/fio.h
@@ -245,8 +245,6 @@ struct thread_options {
 	unsigned int gid;
 
 	unsigned int sync_file_range;
-
-	unsigned int userspace_libaio_reap;
 };
 
 /*
@@ -254,6 +252,7 @@ struct thread_options {
  */
 struct thread_data {
 	struct thread_options o;
+	void *eo;
 	char verror[FIO_VERROR_SIZE];
 	pthread_t thread;
 	int thread_number;
@@ -357,7 +356,7 @@ struct thread_data {
 	/*
 	 * Rate state
 	 */
-	unsigned long rate_nsec_cycle[2];
+	unsigned long long rate_bps[2];
 	long rate_pending_usleep[2];
 	unsigned long rate_bytes[2];
 	unsigned long rate_blocks[2];
@@ -551,16 +550,20 @@ 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 *);
+extern int fio_cmd_ioengine_option_parse(struct thread_data *, const char *, char *);
 extern void fio_fill_default_options(struct thread_data *);
 extern int fio_show_option_help(const char *);
+extern void fio_options_set_ioengine_opts(struct option *long_options, struct thread_data *td);
 extern void fio_options_dup_and_init(struct option *);
-extern void options_mem_dupe(struct thread_data *);
-extern void options_mem_free(struct thread_data *);
+extern void fio_options_mem_dupe(struct thread_data *);
+extern void options_mem_dupe(void *data, struct fio_option *options);
 extern void td_fill_rand_seeds(struct thread_data *);
 extern void add_job_opts(const char **);
 extern char *num2str(unsigned long, int, int, int);
+extern int ioengine_load(struct thread_data *);
 
-#define FIO_GETOPT_JOB		0x89988998
+#define FIO_GETOPT_JOB		0x89000000
+#define FIO_GETOPT_IOENGINE	0x98000000
 #define FIO_NR_OPTIONS		(FIO_MAX_OPTS + 128)
 
 /*
diff --git a/fio_version.h b/fio_version.h
index 49a2dc3..c326eba 100644
--- a/fio_version.h
+++ b/fio_version.h
@@ -3,6 +3,6 @@
 
 #define FIO_MAJOR	1
 #define FIO_MINOR	99
-#define FIO_PATCH	10
+#define FIO_PATCH	12
 
 #endif
diff --git a/init.c b/init.c
index 9fafadf..482ce09 100644
--- a/init.c
+++ b/init.c
@@ -139,6 +139,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = {
 		.val		= 'c' | FIO_CLIENT_FLAG,
 	},
 	{
+		.name		   = (char *) "enghelp",
+		.has_arg	= optional_argument,
+		.val		    = 'i' | FIO_CLIENT_FLAG,
+	},
+	{
 		.name		= (char *) "showcmd",
 		.has_arg	= no_argument,
 		.val		= 's' | FIO_CLIENT_FLAG,
@@ -278,7 +283,8 @@ static int setup_thread_area(void)
 /*
  * Return a free job structure.
  */
-static struct thread_data *get_new_job(int global, struct thread_data *parent)
+static struct thread_data *get_new_job(int global, struct thread_data *parent,
+				       int preserve_eo)
 {
 	struct thread_data *td;
 
@@ -297,10 +303,14 @@ static struct thread_data *get_new_job(int global, struct thread_data *parent)
 	td = &threads[thread_number++];
 	*td = *parent;
 
+	td->io_ops = NULL;
+	if (!preserve_eo)
+		td->eo = NULL;
+
 	td->o.uid = td->o.gid = -1U;
 
 	dup_files(td, parent);
-	options_mem_dupe(td);
+	fio_options_mem_dupe(td);
 
 	profile_add_hooks(td);
 
@@ -319,6 +329,8 @@ static void put_job(struct thread_data *td)
 		log_info("fio: %s\n", td->verror);
 
 	fio_options_free(td);
+	if (td->io_ops)
+		free_ioengine(td);
 
 	memset(&threads[td->thread_number - 1], 0, sizeof(*td));
 	thread_number--;
@@ -327,21 +339,19 @@ static void put_job(struct thread_data *td)
 static int __setup_rate(struct thread_data *td, enum fio_ddir ddir)
 {
 	unsigned int bs = td->o.min_bs[ddir];
-	unsigned long long bytes_per_sec;
 
 	assert(ddir_rw(ddir));
 
 	if (td->o.rate[ddir])
-		bytes_per_sec = td->o.rate[ddir];
+		td->rate_bps[ddir] = td->o.rate[ddir];
 	else
-		bytes_per_sec = td->o.rate_iops[ddir] * bs;
+		td->rate_bps[ddir] = td->o.rate_iops[ddir] * bs;
 
-	if (!bytes_per_sec) {
+	if (!td->rate_bps[ddir]) {
 		log_err("rate lower than supported\n");
 		return -1;
 	}
 
-	td->rate_nsec_cycle[ddir] = 1000000000ULL / bytes_per_sec;
 	td->rate_pending_usleep[ddir] = 0;
 	return 0;
 }
@@ -665,6 +675,62 @@ static int init_random_state(struct thread_data *td)
 }
 
 /*
+ * Initializes the ioengine configured for a job, if it has not been done so
+ * already.
+ */
+int ioengine_load(struct thread_data *td)
+{
+	const char *engine;
+
+	/*
+	 * Engine has already been loaded.
+	 */
+	if (td->io_ops)
+		return 0;
+
+	engine = get_engine_name(td->o.ioengine);
+	td->io_ops = load_ioengine(td, engine);
+	if (!td->io_ops) {
+		log_err("fio: failed to load engine %s\n", engine);
+		return 1;
+	}
+
+	if (td->io_ops->option_struct_size && td->io_ops->options) {
+		/*
+		 * In cases where td->eo is set, clone it for a child thread.
+		 * This requires that the parent thread has the same ioengine,
+		 * but that requirement must be enforced by the code which
+		 * cloned the thread.
+		 */
+		void *origeo = td->eo;
+		/*
+		 * Otherwise use the default thread options.
+		 */
+		if (!origeo && td != &def_thread && def_thread.eo &&
+		    def_thread.io_ops->options == td->io_ops->options)
+			origeo = def_thread.eo;
+
+		options_init(td->io_ops->options);
+		td->eo = malloc(td->io_ops->option_struct_size);
+		/*
+		 * Use the default thread as an option template if this uses the
+		 * same options structure and there are non-default options
+		 * used.
+		 */
+		if (origeo) {
+			memcpy(td->eo, origeo, td->io_ops->option_struct_size);
+			options_mem_dupe(td->eo, td->io_ops->options);
+		} else {
+			memset(td->eo, 0, td->io_ops->option_struct_size);
+			fill_default_options(td->eo, td->io_ops->options);
+		}
+		*(struct thread_data **)td->eo = td;
+	}
+
+	return 0;
+}
+
+/*
  * Adds a job to the list of things todo. Sanitizes the various options
  * to make sure we don't have conflicts, and initializes various
  * members of td.
@@ -674,7 +740,6 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
 	const char *ddir_str[] = { NULL, "read", "write", "rw", NULL,
 				   "randread", "randwrite", "randrw" };
 	unsigned int i;
-	const char *engine;
 	char fname[PATH_MAX];
 	int numjobs, file_alloced;
 
@@ -695,12 +760,8 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
 	if (profile_td_init(td))
 		goto err;
 
-	engine = get_engine_name(td->o.ioengine);
-	td->io_ops = load_ioengine(td, engine);
-	if (!td->io_ops) {
-		log_err("fio: failed to load engine %s\n", engine);
+	if (ioengine_load(td))
 		goto err;
-	}
 
 	if (td->o.use_thread)
 		nr_thread++;
@@ -728,6 +789,13 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
 	if (fixup_options(td))
 		goto err;
 
+	/*
+	 * IO engines only need this for option callbacks, and the address may
+	 * change in subprocesses.
+	 */
+	if (td->eo)
+		*(struct thread_data **)td->eo = NULL;
+
 	if (td->io_ops->flags & FIO_DISKLESSIO) {
 		struct fio_file *f;
 
@@ -814,7 +882,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num)
 	 */
 	numjobs = td->o.numjobs;
 	while (--numjobs) {
-		struct thread_data *td_new = get_new_job(0, td);
+		struct thread_data *td_new = get_new_job(0, td, 1);
 
 		if (!td_new)
 			goto err;
@@ -862,11 +930,11 @@ void add_job_opts(const char **o)
 			sprintf(jobname, "%s", o[i] + 5);
 		}
 		if (in_global && !td_parent)
-			td_parent = get_new_job(1, &def_thread);
+			td_parent = get_new_job(1, &def_thread, 0);
 		else if (!in_global && !td) {
 			if (!td_parent)
 				td_parent = &def_thread;
-			td = get_new_job(0, td_parent);
+			td = get_new_job(0, td_parent, 0);
 		}
 		if (in_global)
 			fio_options_parse(td_parent, (char **) &o[i], 1);
@@ -1001,7 +1069,7 @@ int parse_jobs_ini(char *file, int is_buf, int stonewall_flag)
 			first_sect = 0;
 		}
 
-		td = get_new_job(global, &def_thread);
+		td = get_new_job(global, &def_thread, 0);
 		if (!td) {
 			ret = 1;
 			break;
@@ -1120,6 +1188,10 @@ static void usage(const char *name)
 	printf("  --help\t\tPrint this page\n");
 	printf("  --cmdhelp=cmd\t\tPrint command help, \"all\" for all of"
 		" them\n");
+	printf("  --enghelp=engine\tPrint ioengine help, or list"
+		" available ioengines\n");
+	printf("  --enghelp=engine,cmd\tPrint help for an ioengine"
+		" cmd\n");
 	printf("  --showcmd\t\tTurn a job file into command line options\n");
 	printf("  --eta=when\t\tWhen ETA estimate should be printed\n");
 	printf("            \t\tMay be \"always\", \"never\" or \"auto\"\n");
@@ -1318,6 +1390,12 @@ int parse_cmd_line(int argc, char *argv[])
 				do_exit++;
 			}
 			break;
+		case 'i':
+			if (!cur_client) {
+				fio_show_ioengine_help(optarg);
+				do_exit++;
+			}
+			break;
 		case 's':
 			dump_cmdline = 1;
 			break;
@@ -1387,12 +1465,28 @@ int parse_cmd_line(int argc, char *argv[])
 				if (is_section && skip_this_section(val))
 					continue;
 
-				td = get_new_job(global, &def_thread);
-				if (!td)
+				td = get_new_job(global, &def_thread, 1);
+				if (!td || ioengine_load(td))
 					return 0;
+				fio_options_set_ioengine_opts(l_opts, td);
 			}
 
 			ret = fio_cmd_option_parse(td, opt, val);
+
+			if (!ret && !strcmp(opt, "ioengine")) {
+				free_ioengine(td);
+				if (ioengine_load(td))
+					return 0;
+				fio_options_set_ioengine_opts(l_opts, td);
+			}
+			break;
+		}
+		case FIO_GETOPT_IOENGINE: {
+			const char *opt = l_opts[lidx].name;
+			char *val = optarg;
+			opt = l_opts[lidx].name;
+			val = optarg;
+			ret = fio_cmd_ioengine_option_parse(td, opt, val);
 			break;
 		}
 		case 'w':
diff --git a/io_u.c b/io_u.c
index fc3ee49..0ff66f9 100644
--- a/io_u.c
+++ b/io_u.c
@@ -1296,6 +1296,16 @@ static void account_io_completion(struct thread_data *td, struct io_u *io_u,
 	add_iops_sample(td, idx, &icd->time);
 }
 
+static long long usec_for_io(struct thread_data *td, enum fio_ddir ddir)
+{
+	unsigned long long secs, remainder, bps, bytes;
+	bytes = td->this_io_bytes[ddir];
+	bps = td->rate_bps[ddir];
+	secs = bytes / bps;
+	remainder = bytes % bps;
+	return remainder * 1000000 / bps + secs * 1000000;
+}
+
 static void io_completed(struct thread_data *td, struct io_u *io_u,
 			 struct io_completion_data *icd)
 {
@@ -1354,14 +1364,12 @@ static void io_completed(struct thread_data *td, struct io_u *io_u,
 
 			if (__should_check_rate(td, idx)) {
 				td->rate_pending_usleep[idx] =
-					((td->this_io_bytes[idx] *
-					  td->rate_nsec_cycle[idx]) / 1000 -
+					(usec_for_io(td, idx) -
 					 utime_since_now(&td->start));
 			}
-			if (__should_check_rate(td, idx ^ 1))
+			if (__should_check_rate(td, odx))
 				td->rate_pending_usleep[odx] =
-					((td->this_io_bytes[odx] *
-					  td->rate_nsec_cycle[odx]) / 1000 -
+					(usec_for_io(td, odx) -
 					 utime_since_now(&td->start));
 		}
 
diff --git a/ioengine.h b/ioengine.h
index 044c4da..be74b66 100644
--- a/ioengine.h
+++ b/ioengine.h
@@ -1,7 +1,7 @@
 #ifndef FIO_IOENGINE_H
 #define FIO_IOENGINE_H
 
-#define FIO_IOOPS_VERSION	12
+#define FIO_IOOPS_VERSION	13
 
 enum {
 	IO_U_F_FREE		= 1 << 0,
@@ -121,6 +121,8 @@ struct ioengine_ops {
 	int (*open_file)(struct thread_data *, struct fio_file *);
 	int (*close_file)(struct thread_data *, struct fio_file *);
 	int (*get_file_size)(struct thread_data *, struct fio_file *);
+	int option_struct_size;
+	struct fio_option *options;
 	void *data;
 	void *dlhandle;
 };
@@ -155,8 +157,11 @@ extern int __must_check td_io_get_file_size(struct thread_data *, struct fio_fil
 extern struct ioengine_ops *load_ioengine(struct thread_data *, const char *);
 extern void register_ioengine(struct ioengine_ops *);
 extern void unregister_ioengine(struct ioengine_ops *);
+extern void free_ioengine(struct thread_data *);
 extern void close_ioengine(struct thread_data *);
 
+extern int fio_show_ioengine_help(const char *engine);
+
 /*
  * io unit handling
  */
diff --git a/ioengines.c b/ioengines.c
index 7f4e104..e8ed871 100644
--- a/ioengines.c
+++ b/ioengines.c
@@ -152,13 +152,17 @@ struct ioengine_ops *load_ioengine(struct thread_data *td, const char *name)
 	return ret;
 }
 
-void close_ioengine(struct thread_data *td)
+/*
+ * For cleaning up an ioengine which never made it to init().
+ */
+void free_ioengine(struct thread_data *td)
 {
-	dprint(FD_IO, "close ioengine %s\n", td->io_ops->name);
+	dprint(FD_IO, "free ioengine %s\n", td->io_ops->name);
 
-	if (td->io_ops->cleanup) {
-		td->io_ops->cleanup(td);
-		td->io_ops->data = NULL;
+	if (td->eo && td->io_ops->options) {
+		options_free(td->io_ops->options, td->eo);
+		free(td->eo);
+		td->eo = NULL;
 	}
 
 	if (td->io_ops->dlhandle)
@@ -168,6 +172,18 @@ void close_ioengine(struct thread_data *td)
 	td->io_ops = NULL;
 }
 
+void close_ioengine(struct thread_data *td)
+{
+	dprint(FD_IO, "close ioengine %s\n", td->io_ops->name);
+
+	if (td->io_ops->cleanup) {
+		td->io_ops->cleanup(td);
+		td->io_ops->data = NULL;
+	}
+
+	free_ioengine(td);
+}
+
 int td_io_prep(struct thread_data *td, struct io_u *io_u)
 {
 	dprint_io_u(io_u, "prep");
@@ -501,3 +517,43 @@ int do_io_u_trim(struct thread_data *td, struct io_u *io_u)
 	return 0;
 #endif
 }
+
+int fio_show_ioengine_help(const char *engine)
+{
+	struct flist_head *entry;
+	struct thread_data td;
+	char *sep;
+	int ret = 1;
+
+	if (!engine || !*engine) {
+		log_info("Available IO engines:\n");
+		flist_for_each(entry, &engine_list) {
+			td.io_ops = flist_entry(entry, struct ioengine_ops,
+						list);
+			log_info("\t%s\n", td.io_ops->name);
+		}
+		return 0;
+	}
+	sep = strchr(engine, ',');
+	if (sep) {
+		*sep = 0;
+		sep++;
+	}
+
+	memset(&td, 0, sizeof(td));
+
+	td.io_ops = load_ioengine(&td, engine);
+	if (!td.io_ops) {
+		log_info("IO engine %s not found\n", engine);
+		return 1;
+	}
+
+	if (td.io_ops->options)
+		ret = show_cmd_help(td.io_ops->options, sep);
+	else
+		log_info("IO engine %s has no options\n", td.io_ops->name);
+
+	free_ioengine(&td);
+
+	return ret;
+}
diff --git a/options.c b/options.c
index 84bf5ac..53c3a82 100644
--- a/options.c
+++ b/options.c
@@ -226,21 +226,6 @@ static int str_rw_cb(void *data, const char *str)
 	return 0;
 }
 
-#ifdef FIO_HAVE_LIBAIO
-static int str_libaio_cb(void *data, const char *str)
-{
-	struct thread_data *td = data;
-
-	if (!strcmp(str, "userspace_reap")) {
-		td->o.userspace_libaio_reap = 1;
-		return 0;
-	}
-
-	log_err("fio: bad libaio sub-option: %s\n", str);
-	return 1;
-}
-#endif
-
 static int str_mem_cb(void *data, const char *mem)
 {
 	struct thread_data *td = data;
@@ -595,14 +580,6 @@ static char *get_next_file_name(char **ptr)
 	return start;
 }
 
-static int str_hostname_cb(void *data, const char *input)
-{
-	struct thread_data *td = data;
-
-	td->o.filename = strdup(input);
-	return 0;
-}
-
 static int str_filename_cb(void *data, const char *input)
 {
 	struct thread_data *td = data;
@@ -881,12 +858,6 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 		.help	= "File(s) to use for the workload",
 	},
 	{
-		.name	= "hostname",
-		.type	= FIO_OPT_STR_STORE,
-		.cb	= str_hostname_cb,
-		.help	= "Hostname for net IO engine",
-	},
-	{
 		.name	= "kb_base",
 		.type	= FIO_OPT_INT,
 		.off1	= td_var_offset(kb_base),
@@ -999,7 +970,6 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 #ifdef FIO_HAVE_LIBAIO
 			  { .ival = "libaio",
 			    .help = "Linux native asynchronous IO",
-			    .cb   = str_libaio_cb,
 			  },
 #endif
 #ifdef FIO_HAVE_POSIXAIO
@@ -1015,7 +985,7 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 #ifdef FIO_HAVE_WINDOWSAIO
 			  { .ival = "windowsaio",
 			    .help = "Windows native asynchronous IO"
-		  	  },
+			  },
 #endif
 			  { .ival = "mmap",
 			    .help = "Memory mapped IO"
@@ -2137,33 +2107,26 @@ static struct fio_option options[FIO_MAX_OPTS] = {
 };
 
 static void add_to_lopt(struct option *lopt, struct fio_option *o,
-			const char *name)
+			const char *name, int val)
 {
 	lopt->name = (char *) name;
-	lopt->val = FIO_GETOPT_JOB;
+	lopt->val = val;
 	if (o->type == FIO_OPT_STR_SET)
 		lopt->has_arg = no_argument;
 	else
 		lopt->has_arg = required_argument;
 }
 
-void fio_options_dup_and_init(struct option *long_options)
+static void options_to_lopts(struct fio_option *opts,
+			      struct option *long_options,
+			      int i, int option_type)
 {
-	struct fio_option *o;
-	unsigned int i;
-
-	options_init(options);
-
-	i = 0;
-	while (long_options[i].name)
-		i++;
-
-	o = &options[0];
+	struct fio_option *o = &opts[0];
 	while (o->name) {
-		add_to_lopt(&long_options[i], o, o->name);
+		add_to_lopt(&long_options[i], o, o->name, option_type);
 		if (o->alias) {
 			i++;
-			add_to_lopt(&long_options[i], o, o->alias);
+			add_to_lopt(&long_options[i], o, o->alias, option_type);
 		}
 
 		i++;
@@ -2172,6 +2135,43 @@ void fio_options_dup_and_init(struct option *long_options)
 	}
 }
 
+void fio_options_set_ioengine_opts(struct option *long_options,
+				   struct thread_data *td)
+{
+	unsigned int i;
+
+	i = 0;
+	while (long_options[i].name) {
+		if (long_options[i].val == FIO_GETOPT_IOENGINE) {
+			memset(&long_options[i], 0, sizeof(*long_options));
+			break;
+		}
+		i++;
+	}
+
+	/*
+	 * Just clear out the prior ioengine options.
+	 */
+	if (!td || !td->eo)
+		return;
+
+	options_to_lopts(td->io_ops->options, long_options, i,
+			 FIO_GETOPT_IOENGINE);
+}
+
+void fio_options_dup_and_init(struct option *long_options)
+{
+	unsigned int i;
+
+	options_init(options);
+
+	i = 0;
+	while (long_options[i].name)
+		i++;
+
+	options_to_lopts(options, long_options, i, FIO_GETOPT_JOB);
+}
+
 struct fio_keyword {
 	const char *word;
 	const char *desc;
@@ -2389,18 +2389,53 @@ static char **dup_and_sub_options(char **opts, int num_opts)
 
 int fio_options_parse(struct thread_data *td, char **opts, int num_opts)
 {
-	int i, ret;
+	int i, ret, unknown;
 	char **opts_copy;
 
 	sort_options(opts, options, num_opts);
 	opts_copy = dup_and_sub_options(opts, num_opts);
 
-	for (ret = 0, i = 0; i < num_opts; i++) {
-		ret |= parse_option(opts_copy[i], opts[i], options, td);
+	for (ret = 0, i = 0, unknown = 0; i < num_opts; i++) {
+		struct fio_option *o;
+		int newret = parse_option(opts_copy[i], opts[i], options, &o,
+					  td);
+
+		if (opts_copy[i]) {
+			if (newret && !o) {
+				unknown++;
+				continue;
+			}
+			free(opts_copy[i]);
+			opts_copy[i] = NULL;
+		}
+
+		ret |= newret;
+	}
+
+	if (unknown) {
+		ret |= ioengine_load(td);
+		if (td->eo) {
+			sort_options(opts_copy, td->io_ops->options, num_opts);
+			opts = opts_copy;
+		}
+		for (i = 0; i < num_opts; i++) {
+			struct fio_option *o = NULL;
+			int newret = 1;
+			if (!opts_copy[i])
+				continue;
+
+			if (td->eo)
+				newret = parse_option(opts_copy[i], opts[i],
+						      td->io_ops->options, &o,
+						      td->eo);
+
+			ret |= newret;
+			if (!o)
+				log_err("Bad option <%s>\n", opts[i]);
 
-		if (opts_copy[i])
 			free(opts_copy[i]);
-		opts_copy[i] = NULL;
+			opts_copy[i] = NULL;
+		}
 	}
 
 	free(opts_copy);
@@ -2412,6 +2447,12 @@ int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val)
 	return parse_cmd_option(opt, val, options, td);
 }
 
+int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt,
+				char *val)
+{
+	return parse_cmd_option(opt, val, td->io_ops->options, td);
+}
+
 void fio_fill_default_options(struct thread_data *td)
 {
 	fill_default_options(td, options);
@@ -2422,28 +2463,37 @@ int fio_show_option_help(const char *opt)
 	return show_cmd_help(options, opt);
 }
 
-/*
- * dupe FIO_OPT_STR_STORE options
- */
-void options_mem_dupe(struct thread_data *td)
+void options_mem_dupe(void *data, struct fio_option *options)
 {
-	struct thread_options *o = &td->o;
-	struct fio_option *opt;
+	struct fio_option *o;
 	char **ptr;
-	int i;
 
-	for (i = 0, opt = &options[0]; opt->name; i++, opt = &options[i]) {
-		if (opt->type != FIO_OPT_STR_STORE)
+	for (o = &options[0]; o->name; o++) {
+		if (o->type != FIO_OPT_STR_STORE)
 			continue;
 
-		ptr = (void *) o + opt->off1;
-		if (!*ptr)
-			ptr = td_var(o, opt->off1);
+		ptr = td_var(data, o->off1);
 		if (*ptr)
 			*ptr = strdup(*ptr);
 	}
 }
 
+/*
+ * dupe FIO_OPT_STR_STORE options
+ */
+void fio_options_mem_dupe(struct thread_data *td)
+{
+	options_mem_dupe(&td->o, options);
+
+	if (td->eo && td->io_ops) {
+		void *oldeo = td->eo;
+
+		td->eo = malloc(td->io_ops->option_struct_size);
+		memcpy(td->eo, oldeo, td->io_ops->option_struct_size);
+		options_mem_dupe(td->eo, td->io_ops->options);
+	}
+}
+
 unsigned int fio_get_kb_base(void *data)
 {
 	struct thread_data *td = data;
@@ -2528,4 +2578,9 @@ void del_opt_posval(const char *optname, const char *ival)
 void fio_options_free(struct thread_data *td)
 {
 	options_free(options, td);
+	if (td->eo && td->io_ops && td->io_ops->options) {
+		options_free(td->io_ops->options, td->eo);
+		free(td->eo);
+		td->eo = NULL;
+	}
 }
diff --git a/os/windows/install.wxs b/os/windows/install.wxs
index eecef14..f89ad2e 100755
--- a/os/windows/install.wxs
+++ b/os/windows/install.wxs
@@ -3,7 +3,7 @@
 
 <?define VersionMajor = 1?>
 <?define VersionMinor = 99?>
-<?define VersionBuild = 10?>
+<?define VersionBuild = 12?>
 
 	<Product Id="*"
 	  Codepage="1252" Language="1033"
diff --git a/os/windows/version.h b/os/windows/version.h
index f7624a4..50bf54a 100644
--- a/os/windows/version.h
+++ b/os/windows/version.h
@@ -3,4 +3,4 @@
 #define FIO_VERSION_MAJOR FIO_MAJOR
 #define FIO_VERSION_MINOR FIO_MINOR
 #define FIO_VERSION_BUILD FIO_PATCH
-#define FIO_VERSION_STRING "1.99.10"
+#define FIO_VERSION_STRING "1.99.12"
diff --git a/parse.c b/parse.c
index 7f7851c..9a65e31 100644
--- a/parse.c
+++ b/parse.c
@@ -503,7 +503,7 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
 
 		posval_sort(o, posval);
 
-		if (!o->posval[0].ival) {
+		if ((o->roff1 || o->off1) && !o->posval[0].ival) {
 			vp = NULL;
 			goto match;
 		}
@@ -728,7 +728,7 @@ static int handle_option(struct fio_option *o, const char *__ptr, void *data)
 	return ret;
 }
 
-static struct fio_option *get_option(const char *opt,
+static struct fio_option *get_option(char *opt,
 				     struct fio_option *options, char **post)
 {
 	struct fio_option *o;
@@ -738,7 +738,7 @@ static struct fio_option *get_option(const char *opt,
 	if (ret) {
 		*post = ret;
 		*ret = '\0';
-		ret = (char *) opt;
+		ret = opt;
 		(*post)++;
 		strip_blank_end(ret);
 		o = find_option(options, ret);
@@ -752,24 +752,27 @@ static struct fio_option *get_option(const char *opt,
 
 static int opt_cmp(const void *p1, const void *p2)
 {
-	struct fio_option *o1, *o2;
-	char *s1, *s2, *foo;
+	struct fio_option *o;
+	char *s, *foo;
 	int prio1, prio2;
 
-	s1 = strdup(*((char **) p1));
-	s2 = strdup(*((char **) p2));
-
-	o1 = get_option(s1, fio_options, &foo);
-	o2 = get_option(s2, fio_options, &foo);
-
 	prio1 = prio2 = 0;
-	if (o1)
-		prio1 = o1->prio;
-	if (o2)
-		prio2 = o2->prio;
 
-	free(s1);
-	free(s2);
+	if (*(char **)p1) {
+		s = strdup(*((char **) p1));
+		o = get_option(s, fio_options, &foo);
+		if (o)
+			prio1 = o->prio;
+		free(s);
+	}
+	if (*(char **)p2) {
+		s = strdup(*((char **) p2));
+		o = get_option(s, fio_options, &foo);
+		if (o)
+			prio2 = o->prio;
+		free(s);
+	}
+
 	return prio2 - prio1;
 }
 
@@ -798,24 +801,29 @@ int parse_cmd_option(const char *opt, const char *val,
 	return 1;
 }
 
-int parse_option(const char *opt, const char *input,
-		 struct fio_option *options, void *data)
+int parse_option(char *opt, const char *input,
+		 struct fio_option *options, struct fio_option **o, void *data)
 {
-	struct fio_option *o;
 	char *post;
 
 	if (!opt) {
 		log_err("fio: failed parsing %s\n", input);
+		*o = NULL;
 		return 1;
 	}
 
-	o = get_option(opt, options, &post);
-	if (!o) {
-		log_err("Bad option <%s>\n", input);
+	*o = get_option(opt, options, &post);
+	if (!*o) {
+		if (post) {
+			int len = strlen(opt);
+			if (opt + len + 1 != post)
+				memmove(opt + len + 1, post, strlen(post));
+			opt[len] = '=';
+		}
 		return 1;
 	}
 
-	if (!handle_option(o, post, data)) {
+	if (!handle_option(*o, post, data)) {
 		return 0;
 	}
 
@@ -1053,7 +1061,7 @@ void options_free(struct fio_option *options, void *data)
 	dprint(FD_PARSE, "free options\n");
 
 	for (o = &options[0]; o->name; o++) {
-		if (o->type != FIO_OPT_STR_STORE)
+		if (o->type != FIO_OPT_STR_STORE || !o->off1)
 			continue;
 
 		ptr = td_var(data, o->off1);
diff --git a/parse.h b/parse.h
index 091923e..4edf75e 100644
--- a/parse.h
+++ b/parse.h
@@ -65,7 +65,7 @@ struct fio_option {
 
 typedef int (str_cb_fn)(void *, char *);
 
-extern int parse_option(const char *, const char *, struct fio_option *, void *);
+extern int parse_option(char *, const char *, struct fio_option *, struct fio_option **, void *);
 extern void sort_options(char **, struct fio_option *, int);
 extern int parse_cmd_option(const char *t, const char *l, struct fio_option *, void *);
 extern int show_cmd_help(struct fio_option *, const char *);
--
To unsubscribe from this list: send the line "unsubscribe fio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel]     [Linux SCSI]     [Linux IDE]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux SCSI]

  Powered by Linux