/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2014-2018. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Test the carrier migration logic
*/
#ifndef __WIN32__
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "testcase_driver.h"
#include "allocator_test.h"
#define FATAL_ASSERT(A) \
((void) ((A) \
? 1 \
: (fatal_assert_failed(#A, \
(char *) __FILE__, \
__LINE__), \
0)))
static void
fatal_assert_failed(char* expr, char* file, int line)
{
fflush(stdout);
fprintf(stderr, "%s:%d: Assertion failed: %s\n",
file, line, expr);
fflush(stderr);
abort();
}
char *
testcase_name(void)
{
return "migration";
}
/* Turns out random_r() is a nonstandard glibc extension.
#define HAVE_RANDOM_R
*/
#ifdef HAVE_RANDOM_R
typedef struct { struct random_data rnd; char rndbuf[32]; } MyRandState;
static void myrand_init(MyRandState* mrs, unsigned int seed)
{
int res;
memset(&mrs->rnd, 0, sizeof(mrs->rnd));
res = initstate_r(seed, mrs->rndbuf, sizeof(mrs->rndbuf), &mrs->rnd);
FATAL_ASSERT(res == 0);
}
static int myrand(MyRandState* mrs)
{
int32_t x;
int res = random_r(&mrs->rnd, &x);
FATAL_ASSERT(res == 0);
return (int)x;
}
#else /* !HAVE_RANDOM_R */
typedef unsigned int MyRandState;
static void myrand_init(MyRandState* mrs, unsigned int seed)
{
*mrs = seed;
}
static int myrand(MyRandState* mrs)
{
/* Taken from rand(3) man page.
* Modified to return a full 31-bit value by using low half of *mrs as well.
*/
*mrs = (*mrs) * 1103515245 + 12345;
return (int) (((*mrs >> 16) | (*mrs << 16)) & ~(1 << 31));
}
#endif /* !HAVE_RANDOM_R */
#define MAX_BLOCK_PER_THR 200
#define BLOCKS_PER_MBC 10
#define MAX_ROUNDS 10000
typedef struct MyBlock_ {
struct MyBlock_* next;
struct MyBlock_** prevp;
} MyBlock;
typedef struct {
MyBlock* blockv[MAX_BLOCK_PER_THR];
MyRandState rand_state;
enum { GROWING, SHRINKING, CLEANUP, DONE } phase;
int nblocks;
int goal_nblocks;
int round;
int nr_of_migrations;
int nr_of_carriers;
int max_blocks_in_mbc;
int block_size;
int max_nblocks;
} MigrationState;
typedef struct {
ErlNifMutex* mtx;
int nblocks;
MyBlock* first;
MigrationState* employer;
} MyCrrInfo;
static int crr_info_offset = -1;
static void (*orig_create_mbc_fn)(Allctr_t *allctr, Carrier_t *carrier);
static void (*orig_destroying_mbc_fn)(Allctr_t *allctr, Carrier_t *carrier);
static void my_creating_mbc(Allctr_t *allctr, Carrier_t *carrier)
{
MyCrrInfo* mci = (MyCrrInfo*) ((char*)carrier + crr_info_offset);
if (orig_create_mbc_fn)
orig_create_mbc_fn(allctr, carrier);
mci->mtx = enif_mutex_create("alloc_SUITE.migration");
mci->nblocks = 0;
mci->first = NULL;
mci->employer = NULL;
}
static void my_destroying_mbc(Allctr_t *allctr, Carrier_t *carrier)
{
MyCrrInfo* mci = (MyCrrInfo*) ((char*)carrier + crr_info_offset);
FATAL_ASSERT(mci->nblocks == 0);
FATAL_ASSERT(mci->first == NULL);
enif_mutex_destroy(mci->mtx);
if (orig_destroying_mbc_fn)
orig_destroying_mbc_fn(allctr, carrier);
}
static int migration_init(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
{
void* creating_mbc_arg = (void*)my_creating_mbc;
void* destroying_mbc_arg = (void*)my_destroying_mbc;
if (testcase_nif_init(env, priv_data, load_info))
return -1;
crr_info_offset = SET_TEST_MBC_USER_HEADER(sizeof(MyCrrInfo),
&creating_mbc_arg,
&destroying_mbc_arg);
FATAL_ASSERT(crr_info_offset >= 0);
orig_create_mbc_fn = creating_mbc_arg;
orig_destroying_mbc_fn = destroying_mbc_arg;
return 0;
}
static void add_block(MyBlock* p, MigrationState* state)
{
MyCrrInfo* mci = (MyCrrInfo*)((char*)BLK_TO_MBC(UMEM2BLK_TEST(p)) + crr_info_offset);
enif_mutex_lock(mci->mtx);
if (++mci->nblocks > state->max_blocks_in_mbc)
state->max_blocks_in_mbc = mci->nblocks;
p->next = mci->first;
p->prevp = &mci->first;
mci->first = p;
if (p->next)
p->next->prevp = &p->next;
if (mci->employer != state) {
if (!mci->employer) {
FATAL_ASSERT(mci->nblocks == 1);
state->nr_of_carriers++;
}
else {
state->nr_of_migrations++;
}
mci->employer = state;
}
enif_mutex_unlock(mci->mtx);
}
static void remove_block(MyBlock* p)
{
MyCrrInfo* mci = (MyCrrInfo*)((char*)BLK_TO_MBC(UMEM2BLK_TEST(p)) + crr_info_offset);
enif_mutex_lock(mci->mtx);
mci->nblocks--;
if (p->next)
p->next->prevp = p->prevp;
*p->prevp = p->next;
enif_mutex_unlock(mci->mtx);
}
static int rand_int(MigrationState* state, int low, int high)
{
int x;
FATAL_ASSERT(high >= low);
x = myrand(&state->rand_state);
return low + (x % (high+1-low));
}
enum Operation
{
ALLOCATE_OP,
FREE_OP,
REALLOC_OP,
CLEANUP_OP
};
static enum Operation rand_op(MigrationState* state)
{
int r = rand_int(state, 1, 100);
switch (state->phase) {
case GROWING:
FATAL_ASSERT(state->nblocks < state->max_nblocks);
if (r > 10 || state->nblocks == 0)
return ALLOCATE_OP;
else if (r > 5)
return FREE_OP;
else
return REALLOC_OP;
case SHRINKING:
FATAL_ASSERT(state->nblocks > 0);
if (r > 10 || state->nblocks == state->max_nblocks)
return FREE_OP;
else if (r > 5)
return ALLOCATE_OP;
else
return REALLOC_OP;
case CLEANUP:
return CLEANUP_OP;
default:
FATAL_ASSERT(!"Invalid op phase");
}
}
static void do_cleanup(TestCaseState_t *tcs, MigrationState* state)
{
if (state->nblocks == 0) {
state->phase = DONE;
testcase_printf(tcs, "%d: Done %d rounds", tcs->thr_nr, state->round);
testcase_printf(tcs, "%d: Cleanup all blocks", tcs->thr_nr);
testcase_printf(tcs, "%d: Empty carriers detected = %d", tcs->thr_nr,
state->nr_of_carriers);
testcase_printf(tcs, "%d: Migrations detected = %d", tcs->thr_nr,
state->nr_of_migrations);
testcase_printf(tcs, "%d: Max blocks in carrier = %d", tcs->thr_nr,
state->max_blocks_in_mbc);
}
else {
state->nblocks--;
if (state->blockv[state->nblocks]) {
remove_block(state->blockv[state->nblocks]);
FREE_TEST(state->blockv[state->nblocks]);
}
}
}
void
testcase_run(TestCaseState_t *tcs)
{
MigrationState* state = (MigrationState*) tcs->extra;
if (!tcs->extra) {
if (!IS_SMP_ENABLED)
testcase_skipped(tcs, "No SMP support");
tcs->extra = enif_alloc(sizeof(MigrationState));
state = (MigrationState*) tcs->extra;
memset(state->blockv, 0, sizeof(state->blockv));
myrand_init(&state->rand_state, tcs->thr_nr);
state->phase = GROWING;
state->nblocks = 0;
state->round = 0;
state->nr_of_migrations = 0;
state->nr_of_carriers = 0;
state->max_blocks_in_mbc = 0;
state->block_size = GET_TEST_MBC_SIZE() / (BLOCKS_PER_MBC+1);
if (MAX_BLOCK_PER_THR * state->block_size < tcs->free_mem) {
state->max_nblocks = MAX_BLOCK_PER_THR;
} else {
state->max_nblocks = tcs->free_mem / state->block_size;
}
state->goal_nblocks = rand_int(state, 1, state->max_nblocks);
}
switch (rand_op(state)) {
case ALLOCATE_OP: {
MyBlock* p;
FATAL_ASSERT(!state->blockv[state->nblocks]);
p = ALLOC_TEST(rand_int(state, state->block_size/2, state->block_size));
FATAL_ASSERT(p);
add_block(p, state);
state->blockv[state->nblocks++] = p;
break;
}
case FREE_OP: {
int ix = rand_int(state, 0, state->nblocks-1);
FATAL_ASSERT(state->blockv[ix]);
remove_block(state->blockv[ix]);
FREE_TEST(state->blockv[ix]);
state->blockv[ix] = state->blockv[--state->nblocks];
state->blockv[state->nblocks] = NULL;
break;
}
case REALLOC_OP: {
int ix = rand_int(state, 0, state->nblocks-1);
MyBlock* p;
FATAL_ASSERT(state->blockv[ix]);
remove_block(state->blockv[ix]);
p = REALLOC_TEST(state->blockv[ix], rand_int(state, state->block_size/2, state->block_size));
FATAL_ASSERT(p);
add_block(p, state);
state->blockv[ix] = p;
break;
}
case CLEANUP_OP:
do_cleanup(tcs, state);
break;
default:
FATAL_ASSERT(!"Invalid operation");
}
switch (state->phase) {
case GROWING: {
if (state->nblocks >= state->goal_nblocks) {
/*testcase_printf(tcs, "%d: Grown to %d blocks", tcs->thr_nr, state->nblocks);*/
state->phase = SHRINKING;
state->goal_nblocks = rand_int(state, 0, state->goal_nblocks-1);
}
else
FATAL_ASSERT(!state->blockv[state->nblocks]);
break;
}
case SHRINKING: {
if (state->nblocks <= state->goal_nblocks) {
/*testcase_printf(tcs, "%d: Shrunk to %d blocks", tcs->thr_nr, state->nblocks);*/
if (++state->round >= MAX_ROUNDS) {
state->phase = CLEANUP;
} else {
state->phase = GROWING;
state->goal_nblocks = rand_int(state, state->goal_nblocks+1, state->max_nblocks);
}
}
break;
}
case CLEANUP:
case DONE:
break;
default:
FATAL_ASSERT(!"Invalid phase");
}
if (state->phase != DONE) {
testcase_continue(tcs);
}
}
void
testcase_cleanup(TestCaseState_t *tcs)
{
MigrationState* state = (MigrationState*) tcs->extra;
while (state->phase != DONE)
do_cleanup(tcs, state);
enif_free(tcs->extra);
tcs->extra = NULL;
}
ERL_NIF_INIT(migration, testcase_nif_funcs, migration_init,
NULL, NULL, NULL);