mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #20363 from mguetschow/malloc-monitor
Monitoring malloc usage
This commit is contained in:
commit
a9d052bc32
@ -259,7 +259,11 @@ PSEUDOMODULES += libc_gettimeofday
|
||||
## @defgroup pseudomodule_malloc_tracing malloc_tracing
|
||||
## @brief Debug dynamic memory management by hooking in a print into each call
|
||||
## of malloc(), calloc(), realloc() and free
|
||||
## @{
|
||||
## @deprecated Use module `malloc_monitor` with verbous configuration instead;
|
||||
## will be removed after 2024.07 release.
|
||||
PSEUDOMODULES += malloc_tracing
|
||||
## @}
|
||||
|
||||
## @defgroup pseudomodule_mpu_stack_guard mpu_stack_guard
|
||||
## @brief MPU based stack guard
|
||||
|
@ -79,6 +79,7 @@ rsource "evtimer/Kconfig"
|
||||
rsource "log_color/Kconfig"
|
||||
rsource "log_printfnoformat/Kconfig"
|
||||
rsource "luid/Kconfig"
|
||||
rsource "malloc_monitor/Kconfig"
|
||||
rsource "malloc_thread_safe/Kconfig"
|
||||
rsource "matstat/Kconfig"
|
||||
rsource "memarray/Kconfig"
|
||||
|
59
sys/include/malloc_monitor.h
Normal file
59
sys/include/malloc_monitor.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2024 TU Dresden
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
/**
|
||||
* @ingroup sys_malloc_monitor
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef MALLOC_MONITOR_H
|
||||
#define MALLOC_MONITOR_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "architecture.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Obtain current heap memory usage.
|
||||
*
|
||||
* @return current heap memory usage in bytes
|
||||
*/
|
||||
size_t malloc_monitor_get_usage_current(void);
|
||||
|
||||
/**
|
||||
* @brief Obtain maximum heap memory usage since last call to
|
||||
* @ref malloc_monitor_reset_high_watermark().
|
||||
*
|
||||
* @return maximum heap memory usage in bytes
|
||||
*/
|
||||
size_t malloc_monitor_get_usage_high_watermark(void);
|
||||
|
||||
/**
|
||||
* @brief Reset maximum heap memory usage.
|
||||
*
|
||||
* After calling this function, @ref malloc_monitor_get_usage_high_watermark()
|
||||
* will return @ref malloc_monitor_get_usage_current() until further changes
|
||||
* to heap memory usage.
|
||||
*/
|
||||
void malloc_monitor_reset_high_watermark(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MALLOC_MONITOR_H */
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
73
sys/include/malloc_monitor_internal.h
Normal file
73
sys/include/malloc_monitor_internal.h
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2024 TU Dresden
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
/**
|
||||
* @defgroup sys_malloc_monitor_internals Heap Memory Usage Monitor internals
|
||||
* @ingroup sys_malloc_monitor
|
||||
* @{
|
||||
*
|
||||
* @brief internals for monitoring heap memory usage (calls to malloc/calloc/realloc/free)
|
||||
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
|
||||
*/
|
||||
|
||||
#ifndef MALLOC_MONITOR_INTERNAL_H
|
||||
#define MALLOC_MONITOR_INTERNAL_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "architecture.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Record malloc/calloc/realloc call increasing heap usage.
|
||||
*
|
||||
* @param[in] ptr pointer to newly allocated memory
|
||||
* @param[in] size size of newly allocated memory
|
||||
* @param[in] pc PC of calling function
|
||||
* @param[in] func_prefix prefix identifying memory function, one of "m","c","re"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
void malloc_monitor_add(void *ptr, size_t size, uinttxtptr_t pc, char *func_prefix);
|
||||
|
||||
/**
|
||||
* @brief Record free/realloc call decreasing heap usage.
|
||||
*
|
||||
* @param[in] ptr pointer to memory that is being freed
|
||||
* @param[in] pc PC of calling function
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
void malloc_monitor_rm(void *ptr, uinttxtptr_t pc);
|
||||
|
||||
/**
|
||||
* @brief Record realloc call either increasing or decreasing heap usage.
|
||||
*
|
||||
* @param[in] ptr_old pointer to previously allocated memory
|
||||
* @param[in] ptr_new pointer to newly allocated memory
|
||||
* @param[in] size_new size of newly allocated memory
|
||||
* @param[in] pc PC of calling function
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
void malloc_monitor_mv(void *ptr_old, void *ptr_new, size_t size_new, uinttxtptr_t pc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MALLOC_MONITOR_INTERNAL_H */
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
23
sys/malloc_monitor/Kconfig
Normal file
23
sys/malloc_monitor/Kconfig
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright (C) 2024 TU Dresden
|
||||
#
|
||||
# This file is subject to the terms and conditions of the GNU Lesser
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
#
|
||||
|
||||
menuconfig MODULE_SYS_MALLOC_MONITOR
|
||||
bool "Heap Memory Usage Monitor"
|
||||
|
||||
config MODULE_SYS_MALLOC_MONITOR_SIZE
|
||||
int "Monitor Size"
|
||||
default 100
|
||||
depends on MODULE_SYS_MALLOC_MONITOR
|
||||
help
|
||||
Specifies maximum number of pointers that can be monitored at once.
|
||||
|
||||
config MODULE_SYS_MALLOC_MONITOR_VERBOSE
|
||||
bool "Verbose"
|
||||
default false
|
||||
depends on MODULE_SYS_MALLOC_MONITOR
|
||||
help
|
||||
Print detailed log of calls to malloc/realloc/free
|
1
sys/malloc_monitor/Makefile
Normal file
1
sys/malloc_monitor/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
1
sys/malloc_monitor/Makefile.dep
Normal file
1
sys/malloc_monitor/Makefile.dep
Normal file
@ -0,0 +1 @@
|
||||
USEMODULE += malloc_thread_safe
|
72
sys/malloc_monitor/doc.txt
Normal file
72
sys/malloc_monitor/doc.txt
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @defgroup sys_malloc_monitor Heap Memory Usage Monitor
|
||||
* @ingroup sys_memory_management
|
||||
* @brief This module allows to monitor the dynamic memory usage of a certain piece of code.
|
||||
* @warning This module automatically selects @ref sys_malloc_ts and naturally
|
||||
* incurs a certain runtime overhead. It is not meant for production usage.
|
||||
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
|
||||
*
|
||||
* # Description
|
||||
*
|
||||
* This module allows to monitor the dynamic memory usage of a certain piece of code.
|
||||
* It works by hooking into (wrappers to) @ref malloc(), @ref calloc(), @ref realloc(),
|
||||
* and @ref free() calls to internally record the current and all-time maximum heap memory usage.
|
||||
*
|
||||
* Note that in general dynamic memory management is a bad idea on the constrained devices RIOT
|
||||
* is targeting. So maybe it is better to just adapt your code to use static memory management instead.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* Enable the module with `USEMODULE += malloc_monitor`.
|
||||
*
|
||||
* Add `#include "malloc_monitor.h"` to the file in which you want to monitor dynamic memory usage.
|
||||
* Use @ref malloc_monitor_get_usage_current() to retrieve the size of the currently allocated
|
||||
* heap memory in bytes. @ref malloc_monitor_get_usage_high_watermark() returns the all-time maximum
|
||||
* since startup or the last call to @ref malloc_monitor_reset_high_watermark().
|
||||
*
|
||||
* Note that `malloc_monitor` currently has no notion of threads and will at any point in time report
|
||||
* the global dynamic memory usage, not the one used by the currently running thread.
|
||||
* Thread-safety is achieved through usage of @ref sys_malloc_ts, though.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Imagine you want to investigate the dynamic memory consumption of a certain function `func()`.
|
||||
* The following snippet could get you started:
|
||||
*
|
||||
* ```c
|
||||
* #include <stddef.h>
|
||||
* #include <stdio.h>
|
||||
*
|
||||
* #include "malloc_monitor.h"
|
||||
*
|
||||
* int main(void)
|
||||
* {
|
||||
* size_t before = malloc_monitor_get_usage_current();
|
||||
* size_t before_max = malloc_monitor_get_usage_high_watermark();
|
||||
* func();
|
||||
* size_t after = malloc_monitor_get_usage_current();
|
||||
* size_t after_max = malloc_monitor_get_usage_high_watermark();
|
||||
*
|
||||
* if (after != before) {
|
||||
* puts("func() " (after < before ? "decreased" : "increased") " global dynamic memory usage.");
|
||||
* }
|
||||
* printf("The maximal dynamic memory usage of func() was %d bytes.", after_max - before_max);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* For further usage examples, refer to the corresponding tests in `tests/sys/malloc_monitor`.
|
||||
*
|
||||
* # Configuration
|
||||
*
|
||||
* The maximum number of pointers that can be monitored at once can be set with Kconfig
|
||||
* in System > Heap Memory Usage Monitor > Monitor Size or by setting the corresponding
|
||||
* CFlag in your application's Makefile as `CFLAGS += CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE=42`.
|
||||
* It defaults to 100.
|
||||
*
|
||||
* For more fine-grained debugging of invalid calls to @ref free(), duplicated calls to @ref free(),
|
||||
* or memory leaks, the module can be configured to print information on every call to @ref malloc(),
|
||||
* @ref calloc(), @ref realloc(), or @ref free() by setting System > Heap Memory Usage Monitor > Verbose
|
||||
* or adding `CFLAGS += CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE=1` to your Makefile.
|
||||
* `malloc_monitor` defaults to be non-verbose.
|
||||
*
|
||||
*/
|
170
sys/malloc_monitor/malloc_monitor.c
Normal file
170
sys/malloc_monitor/malloc_monitor.c
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2024 TU Dresden
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief monitor heap memory usage (calls to malloc/realloc/free)
|
||||
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "architecture.h"
|
||||
#include "assert.h"
|
||||
#include "cpu.h"
|
||||
#include "irq.h"
|
||||
#include "mutex.h"
|
||||
|
||||
#include "malloc_monitor.h"
|
||||
#include "malloc_monitor_internal.h"
|
||||
|
||||
#ifndef CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE
|
||||
#define CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE 100
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
|
||||
#define CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE 0
|
||||
#endif
|
||||
|
||||
static struct {
|
||||
void *addr[CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE];
|
||||
size_t size[CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE];
|
||||
size_t current;
|
||||
size_t high_watermark;
|
||||
} malloc_monitor = {
|
||||
.addr = {NULL},
|
||||
.current = 0,
|
||||
.high_watermark = 0,
|
||||
};
|
||||
|
||||
/* guards access to malloc_monitor */
|
||||
static mutex_t _lock;
|
||||
|
||||
void malloc_monitor_add(void *ptr, size_t size, uinttxtptr_t pc, char *func_prefix)
|
||||
{
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
#if CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
|
||||
printf("%salloc(%" PRIuSIZE ") @ 0x%" PRIxTXTPTR " returned %p\n",
|
||||
func_prefix, size, pc, ptr);
|
||||
#endif
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
for (uint8_t i=0; i<CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE; i++) {
|
||||
if (malloc_monitor.addr[i] == NULL) {
|
||||
malloc_monitor.addr[i] = ptr;
|
||||
malloc_monitor.size[i] = size;
|
||||
malloc_monitor.current += size;
|
||||
if (malloc_monitor.current > malloc_monitor.high_watermark) {
|
||||
malloc_monitor.high_watermark = malloc_monitor.current;
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
printf("malloc_monitor: maximum number of pointers to be monitored "
|
||||
"(as set by CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE) exceeded.\n");
|
||||
(void)func_prefix;
|
||||
(void)pc;
|
||||
}
|
||||
|
||||
void malloc_monitor_rm(void *ptr, uinttxtptr_t pc)
|
||||
{
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
#if CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
|
||||
printf("malloc_monitor: free(%p) @ 0x%" PRIxTXTPTR " \n", ptr, pc);
|
||||
#endif
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
for (uint8_t i=0; i<CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE; i++) {
|
||||
if (malloc_monitor.addr[i] == ptr) {
|
||||
malloc_monitor.addr[i] = NULL;
|
||||
malloc_monitor.current -= malloc_monitor.size[i];
|
||||
mutex_unlock(&_lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
printf("malloc_monitor: free(%p) @ 0x%" PRIxTXTPTR " invalid\n", ptr, pc);
|
||||
}
|
||||
|
||||
void malloc_monitor_mv(void *ptr_old, void *ptr_new, size_t size_new, uinttxtptr_t pc)
|
||||
{
|
||||
if (ptr_old == NULL) {
|
||||
malloc_monitor_add(ptr_new, size_new, pc, "re");
|
||||
return;
|
||||
}
|
||||
if (size_new == 0) {
|
||||
malloc_monitor_rm(ptr_old, pc);
|
||||
return;
|
||||
}
|
||||
if (ptr_new == NULL) {
|
||||
return;
|
||||
}
|
||||
#if CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
|
||||
printf("malloc_monitor: realloc(%p, %" PRIuSIZE ") @0x%" PRIxTXTPTR " returned %p\n",
|
||||
ptr_old, size_new, pc, ptr_new);
|
||||
#endif
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
for (uint8_t i=0; i<CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE; i++) {
|
||||
if (malloc_monitor.addr[i] == ptr_old) {
|
||||
malloc_monitor.addr[i] = ptr_new;
|
||||
size_t size_old = malloc_monitor.size[i];
|
||||
malloc_monitor.size[i] = size_new;
|
||||
if (size_new > size_old) {
|
||||
malloc_monitor.current += size_new - size_old;
|
||||
if (malloc_monitor.current > malloc_monitor.high_watermark) {
|
||||
malloc_monitor.high_watermark = malloc_monitor.current;
|
||||
}
|
||||
}
|
||||
else {
|
||||
malloc_monitor.current -= size_old - size_new;
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
printf("malloc_monitor: realloc(%p) @ 0x%" PRIxTXTPTR " invalid\n", ptr_old, pc);
|
||||
}
|
||||
|
||||
size_t malloc_monitor_get_usage_current(void)
|
||||
{
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
size_t ret = malloc_monitor.current;
|
||||
mutex_unlock(&_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t malloc_monitor_get_usage_high_watermark(void)
|
||||
{
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
size_t ret = malloc_monitor.high_watermark;
|
||||
mutex_unlock(&_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void malloc_monitor_reset_high_watermark(void)
|
||||
{
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
malloc_monitor.high_watermark = malloc_monitor.current;
|
||||
mutex_unlock(&_lock);
|
||||
}
|
||||
|
||||
/** @} */
|
@ -30,4 +30,4 @@ config MODULE_MALLOC_TRACING
|
||||
|
||||
Note that generally dynamic memory management is a bad idea on the
|
||||
constrained devices RIOT is targeting. So maybe it is better to just
|
||||
adapt your code to use static memory management instead.
|
||||
adapt your code to use static memory management instead.
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
@defgroup sys_malloc_ts Thread-safe wrappers for malloc and friends
|
||||
@ingroup sys
|
||||
@ingroup sys_memory_management
|
||||
@brief This module provides wrappers for malloc, calloc, realloc and free
|
||||
that provide mutually exclusive access to those functions.
|
||||
@warning This module is automatically selected, if needed. Never add it
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "cpu.h"
|
||||
#include "irq.h"
|
||||
#include "kernel_defines.h"
|
||||
#include "malloc_monitor_internal.h"
|
||||
#include "mutex.h"
|
||||
|
||||
extern void *__real_malloc(size_t size);
|
||||
@ -41,6 +42,9 @@ void __attribute__((used)) *__wrap_malloc(size_t size)
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
void *ptr = __real_malloc(size);
|
||||
if (IS_USED(MODULE_MALLOC_MONITOR)) {
|
||||
malloc_monitor_add(ptr, size, cpu_get_caller_pc(), "m");
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
if (IS_USED(MODULE_MALLOC_TRACING)) {
|
||||
printf("malloc(%" PRIuSIZE ") @ 0x%" PRIxTXTPTR " returned %p\n",
|
||||
@ -58,6 +62,9 @@ void __attribute__((used)) __wrap_free(void *ptr)
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
__real_free(ptr);
|
||||
if (IS_USED(MODULE_MALLOC_MONITOR)) {
|
||||
malloc_monitor_rm(ptr, cpu_get_caller_pc());
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
}
|
||||
|
||||
@ -81,6 +88,9 @@ void * __attribute__((used)) __wrap_calloc(size_t nmemb, size_t size)
|
||||
|
||||
mutex_lock(&_lock);
|
||||
void *res = __real_malloc(total_size);
|
||||
if (IS_USED(MODULE_MALLOC_MONITOR)) {
|
||||
malloc_monitor_add(res, total_size, cpu_get_caller_pc(), "c");
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
if (res) {
|
||||
memset(res, 0, total_size);
|
||||
@ -104,6 +114,9 @@ void * __attribute__((used))__wrap_realloc(void *ptr, size_t size)
|
||||
assert(!irq_is_in());
|
||||
mutex_lock(&_lock);
|
||||
void *new = __real_realloc(ptr, size);
|
||||
if (IS_USED(MODULE_MALLOC_MONITOR)) {
|
||||
malloc_monitor_mv(ptr, new, size, cpu_get_caller_pc());
|
||||
}
|
||||
mutex_unlock(&_lock);
|
||||
|
||||
if (IS_USED(MODULE_MALLOC_TRACING)) {
|
||||
|
7
tests/sys/malloc_monitor/Makefile
Normal file
7
tests/sys/malloc_monitor/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
include ../Makefile.sys_common
|
||||
|
||||
USEMODULE += embunit
|
||||
|
||||
USEMODULE += malloc_monitor
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
4
tests/sys/malloc_monitor/Makefile.ci
Normal file
4
tests/sys/malloc_monitor/Makefile.ci
Normal file
@ -0,0 +1,4 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
atmega8 \
|
||||
nucleo-l011k4 \
|
||||
#
|
212
tests/sys/malloc_monitor/main.c
Normal file
212
tests/sys/malloc_monitor/main.c
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2024 TU Dresden
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup tests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Test application for MODULE_MALLOC_MONITOR
|
||||
*
|
||||
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "embUnit.h"
|
||||
|
||||
#include "malloc_monitor.h"
|
||||
|
||||
#define MALLOC_SIZE 1
|
||||
|
||||
#define TEST_MALLOC_MONITOR_SAVE \
|
||||
size_t curr = malloc_monitor_get_usage_current(); \
|
||||
size_t water = malloc_monitor_get_usage_high_watermark();
|
||||
|
||||
#define TEST_ASSERT_CURRENT(num_alloc) TEST_ASSERT_EQUAL_INT(curr+num_alloc*MALLOC_SIZE, malloc_monitor_get_usage_current());
|
||||
#define TEST_ASSERT_WATERMARK(num_alloc) TEST_ASSERT_EQUAL_INT(water+num_alloc*MALLOC_SIZE, malloc_monitor_get_usage_high_watermark());
|
||||
|
||||
/*
|
||||
* malloc and free should be reflected by `malloc_monitor_get_usage_current()`.
|
||||
* `malloc_monitor_get_usage_high_watermark()` should only be decreased on a call to
|
||||
* `malloc_monitor_reset_high_watermark()`
|
||||
*/
|
||||
static void test_malloc_free(void)
|
||||
{
|
||||
TEST_MALLOC_MONITOR_SAVE
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
|
||||
void *volatile alloc1 = malloc(MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
void *volatile alloc2 = malloc(MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc2);
|
||||
|
||||
TEST_ASSERT_CURRENT(2);
|
||||
TEST_ASSERT_WATERMARK(2);
|
||||
|
||||
free(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(2);
|
||||
|
||||
malloc_monitor_reset_high_watermark();
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
free(alloc2);
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
malloc_monitor_reset_high_watermark();
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
}
|
||||
/*
|
||||
* using calloc instead of malloc should be reflected correctly
|
||||
*/
|
||||
static void test_calloc(void)
|
||||
{
|
||||
TEST_MALLOC_MONITOR_SAVE
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
|
||||
void *volatile alloc1 = calloc(1, MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
void *volatile alloc2 = calloc(2, MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc2);
|
||||
|
||||
TEST_ASSERT_CURRENT(3);
|
||||
TEST_ASSERT_WATERMARK(3);
|
||||
|
||||
free(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(2);
|
||||
TEST_ASSERT_WATERMARK(3);
|
||||
|
||||
malloc_monitor_reset_high_watermark();
|
||||
|
||||
TEST_ASSERT_CURRENT(2);
|
||||
TEST_ASSERT_WATERMARK(2);
|
||||
|
||||
free(alloc2);
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(2);
|
||||
|
||||
malloc_monitor_reset_high_watermark();
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* using realloc instead of malloc/free should be reflected correctly
|
||||
*/
|
||||
static void test_realloc(void)
|
||||
{
|
||||
TEST_MALLOC_MONITOR_SAVE
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
|
||||
void *volatile alloc1 = realloc(NULL, MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
alloc1 = realloc(alloc1, 2*MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(2);
|
||||
TEST_ASSERT_WATERMARK(2);
|
||||
|
||||
alloc1 = realloc(alloc1, 1*MALLOC_SIZE);
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(2);
|
||||
|
||||
malloc_monitor_reset_high_watermark();
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
alloc1 = realloc(alloc1, 0*MALLOC_SIZE);
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
malloc_monitor_reset_high_watermark();
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* freeing NULL shouldn't change anything
|
||||
*/
|
||||
static void test_free_NULL(void)
|
||||
{
|
||||
TEST_MALLOC_MONITOR_SAVE
|
||||
|
||||
free(NULL);
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(0);
|
||||
|
||||
void *volatile alloc1 = malloc(MALLOC_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(1);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
|
||||
free(alloc1);
|
||||
|
||||
TEST_ASSERT_CURRENT(0);
|
||||
TEST_ASSERT_WATERMARK(1);
|
||||
}
|
||||
|
||||
static Test *tests_malloc_monitor(void)
|
||||
{
|
||||
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||
new_TestFixture(test_malloc_free),
|
||||
new_TestFixture(test_calloc),
|
||||
new_TestFixture(test_realloc),
|
||||
new_TestFixture(test_free_NULL),
|
||||
};
|
||||
|
||||
EMB_UNIT_TESTCALLER(tests, NULL, NULL, fixtures);
|
||||
return (Test *)&tests;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
puts("malloc_monitor test");
|
||||
TESTS_START();
|
||||
TESTS_RUN(tests_malloc_monitor());
|
||||
TESTS_END();
|
||||
|
||||
return 0;
|
||||
}
|
14
tests/sys/malloc_monitor/tests/01-run.py
Executable file
14
tests/sys/malloc_monitor/tests/01-run.py
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2024 TU Dresden
|
||||
#
|
||||
# This file is subject to the terms and conditions of the GNU Lesser
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
|
||||
import sys
|
||||
from testrunner import run_check_unittests
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_check_unittests())
|
Loading…
Reference in New Issue
Block a user