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