You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
9.7 KiB
445 lines
9.7 KiB
/* |
|
* pmemblk: IO engine that uses NVML 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., 59 Temple Place, Suite 330, |
|
* Boston, MA 02111-1307 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 int 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); |
|
}
|
|
|