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