218 lines
4.8 KiB
C
218 lines
4.8 KiB
C
/*
|
|
* ioe_e4defrag: ioengine for git://git.kernel.dk/fio.git
|
|
*
|
|
* IO engine that does regular EXT4_IOC_MOVE_EXT ioctls to simulate
|
|
* defragment activity
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/uio.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "../fio.h"
|
|
#include "../optgroup.h"
|
|
|
|
#ifndef EXT4_IOC_MOVE_EXT
|
|
#define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent)
|
|
struct move_extent {
|
|
__u32 reserved; /* should be zero */
|
|
__u32 donor_fd; /* donor file descriptor */
|
|
__u64 orig_start; /* logical start offset in block for orig */
|
|
__u64 donor_start; /* logical start offset in block for donor */
|
|
__u64 len; /* block length to be moved */
|
|
__u64 moved_len; /* moved block length */
|
|
};
|
|
#endif
|
|
|
|
struct e4defrag_data {
|
|
int donor_fd;
|
|
int bsz;
|
|
};
|
|
|
|
struct e4defrag_options {
|
|
void *pad;
|
|
unsigned int inplace;
|
|
char * donor_name;
|
|
};
|
|
|
|
static struct fio_option options[] = {
|
|
{
|
|
.name = "donorname",
|
|
.lname = "Donor Name",
|
|
.type = FIO_OPT_STR_STORE,
|
|
.off1 = offsetof(struct e4defrag_options, donor_name),
|
|
.help = "File used as a block donor",
|
|
.category = FIO_OPT_C_ENGINE,
|
|
.group = FIO_OPT_G_E4DEFRAG,
|
|
},
|
|
{
|
|
.name = "inplace",
|
|
.lname = "In Place",
|
|
.type = FIO_OPT_INT,
|
|
.off1 = offsetof(struct e4defrag_options, inplace),
|
|
.minval = 0,
|
|
.maxval = 1,
|
|
.help = "Alloc and free space inside defrag event",
|
|
.category = FIO_OPT_C_ENGINE,
|
|
.group = FIO_OPT_G_E4DEFRAG,
|
|
},
|
|
{
|
|
.name = NULL,
|
|
},
|
|
};
|
|
|
|
static int fio_e4defrag_init(struct thread_data *td)
|
|
{
|
|
int r, len = 0;
|
|
struct e4defrag_options *o = td->eo;
|
|
struct e4defrag_data *ed;
|
|
struct stat stub;
|
|
char donor_name[PATH_MAX];
|
|
|
|
if (!strlen(o->donor_name)) {
|
|
log_err("'donorname' options required\n");
|
|
return 1;
|
|
}
|
|
|
|
ed = malloc(sizeof(*ed));
|
|
if (!ed) {
|
|
td_verror(td, ENOMEM, "io_queue_init");
|
|
return 1;
|
|
}
|
|
memset(ed, 0 ,sizeof(*ed));
|
|
|
|
if (td->o.directory)
|
|
len = sprintf(donor_name, "%s/", td->o.directory);
|
|
sprintf(donor_name + len, "%s", o->donor_name);
|
|
|
|
ed->donor_fd = open(donor_name, O_CREAT|O_WRONLY, 0644);
|
|
if (ed->donor_fd < 0) {
|
|
td_verror(td, errno, "io_queue_init");
|
|
log_err("Can't open donor file %s err:%d\n", donor_name, ed->donor_fd);
|
|
free(ed);
|
|
return 1;
|
|
}
|
|
|
|
if (!o->inplace) {
|
|
long long __len = td->o.file_size_high - td->o.start_offset;
|
|
r = fallocate(ed->donor_fd, 0, td->o.start_offset, __len);
|
|
if (r)
|
|
goto err;
|
|
}
|
|
r = fstat(ed->donor_fd, &stub);
|
|
if (r)
|
|
goto err;
|
|
|
|
ed->bsz = stub.st_blksize;
|
|
td->io_ops_data = ed;
|
|
return 0;
|
|
err:
|
|
td_verror(td, errno, "io_queue_init");
|
|
close(ed->donor_fd);
|
|
free(ed);
|
|
return 1;
|
|
}
|
|
|
|
static void fio_e4defrag_cleanup(struct thread_data *td)
|
|
{
|
|
struct e4defrag_data *ed = td->io_ops_data;
|
|
if (ed) {
|
|
if (ed->donor_fd >= 0)
|
|
close(ed->donor_fd);
|
|
free(ed);
|
|
}
|
|
}
|
|
|
|
|
|
static int fio_e4defrag_queue(struct thread_data *td, struct io_u *io_u)
|
|
{
|
|
|
|
int ret;
|
|
unsigned long long len;
|
|
struct move_extent me;
|
|
struct fio_file *f = io_u->file;
|
|
struct e4defrag_data *ed = td->io_ops_data;
|
|
struct e4defrag_options *o = td->eo;
|
|
|
|
fio_ro_check(td, io_u);
|
|
|
|
/* Theoretically defragmentation should not change data, but it
|
|
* changes data layout. So this function handle only DDIR_WRITE
|
|
* in order to satisfy strict read only access pattern
|
|
*/
|
|
if (io_u->ddir != DDIR_WRITE) {
|
|
io_u->error = EINVAL;
|
|
return FIO_Q_COMPLETED;
|
|
}
|
|
|
|
if (o->inplace) {
|
|
ret = fallocate(ed->donor_fd, 0, io_u->offset, io_u->xfer_buflen);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
memset(&me, 0, sizeof(me));
|
|
me.donor_fd = ed->donor_fd;
|
|
me.orig_start = io_u->offset / ed->bsz;
|
|
me.donor_start = me.orig_start;
|
|
len = (io_u->offset + io_u->xfer_buflen + ed->bsz -1);
|
|
me.len = len / ed->bsz - me.orig_start;
|
|
|
|
ret = ioctl(f->fd, EXT4_IOC_MOVE_EXT, &me);
|
|
len = me.moved_len * ed->bsz;
|
|
|
|
if (len > io_u->xfer_buflen)
|
|
len = io_u->xfer_buflen;
|
|
|
|
if (len != io_u->xfer_buflen) {
|
|
if (len) {
|
|
io_u->resid = io_u->xfer_buflen - len;
|
|
io_u->error = 0;
|
|
} else {
|
|
/* access beyond i_size */
|
|
io_u->error = EINVAL;
|
|
}
|
|
}
|
|
if (ret)
|
|
io_u->error = errno;
|
|
|
|
if (o->inplace)
|
|
ret = ftruncate(ed->donor_fd, 0);
|
|
out:
|
|
if (ret && !io_u->error)
|
|
io_u->error = errno;
|
|
|
|
return FIO_Q_COMPLETED;
|
|
}
|
|
|
|
static struct ioengine_ops ioengine = {
|
|
.name = "e4defrag",
|
|
.version = FIO_IOOPS_VERSION,
|
|
.init = fio_e4defrag_init,
|
|
.queue = fio_e4defrag_queue,
|
|
.open_file = generic_open_file,
|
|
.close_file = generic_close_file,
|
|
.get_file_size = generic_get_file_size,
|
|
.flags = FIO_SYNCIO,
|
|
.cleanup = fio_e4defrag_cleanup,
|
|
.options = options,
|
|
.option_struct_size = sizeof(struct e4defrag_options),
|
|
|
|
};
|
|
|
|
static void fio_init fio_syncio_register(void)
|
|
{
|
|
register_ioengine(&ioengine);
|
|
}
|
|
|
|
static void fio_exit fio_syncio_unregister(void)
|
|
{
|
|
unregister_ioengine(&ioengine);
|
|
}
|