blob: 45f6fb6501a216b78dbfdae34f7ef3807aa77669 [file] [log] [blame]
/*
* pmemblk: IO engine that uses PMDK libpmemblk to read and write data
*
* Copyright (C) 2016 Hewlett Packard Enterprise Development LP
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License,
* version 2 as published by the Free Software Foundation..
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/*
* pmemblk engine
*
* IO engine that uses libpmemblk to read and write data
*
* To use:
* ioengine=pmemblk
*
* Other relevant settings:
* thread=1 REQUIRED
* iodepth=1
* direct=1
* unlink=1
* filename=/mnt/pmem0/fiotestfile,BSIZE,FSIZEMiB
*
* thread must be set to 1 for pmemblk as multiple processes cannot
* open the same block pool file.
*
* iodepth should be set to 1 as pmemblk is always synchronous.
* Use numjobs to scale up.
*
* direct=1 is implied as pmemblk is always direct. A warning message
* is printed if this is not specified.
*
* unlink=1 removes the block pool file after testing, and is optional.
*
* The pmem device must have a DAX-capable filesystem and be mounted
* with DAX enabled. filename must point to a file on that filesystem.
*
* Example:
* mkfs.xfs /dev/pmem0
* mkdir /mnt/pmem0
* mount -o dax /dev/pmem0 /mnt/pmem0
*
* When specifying the filename, if the block pool file does not already
* exist, then the pmemblk engine creates the pool file if you specify
* the block and file sizes. BSIZE is the block size in bytes.
* FSIZEMB is the pool file size in MiB.
*
* See examples/pmemblk.fio for more.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/uio.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <libpmem.h>
#include <libpmemblk.h>
#include "../fio.h"
/*
* libpmemblk
*/
typedef struct fio_pmemblk_file *fio_pmemblk_file_t;
struct fio_pmemblk_file {
fio_pmemblk_file_t pmb_next;
char *pmb_filename;
uint64_t pmb_refcnt;
PMEMblkpool *pmb_pool;
size_t pmb_bsize;
size_t pmb_nblocks;
};
static fio_pmemblk_file_t Cache;
static pthread_mutex_t CacheLock = PTHREAD_MUTEX_INITIALIZER;
#define PMB_CREATE (0x0001) /* should create file */
fio_pmemblk_file_t fio_pmemblk_cache_lookup(const char *filename)
{
fio_pmemblk_file_t i;
for (i = Cache; i != NULL; i = i->pmb_next)
if (!strcmp(filename, i->pmb_filename))
return i;
return NULL;
}
static void fio_pmemblk_cache_insert(fio_pmemblk_file_t pmb)
{
pmb->pmb_next = Cache;
Cache = pmb;
}
static void fio_pmemblk_cache_remove(fio_pmemblk_file_t pmb)
{
fio_pmemblk_file_t i;
if (pmb == Cache) {
Cache = Cache->pmb_next;
pmb->pmb_next = NULL;
return;
}
for (i = Cache; i != NULL; i = i->pmb_next)
if (pmb == i->pmb_next) {
i->pmb_next = i->pmb_next->pmb_next;
pmb->pmb_next = NULL;
return;
}
}
/*
* to control block size and gross file size at the libpmemblk
* level, we allow the block size and file size to be appended
* to the file name:
*
* path[,bsize,fsizemib]
*
* note that we do not use the fio option "filesize" to dictate
* the file size because we can only give libpmemblk the gross
* file size, which is different from the net or usable file
* size (which is probably what fio wants).
*
* the final path without the parameters is returned in ppath.
* the block size and file size are returned in pbsize and fsize.
*
* note that the user specifies the file size in MiB, but
* we return bytes from here.
*/
static void pmb_parse_path(const char *pathspec, char **ppath, uint64_t *pbsize,
uint64_t *pfsize)
{
char *path;
char *s;
uint64_t bsize;
uint64_t fsizemib;
path = strdup(pathspec);
if (!path) {
*ppath = NULL;
return;
}
/* extract sizes, if given */
s = strrchr(path, ',');
if (s && (fsizemib = strtoull(s + 1, NULL, 10))) {
*s = 0;
s = strrchr(path, ',');
if (s && (bsize = strtoull(s + 1, NULL, 10))) {
*s = 0;
*ppath = path;
*pbsize = bsize;
*pfsize = fsizemib << 20;
return;
}
}
/* size specs not found */
strcpy(path, pathspec);
*ppath = path;
*pbsize = 0;
*pfsize = 0;
}
static fio_pmemblk_file_t pmb_open(const char *pathspec, int flags)
{
fio_pmemblk_file_t pmb;
char *path = NULL;
uint64_t bsize = 0;
uint64_t fsize = 0;
pmb_parse_path(pathspec, &path, &bsize, &fsize);
if (!path)
return NULL;
pthread_mutex_lock(&CacheLock);
pmb = fio_pmemblk_cache_lookup(path);
if (!pmb) {
pmb = malloc(sizeof(*pmb));
if (!pmb)
goto error;
/* try opening existing first, create it if needed */
pmb->pmb_pool = pmemblk_open(path, bsize);
if (!pmb->pmb_pool && (errno == ENOENT) &&
(flags & PMB_CREATE) && (0 < fsize) && (0 < bsize)) {
pmb->pmb_pool =
pmemblk_create(path, bsize, fsize, 0644);
}
if (!pmb->pmb_pool) {
log_err("pmemblk: unable to open pmemblk pool file %s (%s)\n",
path, strerror(errno));
goto error;
}
pmb->pmb_filename = path;
pmb->pmb_next = NULL;
pmb->pmb_refcnt = 0;
pmb->pmb_bsize = pmemblk_bsize(pmb->pmb_pool);
pmb->pmb_nblocks = pmemblk_nblock(pmb->pmb_pool);
fio_pmemblk_cache_insert(pmb);
}
pmb->pmb_refcnt += 1;
pthread_mutex_unlock(&CacheLock);
return pmb;
error:
if (pmb) {
if (pmb->pmb_pool)
pmemblk_close(pmb->pmb_pool);
pmb->pmb_pool = NULL;
pmb->pmb_filename = NULL;
free(pmb);
}
if (path)
free(path);
pthread_mutex_unlock(&CacheLock);
return NULL;
}
static void pmb_close(fio_pmemblk_file_t pmb, const bool keep)
{
pthread_mutex_lock(&CacheLock);
pmb->pmb_refcnt--;
if (!keep && !pmb->pmb_refcnt) {
pmemblk_close(pmb->pmb_pool);
pmb->pmb_pool = NULL;
free(pmb->pmb_filename);
pmb->pmb_filename = NULL;
fio_pmemblk_cache_remove(pmb);
free(pmb);
}
pthread_mutex_unlock(&CacheLock);
}
static int pmb_get_flags(struct thread_data *td, uint64_t *pflags)
{
static int thread_warned = 0;
static int odirect_warned = 0;
uint64_t flags = 0;
if (!td->o.use_thread) {
if (!thread_warned) {
thread_warned = 1;
log_err("pmemblk: must set thread=1 for pmemblk engine\n");
}
return 1;
}
if (!td->o.odirect && !odirect_warned) {
odirect_warned = 1;
log_info("pmemblk: direct == 0, but pmemblk is always direct\n");
}
if (td->o.allow_create)
flags |= PMB_CREATE;
(*pflags) = flags;
return 0;
}
static int fio_pmemblk_open_file(struct thread_data *td, struct fio_file *f)
{
uint64_t flags = 0;
fio_pmemblk_file_t pmb;
if (pmb_get_flags(td, &flags))
return 1;
pmb = pmb_open(f->file_name, flags);
if (!pmb)
return 1;
FILE_SET_ENG_DATA(f, pmb);
return 0;
}
static int fio_pmemblk_close_file(struct thread_data fio_unused *td,
struct fio_file *f)
{
fio_pmemblk_file_t pmb = FILE_ENG_DATA(f);
if (pmb)
pmb_close(pmb, false);
FILE_SET_ENG_DATA(f, NULL);
return 0;
}
static int fio_pmemblk_get_file_size(struct thread_data *td, struct fio_file *f)
{
uint64_t flags = 0;
fio_pmemblk_file_t pmb = FILE_ENG_DATA(f);
if (fio_file_size_known(f))
return 0;
if (!pmb) {
if (pmb_get_flags(td, &flags))
return 1;
pmb = pmb_open(f->file_name, flags);
if (!pmb)
return 1;
}
f->real_file_size = pmb->pmb_bsize * pmb->pmb_nblocks;
fio_file_set_size_known(f);
if (!FILE_ENG_DATA(f))
pmb_close(pmb, true);
return 0;
}
static enum fio_q_status fio_pmemblk_queue(struct thread_data *td,
struct io_u *io_u)
{
struct fio_file *f = io_u->file;
fio_pmemblk_file_t pmb = FILE_ENG_DATA(f);
unsigned long long off;
unsigned long len;
void *buf;
fio_ro_check(td, io_u);
switch (io_u->ddir) {
case DDIR_READ:
case DDIR_WRITE:
off = io_u->offset;
len = io_u->xfer_buflen;
io_u->error = EINVAL;
if (off % pmb->pmb_bsize)
break;
if (len % pmb->pmb_bsize)
break;
if ((off + len) / pmb->pmb_bsize > pmb->pmb_nblocks)
break;
io_u->error = 0;
buf = io_u->xfer_buf;
off /= pmb->pmb_bsize;
len /= pmb->pmb_bsize;
while (0 < len) {
if (io_u->ddir == DDIR_READ &&
0 != pmemblk_read(pmb->pmb_pool, buf, off)) {
io_u->error = errno;
break;
} else if (0 != pmemblk_write(pmb->pmb_pool, buf, off)) {
io_u->error = errno;
break;
}
buf += pmb->pmb_bsize;
off++;
len--;
}
off *= pmb->pmb_bsize;
len *= pmb->pmb_bsize;
io_u->resid = io_u->xfer_buflen - (off - io_u->offset);
break;
case DDIR_SYNC:
case DDIR_DATASYNC:
case DDIR_SYNC_FILE_RANGE:
/* we're always sync'd */
io_u->error = 0;
break;
default:
io_u->error = EINVAL;
break;
}
return FIO_Q_COMPLETED;
}
static int fio_pmemblk_unlink_file(struct thread_data *td, struct fio_file *f)
{
char *path = NULL;
uint64_t bsize = 0;
uint64_t fsize = 0;
/*
* we need our own unlink in case the user has specified
* the block and file sizes in the path name. we parse
* the file_name to determine the file name we actually used.
*/
pmb_parse_path(f->file_name, &path, &bsize, &fsize);
if (!path)
return ENOENT;
unlink(path);
free(path);
return 0;
}
static struct ioengine_ops ioengine = {
.name = "pmemblk",
.version = FIO_IOOPS_VERSION,
.queue = fio_pmemblk_queue,
.open_file = fio_pmemblk_open_file,
.close_file = fio_pmemblk_close_file,
.get_file_size = fio_pmemblk_get_file_size,
.unlink_file = fio_pmemblk_unlink_file,
.flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOEXTEND | FIO_NODISKUTIL,
};
static void fio_init fio_pmemblk_register(void)
{
register_ioengine(&ioengine);
}
static void fio_exit fio_pmemblk_unregister(void)
{
unregister_ioengine(&ioengine);
}