modules/malloc_test/malloc_test.c
0ff010fa
 /*$Id$
  *
  * Memory allocators debugging/test sip-router module.
  *
  * Copyright (C) 2010 iptelorg GmbH
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * copyright notice and this permission notice appear in all copies.
  *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 /*
  * History:
  * --------
  *  2010-03-10  initial version (andrei)
  */
 
 
 #include "../../sr_module.h"
 #include "../../mem/mem.h"
 #include "../../str.h"
 #include "../../dprint.h"
 #include "../../locking.h"
 #include "../../atomic_ops.h"
 #include "../../cfg/cfg.h"
 #include "../../rpc.h"
 #include "../../rand/fastrand.h"
 #include "../../timer.h"
 #include "../../mod_fix.h"
 
 MODULE_VERSION
 
 static int mt_mem_alloc_f(struct sip_msg*, char*,char*);
 static int mt_mem_free_f(struct sip_msg*, char*,char*);
 static int mod_init(void);
 static void mod_destroy(void);
 
 
 static cmd_export_t cmds[]={
 	{"mt_mem_alloc", mt_mem_alloc_f, 1, fixup_var_int_1,
 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE|ONSEND_ROUTE},
 	{"mt_mem_free", mt_mem_free_f, 1, fixup_var_int_1,
 		REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE|ONSEND_ROUTE},
 	{0, 0, 0, 0, 0}
 };
 
 
 
 struct cfg_group_malloc_test {
 	int check_content;
c05afff1
 	int realloc_p; /* realloc probability */
0ff010fa
 };
 
 
 static struct cfg_group_malloc_test default_mt_cfg = {
c05afff1
 	0, /* check_content, off by default */
 	0  /* realloc probability, 0 by default */
0ff010fa
 };
 
 static void * mt_cfg = &default_mt_cfg;
 
 static cfg_def_t malloc_test_cfg_def[] = {
 	{"check_content", CFG_VAR_INT | CFG_ATOMIC, 0, 1, 0, 0,
 		"check if allocated memory was overwritten by filling it with "
c05afff1
 		"a special pattern and checking it on free."},
 	{"realloc_p", CFG_VAR_INT | CFG_ATOMIC, 0, 90, 0, 0,
 		"realloc probability in percents. During tests and mem_rnd_alloc"
 		" realloc_p percents of the allocations will be made by realloc'ing"
 		" and existing chunk. The maximum value is limited to 90, to avoid"
 		" very long mem_rnd_alloc runs (a realloc might also free memory)." },
0ff010fa
 	{0, 0, 0, 0, 0, 0}
 };
 
 
 
 static rpc_export_t mt_rpc[];
 
 
 
 static param_export_t params[]={
 	{"check_content", PARAM_INT, &default_mt_cfg.check_content},
 	{0,0,0}
 };
 
 
 
 struct module_exports exports = {
 	"malloc_test",
 	cmds,
 	mt_rpc,        /* RPC methods */
 	params,
 	mod_init, /* module initialization function */
 	0,        /* response function*/
 	mod_destroy, /* destroy function */
 	0,        /* oncancel function */
 	0         /* per-child init function */
 };
 
 
 
 #define MC_F_CHECK_CONTENTS 1
 
 struct mem_chunk{
 	struct mem_chunk* next;
 	void* addr;
 	unsigned long size;
 	unsigned long flags;
 };
 
 struct allocated_list {
 	struct mem_chunk* chunks;
 	gen_lock_t lock;
 	volatile long size;
c05afff1
 	volatile int no;
0ff010fa
 };
 
 struct allocated_list* alloc_lst;
 
 
 struct rnd_time_test {
 	unsigned long min;
 	unsigned long max;
 	unsigned long total;
 	unsigned long crt;
 	ticks_t min_intvrl;
 	ticks_t max_intvrl;
 	ticks_t stop_time;
 	ticks_t start_time;
 	unsigned long calls;
c05afff1
 	unsigned long reallocs;
0ff010fa
 	unsigned int errs;
 	unsigned int overfl;
 	struct rnd_time_test* next;
 	struct timer_ln timer;
 	int id;
 };
 
 struct rnd_time_test_lst {
 	struct rnd_time_test* tests;
 	gen_lock_t lock;
 	volatile int last_id;
 };
 
 
 struct rnd_time_test_lst* rndt_lst;
 
 static unsigned long mem_unleak(unsigned long size);
 static void mem_destroy_all_tests();
 
 static int mod_init(void)
 {
 	WARN("This is a test/debugging module, don't use it in production\n");
 	/* declare configuration */
 	if (cfg_declare("malloc_test", malloc_test_cfg_def, &default_mt_cfg,
 					cfg_sizeof(malloc_test), &mt_cfg)){
 		ERR("failed to register the configuration\n");
 		goto error;
 	}
 	
 	alloc_lst = shm_malloc(sizeof(*alloc_lst));
 	if (alloc_lst == 0)
 		goto error;
 	alloc_lst->chunks = 0;
 	atomic_set_long(&alloc_lst->size, 0);
c05afff1
 	atomic_set_int(&alloc_lst->no, 0);
0ff010fa
 	if (lock_init(&alloc_lst->lock) == 0)
 		goto error;
 	rndt_lst = shm_malloc(sizeof(*rndt_lst));
 	if (rndt_lst == 0)
 		goto error;
 	rndt_lst->tests = 0;
 	atomic_set_int(&rndt_lst->last_id, 0);
 	if (lock_init(&rndt_lst->lock) == 0)
 		goto error;
 	return 0;
 error:
 	return -1;
 }
 
 
 
 static void mod_destroy()
 {
 	if (rndt_lst) {
 		mem_destroy_all_tests();
 		lock_destroy(&rndt_lst->lock);
 		shm_free(rndt_lst);
 		rndt_lst = 0;
 	}
 	if (alloc_lst) {
 		mem_unleak(-1);
 		lock_destroy(&alloc_lst->lock);
 		shm_free(alloc_lst);
 		alloc_lst = 0;
 	}
 }
 
 
 
 /** record a memory chunk list entry.
  * @param addr - address of the newly allocated memory
  * @oaram size - size
  * @return 0 on success, -1 on error (no more mem).
  */
 static int mem_track(void* addr, unsigned long size)
 {
 	struct mem_chunk* mc;
 	unsigned long* d;
 	unsigned long r,i;
 	
 	mc = shm_malloc(sizeof(*mc));
 	if (mc == 0) goto error;
 	mc->addr = addr;
 	mc->size = size;
 	mc->flags = 0;
 	if (cfg_get(malloc_test, mt_cfg, check_content)){
 		mc->flags |=  MC_F_CHECK_CONTENTS;
 		d = addr;
 		for (r = 0; r < size/sizeof(*d); r++){
c05afff1
 			d[r]=~(unsigned long)&d[r];
0ff010fa
 		}
 		for (i=0; i< size % sizeof(*d); i++){
c05afff1
 			((char*)&d[r])[i]=~((unsigned long)&d[r] >> i*8);
0ff010fa
 		}
 	}
 	lock_get(&alloc_lst->lock);
 		mc->next = alloc_lst->chunks;
 		alloc_lst->chunks = mc;
 	lock_release(&alloc_lst->lock);
 	atomic_add_long(&alloc_lst->size, size);
c05afff1
 	atomic_inc_int(&alloc_lst->no);
0ff010fa
 	return 0;
 error:
 	return -1;
 }
 
 
 
 /** allocate memory.
  * Allocates memory, but keeps track of it, so that mem_unleak() can
  * free it.
  * @param size - how many bytes
  * @return 0 on success, -1 on error
  */
 static int mem_leak(unsigned long size)
 {
 	void *d;
 	
 	d = shm_malloc(size);
 	if (d) {
 		if (mem_track(d, size) < 0){
 			shm_free(d);
 		}else
 			return 0;
 	}
 	return -1;
 }
 
 
 
c05afff1
 /* realloc a chunk, unsafe (requires external locking) version.
  * @return 0 on success, -1 on error
  */
 static int _mem_chunk_realloc_unsafe(struct mem_chunk *c, unsigned long size)
 {
 	unsigned long* d;
 	int r, i;
 	
 	d = shm_realloc(c->addr, size);
 	if (d) {
 		if (cfg_get(malloc_test, mt_cfg, check_content) &&
 				c->flags & MC_F_CHECK_CONTENTS) {
 			/* re-fill the test patterns (the address might have changed
 			   and they depend on it) */
 			for (r = 0; r < size/sizeof(*d); r++){
 				d[r]=~(unsigned long)&d[r];
 			}
 			for (i=0; i< size % sizeof(*d); i++){
 				((char*)&d[r])[i]=~((unsigned long)&d[r] >> i*8);
 			}
 		}
 		c->addr = d;
 		c->size = size;
 		return 0;
 	}
 	return -1;
 }
 
 
 
0ff010fa
 static void mem_chunk_free(struct mem_chunk* c)
 {
 	unsigned long* d;
 	unsigned long r,i;
 	int err;
 
 	if (cfg_get(malloc_test, mt_cfg, check_content) &&
 			c->flags & MC_F_CHECK_CONTENTS) {
 		d = c->addr;
 		err = 0;
 		for (r = 0; r < c->size/sizeof(*d); r++){
c05afff1
 			if (d[r]!=~(unsigned long)&d[r])
0ff010fa
 				err++;
c05afff1
 			d[r] = (unsigned long)&d[r]; /* fill it with something else */
0ff010fa
 		}
 		for (i=0; i< c->size % sizeof(*d); i++){
 			if (((unsigned char*)&d[r])[i] !=
c05afff1
 					(unsigned char)~((unsigned long)&d[r] >> i*8))
0ff010fa
 				err++;
c05afff1
 			((char*)&d[r])[i] = (unsigned char)((unsigned long)&d[r] >> i*8);
0ff010fa
 		}
 		if (err)
 			ERR("%d errors while checking %ld bytes at %p\n", err, c->size, d);
 	}
 	shm_free(c->addr);
 	c->addr = 0;
 	c->flags = 0;
 }
 
 
 
 /** free memory.
  * Frees previously allocated memory chunks until at least size bytes are 
  * released. Use -1 to free all,
  * @param size - at least free size bytes.
  * @return  bytes_freed (>=0)
  */
 static unsigned long mem_unleak(unsigned long size)
 {
 	struct mem_chunk** mc;
 	struct mem_chunk* t;
 	struct mem_chunk** min_chunk;
 	unsigned long freed;
c05afff1
 	unsigned int no;
0ff010fa
 	
 	freed = 0;
c05afff1
 	no = 0;
0ff010fa
 	min_chunk = 0;
 	lock_get(&alloc_lst->lock);
 	if (size>=atomic_get_long(&alloc_lst->size)){
 		/* free all */
 		for (mc = &alloc_lst->chunks; *mc; ){
 			t = *mc;
 			mem_chunk_free(t);
 			freed += t->size;
c05afff1
 			no++;
0ff010fa
 			*mc = t->next;
 			shm_free(t);
 		}
 		alloc_lst->chunks=0;
 	} else {
 		/* free at least size bytes, trying smaller chunks first */
 		for (mc = &alloc_lst->chunks; *mc && (freed < size);) {
 			if ((*mc)->size <= (size - freed)) {
 				t = *mc;
 				mem_chunk_free(t);
 				freed += t->size;
c05afff1
 				no++;
0ff010fa
 				*mc = t->next;
 				shm_free(t);
 				continue;
 			} else if (min_chunk == 0 || (*min_chunk)->size > (*mc)->size) {
 				/* find minimum remaining chunk  */
 				min_chunk = mc;
 			}
 			mc = &(*mc)->next;
 		}
 		if (size > freed && min_chunk) {
 			mc = min_chunk;
 			t = *mc;
 			mem_chunk_free(t);
 			freed += t->size;
c05afff1
 			no++;
0ff010fa
 			*mc = (*mc)->next;
 			shm_free(t);
 		}
 	}
 	lock_release(&alloc_lst->lock);
 	atomic_add_long(&alloc_lst->size, -freed);
c05afff1
 	atomic_add_int(&alloc_lst->no, -no);
0ff010fa
 	return freed;
 }
 
 
c05afff1
 
 /** realloc randomly size bytes.
  * Chooses randomly a previously allocated chunk and realloc's it.
  * @param size - size.
  * @param diff - filled with difference, >= 0 means more bytes were alloc.,
  *               < 0 means bytes were freed.
  * @return  >= 0 on success, -1 on error/ not found
  * (empty list is a valid error reason)
  */
 static int mem_rnd_realloc(unsigned long size, long* diff)
 {
 	struct mem_chunk* t;
 	int ret;
 	int target, i;
 	
 	*diff = 0;
 	ret = -1;
 	lock_get(&alloc_lst->lock);
 		target = fastrand_max(atomic_get_int(&alloc_lst->no));
 		for (t = alloc_lst->chunks, i=0; t; t=t->next, i++ ){
 			if (target == i) {
 				*diff = (long)size - (long)t->size;
 				if ((ret=_mem_chunk_realloc_unsafe(t, size)) < 0)
 					*diff = 0;
 				break;
 			}
 		}
 	lock_release(&alloc_lst->lock);
 	atomic_add_long(&alloc_lst->size, *diff);
 	return ret;
 }
 
 
 
0ff010fa
 #define MIN_ulong(a, b) \
 	(unsigned long)((unsigned long)(a)<(unsigned long)(b)?(a):(b))
 
 /*
  * Randomly alloc. total_size bytes, in chunks of size between
  * min & max. max - min should be smaller then 4G.
  * @return < 0 if there were some alloc errors, 0 on success.
  */
 static int mem_rnd_leak(unsigned long min, unsigned long max,
 						unsigned long total_size)
 {
 	unsigned long size;
 	unsigned long crt_size, crt_min;
c05afff1
 	long diff;
 	int err, p;
0ff010fa
 	
 	size = total_size;
 	err = 0;
 	while(size){
 		crt_min = MIN_ulong(min, size);
 		crt_size = fastrand_max(MIN_ulong(max, size) - crt_min) + crt_min;
c05afff1
 		p = cfg_get(malloc_test, mt_cfg, realloc_p);
 		if (p && ((fastrand_max(99) +1) <= p)){
 			if (mem_rnd_realloc(crt_size, &diff) == 0){
 				size -= diff;
 				continue;
 			} /* else fallback to normal alloc. */
 		}
0ff010fa
 		size -= crt_size;
 		err += mem_leak(crt_size) < 0;
 	}
 	return -err;
 }
 
 
 
 /* test timer */
 static ticks_t tst_timer(ticks_t ticks, struct timer_ln* tl, void* data)
 {
 	struct rnd_time_test* tst;
 	ticks_t next_int;
 	ticks_t max_int;
 	unsigned long crt_size, crt_min, remaining;
c05afff1
 	long diff;
 	int p;
0ff010fa
 	
 	tst = data;
 	
 	next_int = 0;
 	max_int = 0;
 	
 	if (tst->total <= tst->crt) {
 		mem_unleak(tst->crt);
 		tst->crt = 0;
 		tst->overfl++;
 	}
 	remaining = tst->total - tst->crt;
 	crt_min = MIN_ulong(tst->min, remaining);
 	crt_size = fastrand_max(MIN_ulong(tst->max, remaining) - crt_min) +
 				crt_min;
c05afff1
 	p = cfg_get(malloc_test, mt_cfg, realloc_p);
 	if (p && ((fastrand_max(99) +1) <= p)) {
 		if (mem_rnd_realloc(crt_size, &diff) == 0){
 			tst->crt -= diff;
 			tst->reallocs++;
 			goto skip_alloc;
 		}
 	}
0ff010fa
 	if (mem_leak(crt_size) >= 0)
 		tst->crt += crt_size;
 	else
 		tst->errs ++;
c05afff1
 skip_alloc:
0ff010fa
 	tst->calls++;
 	
 	if (TICKS_GT(tst->stop_time, ticks)) {
 		next_int = fastrand_max(tst->max_intvrl - tst->min_intvrl) +
 				tst->min_intvrl;
 		max_int = tst->stop_time - ticks;
 	} else {
 		/* stop test */
 		WARN("test %d time expired, stopping"
 				" (%d s runtime, %ld calls, %d overfl, %d errors,"
 				" crt %ld bytes)\n",
 				tst->id, TICKS_TO_S(ticks - tst->start_time),
 				tst->calls, tst->overfl, tst->errs, tst->crt);
 		mem_unleak(tst->crt);
 		/* tst->crt = 0 */;
 	}
 	
 	/* 0 means stop stop, so if next_int == 0 => stop */
 	return MIN_unsigned(next_int, max_int);
 }
 
 
 /*
  * start a malloc test of a test_time length:
  *  - randomly between min_intvrl and max_intvrl, alloc.
  *    a random number of bytes, between min & max.
  *  - if total_size is reached, free everything.
  *
a5e2e4bc
  * @returns test id (>=0) on success, -1 on error.
0ff010fa
  */
 static int mem_leak_time_test(unsigned long min, unsigned long max,
 								unsigned long total_size,
 								ticks_t min_intvrl, ticks_t max_intvrl,
 								ticks_t test_time)
 {
 	struct rnd_time_test* tst;
 	struct rnd_time_test* l;
 	ticks_t first_int;
a5e2e4bc
 	int id;
0ff010fa
 	
 	tst = shm_malloc(sizeof(*tst));
 	if (tst == 0)
 		goto error;
 	memset(tst, 0, sizeof(*tst));
a5e2e4bc
 	id = tst->id = atomic_add_int(&rndt_lst->last_id, 1);
0ff010fa
 	tst->min = min;
 	tst->max = max;
 	tst-> total = total_size;
 	tst->min_intvrl = min_intvrl;
 	tst->max_intvrl = max_intvrl;
 	tst->start_time = get_ticks_raw();
 	tst->stop_time = get_ticks_raw() + test_time;
 	first_int = fastrand_max(max_intvrl - min_intvrl) + min_intvrl;
 	timer_init(&tst->timer, tst_timer, tst, 0);
 	lock_get(&rndt_lst->lock);
 		tst->next=rndt_lst->tests;
 		rndt_lst->tests=tst;
 	lock_release(&rndt_lst->lock);
 	if (timer_add(&tst->timer, MIN_unsigned(first_int, test_time)) < 0 )
 		goto error;
a5e2e4bc
 	return id;
0ff010fa
 error:
 	if (tst) {
 		lock_get(&rndt_lst->lock);
 			for (l=rndt_lst->tests; l; l=l->next)
 				if (l->next == tst) {
 					l->next = tst->next;
 					break;
 				}
 		lock_release(&rndt_lst->lock);
 		shm_free(tst);
 	}
 	return -1;
 }
 
 
 static int is_mem_test_stopped(struct rnd_time_test* tst)
 {
 	return TICKS_LE(tst->stop_time, get_ticks_raw());
 }
 
 /** stops test tst.
  * @return 0 on success, -1 on error (test already stopped)
  */
 static int mem_test_stop_tst(struct rnd_time_test* tst)
 {
 	if (!is_mem_test_stopped(tst)) {
 		if (timer_del(&tst->timer) == 0) {
 			tst->stop_time=get_ticks_raw();
 			return 0;
 		}
 	}
 	return -1;
 }
 
 
 /** stops test id.
  * @return 0 on success, -1 on error (not found).
  */
 static int mem_test_stop(int id)
 {
 	struct rnd_time_test* tst;
 	
 	lock_get(&rndt_lst->lock);
 		for (tst = rndt_lst->tests; tst; tst = tst->next)
 			if (tst->id == id) {
 				mem_test_stop_tst(tst);
 				break;
 			}
 	lock_release(&rndt_lst->lock);
 	return -(tst == 0);
 }
 
 
 static void mem_destroy_all_tests()
 {
 	struct rnd_time_test* tst;
 	struct rnd_time_test* nxt;
 	
 	lock_get(&rndt_lst->lock);
 		for (tst = rndt_lst->tests; tst;) {
 			nxt = tst->next;
 			mem_test_stop_tst(tst);
 			shm_free(tst);
 			tst = nxt;
 		}
 		rndt_lst->tests = 0;
 	lock_release(&rndt_lst->lock);
 }
 
 
 static int mem_test_destroy(int id)
 {
 	struct rnd_time_test* tst;
 	struct rnd_time_test** crt_lnk;
 	
 	lock_get(&rndt_lst->lock);
 		for (tst = 0, crt_lnk = &rndt_lst->tests; *crt_lnk;
 				crt_lnk = &(*crt_lnk)->next)
 			if ((*crt_lnk)->id == id) {
 				tst=*crt_lnk;
 				mem_test_stop_tst(tst);
 				*crt_lnk=tst->next;
 				shm_free(tst);
 				break;
 			}
 	lock_release(&rndt_lst->lock);
 	return -(tst == 0);
 }
 
 /* script functions: */
 
 
 static int mt_mem_alloc_f(struct sip_msg* msg, char* sz, char* foo)
 {
 	int size;
 	
 	if (sz == 0 || get_int_fparam(&size, msg, (fparam_t*)sz) < 0)
 		return -1;
 	return mem_leak(size)>=0?1:-1;
 }
 
 
 
 static int mt_mem_free_f(struct sip_msg* msg, char* sz, char* foo)
 {
 	int size;
 	unsigned long freed;
 	
 	size=-1;
 	if (sz != 0 && get_int_fparam(&size, msg, (fparam_t*)sz) < 0)
 		return -1;
 	freed=mem_unleak(size);
 	return (freed==0)?1:freed;
 }
 
 
 
 /* RPC exports: */
 
 
 
 /* helper functions, parses an optional b[ytes]|k|m|g to a numeric shift value
    (e.g. b -> 0, k -> 10, ...)
    returns bit shift value on success, -1 on error
 */
 static int rpc_get_size_mod(rpc_t* rpc, void* c)
 {
 	char* m;
 	
 	if (rpc->scan(c, "*s", &m) > 0) {
 		switch(*m) {
 			case 'b':
 			case 'B':
 				return 0;
 			case 'k':
 			case 'K':
 				return 10;
 			case 'm':
 			case 'M':
 				return 20;
 			case 'g':
 			case 'G':
 				return 30;
 			default:
 				rpc->fault(c, 500, "bad param use b|k|m|g");
 				return -1;
 		}
 	}
 	return 0;
 }
 
 
 
 static const char* rpc_mt_alloc_doc[2] = {
 	"Allocates the specified number of bytes (debugging/test function)."
 	"Use b|k|m|g to specify the desired size unit",
 	0
 };
 
 static void rpc_mt_alloc(rpc_t* rpc, void* c)
 {
 	int size;
 	int rs;
 	
 	if (rpc->scan(c, "d", &size) < 1) {
 		return;
 	}
 	rs=rpc_get_size_mod(rpc, c);
 	if (rs<0)
 		/* fault already generated on rpc_get_size_mod() error */
 		return;
 	if (mem_leak((unsigned long)size << rs) < 0) {
 		rpc->fault(c, 400, "memory allocation failed");
 	}
 	return;
 }
 
 
c05afff1
 static const char* rpc_mt_realloc_doc[2] = {
 	"Reallocates the specified number of bytes from a pre-allocated"
 	" randomly selected memory chunk. If no pre-allocated memory"
 	" chunks exists, it will fail."
 	" Make sure mt.mem_used is non 0 or call mt.mem_alloc prior to calling"
 	" this function."
 	" Returns the difference in bytes (<0 if bytes were freed, >0 if more"
 	" bytes were allocated)."
 	"Use b|k|m|g to specify the desired size unit",
 	0
 };
 
 static void rpc_mt_realloc(rpc_t* rpc, void* c)
 {
 	int size;
 	int rs;
 	long diff;
 	
 	if (rpc->scan(c, "d", &size) < 1) {
 		return;
 	}
 	rs=rpc_get_size_mod(rpc, c);
 	if (rs<0)
 		/* fault already generated on rpc_get_size_mod() error */
 		return;
 	if (mem_rnd_realloc((unsigned long)size << rs, &diff) < 0) {
 		rpc->fault(c, 400, "memory allocation failed");
 	}
 	rpc->add(c, "d", diff >> rs);
 	return;
 }
 
 
0ff010fa
 static const char* rpc_mt_free_doc[2] = {
 	"Frees the specified number of bytes, previously allocated by one of the"
 	" other malloc_test functions (e.g. mt.mem_alloc or the script "
 	"mt_mem_alloc). Use b|k|m|g to specify the desired size unit."
 	"Returns the number of bytes freed (can be higher or"
 	 " smaller then the requested size)",
 	0
 };
 
 
 static void rpc_mt_free(rpc_t* rpc, void* c)
 {
 	int size;
 	int rs;
 	
 	size = -1;
 	rs = 0;
 	if (rpc->scan(c, "*d", &size) > 0) {
 		/* found size, look if a size modifier is present */
 		rs=rpc_get_size_mod(rpc, c);
 		if (rs<0)
 			/* fault already generated on rpc_get_size_mod() error */
 			return;
 	}
 	rpc->add(c, "d", (int)(mem_unleak((unsigned long)size << rs) >> rs));
 	return;
 }
 
 
 
 static const char* rpc_mt_used_doc[2] = {
c05afff1
 	"Returns how many memory chunks and how many bytes are currently"
 	" allocated via the mem_alloc module functions."
 	" Use b|k|m|g to specify the desired size unit.",
0ff010fa
 	0
 };
 
 
 static void rpc_mt_used(rpc_t* rpc, void* c)
 {
 	int rs;
 	
 	rs = 0;
 	rs=rpc_get_size_mod(rpc, c);
 	if (rs<0)
 		/* fault already generated on rpc_get_size_mod() error */
 		return;
c05afff1
 	rpc->add(c, "d", atomic_get_int(&alloc_lst->no));
0ff010fa
 	rpc->add(c, "d", (int)(atomic_get_long(&alloc_lst->size) >> rs));
 	return;
 }
 
 
 static const char* rpc_mt_rnd_alloc_doc[2] = {
 	"Takes 4 parameters: min, max, total_size and an optional unit (b|k|m|g)."
 	" It will allocate total_size memory, in pieces of random size between"
 	"min .. max (inclusive).",
 	0
 };
 
 
 static void rpc_mt_rnd_alloc(rpc_t* rpc, void* c)
 {
 	int min, max, total_size;
 	int rs;
c05afff1
 	int err;
0ff010fa
 	
 	if (rpc->scan(c, "ddd", &min, &max, &total_size) < 3) {
 		return;
 	}
 	rs=rpc_get_size_mod(rpc, c);
 	if (rs<0)
 		/* fault already generated on rpc_get_size_mod() error */
 		return;
 	if (min > max || min < 0 || max > total_size) {
 		rpc->fault(c, 400, "invalid parameter values");
 		return;
 	}
c05afff1
 	if ((err=mem_rnd_leak((unsigned long)min << rs,
 						 (unsigned long)max << rs,
 						 (unsigned long)total_size <<rs )) < 0) {
 		rpc->fault(c, 400, "memory allocation failed (%d errors)", -err);
0ff010fa
 	}
 	return;
 }
 
 
 static const char* rpc_mt_test_start_doc[2] = {
 	"Takes 7 parameters: min, max, total_size, min_interval, max_interval, "
 	"test_time and an optional size unit (b|k|m|g). All the time units are ms."
 	" It will run a memory allocation test for test_time ms. At a random"
 	" interval between min_interval and max_interval ms. it will allocate a"
 	" memory chunk with random size, between min and max. Each time total_size"
 	" is reached, it will free all the memory allocated and start again."
 	"Returns the test id (integer)",
 	0
 };
 
 
 static void rpc_mt_test_start(rpc_t* rpc, void* c)
 {
 	int min, max, total_size;
 	int min_intvrl, max_intvrl, total_time;
 	int rs;
a5e2e4bc
 	int id;
0ff010fa
 	
 	if (rpc->scan(c, "dddddd", &min, &max, &total_size,
 								&min_intvrl, &max_intvrl, &total_time) < 6) {
 		return;
 	}
 	rs=rpc_get_size_mod(rpc, c);
 	if (rs<0)
 		/* fault already generated on rpc_get_size_mod() error */
 		return;
 	if (min > max || min < 0 || max > total_size) {
 		rpc->fault(c, 400, "invalid size parameters values");
 		return;
 	}
 	if (min_intvrl > max_intvrl || min_intvrl <= 0 || max_intvrl > total_time){
 		rpc->fault(c, 400, "invalid time intervals values");
 		return;
 	}
a5e2e4bc
 	if ((id=mem_leak_time_test((unsigned long)min << rs,
0ff010fa
 					 (unsigned long)max << rs,
 					 (unsigned long)total_size <<rs,
 					 MS_TO_TICKS(min_intvrl),
 					 MS_TO_TICKS(max_intvrl),
 					 MS_TO_TICKS(total_time)
a5e2e4bc
 					 )) < 0) {
0ff010fa
 		rpc->fault(c, 400, "memory allocation failed");
a5e2e4bc
 	} else {
 		rpc->add(c, "d", id);
0ff010fa
 	}
 	return;
 }
 
 
 static const char* rpc_mt_test_stop_doc[2] = {
 	"Takes 1 parameter: the test id. It will stop the corresponding test."
 	"Note: the test is stopped, but not destroyed." ,
 	0
 };
 
 
 static void rpc_mt_test_stop(rpc_t* rpc, void* c)
 {
 	int id;
 	
 	if (rpc->scan(c, "d", &id) < 1) {
 		return;
 	}
 	if (mem_test_stop(id)<0) {
 		rpc->fault(c, 400, "test %d not found", id);
 	}
 	return;
 }
 
 
 static const char* rpc_mt_test_destroy_doc[2] = {
 	"Takes 1 parameter: the test id. It will destroy the corresponding test.",
 	0
 };
 
 
 static void rpc_mt_test_destroy(rpc_t* rpc, void* c)
 {
 	int id;
 	
 	if (rpc->scan(c, "*d", &id) > 0 && id!=-1) {
 		if (mem_test_destroy(id) < 0 )
 			rpc->fault(c, 400, "test %d not found", id);
 	} else {
 		mem_destroy_all_tests();
 	}
 	return;
 }
 
 
 static const char* rpc_mt_test_destroy_all_doc[2] = {
 	"It will destroy all the tests (running or stopped).",
 	0
 };
 
 
 static void rpc_mt_test_destroy_all(rpc_t* rpc, void* c)
 {
 	mem_destroy_all_tests();
 	return;
 }
 
 
 static const char* rpc_mt_test_list_doc[2] = {
 	"If a test id parameter is provided it will list the corresponding test,"
a5e2e4bc
 	" else it will list all of them. Use b |k | m | g as a second parameter"
 	" for the size units (default bytes)",
0ff010fa
 	0
 };
 
 
 static void rpc_mt_test_list(rpc_t* rpc, void* c)
 {
 	int id, rs;
 	struct rnd_time_test* tst;
 	void *h;
 	
 	rs = 0;
 	if (rpc->scan(c, "*d", &id) < 1) {
 		id = -1;
 	} else {
 		rs=rpc_get_size_mod(rpc, c);
 		if (rs < 0)
 			return;
 	}
 	lock_get(&rndt_lst->lock);
 		for (tst = rndt_lst->tests; tst; tst=tst->next)
 			if (tst->id == id || id == -1) {
 				rpc->add(c, "{", &h);
c05afff1
 				rpc->struct_add(h, "ddddddddddd",
0ff010fa
 						"ID           ",  tst->id,
 						"run time (s) ", (int)TICKS_TO_S((
 											TICKS_LE(tst->stop_time,
 													get_ticks_raw()) ?
 											tst->stop_time : get_ticks_raw()) -
 													tst->start_time),
 						"remaining (s)", TICKS_LE(tst->stop_time,
 												get_ticks_raw()) ? 0 :
 										(int)TICKS_TO_S(tst->stop_time -
 														get_ticks_raw()),
c05afff1
 						"total calls  ", (int)tst->calls,
 						"reallocs     ", (int)tst->reallocs,
0ff010fa
 						"errors       ", (int)tst->errs,
 						"overflows    ", (int)tst->overfl,
 						"total alloc  ", (int)((tst->crt +
 											tst->overfl * tst->total)>>rs),
 						"min          ", (int)(tst->min>>rs),
 						"max          ", (int)(tst->max>>rs),
 						"total        ", (int)(tst->total>>rs) );
 				if (id != -1) break;
 			}
 	lock_release(&rndt_lst->lock);
 	
 	return;
 }
 
 
 static rpc_export_t mt_rpc[] = {
 	{"mt.mem_alloc", rpc_mt_alloc, rpc_mt_alloc_doc, 0},
 	{"mt.mem_free", rpc_mt_free, rpc_mt_free_doc, 0},
c05afff1
 	{"mt.mem_realloc", rpc_mt_realloc, rpc_mt_realloc_doc, 0},
0ff010fa
 	{"mt.mem_used", rpc_mt_used, rpc_mt_used_doc, 0},
 	{"mt.mem_rnd_alloc", rpc_mt_rnd_alloc, rpc_mt_rnd_alloc_doc, 0},
 	{"mt.mem_test_start", rpc_mt_test_start, rpc_mt_test_start_doc, 0},
 	{"mt.mem_test_stop", rpc_mt_test_stop, rpc_mt_test_stop_doc, 0},
 	{"mt.mem_test_destroy", rpc_mt_test_destroy, rpc_mt_test_destroy_doc, 0},
 	{"mt.mem_test_destroy_all", rpc_mt_test_destroy_all,
 								rpc_mt_test_destroy_all_doc, 0},
 	{"mt.mem_test_list", rpc_mt_test_list, rpc_mt_test_list_doc, 0},
 	{0, 0, 0, 0}
 };