malloc_test: new module for testing/debugging memory problems
authorAndrei Pelinescu-Onciul <andrei@iptel.org>
Thu, 11 Mar 2010 18:38:04 +0000 (19:38 +0100)
committerAndrei Pelinescu-Onciul <andrei@iptel.org>
Thu, 11 Mar 2010 20:16:12 +0000 (21:16 +0100)
malloc_test is a new module for stressing the memory allocators
and easily simulating out-of-memory conditions or memory leaks.

Implemented RPCs:
mt.mem_alloc size - allocates size bytes
mt.mem_free [size]  - frees at least size bytes. If size is missing
  frees everything allocated by any malloc_test test function.
mt.mem_used  - amount of currently allocated mem. by malloc_test.
mt.mem_rnd_alloc min max total [unit] - allocates total
  <unit> bytes in chunks with the size randomly chosen between min
  and max.  <unit> is optional and can be one of b-bytes, k - kb,
  m - mb, g -gb.
mt.mem_test_start min max total min_int max_int total_time [unit]
  starts a malloc test that will take total_time to execute.
  Memory allocations will be performed at intervals randomly
  chosen between min_int and max_int (in ms). Each allocation will
  have a randomly chosen size between min and max <unit> bytes.
  After total <unit> bytes are allocated, everything is
  released/freed again and the allocations are restarted. The
  total_time is expressed in milliseconds.
  Several tests can be run in the same time.
mt.mem_test_stop id - stops the test identified by id.
mt.mem_test_destroy id - destroys the test identified by id
   (besides stopping it, it also frees all the data, including the
   statistics).
mt.mem_test_destroy_all - destroys all the running or stopped
   tests.
mt.mem_test_list [id] - Prints data about test id (running time,
  total allocations, errors a.s.o.). If id is missing, it will lists
  all the tests.

Script functions:
mt_mem_alloc(size) - equivalent to the mt.mem_alloc RPC.
mt_mem_free(size) - equivalent to the mt.mem_free RPC.

modules/malloc_test/Makefile [new file with mode: 0644]
modules/malloc_test/doc/Makefile [new file with mode: 0644]
modules/malloc_test/doc/functions.xml [new file with mode: 0644]
modules/malloc_test/doc/malloc_test.xml [new file with mode: 0644]
modules/malloc_test/doc/params.xml [new file with mode: 0644]
modules/malloc_test/malloc_test.c [new file with mode: 0644]

diff --git a/modules/malloc_test/Makefile b/modules/malloc_test/Makefile
new file mode 100644 (file)
index 0000000..a83936d
--- /dev/null
@@ -0,0 +1,15 @@
+# $Id$
+#
+#malloc_test module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=malloc_test.so
+LIBS=
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules
diff --git a/modules/malloc_test/doc/Makefile b/modules/malloc_test/doc/Makefile
new file mode 100644 (file)
index 0000000..ea5c788
--- /dev/null
@@ -0,0 +1,4 @@
+docs = malloc_test.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module
diff --git a/modules/malloc_test/doc/functions.xml b/modules/malloc_test/doc/functions.xml
new file mode 100644 (file)
index 0000000..8b69d02
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="malloc_test.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+       <sectioninfo>
+       <revhistory>
+               <revision>
+               <revnumber>$Revision$</revnumber>
+               <date>$Date$</date>
+               </revision>
+       </revhistory>
+       </sectioninfo>
+
+       <title>Functions</title>
+
+       <section id="malloc_test.mt_mem_alloc">
+               <title><function>mt_mem_alloc(size)</function></title>
+               <para>
+                       Allocates size bytes.
+               </para>
+               <note><para>This is a debugging function for simulating memory
+                                       leaks or stressing the memory allocator. It should not
+                                       be used in production setups
+               </para></note>
+               <example>
+                       <title><function>men_alloc</function> usage</title>
+                       <programlisting>
+...
+mem_alloc(1048576); # 1MB
+...
+                       </programlisting>
+               </example>
+       </section>
+
+       <section id="malloc_test.mt_mem_free">
+               <title><function>mt_mem_free()</function></title>
+               <para>
+                       Frees all the memory allocated with mem_alloc() up to this
+                       point.
+               </para>
+               <note><para>This is a debugging function for simulating memory
+                                       leaks or stressing the memory allocator. It should not
+                                       be used in production setups
+               </para></note>
+               <example>
+                       <title><function>mem_free</function> usage</title>
+                       <programlisting>
+...
+mem_free();
+...
+                       </programlisting>
+               </example>
+       </section>
+
+</section>
diff --git a/modules/malloc_test/doc/malloc_test.xml b/modules/malloc_test/doc/malloc_test.xml
new file mode 100644 (file)
index 0000000..e53fe56
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+       "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+       [ <!ENTITY % local.common.attrib
+        "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">]
+>
+
+<section id="malloc_test" xmlns:xi="http://www.w3.org/2001/XInclude">
+       <sectioninfo>
+               <authorgroup>
+                       <author>
+                               <firstname>Andrei</firstname>
+                               <surname>Pelinescu-Onciul</surname>
+                               <affiliation><orgname>iptelorg GmbH</orgname></affiliation>
+                       <address>
+                               <email>andrei@iptel.org</email>
+                       </address>
+                       </author>
+               </authorgroup>
+               <copyright>
+                       <year>2010</year>
+                       <holder>iptelorg GmbH</holder>
+               </copyright>
+               <revhistory>
+                       <revision>
+                       <revnumber>$Revision$</revnumber>
+                       <date>$Date$</date>
+                       </revision>
+               </revhistory>
+       </sectioninfo>
+
+       <title>malloc_test Module</title>
+
+       <section id="malloc_test.overview">
+               <title>Overview</title>
+               <para>
+                       This is a debugging/test module. It implements functions (both
+                       script and rpcs) that can be used to stress the memory allocator
+                       or force memory leaks.
+               </para>
+               <warning><para>
+                       This module should never be used in a production environment.
+               </para></warning>
+       </section>
+       <xi:include href="params.xml"/>
+       <xi:include href="functions.xml"/>
+</section>
diff --git a/modules/malloc_test/doc/params.xml b/modules/malloc_test/doc/params.xml
new file mode 100644 (file)
index 0000000..3fe8396
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="malloc_test.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+       <sectioninfo>
+               <revhistory>
+                       <revision>
+                               <revnumber>$Revision$</revnumber>
+                               <date>$Date$</date>
+                       </revision>
+               </revhistory>
+       </sectioninfo>
+       <title>Parameters</title>
+
+       <section id="check_content">
+               <title><varname>check_content</varname></title>
+               <para>
+                       When doing the tests, check also for the possibility of the
+                       memory being overwritten. When activated, the allocated memory
+                       will be filled with a special pattern, that will be checked on
+                       free.
+               </para>
+               <para>
+                       Default: 0 (off).
+               </para>
+               <para>
+                       It can be changed also at runtime, via the rpc interface.
+               </para>
+               <example>
+                       <title>
+                               Set <varname>check_content</varname> in the config file
+                       </title>
+                       <programlisting>
+modparam("malloc_test", "check_content", 1)
+                       </programlisting>
+               </example>
+               <example>
+                       <title>
+                               Set <varname>check_content</varname> at runtime via sercmd
+                       </title>
+                       <programlisting>
+$ sercmd cfg.set_now_int malloc_test check_content 1
+                       </programlisting>
+               </example>
+       </section>
+
+</section>
diff --git a/modules/malloc_test/malloc_test.c b/modules/malloc_test/malloc_test.c
new file mode 100644 (file)
index 0000000..6cca8c8
--- /dev/null
@@ -0,0 +1,887 @@
+/*$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;
+};
+
+
+static struct cfg_group_malloc_test default_mt_cfg = {
+       0 /* check_content, off by default */
+};
+
+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 "
+               "a special pattern and checking it on free"},
+       {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;
+};
+
+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;
+       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);
+       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++){
+                       d[r]=~(unsigned long)d;
+               }
+               for (i=0; i< size % sizeof(*d); i++){
+                       ((char*)&d[r])[i]=~((unsigned long)d >> i*8);
+               }
+       }
+       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);
+       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;
+}
+
+
+
+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++){
+                       if (d[r]!=~(unsigned long)d)
+                               err++;
+                       d[r] = r; /* fill it with something else */
+               }
+               for (i=0; i< c->size % sizeof(*d); i++){
+                       if (((unsigned char*)&d[r])[i] !=
+                                       (unsigned char)~((unsigned long)d >> i*8))
+                               err++;
+                       ((char*)&d[r])[i] = (unsigned char)((unsigned long)d >> i*8);
+               }
+               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;
+       
+       freed = 0;
+       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;
+                       *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;
+                               *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;
+                       *mc = (*mc)->next;
+                       shm_free(t);
+               }
+       }
+       lock_release(&alloc_lst->lock);
+       atomic_add_long(&alloc_lst->size, -freed);
+       return freed;
+}
+
+
+#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;
+       int err;
+       
+       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;
+               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;
+       
+       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;
+       if (mem_leak(crt_size) >= 0)
+               tst->crt += crt_size;
+       else
+               tst->errs ++;
+       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.
+ *
+ * @returns 0 on success, -1 on error.
+ */
+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;
+       
+       tst = shm_malloc(sizeof(*tst));
+       if (tst == 0)
+               goto error;
+       memset(tst, 0, sizeof(*tst));
+       tst->id = atomic_add_int(&rndt_lst->last_id, 1);
+       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;
+       return 0;
+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;
+}
+
+
+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] = {
+       "Returns how many bytes are currently allocated via the mem_alloc module"
+       " functions. Use b|k|m|g to specify the desired size unit.",
+       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;
+       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;
+       
+       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;
+       }
+       if (mem_rnd_leak((unsigned long)min << rs,
+                                        (unsigned long)max << rs,
+                                        (unsigned long)total_size <<rs ) < 0) {
+               rpc->fault(c, 400, "memory allocation failed");
+       }
+       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;
+       
+       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;
+       }
+       if (mem_leak_time_test((unsigned long)min << rs,
+                                        (unsigned long)max << rs,
+                                        (unsigned long)total_size <<rs,
+                                        MS_TO_TICKS(min_intvrl),
+                                        MS_TO_TICKS(max_intvrl),
+                                        MS_TO_TICKS(total_time)
+                                        ) < 0) {
+               rpc->fault(c, 400, "memory allocation failed");
+       }
+       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,"
+       " else it will list all of them",
+       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);
+                               rpc->struct_add(h, "dddddddddd",
+                                               "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()),
+                                               "allocations  ", (int)tst->calls,
+                                               "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},
+       {"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}
+};
+