1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2014-2018. All Rights Reserved.
5  *
6  * The contents of this file are subject to the Erlang Public License,
7  * Version 1.1, (the "License"); you may not use this file except in
8  * compliance with the License. You should have received a copy of the
9  * Erlang Public License along with this software. If not, it can be
10  * retrieved online at http://www.erlang.org/.
11  *
12  * Software distributed under the License is distributed on an "AS IS"
13  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14  * the License for the specific language governing rights and limitations
15  * under the License.
16  *
17  * %CopyrightEnd%
18  */
19 
20 /*
21  * Test the carrier migration logic
22  */
23 
24 #ifndef __WIN32__
25 #include <sys/types.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <string.h>
33 #include "testcase_driver.h"
34 #include "allocator_test.h"
35 
36 #define FATAL_ASSERT(A)						\
37     ((void) ((A)						\
38 	     ? 1						\
39 	     : (fatal_assert_failed(#A,				\
40 				    (char *) __FILE__,		\
41 				    __LINE__),			\
42 		0)))
43 
44 static void
fatal_assert_failed(char * expr,char * file,int line)45 fatal_assert_failed(char* expr, char* file, int line)
46 {
47     fflush(stdout);
48     fprintf(stderr, "%s:%d: Assertion failed: %s\n",
49 	    file, line, expr);
50     fflush(stderr);
51     abort();
52 }
53 
54 
55 char *
testcase_name(void)56 testcase_name(void)
57 {
58     return "migration";
59 }
60 
61 /* Turns out random_r() is a nonstandard glibc extension.
62 #define HAVE_RANDOM_R
63 */
64 #ifdef HAVE_RANDOM_R
65 
66 typedef struct { struct random_data rnd; char rndbuf[32]; } MyRandState;
67 
myrand_init(MyRandState * mrs,unsigned int seed)68 static void myrand_init(MyRandState* mrs, unsigned int seed)
69 {
70     int res;
71     memset(&mrs->rnd, 0, sizeof(mrs->rnd));
72     res = initstate_r(seed, mrs->rndbuf, sizeof(mrs->rndbuf), &mrs->rnd);
73     FATAL_ASSERT(res == 0);
74 }
75 
myrand(MyRandState * mrs)76 static int myrand(MyRandState* mrs)
77 {
78     int32_t x;
79     int res = random_r(&mrs->rnd, &x);
80     FATAL_ASSERT(res == 0);
81     return (int)x;
82 }
83 
84 #else /* !HAVE_RANDOM_R */
85 
86 typedef unsigned int MyRandState;
87 
myrand_init(MyRandState * mrs,unsigned int seed)88 static void myrand_init(MyRandState* mrs, unsigned int seed)
89 {
90     *mrs = seed;
91 }
92 
myrand(MyRandState * mrs)93 static int myrand(MyRandState* mrs)
94 {
95     /* Taken from rand(3) man page.
96      * Modified to return a full 31-bit value by using low half of *mrs as well.
97      */
98     *mrs = (*mrs) * 1103515245 + 12345;
99     return (int) (((*mrs >> 16) | (*mrs << 16)) & ~(1 << 31));
100 }
101 
102 #endif /* !HAVE_RANDOM_R */
103 
104 #define MAX_BLOCK_PER_THR 200
105 #define BLOCKS_PER_MBC 10
106 #define MAX_ROUNDS 10000
107 
108 typedef struct MyBlock_ {
109     struct MyBlock_* next;
110     struct MyBlock_** prevp;
111 } MyBlock;
112 
113 typedef struct {
114     MyBlock* blockv[MAX_BLOCK_PER_THR];
115     MyRandState rand_state;
116     enum { GROWING, SHRINKING, CLEANUP, DONE } phase;
117     int nblocks;
118     int goal_nblocks;
119     int round;
120     int nr_of_migrations;
121     int nr_of_carriers;
122     int max_blocks_in_mbc;
123     int block_size;
124     int max_nblocks;
125 } MigrationState;
126 
127 typedef struct {
128     ErlNifMutex* mtx;
129     int nblocks;
130     MyBlock* first;
131     MigrationState* employer;
132 } MyCrrInfo;
133 
134 
135 static int crr_info_offset = -1;
136 static void (*orig_create_mbc_fn)(Allctr_t *allctr, Carrier_t *carrier);
137 static void (*orig_destroying_mbc_fn)(Allctr_t *allctr, Carrier_t *carrier);
138 
my_creating_mbc(Allctr_t * allctr,Carrier_t * carrier)139 static void my_creating_mbc(Allctr_t *allctr, Carrier_t *carrier)
140 {
141     MyCrrInfo* mci = (MyCrrInfo*) ((char*)carrier + crr_info_offset);
142     if (orig_create_mbc_fn)
143 	orig_create_mbc_fn(allctr, carrier);
144 
145     mci->mtx = enif_mutex_create("alloc_SUITE.migration");
146     mci->nblocks = 0;
147     mci->first = NULL;
148     mci->employer = NULL;
149 }
150 
my_destroying_mbc(Allctr_t * allctr,Carrier_t * carrier)151 static void my_destroying_mbc(Allctr_t *allctr, Carrier_t *carrier)
152 {
153     MyCrrInfo* mci = (MyCrrInfo*) ((char*)carrier + crr_info_offset);
154 
155     FATAL_ASSERT(mci->nblocks == 0);
156     FATAL_ASSERT(mci->first == NULL);
157     enif_mutex_destroy(mci->mtx);
158 
159     if (orig_destroying_mbc_fn)
160 	orig_destroying_mbc_fn(allctr, carrier);
161 }
162 
migration_init(ErlNifEnv * env,void ** priv_data,ERL_NIF_TERM load_info)163 static int migration_init(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
164 {
165     void* creating_mbc_arg = (void*)my_creating_mbc;
166     void* destroying_mbc_arg = (void*)my_destroying_mbc;
167 
168     if (testcase_nif_init(env, priv_data, load_info))
169 	return -1;
170 
171     crr_info_offset = SET_TEST_MBC_USER_HEADER(sizeof(MyCrrInfo),
172 					       &creating_mbc_arg,
173 					       &destroying_mbc_arg);
174     FATAL_ASSERT(crr_info_offset >= 0);
175     orig_create_mbc_fn = creating_mbc_arg;
176     orig_destroying_mbc_fn = destroying_mbc_arg;
177 
178     return 0;
179 }
180 
add_block(MyBlock * p,MigrationState * state)181 static void add_block(MyBlock* p, MigrationState* state)
182 {
183     MyCrrInfo* mci = (MyCrrInfo*)((char*)BLK_TO_MBC(UMEM2BLK_TEST(p)) + crr_info_offset);
184 
185     enif_mutex_lock(mci->mtx);
186     if (++mci->nblocks > state->max_blocks_in_mbc)
187 	state->max_blocks_in_mbc = mci->nblocks;
188     p->next = mci->first;
189     p->prevp = &mci->first;
190     mci->first = p;
191     if (p->next)
192 	p->next->prevp = &p->next;
193     if (mci->employer != state) {
194 	if (!mci->employer) {
195 	    FATAL_ASSERT(mci->nblocks == 1);
196 	    state->nr_of_carriers++;
197 	}
198 	else {
199 	    state->nr_of_migrations++;
200 	}
201 	mci->employer = state;
202     }
203     enif_mutex_unlock(mci->mtx);
204 }
205 
remove_block(MyBlock * p)206 static void remove_block(MyBlock* p)
207 {
208     MyCrrInfo* mci = (MyCrrInfo*)((char*)BLK_TO_MBC(UMEM2BLK_TEST(p)) + crr_info_offset);
209 
210     enif_mutex_lock(mci->mtx);
211     mci->nblocks--;
212     if (p->next)
213 	p->next->prevp = p->prevp;
214     *p->prevp = p->next;
215     enif_mutex_unlock(mci->mtx);
216 }
217 
rand_int(MigrationState * state,int low,int high)218 static int rand_int(MigrationState* state, int low, int high)
219 {
220     int x;
221     FATAL_ASSERT(high >= low);
222     x = myrand(&state->rand_state);
223     return low + (x % (high+1-low));
224 }
225 
226 enum Operation
227 {
228     ALLOCATE_OP,
229     FREE_OP,
230     REALLOC_OP,
231     CLEANUP_OP
232 };
233 
rand_op(MigrationState * state)234 static enum Operation rand_op(MigrationState* state)
235 {
236     int r = rand_int(state, 1, 100);
237     switch (state->phase) {
238     case GROWING:
239         FATAL_ASSERT(state->nblocks < state->max_nblocks);
240         if (r > 10 || state->nblocks == 0)
241             return ALLOCATE_OP;
242         else if (r > 5)
243             return FREE_OP;
244         else
245             return REALLOC_OP;
246 
247     case SHRINKING:
248         FATAL_ASSERT(state->nblocks > 0);
249         if (r > 10 || state->nblocks == state->max_nblocks)
250             return FREE_OP;
251         else if (r > 5)
252             return ALLOCATE_OP;
253         else
254             return REALLOC_OP;
255 
256     case CLEANUP:
257         return CLEANUP_OP;
258     default:
259         FATAL_ASSERT(!"Invalid op phase");
260     }
261 }
262 
do_cleanup(TestCaseState_t * tcs,MigrationState * state)263 static void do_cleanup(TestCaseState_t *tcs, MigrationState* state)
264 {
265     if (state->nblocks == 0) {
266 	state->phase = DONE;
267 	testcase_printf(tcs, "%d: Done %d rounds", tcs->thr_nr, state->round);
268 	testcase_printf(tcs, "%d: Cleanup all blocks", tcs->thr_nr);
269 	testcase_printf(tcs, "%d: Empty carriers detected = %d", tcs->thr_nr,
270 			state->nr_of_carriers);
271 	testcase_printf(tcs, "%d: Migrations detected     = %d", tcs->thr_nr,
272 			state->nr_of_migrations);
273 	testcase_printf(tcs, "%d: Max blocks in carrier   = %d", tcs->thr_nr,
274 			state->max_blocks_in_mbc);
275     }
276     else {
277 	state->nblocks--;
278 	if (state->blockv[state->nblocks]) {
279 	    remove_block(state->blockv[state->nblocks]);
280 	    FREE_TEST(state->blockv[state->nblocks]);
281 	}
282     }
283 }
284 
285 
286 void
testcase_run(TestCaseState_t * tcs)287 testcase_run(TestCaseState_t *tcs)
288 {
289     MigrationState* state = (MigrationState*) tcs->extra;
290 
291     if (!tcs->extra) {
292 	if (!IS_SMP_ENABLED)
293 	    testcase_skipped(tcs, "No SMP support");
294 
295 	tcs->extra = enif_alloc(sizeof(MigrationState));
296 	state = (MigrationState*) tcs->extra;
297 	memset(state->blockv, 0, sizeof(state->blockv));
298 	myrand_init(&state->rand_state, tcs->thr_nr);
299 	state->phase = GROWING;
300 	state->nblocks = 0;
301 	state->round = 0;
302 	state->nr_of_migrations = 0;
303 	state->nr_of_carriers = 0;
304 	state->max_blocks_in_mbc = 0;
305 	state->block_size = GET_TEST_MBC_SIZE() / (BLOCKS_PER_MBC+1);
306 	if (MAX_BLOCK_PER_THR * state->block_size < tcs->free_mem) {
307 	    state->max_nblocks = MAX_BLOCK_PER_THR;
308 	} else {
309 	    state->max_nblocks = tcs->free_mem / state->block_size;
310 	}
311 	state->goal_nblocks = rand_int(state, 1, state->max_nblocks);
312     }
313 
314     switch (rand_op(state)) {
315     case ALLOCATE_OP: {
316 	MyBlock* p;
317 	FATAL_ASSERT(!state->blockv[state->nblocks]);
318         p = ALLOC_TEST(rand_int(state, state->block_size/2, state->block_size));
319 	FATAL_ASSERT(p);
320 	add_block(p, state);
321 	state->blockv[state->nblocks++] = p;
322 	break;
323     }
324     case FREE_OP: {
325 	int ix = rand_int(state, 0, state->nblocks-1);
326 	FATAL_ASSERT(state->blockv[ix]);
327 	remove_block(state->blockv[ix]);
328 	FREE_TEST(state->blockv[ix]);
329 	state->blockv[ix] = state->blockv[--state->nblocks];
330 	state->blockv[state->nblocks] = NULL;
331 	break;
332     }
333     case REALLOC_OP: {
334         int ix = rand_int(state, 0, state->nblocks-1);
335         MyBlock* p;
336         FATAL_ASSERT(state->blockv[ix]);
337         remove_block(state->blockv[ix]);
338         p = REALLOC_TEST(state->blockv[ix], rand_int(state, state->block_size/2, state->block_size));
339         FATAL_ASSERT(p);
340         add_block(p, state);
341         state->blockv[ix] = p;
342         break;
343     }
344     case CLEANUP_OP:
345         do_cleanup(tcs, state);
346         break;
347     default:
348 	FATAL_ASSERT(!"Invalid operation");
349     }
350 
351     switch (state->phase) {
352     case GROWING: {
353         if (state->nblocks >= state->goal_nblocks) {
354             /*testcase_printf(tcs, "%d: Grown to %d blocks", tcs->thr_nr, state->nblocks);*/
355             state->phase = SHRINKING;
356             state->goal_nblocks = rand_int(state, 0, state->goal_nblocks-1);
357         }
358         else
359             FATAL_ASSERT(!state->blockv[state->nblocks]);
360         break;
361     }
362     case SHRINKING: {
363         if (state->nblocks <= state->goal_nblocks) {
364             /*testcase_printf(tcs, "%d: Shrunk to %d blocks", tcs->thr_nr, state->nblocks);*/
365             if (++state->round >= MAX_ROUNDS) {
366                 state->phase = CLEANUP;
367             } else {
368                 state->phase = GROWING;
369                 state->goal_nblocks = rand_int(state, state->goal_nblocks+1, state->max_nblocks);
370             }
371         }
372         break;
373     }
374     case CLEANUP:
375     case DONE:
376         break;
377 
378     default:
379 	FATAL_ASSERT(!"Invalid phase");
380     }
381 
382     if (state->phase != DONE) {
383 	testcase_continue(tcs);
384     }
385 }
386 
387 void
testcase_cleanup(TestCaseState_t * tcs)388 testcase_cleanup(TestCaseState_t *tcs)
389 {
390     MigrationState* state = (MigrationState*) tcs->extra;
391 
392     while (state->phase != DONE)
393 	do_cleanup(tcs, state);
394 
395     enif_free(tcs->extra);
396     tcs->extra = NULL;
397 }
398 
399 
400 ERL_NIF_INIT(migration, testcase_nif_funcs, migration_init,
401 	     NULL, NULL, NULL);
402