Heap statistics

Keep track of the current size allocated, maximum size allocated,
number of allocations, failed allocations and total size allocated for
both GCC and ARM. Report the maximum size allocated at the end of
testing.

Also, add a test to verify heap metrics are working as expected.
pull/2442/head
Russ Butler 2016-08-13 17:12:03 -05:00 committed by Russ Butler
parent dda7f7d77a
commit 6a31ffbf3e
5 changed files with 439 additions and 3 deletions

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2013-2016, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity/unity.h"
#include "utest/utest.h"
#include "mbed_stats.h"
#include <stdlib.h>
#include <stdio.h>
#if !defined(MBED_HEAP_STATS_ENABLED) || !MBED_HEAP_STATS_ENABLED || defined(__ICCARM__)
#error [NOT_SUPPORTED] test not supported
#endif
using namespace utest::v1;
#define ALLOCATION_SIZE_DEFAULT 564
#define ALLOCATION_SIZE_SMALL 124
#define ALLOCATION_SIZE_LARGE 790
#define ALLOCATION_SIZE_FAIL (1024 * 1024 *1024)
typedef void* (*malloc_cb_t) (uint32_t size);
static void* thunk_malloc(uint32_t size);
static void* thunk_calloc_1(uint32_t size);
static void* thunk_calloc_4(uint32_t size);
static void* thunk_realloc(uint32_t size);
malloc_cb_t malloc_thunk_array[] = {
thunk_malloc,
thunk_calloc_1,
thunk_calloc_4,
thunk_realloc,
};
void test_case_malloc_free_size()
{
printf("Initial print to setup stdio buffers\n");
mbed_stats_heap_t stats_start;
mbed_stats_heap_t stats_current;
void *data;
mbed_stats_heap_get(&stats_start);
for (uint32_t i = 0; i < sizeof(malloc_thunk_array) / sizeof(malloc_cb_t); i++) {
// Allocate memory and assert size change
data = malloc_thunk_array[i](ALLOCATION_SIZE_DEFAULT);
TEST_ASSERT(data != NULL);
mbed_stats_heap_get(&stats_current);
uint32_t increase = stats_current.current_size - stats_start.current_size;
TEST_ASSERT_EQUAL_UINT32(ALLOCATION_SIZE_DEFAULT, increase);
TEST_ASSERT_EQUAL_UINT32(stats_start.total_size + ALLOCATION_SIZE_DEFAULT * (i + 1), stats_current.total_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_cnt + 1, stats_current.alloc_cnt);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_fail_cnt, stats_current.alloc_fail_cnt);
// Free memory and assert back to starting size
free(data);
mbed_stats_heap_get(&stats_current);
TEST_ASSERT_EQUAL_UINT32(stats_start.current_size, stats_current.current_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_cnt, stats_current.alloc_cnt);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_fail_cnt, stats_current.alloc_fail_cnt);
}
}
void test_case_allocate_zero()
{
mbed_stats_heap_t stats_start;
mbed_stats_heap_t stats_current;
void *data;
mbed_stats_heap_get(&stats_start);
for (uint32_t i = 0; i < sizeof(malloc_thunk_array) / sizeof(malloc_cb_t); i++) {
// Allocate memory and assert size change
data = malloc_thunk_array[i](0);
// Return can be NULL
mbed_stats_heap_get(&stats_current);
TEST_ASSERT_EQUAL_UINT32(stats_start.current_size, stats_current.current_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.total_size, stats_current.total_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_fail_cnt, stats_current.alloc_fail_cnt);
// Free memory and assert back to starting size
free(data);
mbed_stats_heap_get(&stats_current);
TEST_ASSERT_EQUAL_UINT32(stats_start.current_size, stats_current.current_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_cnt, stats_current.alloc_cnt);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_fail_cnt, stats_current.alloc_fail_cnt);
}
}
void test_case_allocate_fail()
{
mbed_stats_heap_t stats_start;
mbed_stats_heap_t stats_current;
void *data;
mbed_stats_heap_get(&stats_start);
for (uint32_t i = 0; i < sizeof(malloc_thunk_array) / sizeof(malloc_cb_t); i++) {
// Trigger a failure by trying to allocate a buffer that won't fit
data = malloc_thunk_array[i](ALLOCATION_SIZE_FAIL);
TEST_ASSERT(data == NULL);
mbed_stats_heap_get(&stats_current);
TEST_ASSERT_EQUAL_UINT32(stats_start.current_size, stats_current.current_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.total_size, stats_current.total_size);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_cnt, stats_current.alloc_cnt);
TEST_ASSERT_EQUAL_UINT32(stats_start.alloc_fail_cnt + i + 1, stats_current.alloc_fail_cnt);
}
}
static void* thunk_malloc(uint32_t size)
{
printf("Malloc thunk\n");
return malloc(size);
}
static void* thunk_calloc_1(uint32_t size)
{
printf("Calloc thunk 1 byte\n");
return calloc(size / 1, 1);
}
static void* thunk_calloc_4(uint32_t size)
{
printf("Calloc thunk 4 bytes\n");
return calloc(size / 4, 4);
}
static void* thunk_realloc(uint32_t size)
{
printf("Realloc thunk\n");
return realloc(NULL, size);
}
void test_case_realloc_size()
{
mbed_stats_heap_t stats_start;
mbed_stats_heap_t stats_current;
uint32_t increase;
void *data;
mbed_stats_heap_get(&stats_start);
// Allocate memory and assert size change
data = realloc(NULL, ALLOCATION_SIZE_DEFAULT);
TEST_ASSERT(data != NULL);
mbed_stats_heap_get(&stats_current);
increase = stats_current.current_size - stats_start.current_size;
TEST_ASSERT_EQUAL_UINT32(increase, ALLOCATION_SIZE_DEFAULT);
// Decrease size and assert size change
data = realloc(data, ALLOCATION_SIZE_SMALL);
TEST_ASSERT(data != NULL);
mbed_stats_heap_get(&stats_current);
increase = stats_current.current_size - stats_start.current_size;
TEST_ASSERT_EQUAL_UINT32(increase, ALLOCATION_SIZE_SMALL);
// Increase size and assert size change
data = realloc(data, ALLOCATION_SIZE_LARGE);
TEST_ASSERT(data != NULL);
mbed_stats_heap_get(&stats_current);
increase = stats_current.current_size - stats_start.current_size;
TEST_ASSERT_EQUAL_UINT32(increase, ALLOCATION_SIZE_LARGE);
// Free memory and assert back to starting size
free(data);
mbed_stats_heap_get(&stats_current);
TEST_ASSERT_EQUAL_UINT32(stats_start.current_size, stats_current.current_size);
}
Case cases[] = {
Case("malloc and free size", test_case_malloc_free_size),
Case("allocate size zero", test_case_allocate_zero),
Case("allocation failure", test_case_allocate_fail),
Case("realloc size", test_case_realloc_size),
};
utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(20, "default_auto");
return greentea_test_setup_handler(number_of_cases);
}
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
int main()
{
Harness::run(specification);
}

View File

@ -21,6 +21,7 @@
#include "greentea-client/test_env.h"
#include "utest/utest_stack_trace.h"
#include "utest/utest_serial.h"
#include "mbed_stats.h"
using namespace utest::v1;
@ -105,7 +106,10 @@ utest::v1::status_t utest::v1::greentea_test_setup_handler(const size_t number_o
void utest::v1::greentea_test_teardown_handler(const size_t passed, const size_t failed, const failure_t failure)
{
UTEST_LOG_FUNCTION();
mbed_stats_heap_t heap_stats;
verbose_test_teardown_handler(passed, failed, failure);
mbed_stats_heap_get(&heap_stats);
greentea_send_kv("max_heap_usage",heap_stats.max_size);
greentea_send_kv(TEST_ENV_TESTCASE_SUMMARY, passed, failed);
int result = !(failed || (failure.reason && !(failure.reason & REASON_IGNORE)));
GREENTEA_TESTSUITE_RESULT(result);

40
hal/api/mbed_stats.h Normal file
View File

@ -0,0 +1,40 @@
/* mbed Microcontroller Library
* Copyright (c) 2016-2016 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MBED_STATS_H
#define MBED_STATS_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t current_size; /**< Bytes allocated currently. */
uint32_t max_size; /**< Max bytes allocated at a given time. */
uint32_t total_size; /**< Cumulative sum of bytes ever allocated. */
uint32_t alloc_cnt; /**< Current number of allocations. */
uint32_t alloc_fail_cnt; /**< Number of failed allocations. */
} mbed_stats_heap_t;
/**
* Fill the passed in structure with heap stats.
*/
void mbed_stats_heap_get(mbed_stats_heap_t *stats);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -24,7 +24,9 @@
#include "SingletonPtr.h"
#include "PlatformMutex.h"
#include "mbed_error.h"
#include "mbed_stats.h"
#include <stdlib.h>
#include <string.h>
#if DEVICE_STDIO_MESSAGES
#include <stdio.h>
#endif
@ -477,6 +479,22 @@ extern "C" WEAK void __cxa_pure_virtual(void) {
#endif
/* Size must be a multiple of 8 to keep alignment */
typedef struct {
uint32_t size;
uint32_t pad;
} alloc_info_t;
static SingletonPtr<PlatformMutex> malloc_stats_mutex;
static mbed_stats_heap_t heap_stats = {0, 0, 0, 0, 0};
void mbed_stats_heap_get(mbed_stats_heap_t *stats)
{
malloc_stats_mutex->lock();
memcpy(stats, &heap_stats, sizeof(mbed_stats_heap_t));
malloc_stats_mutex->unlock();
}
#if defined(TOOLCHAIN_GCC)
#ifdef FEATURE_UVISOR
#include "uvisor-lib/uvisor-lib.h"
@ -484,17 +502,105 @@ extern "C" WEAK void __cxa_pure_virtual(void) {
#ifndef FEATURE_UVISOR
extern "C" {
extern "C" void __malloc_lock( struct _reent *_r );
extern "C" void __malloc_unlock( struct _reent *_r );
void * __wrap__malloc_r(struct _reent * r, size_t size) {
extern void * __real__malloc_r(struct _reent * r, size_t size);
#if !defined(MBED_HEAP_STATS_ENABLED ) || !MBED_HEAP_STATS_ENABLED
return __real__malloc_r(r, size);
#else
malloc_stats_mutex->lock();
alloc_info_t *alloc_info = (alloc_info_t*)__real__malloc_r(r, size + sizeof(alloc_info_t));
void *ptr = NULL;
if (alloc_info != NULL) {
alloc_info->size = size;
ptr = (void*)(alloc_info + 1);
heap_stats.current_size += size;
heap_stats.total_size += size;
heap_stats.alloc_cnt += 1;
if (heap_stats.current_size > heap_stats.max_size) {
heap_stats.max_size = heap_stats.current_size;
}
} else {
heap_stats.alloc_fail_cnt += 1;
}
malloc_stats_mutex->unlock();
return ptr;
#endif
}
void * __wrap__realloc_r(struct _reent * r, void * ptr, size_t size) {
#if !defined(MBED_HEAP_STATS_ENABLED ) || !MBED_HEAP_STATS_ENABLED
extern void * __real__realloc_r(struct _reent * r, void * ptr, size_t size);
return __real__realloc_r(r, ptr, size);
#else
// Implement realloc_r with malloc and free.
// The function realloc_r can't be used here directly since
// it can call into __wrap__malloc_r (returns ptr + 4) or
// resize memory directly (returns ptr + 0).
// Note - no lock needed since malloc and free are thread safe
// Get old size
uint32_t old_size = 0;
if (ptr != NULL) {
alloc_info_t *alloc_info = ((alloc_info_t*)ptr) - 1;
old_size = alloc_info->size;
}
// Allocate space
void *new_ptr = NULL;
if (size != 0) {
new_ptr = malloc(size);
}
// If the new buffer has been allocated copy the data to it
// and free the old buffer
if (new_ptr != NULL) {
uint32_t copy_size = (old_size < size) ? old_size : size;
memcpy(new_ptr, (void*)ptr, copy_size);
free(ptr);
}
return new_ptr;
#endif
}
void __wrap__free_r(struct _reent * r, void * ptr) {
extern void __real__free_r(struct _reent * r, void * ptr);
#if !defined(MBED_HEAP_STATS_ENABLED ) || !MBED_HEAP_STATS_ENABLED
__real__free_r(r, ptr);
#else
malloc_stats_mutex->lock();
alloc_info_t *alloc_info = NULL;
if (ptr != NULL) {
alloc_info = ((alloc_info_t*)ptr) - 1;
heap_stats.current_size -= alloc_info->size;
heap_stats.alloc_cnt -= 1;
}
__real__free_r(r, (void*)alloc_info);
malloc_stats_mutex->unlock();
#endif
}
void* __wrap__calloc_r(struct _reent * r, size_t num, size_t size) {
#if !defined(MBED_HEAP_STATS_ENABLED ) || !MBED_HEAP_STATS_ENABLED
extern void* __real__calloc_r(struct _reent * r, size_t num, size_t size);
return __real__calloc_r(r, num, size);
#else
// Note - no lock needed since malloc is thread safe
void *ptr = malloc(num * size);
if (ptr != NULL) {
memset(ptr, 0, num * size);
}
return ptr;
#endif
}
}
#endif/* FEATURE_UVISOR */
@ -519,6 +625,85 @@ extern "C" void software_init_hook(void)
}
#endif
#if defined(TOOLCHAIN_ARM) && (defined(MBED_HEAP_STATS_ENABLED ) && MBED_HEAP_STATS_ENABLED )
extern "C" void *$Super$$malloc(size_t size);
extern "C" void *$Super$$realloc(void * ptr, size_t size);
extern "C" void $Super$$free(void * ptr);
extern "C" void *$Sub$$malloc(size_t size)
{
malloc_stats_mutex->lock();
alloc_info_t *alloc_info = (alloc_info_t*)$Super$$malloc(size + sizeof(alloc_info_t));
void *ptr = NULL;
if (alloc_info != NULL) {
alloc_info->size = size;
ptr = (void*)(alloc_info + 1);
heap_stats.current_size += size;
heap_stats.total_size += size;
heap_stats.alloc_cnt += 1;
if (heap_stats.current_size > heap_stats.max_size) {
heap_stats.max_size = heap_stats.current_size;
}
} else {
heap_stats.alloc_fail_cnt += 1;
}
malloc_stats_mutex->unlock();
return ptr;
}
extern "C" void *$Sub$$realloc(void * ptr, size_t size)
{
// Note - no lock needed since malloc and free are thread safe
// Get old size
uint32_t old_size = 0;
if (ptr != NULL) {
alloc_info_t *alloc_info = ((alloc_info_t*)ptr) - 1;
old_size = alloc_info->size;
}
// Allocate space
void *new_ptr = NULL;
if (size != 0) {
new_ptr = malloc(size);
}
// If the new buffer has been allocated copy the data to it
// and free the old buffer
if (new_ptr != NULL) {
uint32_t copy_size = (old_size < size) ? old_size : size;
memcpy(new_ptr, (void*)ptr, copy_size);
free(ptr);
}
return new_ptr;
}
extern "C" void $Sub$$free(void * ptr)
{
malloc_stats_mutex->lock();
alloc_info_t *alloc_info = NULL;
if (ptr != NULL) {
alloc_info = ((alloc_info_t*)ptr) - 1;
heap_stats.current_size -= alloc_info->size;
heap_stats.alloc_cnt -= 1;
}
$Super$$free((void*)alloc_info);
malloc_stats_mutex->unlock();
}
extern "C" void *$Sub$$calloc(size_t num, size_t size)
{
// Note - no lock needed since malloc is thread safe
void *ptr = malloc(num * size);
if (ptr != NULL) {
memset(ptr, 0, num * size);
}
return ptr;
}
#endif /* defined(TOOLCHAIN_ARM) && (defined(MBED_HEAP_STATS_ENABLED ) && MBED_HEAP_STATS_ENABLED ) */
// ****************************************************************************
// mbed_main is a function that is called before main()
// mbed_sdk_init() is also a function that is called before main(), but unlike
@ -684,6 +869,8 @@ char* mbed_gets(char*s, int size, FILE *_file){
#endif
}
} // namespace mbed
#if defined (__ICCARM__)
// Stub out locks when an rtos is not present
extern "C" WEAK void __iar_system_Mtxinit(__iar_Rmtx *mutex) {}
@ -725,8 +912,6 @@ extern "C" void __env_unlock( struct _reent *_r )
}
#endif
} // namespace mbed
void *operator new(std::size_t count)
{
void *buffer = malloc(count);

View File

@ -39,7 +39,7 @@ class GCC(mbedToolchain):
'c': ["-std=gnu99"],
'cxx': ["-std=gnu++98", "-fno-rtti", "-Wvla"],
'ld': ["-Wl,--gc-sections", "-Wl,--wrap,main",
"-Wl,--wrap,_malloc_r", "-Wl,--wrap,_free_r", "-Wl,--wrap,_realloc_r"],
"-Wl,--wrap,_malloc_r", "-Wl,--wrap,_free_r", "-Wl,--wrap,_realloc_r", "-Wl,--wrap,_calloc_r"],
}
def __init__(self, target, options=None, notify=None, macros=None, silent=False, tool_path="", extra_verbose=False):