/*
* Copyright 2015-2018, Intel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* pmemobj_gen.cpp -- benchmark for pmemobj_direct()
* and pmemobj_open() functions.
*/
#include <cassert>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <file.h>
#include <sys/stat.h>
#include <unistd.h>
#include "benchmark.hpp"
#include "libpmemobj.h"
#define LAYOUT_NAME "benchmark"
#define FACTOR 4
#define DIR_MODE 0700
#define FILE_MODE 0666
#define PART_NAME "/part"
#define MAX_DIGITS 2
struct pobj_bench;
struct pobj_worker;
typedef size_t (*fn_type_num_t)(struct pobj_bench *ob, size_t worker_idx,
size_t op_idx);
typedef size_t (*fn_size_t)(struct pobj_bench *ob, size_t idx);
typedef size_t (*fn_num_t)(size_t idx);
/*
* Enumeration used to determine the mode of the assigning type_number
* value to the persistent objects.
*/
enum type_mode {
TYPE_MODE_ONE,
TYPE_MODE_PER_THREAD,
TYPE_MODE_RAND,
MAX_TYPE_MODE,
};
/*
* pobj_args - Stores command line parsed arguments.
*
* rand_type : Use random type number for every new allocated object.
* Default, there is one type number for all objects.
*
* range : Use random allocation size.
*
* min_size : Minimum allocation size.
*
* n_objs : Number of objects allocated per thread
*
* one_pool : Use one common pool for all thread
*
* one_obj : Create and use one object per thread
*
* obj_size : Size of each allocated object
*
* n_ops : Number of operations
*/
struct pobj_args {
char *type_num;
bool range;
unsigned min_size;
size_t n_objs;
bool one_pool;
bool one_obj;
size_t obj_size;
size_t n_ops;
};
/*
* pobj_bench - Stores variables used in benchmark, passed within functions.
*
* pop : Pointer to the persistent pool.
*
* pa : Stores pobj_args structure.
*
* sets : Stores files names using to create pool per thread
*
* random_types : Random type numbers for persistent objects.
*
* rand_sizes : random values with allocation sizes.
*
* n_pools : Number of created pools.
*
* n_objs : Number of object created per thread.
*
* type_mode : Type_mode enum value
*
* fn_type_num : Function returning proper type number for each object.
*
* fn_size : Function returning proper size of allocation.
*
* pool : Functions returning number of thread if
* one pool per thread created or index 0 if not.
*
* obj : Function returning number of operation if flag set
* to false or index 0 if set to true.
*/
struct pobj_bench {
PMEMobjpool **pop;
struct pobj_args *args_priv;
const char **sets;
size_t *random_types;
size_t *rand_sizes;
size_t n_pools;
int type_mode;
fn_type_num_t fn_type_num;
fn_size_t fn_size;
fn_num_t pool;
fn_num_t obj;
};
/*
* pobj_worker - Stores variables used by one thread.
*/
struct pobj_worker {
PMEMoid *oids;
};
/*
* type_mode_one -- always returns 0, as in the mode TYPE_MODE_ONE
* all of the persistent objects have the same type_number value.
*/
static size_t
type_mode_one(struct pobj_bench *bench_priv, size_t worker_idx, size_t op_idx)
{
return 0;
}
/*
* type_mode_per_thread -- always returns worker index, as in the mode
* TYPE_MODE_PER_THREAD all persistent object allocated by the same thread
* have the same type_number value.
*/
static size_t
type_mode_per_thread(struct pobj_bench *bench_priv, size_t worker_idx,
size_t op_idx)
{
return worker_idx;
}
/*
* type_mode_rand -- returns the value from the random_types array assigned
* for the specific operation in a specific thread.
*/
static size_t
type_mode_rand(struct pobj_bench *bench_priv, size_t worker_idx, size_t op_idx)
{
return bench_priv->random_types[op_idx];
}
/*
* range_size -- returns size of object allocation from rand_sizes array.
*/
static size_t
range_size(struct pobj_bench *bench_priv, size_t idx)
{
return bench_priv->rand_sizes[idx];
}
/*
* static_size -- returns always the same size of object allocation.
*/
static size_t
static_size(struct pobj_bench *bench_priv, size_t idx)
{
return bench_priv->args_priv->obj_size;
}
/*
* diff_num -- returns given index
*/
static size_t
diff_num(size_t idx)
{
return idx;
}
/*
* one_num -- returns always the same index.
*/
static size_t
one_num(size_t idx)
{
return 0;
}
static fn_type_num_t type_mode_func[MAX_TYPE_MODE] = {
type_mode_one, type_mode_per_thread, type_mode_rand};
const char *type_mode_names[MAX_TYPE_MODE] = {"one", "per-thread", "rand"};
/*
* parse_type_mode -- parses command line "--type-number" argument
* and returns proper type_mode enum value.
*/
static enum type_mode
parse_type_mode(const char *arg)
{
enum type_mode i = TYPE_MODE_ONE;
for (; i < MAX_TYPE_MODE && strcmp(arg, type_mode_names[i]) != 0;
i = (enum type_mode)(i + 1))
;
return i;
}
/*
* rand_sizes -- allocates array and calculates random values as allocation
* sizes for each object. Used only when range flag set.
*/
static size_t *
rand_sizes(size_t min, size_t max, size_t n_ops)
{
assert(n_ops != 0);
auto *rand_sizes = (size_t *)malloc(n_ops * sizeof(size_t));
if (rand_sizes == nullptr) {
perror("malloc");
return nullptr;
}
for (size_t i = 0; i < n_ops; i++) {
rand_sizes[i] = RRAND(max, min);
}
return rand_sizes;
}
/*
* random_types -- allocates array and calculates random values to assign
* type_number for each object.
*/
static int
random_types(struct pobj_bench *bench_priv, struct benchmark_args *args)
{
assert(bench_priv->args_priv->n_objs != 0);
bench_priv->random_types = (size_t *)malloc(
bench_priv->args_priv->n_objs * sizeof(size_t));
if (bench_priv->random_types == nullptr) {
perror("malloc");
return -1;
}
for (size_t i = 0; i < bench_priv->args_priv->n_objs; i++)
bench_priv->random_types[i] = rand() % UINT32_MAX;
return 0;
}
/*
* pobj_init - common part of the benchmark initialization functions.
* Parses command line arguments, set variables and creates persistent pools.
*/
static int
pobj_init(struct benchmark *bench, struct benchmark_args *args)
{
unsigned i = 0;
size_t psize;
size_t n_objs;
assert(bench != nullptr);
assert(args != nullptr);
enum file_type type = util_file_get_type(args->fname);
if (type == OTHER_ERROR) {
fprintf(stderr, "could not check type of file %s\n",
args->fname);
return -1;
}
auto *bench_priv =
(struct pobj_bench *)malloc(sizeof(struct pobj_bench));
if (bench_priv == nullptr) {
perror("malloc");
return -1;
}
assert(args->opts != nullptr);
bench_priv->args_priv = (struct pobj_args *)args->opts;
bench_priv->args_priv->obj_size = args->dsize;
bench_priv->args_priv->range =
bench_priv->args_priv->min_size > 0 ? true : false;
bench_priv->n_pools =
!bench_priv->args_priv->one_pool ? args->n_threads : 1;
bench_priv->pool = bench_priv->n_pools > 1 ? diff_num : one_num;
bench_priv->obj = !bench_priv->args_priv->one_obj ? diff_num : one_num;
if ((args->is_poolset || type == TYPE_DEVDAX) &&
bench_priv->n_pools > 1) {
fprintf(stderr,
"cannot use poolset nor device dax for multiple pools,"
" please use -P|--one-pool option instead");
goto free_bench_priv;
}
/*
* Multiplication by FACTOR prevents from out of memory error
* as the actual size of the allocated persistent objects
* is always larger than requested.
*/
n_objs = bench_priv->args_priv->n_objs;
if (bench_priv->n_pools == 1)
n_objs *= args->n_threads;
psize = PMEMOBJ_MIN_POOL +
n_objs * args->dsize * args->n_threads * FACTOR;
/* assign type_number determining function */
bench_priv->type_mode =
parse_type_mode(bench_priv->args_priv->type_num);
switch (bench_priv->type_mode) {
case MAX_TYPE_MODE:
fprintf(stderr, "unknown type mode");
goto free_bench_priv;
case TYPE_MODE_RAND:
if (random_types(bench_priv, args))
goto free_bench_priv;
break;
default:
bench_priv->random_types = nullptr;
}
bench_priv->fn_type_num = type_mode_func[bench_priv->type_mode];
/* assign size determining function */
bench_priv->fn_size =
bench_priv->args_priv->range ? range_size : static_size;
bench_priv->rand_sizes = nullptr;
if (bench_priv->args_priv->range) {
if (bench_priv->args_priv->min_size > args->dsize) {
fprintf(stderr, "Invalid allocation size");
goto free_random_types;
}
bench_priv->rand_sizes =
rand_sizes(bench_priv->args_priv->min_size,
bench_priv->args_priv->obj_size,
bench_priv->args_priv->n_objs);
if (bench_priv->rand_sizes == nullptr)
goto free_random_types;
}
assert(bench_priv->n_pools > 0);
bench_priv->pop = (PMEMobjpool **)calloc(bench_priv->n_pools,
sizeof(PMEMobjpool *));
if (bench_priv->pop == nullptr) {
perror("calloc");
goto free_random_sizes;
}
bench_priv->sets = (const char **)calloc(bench_priv->n_pools,
sizeof(const char *));
if (bench_priv->sets == nullptr) {
perror("calloc");
goto free_pop;
}
if (bench_priv->n_pools > 1) {
assert(!args->is_poolset);
if (util_file_mkdir(args->fname, DIR_MODE) != 0) {
fprintf(stderr, "cannot create directory\n");
goto free_sets;
}
size_t path_len = (strlen(PART_NAME) + strlen(args->fname)) +
MAX_DIGITS + 1;
for (i = 0; i < bench_priv->n_pools; i++) {
bench_priv->sets[i] =
(char *)malloc(path_len * sizeof(char));
if (bench_priv->sets[i] == nullptr) {
perror("malloc");
goto free_sets;
}
int ret =
snprintf((char *)bench_priv->sets[i], path_len,
"%s%s%02x", args->fname, PART_NAME, i);
if (ret < 0 || ret >= (int)path_len) {
perror("snprintf");
goto free_sets;
}
bench_priv->pop[i] =
pmemobj_create(bench_priv->sets[i], LAYOUT_NAME,
psize, FILE_MODE);
if (bench_priv->pop[i] == nullptr) {
perror(pmemobj_errormsg());
goto free_sets;
}
}
} else {
if (args->is_poolset || type == TYPE_DEVDAX) {
if (args->fsize < psize) {
fprintf(stderr, "file size too large\n");
goto free_pools;
}
psize = 0;
}
bench_priv->sets[0] = args->fname;
bench_priv->pop[0] = pmemobj_create(
bench_priv->sets[0], LAYOUT_NAME, psize, FILE_MODE);
if (bench_priv->pop[0] == nullptr) {
perror(pmemobj_errormsg());
goto free_pools;
}
}
pmembench_set_priv(bench, bench_priv);
return 0;
free_sets:
for (; i > 0; i--) {
pmemobj_close(bench_priv->pop[i - 1]);
free((char *)bench_priv->sets[i - 1]);
}
free_pools:
free(bench_priv->sets);
free_pop:
free(bench_priv->pop);
free_random_sizes:
free(bench_priv->rand_sizes);
free_random_types:
free(bench_priv->random_types);
free_bench_priv:
free(bench_priv);
return -1;
}
/*
* pobj_direct_init -- special part of pobj_direct benchmark initialization.
*/
static int
pobj_direct_init(struct benchmark *bench, struct benchmark_args *args)
{
auto *pa = (struct pobj_args *)args->opts;
pa->n_objs = pa->one_obj ? 1 : args->n_ops_per_thread;
if (pobj_init(bench, args) != 0)
return -1;
return 0;
}
/*
* pobj_exit -- common part for the benchmarks exit functions
*/
static int
pobj_exit(struct benchmark *bench, struct benchmark_args *args)
{
size_t i;
auto *bench_priv = (struct pobj_bench *)pmembench_get_priv(bench);
if (bench_priv->n_pools > 1) {
for (i = 0; i < bench_priv->n_pools; i++) {
pmemobj_close(bench_priv->pop[i]);
free((char *)bench_priv->sets[i]);
}
} else {
pmemobj_close(bench_priv->pop[0]);
}
free(bench_priv->sets);
free(bench_priv->pop);
free(bench_priv->rand_sizes);
free(bench_priv->random_types);
free(bench_priv);
return 0;
}
/*
* pobj_init_worker -- worker initialization
*/
static int
pobj_init_worker(struct benchmark *bench, struct benchmark_args *args,
struct worker_info *worker)
{
size_t i, idx = worker->index;
auto *bench_priv = (struct pobj_bench *)pmembench_get_priv(bench);
auto *pw = (struct pobj_worker *)calloc(1, sizeof(struct pobj_worker));
if (pw == nullptr) {
perror("calloc");
return -1;
}
worker->priv = pw;
pw->oids = (PMEMoid *)calloc(bench_priv->args_priv->n_objs,
sizeof(PMEMoid));
if (pw->oids == nullptr) {
free(pw);
perror("calloc");
return -1;
}
PMEMobjpool *pop = bench_priv->pop[bench_priv->pool(idx)];
for (i = 0; i < bench_priv->args_priv->n_objs; i++) {
size_t size = bench_priv->fn_size(bench_priv, i);
size_t type = bench_priv->fn_type_num(bench_priv, idx, i);
if (pmemobj_alloc(pop, &pw->oids[i], size, type, nullptr,
nullptr) != 0) {
perror("pmemobj_alloc");
goto out;
}
}
return 0;
out:
for (; i > 0; i--)
pmemobj_free(&pw->oids[i - 1]);
free(pw->oids);
free(pw);
return -1;
}
/*
* pobj_direct_op -- main operations of the obj_direct benchmark.
*/
static int
pobj_direct_op(struct benchmark *bench, struct operation_info *info)
{
auto *bench_priv = (struct pobj_bench *)pmembench_get_priv(bench);
auto *pw = (struct pobj_worker *)info->worker->priv;
size_t idx = bench_priv->obj(info->index);
if (pmemobj_direct(pw->oids[idx]) == nullptr)
return -1;
return 0;
}
/*
* pobj_open_op -- main operations of the obj_open benchmark.
*/
static int
pobj_open_op(struct benchmark *bench, struct operation_info *info)
{
auto *bench_priv = (struct pobj_bench *)pmembench_get_priv(bench);
size_t idx = bench_priv->pool(info->worker->index);
pmemobj_close(bench_priv->pop[idx]);
bench_priv->pop[idx] = pmemobj_open(bench_priv->sets[idx], LAYOUT_NAME);
if (bench_priv->pop[idx] == nullptr)
return -1;
return 0;
}
/*
* pobj_free_worker -- worker exit function
*/
static void
pobj_free_worker(struct benchmark *bench, struct benchmark_args *args,
struct worker_info *worker)
{
auto *pw = (struct pobj_worker *)worker->priv;
auto *bench_priv = (struct pobj_bench *)pmembench_get_priv(bench);
for (size_t i = 0; i < bench_priv->args_priv->n_objs; i++)
pmemobj_free(&pw->oids[i]);
free(pw->oids);
free(pw);
}
static struct benchmark_info obj_open;
static struct benchmark_info obj_direct;
/* Array defining common command line arguments. */
static struct benchmark_clo pobj_direct_clo[4];
static struct benchmark_clo pobj_open_clo[3];
CONSTRUCTOR(pmemobj_gen_constructor)
void
pmemobj_gen_constructor(void)
{
pobj_direct_clo[0].opt_short = 'T';
pobj_direct_clo[0].opt_long = "type-number";
pobj_direct_clo[0].descr = "Type number mode - one, per-thread, "
"rand";
pobj_direct_clo[0].def = "one";
pobj_direct_clo[0].off = clo_field_offset(struct pobj_args, type_num);
pobj_direct_clo[0].type = CLO_TYPE_STR;
pobj_direct_clo[1].opt_short = 'm';
pobj_direct_clo[1].opt_long = "min-size";
pobj_direct_clo[1].type = CLO_TYPE_UINT;
pobj_direct_clo[1].descr = "Minimum allocation size";
pobj_direct_clo[1].off = clo_field_offset(struct pobj_args, min_size);
pobj_direct_clo[1].def = "0";
pobj_direct_clo[1].type_uint.size =
clo_field_size(struct pobj_args, min_size);
pobj_direct_clo[1].type_uint.base = CLO_INT_BASE_DEC | CLO_INT_BASE_HEX;
pobj_direct_clo[1].type_uint.min = 0;
pobj_direct_clo[1].type_uint.max = UINT_MAX;
pobj_direct_clo[2].opt_short = 'P';
pobj_direct_clo[2].opt_long = "one-pool";
pobj_direct_clo[2].descr = "Create one pool for all threads";
pobj_direct_clo[2].type = CLO_TYPE_FLAG;
pobj_direct_clo[2].off = clo_field_offset(struct pobj_args, one_pool);
pobj_direct_clo[3].opt_short = 'O';
pobj_direct_clo[3].opt_long = "one-object";
pobj_direct_clo[3].descr = "Use only one object per thread";
pobj_direct_clo[3].type = CLO_TYPE_FLAG;
pobj_direct_clo[3].off = clo_field_offset(struct pobj_args, one_obj);
pobj_open_clo[0].opt_short = 'T',
pobj_open_clo[0].opt_long = "type-number",
pobj_open_clo[0].descr = "Type number mode - one, "
"per-thread, rand",
pobj_open_clo[0].def = "one",
pobj_open_clo[0].off = clo_field_offset(struct pobj_args, type_num),
pobj_open_clo[0].type = CLO_TYPE_STR,
pobj_open_clo[1].opt_short = 'm',
pobj_open_clo[1].opt_long = "min-size",
pobj_open_clo[1].type = CLO_TYPE_UINT,
pobj_open_clo[1].descr = "Minimum allocation size",
pobj_open_clo[1].off = clo_field_offset(struct pobj_args, min_size),
pobj_open_clo[1].def = "0",
pobj_open_clo[1].type_uint.size =
clo_field_size(struct pobj_args, min_size),
pobj_open_clo[1].type_uint.base = CLO_INT_BASE_DEC | CLO_INT_BASE_HEX,
pobj_open_clo[1].type_uint.min = 0,
pobj_open_clo[1].type_uint.max = UINT_MAX,
pobj_open_clo[2].opt_short = 'o';
pobj_open_clo[2].opt_long = "objects";
pobj_open_clo[2].type = CLO_TYPE_UINT;
pobj_open_clo[2].descr = "Number of objects in each pool";
pobj_open_clo[2].off = clo_field_offset(struct pobj_args, n_objs);
pobj_open_clo[2].def = "1";
pobj_open_clo[2].type_uint.size =
clo_field_size(struct pobj_args, n_objs);
pobj_open_clo[2].type_uint.base = CLO_INT_BASE_DEC | CLO_INT_BASE_HEX;
pobj_open_clo[2].type_uint.min = 1;
pobj_open_clo[2].type_uint.max = UINT_MAX;
obj_open.name = "obj_open";
obj_open.brief = "pmemobj_open() benchmark";
obj_open.init = pobj_init;
obj_open.exit = pobj_exit;
obj_open.multithread = true;
obj_open.multiops = true;
obj_open.init_worker = pobj_init_worker;
obj_open.free_worker = pobj_free_worker;
obj_open.operation = pobj_open_op;
obj_open.measure_time = true;
obj_open.clos = pobj_open_clo;
obj_open.nclos = ARRAY_SIZE(pobj_open_clo);
obj_open.opts_size = sizeof(struct pobj_args);
obj_open.rm_file = true;
obj_open.allow_poolset = true;
REGISTER_BENCHMARK(obj_open);
obj_direct.name = "obj_direct";
obj_direct.brief = "pmemobj_direct() benchmark";
obj_direct.init = pobj_direct_init;
obj_direct.exit = pobj_exit;
obj_direct.multithread = true;
obj_direct.multiops = true;
obj_direct.init_worker = pobj_init_worker;
obj_direct.free_worker = pobj_free_worker;
obj_direct.operation = pobj_direct_op;
obj_direct.measure_time = true;
obj_direct.clos = pobj_direct_clo;
obj_direct.nclos = ARRAY_SIZE(pobj_direct_clo);
obj_direct.opts_size = sizeof(struct pobj_args);
obj_direct.rm_file = true;
obj_direct.allow_poolset = true;
REGISTER_BENCHMARK(obj_direct);
};