[PATCH] Simple support for networked IO
Adds a new ioengine, net. Use with ioengine=net, it supports only strict
reading or writing (no mixed reads/writes) to/from a single host.
The filename given must contain the host and port to connect to (or
listen from).
Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/HOWTO b/HOWTO
index f4ebac9..38f5dce 100644
--- a/HOWTO
+++ b/HOWTO
@@ -203,7 +203,9 @@
filename=str Fio normally makes up a filename 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 filename for each of them to override the default.
+ a filename for each of them to override the default. If
+ the ioengine used is 'net', the filename is the host and
+ port to connect to in the format of =host:port.
rw=str Type of io pattern. Accepted values are:
@@ -278,6 +280,12 @@
to. This is mainly used to exercise fio
itself and for debugging/testing purposes.
+ net Transfer over the network to given host:port.
+ 'filename' must be set appropriately to
+ filename=host:port regardless of send
+ or receive, if the latter only the port
+ argument is used.
+
iodepth=int This defines how many io units to keep in flight against
the file. The default is 1 for each file defined in this
job, can be overridden with a larger value for higher
diff --git a/Makefile b/Makefile
index 3b919d5..fe9a8cd 100644
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,7 @@
OBJS += engines/splice.o
OBJS += engines/sync.o
OBJS += engines/null.o
+OBJS += engines/net.o
INSTALL = install
prefix = /usr/local
diff --git a/Makefile.FreeBSD b/Makefile.FreeBSD
index b862820..5c85a0c 100644
--- a/Makefile.FreeBSD
+++ b/Makefile.FreeBSD
@@ -10,6 +10,7 @@
OBJS += engines/posixaio.o
OBJS += engines/sync.o
OBJS += engines/null.o
+OBJS += engines/net.o
all: depend $(PROGS) $(SCRIPTS)
diff --git a/Makefile.solaris b/Makefile.solaris
index 8d8bec3..4373abf 100644
--- a/Makefile.solaris
+++ b/Makefile.solaris
@@ -10,6 +10,7 @@
OBJS += engines/posixaio.o
OBJS += engines/sync.o
OBJS += engines/null.o
+OBJS += engines/net.o
all: depend $(PROGS) $(SCRIPTS)
diff --git a/README b/README
index be32af4..f7d7457 100644
--- a/README
+++ b/README
@@ -101,11 +101,11 @@
ioengine=x 'x' may be: aio/libaio/linuxaio for Linux aio,
posixaio for POSIX aio, sync for regular read/write io,
mmap for mmap'ed io, splice for using splice/vmsplice,
- or sgio for direct SG_IO io. The latter only works on
- Linux on SCSI (or SCSI-like devices, such as
- usb-storage or sata/libata driven) devices. Fio also
- has a null io engine, which is mainly used for testing
- fio itself.
+ sgio for direct SG_IO io, or net for network io. sgio
+ only works on Linux on SCSI (or SCSI-like devices,
+ such as usb-storage or sata/libata driven) devices.
+ Fio also has a null io engine, which is mainly used
+ for testing fio itself.
iodepth=x For async io, allow 'x' ios in flight
overwrite=x If 'x', layout a write file first.
nrfiles=x Spread io load over 'x' number of files per job,
diff --git a/engines/net.c b/engines/net.c
new file mode 100644
index 0000000..8ef7811
--- /dev/null
+++ b/engines/net.c
@@ -0,0 +1,273 @@
+/*
+ * Transfer data over the net. Pretty basic setup, will only support
+ * 1 file per thread/job.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "../fio.h"
+#include "../os.h"
+
+struct net_data {
+ int send_to_net;
+ struct io_u *last_io_u;
+};
+
+static int fio_netio_getevents(struct thread_data *td, int fio_unused min,
+ int max, struct timespec fio_unused *t)
+{
+ assert(max <= 1);
+
+ /*
+ * we can only have one finished io_u for sync io, since the depth
+ * is always 1
+ */
+ if (list_empty(&td->io_u_busylist))
+ return 0;
+
+ return 1;
+}
+
+static struct io_u *fio_netio_event(struct thread_data *td, int event)
+{
+ struct net_data *nd = td->io_ops->data;
+
+ assert(event == 0);
+
+ return nd->last_io_u;
+}
+
+static int fio_netio_prep(struct thread_data *td, struct io_u *io_u)
+{
+ struct net_data *nd = td->io_ops->data;
+ struct fio_file *f = io_u->file;
+
+ if (nd->send_to_net) {
+ if (io_u->ddir == DDIR_READ) {
+ td_verror(td, EINVAL);
+ return 1;
+ }
+ } else {
+ if (io_u->ddir == DDIR_WRITE) {
+ td_verror(td, EINVAL);
+ return 1;
+ }
+ }
+
+ if (io_u->ddir == DDIR_SYNC)
+ return 0;
+ if (io_u->offset == f->last_completed_pos)
+ return 0;
+
+ td_verror(td, EINVAL);
+ return 1;
+}
+
+static int fio_netio_queue(struct thread_data *td, struct io_u *io_u)
+{
+ struct net_data *nd = td->io_ops->data;
+ struct fio_file *f = io_u->file;
+ unsigned int ret = 0;
+
+ if (io_u->ddir == DDIR_WRITE)
+ ret = write(f->fd, io_u->buf, io_u->buflen);
+ else if (io_u->ddir == DDIR_READ)
+ ret = read(f->fd, io_u->buf, io_u->buflen);
+
+ if (ret != io_u->buflen) {
+ if (ret > 0) {
+ io_u->resid = io_u->buflen - ret;
+ io_u->error = EIO;
+ } else
+ io_u->error = errno;
+ }
+
+ if (!io_u->error)
+ nd->last_io_u = io_u;
+
+ return io_u->error;
+}
+
+static int fio_netio_setup_connect(struct thread_data *td, const char *host,
+ const char *port)
+{
+ struct sockaddr_in addr;
+ struct fio_file *f;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(atoi(port));
+
+ if (inet_aton(host, &addr.sin_addr) != 1) {
+ struct hostent *hent = gethostbyname(host);
+
+ if (!hent) {
+ td_vmsg(td, errno, "gethostbyname");
+ return 1;
+ }
+
+ memcpy(&addr.sin_addr, hent->h_addr, 4);
+ }
+
+ f = &td->files[0];
+
+ f->fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (f->fd < 0) {
+ td_vmsg(td, errno, "socket");
+ return 1;
+ }
+
+ if (connect(f->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ td_vmsg(td, errno, "connect");
+ return 1;
+ }
+
+ return 0;
+
+}
+
+static int fio_netio_setup_listen(struct thread_data *td, const char *port)
+{
+ struct sockaddr_in addr;
+ socklen_t socklen;
+ int fd, opt;
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ td_vmsg(td, errno, "socket");
+ return 1;
+ }
+
+ opt = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+ td_vmsg(td, errno, "setsockopt");
+ return 1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(atoi(port));
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ td_vmsg(td, errno, "bind");
+ return 1;
+ }
+ if (listen(fd, 1) < 0) {
+ td_vmsg(td, errno, "listen");
+ return 1;
+ }
+
+ socklen = sizeof(addr);
+ td->files[0].fd = accept(fd, (struct sockaddr *) &addr, &socklen);
+ if (td->files[0].fd < 0) {
+ td_vmsg(td, errno, "accept");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int fio_netio_setup(struct thread_data *td)
+{
+ char host[64], port[64], buf[128];
+ struct net_data *nd;
+ char *sep;
+ int ret;
+
+ /*
+ * work around for late init call
+ */
+ if (td->io_ops->init(td))
+ return 1;
+
+ nd = td->io_ops->data;
+
+ if (td->iomix) {
+ log_err("fio: network connections must be read OR write\n");
+ return 1;
+ }
+ if (td->nr_files > 1) {
+ log_err("fio: only one file supported for network\n");
+ return 1;
+ }
+
+ strcpy(buf, td->filename);
+
+ sep = strchr(buf, ':');
+ if (!sep) {
+ log_err("fio: bad network host:port <<%s>>\n", td->filename);
+ return 1;
+ }
+
+ *sep = '\0';
+ sep++;
+ strcpy(host, buf);
+ strcpy(port, sep);
+
+ if (td->ddir == READ) {
+ nd->send_to_net = 0;
+ ret = fio_netio_setup_listen(td, port);
+ } else {
+ nd->send_to_net = 1;
+ ret = fio_netio_setup_connect(td, host, port);
+ }
+
+ if (!ret) {
+ td->io_size = td->total_file_size;
+ td->total_io_size = td->io_size;
+ td->files[0].real_file_size = td->io_size;
+ }
+
+ return ret;
+}
+
+static void fio_netio_cleanup(struct thread_data *td)
+{
+ if (td->io_ops->data) {
+ free(td->io_ops->data);
+ td->io_ops->data = NULL;
+ }
+}
+
+static int fio_netio_init(struct thread_data *td)
+{
+ struct net_data *nd;
+
+ if (td->io_ops->data)
+ return 0;
+
+ nd = malloc(sizeof(*nd));
+ nd->last_io_u = NULL;
+ td->io_ops->data = nd;
+ return 0;
+}
+
+static struct ioengine_ops ioengine = {
+ .name = "net",
+ .version = FIO_IOOPS_VERSION,
+ .init = fio_netio_init,
+ .prep = fio_netio_prep,
+ .queue = fio_netio_queue,
+ .getevents = fio_netio_getevents,
+ .event = fio_netio_event,
+ .cleanup = fio_netio_cleanup,
+ .setup = fio_netio_setup,
+ .flags = FIO_SYNCIO | FIO_NETIO,
+};
+
+static void fio_init fio_netio_register(void)
+{
+ register_ioengine(&ioengine);
+}
+
+static void fio_exit fio_netio_unregister(void)
+{
+ unregister_ioengine(&ioengine);
+}
diff --git a/filesetup.c b/filesetup.c
index 2d8193e..610981b 100644
--- a/filesetup.c
+++ b/filesetup.c
@@ -330,6 +330,8 @@
{
int flags = 0;
+ if (td->io_ops->flags & FIO_NETIO)
+ return 0;
if (td->odirect)
flags |= OS_O_DIRECT;
if (td->sync_io)
diff --git a/fio.c b/fio.c
index cc54c83..bce13a7 100644
--- a/fio.c
+++ b/fio.c
@@ -841,7 +841,6 @@
signal(SIGINT, sig_handler);
signal(SIGALRM, sig_handler);
- signal(SIGSEGV, sig_handler);
todo = thread_number;
nr_running = 0;
diff --git a/fio.h b/fio.h
index c6fb6a1..d635e99 100644
--- a/fio.h
+++ b/fio.h
@@ -143,6 +143,7 @@
FIO_CPUIO = 1 << 1,
FIO_MMAPIO = 1 << 2,
FIO_RAWIO = 1 << 3,
+ FIO_NETIO = 1 << 4,
};
struct fio_file {
diff --git a/init.c b/init.c
index 32e22db..7f2747a 100644
--- a/init.c
+++ b/init.c
@@ -81,7 +81,7 @@
.help = "IO engine to use",
.def = "sync",
.posval = { "sync", "libaio", "posixaio", "mmap", "splice",
- "sg", "null", },
+ "sg", "null", "net", },
},
{
.name = "iodepth",
diff --git a/parse.c b/parse.c
index 29e2ff1..cab6ca7 100644
--- a/parse.c
+++ b/parse.c
@@ -312,7 +312,7 @@
* Do this before parsing the first round, to check if we should
* copy set 1 options to set 2.
*/
- if (ptr) {
+ if (ptr && (o->type != FIO_OPT_STR_STORE)) {
ptr2 = strchr(ptr, ',');
if (!ptr2)
ptr2 = strchr(ptr, ':');
diff --git a/stat.c b/stat.c
index 8ea1b8a..a98539f 100644
--- a/stat.c
+++ b/stat.c
@@ -205,7 +205,7 @@
dev_t dev;
char *p;
- if (!td->do_disk_util)
+ if (!td->do_disk_util || (td->io_ops->flags & FIO_NETIO))
return;
/*