3 * Memory allocators debugging/test sip-router module.
5 * Copyright (C) 2010 iptelorg GmbH
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 * 2010-03-10 initial version (andrei)
26 #include "../../sr_module.h"
27 #include "../../mem/mem.h"
28 #include "../../str.h"
29 #include "../../dprint.h"
30 #include "../../locking.h"
31 #include "../../atomic_ops.h"
32 #include "../../cfg/cfg.h"
33 #include "../../rpc.h"
34 #include "../../rand/fastrand.h"
35 #include "../../timer.h"
36 #include "../../mod_fix.h"
40 static int mt_mem_alloc_f(struct sip_msg*, char*,char*);
41 static int mt_mem_free_f(struct sip_msg*, char*,char*);
42 static int mod_init(void);
43 static void mod_destroy(void);
46 static cmd_export_t cmds[]={
47 {"mt_mem_alloc", mt_mem_alloc_f, 1, fixup_var_int_1,
48 REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE|ONSEND_ROUTE},
49 {"mt_mem_free", mt_mem_free_f, 1, fixup_var_int_1,
50 REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE|ONSEND_ROUTE},
56 struct cfg_group_malloc_test {
61 static struct cfg_group_malloc_test default_mt_cfg = {
62 0 /* check_content, off by default */
65 static void * mt_cfg = &default_mt_cfg;
67 static cfg_def_t malloc_test_cfg_def[] = {
68 {"check_content", CFG_VAR_INT | CFG_ATOMIC, 0, 1, 0, 0,
69 "check if allocated memory was overwritten by filling it with "
70 "a special pattern and checking it on free"},
76 static rpc_export_t mt_rpc[];
80 static param_export_t params[]={
81 {"check_content", PARAM_INT, &default_mt_cfg.check_content},
87 struct module_exports exports = {
90 mt_rpc, /* RPC methods */
92 mod_init, /* module initialization function */
93 0, /* response function*/
94 mod_destroy, /* destroy function */
95 0, /* oncancel function */
96 0 /* per-child init function */
101 #define MC_F_CHECK_CONTENTS 1
104 struct mem_chunk* next;
110 struct allocated_list {
111 struct mem_chunk* chunks;
116 struct allocated_list* alloc_lst;
119 struct rnd_time_test {
131 struct rnd_time_test* next;
132 struct timer_ln timer;
136 struct rnd_time_test_lst {
137 struct rnd_time_test* tests;
139 volatile int last_id;
143 struct rnd_time_test_lst* rndt_lst;
145 static unsigned long mem_unleak(unsigned long size);
146 static void mem_destroy_all_tests();
148 static int mod_init(void)
150 WARN("This is a test/debugging module, don't use it in production\n");
151 /* declare configuration */
152 if (cfg_declare("malloc_test", malloc_test_cfg_def, &default_mt_cfg,
153 cfg_sizeof(malloc_test), &mt_cfg)){
154 ERR("failed to register the configuration\n");
158 alloc_lst = shm_malloc(sizeof(*alloc_lst));
161 alloc_lst->chunks = 0;
162 atomic_set_long(&alloc_lst->size, 0);
163 if (lock_init(&alloc_lst->lock) == 0)
165 rndt_lst = shm_malloc(sizeof(*rndt_lst));
169 atomic_set_int(&rndt_lst->last_id, 0);
170 if (lock_init(&rndt_lst->lock) == 0)
179 static void mod_destroy()
182 mem_destroy_all_tests();
183 lock_destroy(&rndt_lst->lock);
189 lock_destroy(&alloc_lst->lock);
197 /** record a memory chunk list entry.
198 * @param addr - address of the newly allocated memory
200 * @return 0 on success, -1 on error (no more mem).
202 static int mem_track(void* addr, unsigned long size)
204 struct mem_chunk* mc;
208 mc = shm_malloc(sizeof(*mc));
209 if (mc == 0) goto error;
213 if (cfg_get(malloc_test, mt_cfg, check_content)){
214 mc->flags |= MC_F_CHECK_CONTENTS;
216 for (r = 0; r < size/sizeof(*d); r++){
217 d[r]=~(unsigned long)d;
219 for (i=0; i< size % sizeof(*d); i++){
220 ((char*)&d[r])[i]=~((unsigned long)d >> i*8);
223 lock_get(&alloc_lst->lock);
224 mc->next = alloc_lst->chunks;
225 alloc_lst->chunks = mc;
226 lock_release(&alloc_lst->lock);
227 atomic_add_long(&alloc_lst->size, size);
236 * Allocates memory, but keeps track of it, so that mem_unleak() can
238 * @param size - how many bytes
239 * @return 0 on success, -1 on error
241 static int mem_leak(unsigned long size)
245 d = shm_malloc(size);
247 if (mem_track(d, size) < 0){
257 static void mem_chunk_free(struct mem_chunk* c)
263 if (cfg_get(malloc_test, mt_cfg, check_content) &&
264 c->flags & MC_F_CHECK_CONTENTS) {
267 for (r = 0; r < c->size/sizeof(*d); r++){
268 if (d[r]!=~(unsigned long)d)
270 d[r] = r; /* fill it with something else */
272 for (i=0; i< c->size % sizeof(*d); i++){
273 if (((unsigned char*)&d[r])[i] !=
274 (unsigned char)~((unsigned long)d >> i*8))
276 ((char*)&d[r])[i] = (unsigned char)((unsigned long)d >> i*8);
279 ERR("%d errors while checking %ld bytes at %p\n", err, c->size, d);
289 * Frees previously allocated memory chunks until at least size bytes are
290 * released. Use -1 to free all,
291 * @param size - at least free size bytes.
292 * @return bytes_freed (>=0)
294 static unsigned long mem_unleak(unsigned long size)
296 struct mem_chunk** mc;
298 struct mem_chunk** min_chunk;
303 lock_get(&alloc_lst->lock);
304 if (size>=atomic_get_long(&alloc_lst->size)){
306 for (mc = &alloc_lst->chunks; *mc; ){
315 /* free at least size bytes, trying smaller chunks first */
316 for (mc = &alloc_lst->chunks; *mc && (freed < size);) {
317 if ((*mc)->size <= (size - freed)) {
324 } else if (min_chunk == 0 || (*min_chunk)->size > (*mc)->size) {
325 /* find minimum remaining chunk */
330 if (size > freed && min_chunk) {
339 lock_release(&alloc_lst->lock);
340 atomic_add_long(&alloc_lst->size, -freed);
345 #define MIN_ulong(a, b) \
346 (unsigned long)((unsigned long)(a)<(unsigned long)(b)?(a):(b))
349 * Randomly alloc. total_size bytes, in chunks of size between
350 * min & max. max - min should be smaller then 4G.
351 * @return < 0 if there were some alloc errors, 0 on success.
353 static int mem_rnd_leak(unsigned long min, unsigned long max,
354 unsigned long total_size)
357 unsigned long crt_size, crt_min;
363 crt_min = MIN_ulong(min, size);
364 crt_size = fastrand_max(MIN_ulong(max, size) - crt_min) + crt_min;
366 err += mem_leak(crt_size) < 0;
374 static ticks_t tst_timer(ticks_t ticks, struct timer_ln* tl, void* data)
376 struct rnd_time_test* tst;
379 unsigned long crt_size, crt_min, remaining;
386 if (tst->total <= tst->crt) {
387 mem_unleak(tst->crt);
391 remaining = tst->total - tst->crt;
392 crt_min = MIN_ulong(tst->min, remaining);
393 crt_size = fastrand_max(MIN_ulong(tst->max, remaining) - crt_min) +
395 if (mem_leak(crt_size) >= 0)
396 tst->crt += crt_size;
401 if (TICKS_GT(tst->stop_time, ticks)) {
402 next_int = fastrand_max(tst->max_intvrl - tst->min_intvrl) +
404 max_int = tst->stop_time - ticks;
407 WARN("test %d time expired, stopping"
408 " (%d s runtime, %ld calls, %d overfl, %d errors,"
410 tst->id, TICKS_TO_S(ticks - tst->start_time),
411 tst->calls, tst->overfl, tst->errs, tst->crt);
412 mem_unleak(tst->crt);
416 /* 0 means stop stop, so if next_int == 0 => stop */
417 return MIN_unsigned(next_int, max_int);
422 * start a malloc test of a test_time length:
423 * - randomly between min_intvrl and max_intvrl, alloc.
424 * a random number of bytes, between min & max.
425 * - if total_size is reached, free everything.
427 * @returns 0 on success, -1 on error.
429 static int mem_leak_time_test(unsigned long min, unsigned long max,
430 unsigned long total_size,
431 ticks_t min_intvrl, ticks_t max_intvrl,
434 struct rnd_time_test* tst;
435 struct rnd_time_test* l;
438 tst = shm_malloc(sizeof(*tst));
441 memset(tst, 0, sizeof(*tst));
442 tst->id = atomic_add_int(&rndt_lst->last_id, 1);
445 tst-> total = total_size;
446 tst->min_intvrl = min_intvrl;
447 tst->max_intvrl = max_intvrl;
448 tst->start_time = get_ticks_raw();
449 tst->stop_time = get_ticks_raw() + test_time;
450 first_int = fastrand_max(max_intvrl - min_intvrl) + min_intvrl;
451 timer_init(&tst->timer, tst_timer, tst, 0);
452 lock_get(&rndt_lst->lock);
453 tst->next=rndt_lst->tests;
455 lock_release(&rndt_lst->lock);
456 if (timer_add(&tst->timer, MIN_unsigned(first_int, test_time)) < 0 )
461 lock_get(&rndt_lst->lock);
462 for (l=rndt_lst->tests; l; l=l->next)
463 if (l->next == tst) {
467 lock_release(&rndt_lst->lock);
474 static int is_mem_test_stopped(struct rnd_time_test* tst)
476 return TICKS_LE(tst->stop_time, get_ticks_raw());
480 * @return 0 on success, -1 on error (test already stopped)
482 static int mem_test_stop_tst(struct rnd_time_test* tst)
484 if (!is_mem_test_stopped(tst)) {
485 if (timer_del(&tst->timer) == 0) {
486 tst->stop_time=get_ticks_raw();
495 * @return 0 on success, -1 on error (not found).
497 static int mem_test_stop(int id)
499 struct rnd_time_test* tst;
501 lock_get(&rndt_lst->lock);
502 for (tst = rndt_lst->tests; tst; tst = tst->next)
504 mem_test_stop_tst(tst);
507 lock_release(&rndt_lst->lock);
512 static void mem_destroy_all_tests()
514 struct rnd_time_test* tst;
515 struct rnd_time_test* nxt;
517 lock_get(&rndt_lst->lock);
518 for (tst = rndt_lst->tests; tst;) {
520 mem_test_stop_tst(tst);
525 lock_release(&rndt_lst->lock);
529 static int mem_test_destroy(int id)
531 struct rnd_time_test* tst;
532 struct rnd_time_test** crt_lnk;
534 lock_get(&rndt_lst->lock);
535 for (tst = 0, crt_lnk = &rndt_lst->tests; *crt_lnk;
536 crt_lnk = &(*crt_lnk)->next)
537 if ((*crt_lnk)->id == id) {
539 mem_test_stop_tst(tst);
544 lock_release(&rndt_lst->lock);
548 /* script functions: */
551 static int mt_mem_alloc_f(struct sip_msg* msg, char* sz, char* foo)
555 if (sz == 0 || get_int_fparam(&size, msg, (fparam_t*)sz) < 0)
557 return mem_leak(size)>=0?1:-1;
562 static int mt_mem_free_f(struct sip_msg* msg, char* sz, char* foo)
568 if (sz != 0 && get_int_fparam(&size, msg, (fparam_t*)sz) < 0)
570 freed=mem_unleak(size);
571 return (freed==0)?1:freed;
580 /* helper functions, parses an optional b[ytes]|k|m|g to a numeric shift value
581 (e.g. b -> 0, k -> 10, ...)
582 returns bit shift value on success, -1 on error
584 static int rpc_get_size_mod(rpc_t* rpc, void* c)
588 if (rpc->scan(c, "*s", &m) > 0) {
603 rpc->fault(c, 500, "bad param use b|k|m|g");
612 static const char* rpc_mt_alloc_doc[2] = {
613 "Allocates the specified number of bytes (debugging/test function)."
614 "Use b|k|m|g to specify the desired size unit",
618 static void rpc_mt_alloc(rpc_t* rpc, void* c)
623 if (rpc->scan(c, "d", &size) < 1) {
626 rs=rpc_get_size_mod(rpc, c);
628 /* fault already generated on rpc_get_size_mod() error */
630 if (mem_leak((unsigned long)size << rs) < 0) {
631 rpc->fault(c, 400, "memory allocation failed");
637 static const char* rpc_mt_free_doc[2] = {
638 "Frees the specified number of bytes, previously allocated by one of the"
639 " other malloc_test functions (e.g. mt.mem_alloc or the script "
640 "mt_mem_alloc). Use b|k|m|g to specify the desired size unit."
641 "Returns the number of bytes freed (can be higher or"
642 " smaller then the requested size)",
647 static void rpc_mt_free(rpc_t* rpc, void* c)
654 if (rpc->scan(c, "*d", &size) > 0) {
655 /* found size, look if a size modifier is present */
656 rs=rpc_get_size_mod(rpc, c);
658 /* fault already generated on rpc_get_size_mod() error */
661 rpc->add(c, "d", (int)(mem_unleak((unsigned long)size << rs) >> rs));
667 static const char* rpc_mt_used_doc[2] = {
668 "Returns how many bytes are currently allocated via the mem_alloc module"
669 " functions. Use b|k|m|g to specify the desired size unit.",
674 static void rpc_mt_used(rpc_t* rpc, void* c)
679 rs=rpc_get_size_mod(rpc, c);
681 /* fault already generated on rpc_get_size_mod() error */
683 rpc->add(c, "d", (int)(atomic_get_long(&alloc_lst->size) >> rs));
688 static const char* rpc_mt_rnd_alloc_doc[2] = {
689 "Takes 4 parameters: min, max, total_size and an optional unit (b|k|m|g)."
690 " It will allocate total_size memory, in pieces of random size between"
691 "min .. max (inclusive).",
696 static void rpc_mt_rnd_alloc(rpc_t* rpc, void* c)
698 int min, max, total_size;
701 if (rpc->scan(c, "ddd", &min, &max, &total_size) < 3) {
704 rs=rpc_get_size_mod(rpc, c);
706 /* fault already generated on rpc_get_size_mod() error */
708 if (min > max || min < 0 || max > total_size) {
709 rpc->fault(c, 400, "invalid parameter values");
712 if (mem_rnd_leak((unsigned long)min << rs,
713 (unsigned long)max << rs,
714 (unsigned long)total_size <<rs ) < 0) {
715 rpc->fault(c, 400, "memory allocation failed");
721 static const char* rpc_mt_test_start_doc[2] = {
722 "Takes 7 parameters: min, max, total_size, min_interval, max_interval, "
723 "test_time and an optional size unit (b|k|m|g). All the time units are ms."
724 " It will run a memory allocation test for test_time ms. At a random"
725 " interval between min_interval and max_interval ms. it will allocate a"
726 " memory chunk with random size, between min and max. Each time total_size"
727 " is reached, it will free all the memory allocated and start again."
728 "Returns the test id (integer)",
733 static void rpc_mt_test_start(rpc_t* rpc, void* c)
735 int min, max, total_size;
736 int min_intvrl, max_intvrl, total_time;
739 if (rpc->scan(c, "dddddd", &min, &max, &total_size,
740 &min_intvrl, &max_intvrl, &total_time) < 6) {
743 rs=rpc_get_size_mod(rpc, c);
745 /* fault already generated on rpc_get_size_mod() error */
747 if (min > max || min < 0 || max > total_size) {
748 rpc->fault(c, 400, "invalid size parameters values");
751 if (min_intvrl > max_intvrl || min_intvrl <= 0 || max_intvrl > total_time){
752 rpc->fault(c, 400, "invalid time intervals values");
755 if (mem_leak_time_test((unsigned long)min << rs,
756 (unsigned long)max << rs,
757 (unsigned long)total_size <<rs,
758 MS_TO_TICKS(min_intvrl),
759 MS_TO_TICKS(max_intvrl),
760 MS_TO_TICKS(total_time)
762 rpc->fault(c, 400, "memory allocation failed");
768 static const char* rpc_mt_test_stop_doc[2] = {
769 "Takes 1 parameter: the test id. It will stop the corresponding test."
770 "Note: the test is stopped, but not destroyed." ,
775 static void rpc_mt_test_stop(rpc_t* rpc, void* c)
779 if (rpc->scan(c, "d", &id) < 1) {
782 if (mem_test_stop(id)<0) {
783 rpc->fault(c, 400, "test %d not found", id);
789 static const char* rpc_mt_test_destroy_doc[2] = {
790 "Takes 1 parameter: the test id. It will destroy the corresponding test.",
795 static void rpc_mt_test_destroy(rpc_t* rpc, void* c)
799 if (rpc->scan(c, "*d", &id) > 0 && id!=-1) {
800 if (mem_test_destroy(id) < 0 )
801 rpc->fault(c, 400, "test %d not found", id);
803 mem_destroy_all_tests();
809 static const char* rpc_mt_test_destroy_all_doc[2] = {
810 "It will destroy all the tests (running or stopped).",
815 static void rpc_mt_test_destroy_all(rpc_t* rpc, void* c)
817 mem_destroy_all_tests();
822 static const char* rpc_mt_test_list_doc[2] = {
823 "If a test id parameter is provided it will list the corresponding test,"
824 " else it will list all of them",
829 static void rpc_mt_test_list(rpc_t* rpc, void* c)
832 struct rnd_time_test* tst;
836 if (rpc->scan(c, "*d", &id) < 1) {
839 rs=rpc_get_size_mod(rpc, c);
843 lock_get(&rndt_lst->lock);
844 for (tst = rndt_lst->tests; tst; tst=tst->next)
845 if (tst->id == id || id == -1) {
846 rpc->add(c, "{", &h);
847 rpc->struct_add(h, "dddddddddd",
849 "run time (s) ", (int)TICKS_TO_S((
850 TICKS_LE(tst->stop_time,
852 tst->stop_time : get_ticks_raw()) -
854 "remaining (s)", TICKS_LE(tst->stop_time,
855 get_ticks_raw()) ? 0 :
856 (int)TICKS_TO_S(tst->stop_time -
858 "allocations ", (int)tst->calls,
859 "errors ", (int)tst->errs,
860 "overflows ", (int)tst->overfl,
861 "total alloc ", (int)((tst->crt +
862 tst->overfl * tst->total)>>rs),
863 "min ", (int)(tst->min>>rs),
864 "max ", (int)(tst->max>>rs),
865 "total ", (int)(tst->total>>rs) );
868 lock_release(&rndt_lst->lock);
874 static rpc_export_t mt_rpc[] = {
875 {"mt.mem_alloc", rpc_mt_alloc, rpc_mt_alloc_doc, 0},
876 {"mt.mem_free", rpc_mt_free, rpc_mt_free_doc, 0},
877 {"mt.mem_used", rpc_mt_used, rpc_mt_used_doc, 0},
878 {"mt.mem_rnd_alloc", rpc_mt_rnd_alloc, rpc_mt_rnd_alloc_doc, 0},
879 {"mt.mem_test_start", rpc_mt_test_start, rpc_mt_test_start_doc, 0},
880 {"mt.mem_test_stop", rpc_mt_test_stop, rpc_mt_test_stop_doc, 0},
881 {"mt.mem_test_destroy", rpc_mt_test_destroy, rpc_mt_test_destroy_doc, 0},
882 {"mt.mem_test_destroy_all", rpc_mt_test_destroy_all,
883 rpc_mt_test_destroy_all_doc, 0},
884 {"mt.mem_test_list", rpc_mt_test_list, rpc_mt_test_list_doc, 0},