legacy/benchmarks/bench_storage.c (270 lines of code) (raw):
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <bench_storage.h>
#include <time/cc_timer.h>
#include <cc_debug.h>
#include <cc_mm.h>
#include <cc_array.h>
static __thread unsigned int rseed = 1234; /* XXX: make this an option */
#define RRAND(min, max) (rand_r(&(rseed)) % ((max) - (min) + 1) + (min))
#define SWAP(a, b) do {\
__typeof__(a) _tmp = (a);\
(a) = (b);\
(b) = _tmp;\
} while (0)
#define BENCHMARK_OPTION(ACTION)\
ACTION(entry_min_size, OPTION_TYPE_UINT, 64, "Min size of cache entry")\
ACTION(entry_max_size, OPTION_TYPE_UINT, 64, "Max size of cache entry")\
ACTION(nentries, OPTION_TYPE_UINT, 1000, "Max total number of cache entries" )\
ACTION(nops, OPTION_TYPE_UINT, 100000,"Total number of operations")\
ACTION(pct_get, OPTION_TYPE_UINT, 80, "% of gets")\
ACTION(pct_put, OPTION_TYPE_UINT, 10, "% of puts")\
ACTION(pct_rem, OPTION_TYPE_UINT, 10, "% of removes")\
ACTION(latency, OPTION_TYPE_BOOL, true, "Collect latency samples")
#define O(b, opt) option_uint(&(b->options->benchmark.opt))
#define O_BOOL(b, opt) option_bool(&(b->options->benchmark.opt))
enum benchmark_operation {
BENCHMARK_GET,
BENCHMARK_PUT,
BENCHMARK_REM,
MAX_BENCHMARK_OPERATION
};
static const char *op_names[MAX_BENCHMARK_OPERATION] = {"get", "put", "rem"};
struct benchmark_specific {
BENCHMARK_OPTION(OPTION_DECLARE)
};
struct benchmark_options {
struct benchmark_specific benchmark;
struct option engine[]; /* storage-engine specific options... */
};
struct benchmark {
struct benchmark_entry *entries;
struct benchmark_options *options;
struct operation_latency {
struct duration *samples;
size_t count;
} latency[MAX_BENCHMARK_OPERATION];
};
static rstatus_i
benchmark_create(struct benchmark *b, const char *config)
{
b->entries = NULL;
unsigned nopts = OPTION_CARDINALITY(struct benchmark_specific);
struct benchmark_specific opts = { BENCHMARK_OPTION(OPTION_INIT) };
option_load_default((struct option *)&opts, nopts);
nopts += bench_storage_config_nopts();
b->options = cc_alloc(sizeof(struct option) * nopts);
ASSERT(b->options != NULL);
b->options->benchmark = opts;
bench_storage_config_init(b->options->engine);
if (config != NULL) {
FILE *fp = fopen(config, "r");
if (fp == NULL) {
log_crit("failed to open the config file");
cc_free(b->options);
return CC_EINVAL;
}
option_load_file(fp, (struct option *)b->options, nopts);
fclose(fp);
}
if (O(b, entry_min_size) <= sizeof(benchmark_key_u)) {
log_crit("entry_min_size must larger than %lu",
sizeof(benchmark_key_u));
cc_free(b->options);
return CC_EINVAL;
}
for (int op = 0; op < MAX_BENCHMARK_OPERATION; ++op) {
b->latency[op].samples = O_BOOL(b, latency) ?
cc_alloc(O(b, nops) * sizeof(struct duration)) : NULL;
b->latency[op].count = 0;
}
return CC_OK;
}
static void
benchmark_destroy(struct benchmark *b)
{
for (int op = 0; op < MAX_BENCHMARK_OPERATION; ++op) {
cc_free(b->latency[op].samples);
}
cc_free(b->options);
}
static struct benchmark_entry
benchmark_entry_create(benchmark_key_u key, size_t size)
{
struct benchmark_entry e;
e.key_size = sizeof(key);
e.value_size = size - sizeof(key);
e.key = cc_alloc(e.key_size);
ASSERT(e.key != NULL);
e.value = cc_alloc(e.value_size);
ASSERT(e.value != NULL);
int ret = snprintf(e.key, e.key_size, "%zu", key);
ASSERT(ret > 0);
memset(e.value, 'a', e.value_size);
e.value[e.value_size - 1] = 0;
return e;
}
static void
benchmark_entry_destroy(struct benchmark_entry *e)
{
cc_free(e->key);
cc_free(e->value);
}
static void
benchmark_entries_populate(struct benchmark *b)
{
size_t nentries = O(b, nentries);
b->entries = cc_alloc(sizeof(struct benchmark_entry) * nentries);
ASSERT(b->entries != NULL);
for (size_t i = 1; i <= nentries; ++i) {
size_t size = RRAND(O(b, entry_min_size), O(b, entry_max_size));
b->entries[i - 1] = benchmark_entry_create(i, size);
}
}
static void
benchmark_entries_delete(struct benchmark *b)
{
for (size_t i = 0; i < O(b, nentries); ++i) {
benchmark_entry_destroy(&b->entries[i]);
}
cc_free(b->entries);
}
static void
benchmark_print_summary(struct benchmark *b, struct duration *d)
{
printf("total benchmark runtime: %f s\n", duration_sec(d));
printf("average operation latency: %f ns\n", duration_ns(d) / O(b, nops));
if (!O_BOOL(b, latency))
return;
for (int op = 0; op < MAX_BENCHMARK_OPERATION; ++op) {
struct operation_latency *latency = &b->latency[op];
qsort(latency->samples, latency->count,
sizeof(struct duration), duration_compare);
struct duration *p50 =
&latency->samples[(size_t)(latency->count * 0.5)];
struct duration *p99 =
&latency->samples[(size_t)(latency->count * 0.99)];
struct duration *p999 =
&latency->samples[(size_t)(latency->count * 0.999)];
printf("Latency p50, p99, p99.9 for %s (%lu samples): %f, %f, %f\n",
op_names[op],
latency->count,
duration_ns(p50),
duration_ns(p99),
duration_ns(p999));
}
}
static rstatus_i
benchmark_run_operation(struct benchmark *b,
struct benchmark_entry *e, enum benchmark_operation op)
{
rstatus_i status = CC_OK;
struct operation_latency *latency = &b->latency[op];
size_t nsample = latency->count++;
if (O_BOOL(b, latency))
duration_start_type(&latency->samples[nsample], DURATION_FAST);
switch (op) {
case BENCHMARK_GET:
status = bench_storage_get(e);
break;
case BENCHMARK_PUT:
status = bench_storage_put(e);
break;
case BENCHMARK_REM:
status = bench_storage_rem(e);
break;
default:
NOT_REACHED();
}
if (O_BOOL(b, latency))
duration_stop(&latency->samples[nsample]);
return status;
}
static struct duration
benchmark_run(struct benchmark *b)
{
struct array *in;
struct array *in2;
struct array *out;
size_t nentries = O(b, nentries);
bench_storage_init(b->options->engine, O(b, entry_max_size), nentries);
array_create(&in, nentries, sizeof(struct benchmark_entry *));
array_create(&in2, nentries, sizeof(struct benchmark_entry *));
array_create(&out, nentries, sizeof(struct benchmark_entry *));
for (size_t i = 0; i < nentries; ++i) {
struct benchmark_entry **e = array_push(in);
*e = &b->entries[i];
ASSERT(bench_storage_put(*e) == CC_OK);
}
struct duration d;
duration_start(&d);
for (size_t i = 0; i < O(b, nops); ++i) {
if (array_nelem(in) == 0) {
SWAP(in, in2);
/* XXX: array_shuffle(in) */
}
unsigned pct = RRAND(0, 100);
unsigned pct_sum = 0;
if (pct_sum <= pct && pct < O(b, pct_get) + pct_sum) {
ASSERT(array_nelem(in) != 0);
struct benchmark_entry **e = array_pop(in);
if (benchmark_run_operation(b, *e, BENCHMARK_GET) != CC_OK) {
log_info("benchmark get() failed");
}
struct benchmark_entry **e2 = array_push(in2);
*e2 = *e;
}
pct_sum += O(b, pct_get);
if (pct_sum <= pct && pct < O(b, pct_put) + pct_sum) {
struct benchmark_entry **e;
if (array_nelem(out) != 0) {
e = array_pop(out);
} else {
ASSERT(array_nelem(in) != 0);
e = array_pop(in);
if (bench_storage_rem(*e) != CC_OK) {
log_info("benchmark rem() failed");
}
}
if (benchmark_run_operation(b, *e, BENCHMARK_PUT) != CC_OK) {
log_info("benchmark put() failed");
}
struct benchmark_entry **e2 = array_push(in2);
*e2 = *e;
}
pct_sum += O(b, pct_put);
if (pct_sum < pct && pct <= O(b, pct_rem) + pct_sum) {
ASSERT(array_nelem(in) != 0);
struct benchmark_entry **e = array_pop(in);
if (benchmark_run_operation(b, *e, BENCHMARK_REM) != CC_OK) {
log_info("benchmark rem() failed");
}
struct benchmark_entry **e2 = array_push(out);
*e2 = *e;
}
}
duration_stop(&d);
bench_storage_deinit();
array_destroy(&in);
array_destroy(&in2);
array_destroy(&out);
return d;
}
int
main(int argc, char *argv[])
{
struct benchmark b;
if (benchmark_create(&b, argv[1]) != 0) {
loga("failed to create benchmark instance");
return -1;
}
benchmark_entries_populate(&b);
struct duration d = benchmark_run(&b);
benchmark_print_summary(&b, &d);
benchmark_entries_delete(&b);
benchmark_destroy(&b);
return 0;
}