Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce configTRACK_TASK_MEMORY_ALLOCATIONS to track dynamic allocations per task #353

Conversation

RichardBarry
Copy link
Contributor

Description

Introduce configTRACK_TASK_MEMORY_ALLOCATIONS. When this is set to 1 and either heap_4 or heap_5 is used then the number of dynamic memory allocations, the number of dynamic memory frees, the total amount of heap memory allocated by a task but not yet freed (by any task), and the maximum amount of heap memory a task has ever had allocated at one time are all tracked on a task by task basis. This additional information is returned in the TaskStatus_t structure populated by the vTaskGetInfo() and the uxTaskGetSystemState() API functions.

uxBasePriority is now only included in the TaskStatus_t structure if configUSE_MUTEXES is set to 1. ulRunTimeCounter is now only included in the TaskStatus_t structure if configGENERATE_RUN_TIME_STATS is set to 1.

Update how the TCB members get initialised. Previously each member was initialised separately. Now, as there are new members, memset() the entire structure to 0 when the structure is allocated, then only explicitly initialise structure members that are not initialised to zero.

Test Steps

Add prvExerciseTaskHeapAllocationStats() to the MSVC Win32 and MPS2 QEMU IAR demos - upstreamed to the FreeRTOS/FreeRTOS repo in a separate PR.

Related Issue

The PR that adds prvExerciseTaskHeapAllocationStats() to the FreeRTOS/FreeRTOS repo.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…and either heap_4 or heap_5 is used then the number of dynamic memory allocations, the number of dynamic memory frees, the total amount of heap memory allocated by a task but not yet freed (by any task), and the maximum amount of heap memory a task has ever had allocated at one time are all tracked on a task by task basis. This additional information is returned in the TaskStatus_t structure populated by the vTaskGetInfo() and the uxTaskGetSystemState() API functions.

uxBasePriority is now only included in the TaskStatus_t structure if configUSE_MUTEXES is set to 1.  ulRunTimeCounter is now only included in the TaskStatus_t structure if configGENERATE_RUN_TIME_STATS is set to 1.

Update how the TCB members get initialised.  Previously each member was initialised separately.  Now, as there are new members, memset() the entire structure to 0 when the structure is allocated, then only explicitly initialise structure members that are not initialised to zero.
@RichardBarry RichardBarry requested a review from a team as a code owner June 14, 2021 01:54
@codecov
Copy link

codecov bot commented Jun 14, 2021

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.15%. Comparing base (8e2dd5b) to head (2d6724e).
Report is 502 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #353   +/-   ##
=======================================
  Coverage   92.15%   92.15%           
=======================================
  Files           4        4           
  Lines        1274     1274           
  Branches      343      343           
=======================================
  Hits         1174     1174           
  Misses         53       53           
  Partials       47       47           
Flag Coverage Δ
unittests 92.15% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

RichardBarry added a commit to RichardBarry/FreeRTOS that referenced this pull request Jun 14, 2021
…EMU IAR demos to exercise the function that track memory allocations on a task by task basis as introduced by FreeRTOS/FreeRTOS-Kernel#353.
@RichardBarry
Copy link
Contributor Author

Spell check is failing because of variable names. Is spell check meant to check the C code, or just the comments?

@mingyue86010
Copy link
Contributor

mingyue86010 commented Jun 14, 2021

Hi @RichardBarry I just checked with team. It looks like the current spell checker checks any line of code with comment. So if a C code line contains a comment it will check the spell of entire line inclduing the code itself. We are look for some more generic spell checker to replace the current one and hopfully can resolve the problem.

But I'm going to unblock this PR by adding exclusion of those words for now.

@mingyue86010
Copy link
Contributor

/bot run checks

n9wxu
n9wxu previously approved these changes Dec 31, 2021

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulSt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like ulTaskGetIdleRunTimePercent() is missing here.

#endif

#if ( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /* The total run time allocated to the task so far, as defined by the run time stats clock. See https://www.FreeRTOS.org/rtos-run-time-stats.html. Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ulRunTimeCounter should now have type configRUN_TIME_COUNTER_TYPE so users can use a 64-bit counter if they wish.


TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulSt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[edit] This comment should be in xTaskUpdateHeapAllocationStats() where the total heap held gets incremented. I don't think there is a risk to the value overflowing (which the comment says should be impossible under normal control flows anyway), but it might be beneficial to cap to 0xffffffff if that does occur. [/edit]


TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulSt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preference is to use "!= pdFALSE", rather than "== pdTRUE" as it can generate more efficient code, especially on devices with a register that is always zero.


TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulSt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think there is a risk from underflow of this variable, but perhaps set xHeapBytesCurrentlyAllocated 0 if that occurs.


TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulSt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inconsistent and brings in a dependency on limits.h. The previous function defined const uint32_t ulMaxUint32 = 0xffffffffUL; which is preferable to using UINT32_MAX.

@Skptak
Copy link
Member

Skptak commented Sep 20, 2023

I think this work has been done at this point?
It looks like we have traceMALLOC and traceFREE calls inside of the heap files now
Should this PR be closed?

Copy link

sonarcloud bot commented Jan 5, 2024

Quality Gate Failed Quality Gate failed

Failed conditions

D Reliability Rating on New Code (required ≥ A)
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarCloud

idea Catch issues before they fail your Quality Gate with our IDE extension SonarLint SonarLint

laroche pushed a commit to laroche/FreeRTOS-Kernel that referenced this pull request Apr 18, 2024
…reeRTOS#353)

To avoid silent errors, configNETWORK_INTERFACE_TO_USE should be defaulted to 0L.
@aggarg
Copy link
Member

aggarg commented Sep 17, 2024

Closing this PR as it is possible to track allocations using trace macros -

#define NUM_ALLOCATION_ENTRIES          1024
#define NUM_PER_TASK_ALLOCATION_ENTRIES 32

/*
 * +-----------------+--------------+----------------+-------------------+
 * | Allocating Task | Entry in use | Allocated Size | Allocated Pointer |
 * +-----------------+--------------+----------------+-------------------+
 * |                 |              |                |                   |
 * +-----------------+--------------+----------------+-------------------+
 * |                 |              |                |                   |
 * +-----------------+--------------+----------------+-------------------+
 */
typedef struct AllocationEntry
{
    BaseType_t inUse;
    TaskHandle_t allocatingTask;
    size_t allocatedSize;
    void * allocatedPtr;
} AllocationEntry_t;

AllocationEntry_t allocationEntries[ NUM_ALLOCATION_ENTRIES ];

/*
 * +------+-----------------------+----------------------+
 * | Task | Memory Currently Held | Max Memory Ever Held |
 * +------+-----------------------+----------------------+
 * |      |                       |                      |
 * +------+-----------------------+----------------------+
 * |      |                       |                      |
 * +------+-----------------------+----------------------+
 */
typedef struct PerTaskAllocationEntry
{
    TaskHandle_t task;
    size_t memoryCurrentlyHeld;
    size_t maxMemoryEverHeld;
} PerTaskAllocationEntry_t;

PerTaskAllocationEntry_t perTaskAllocationEntries[ NUM_PER_TASK_ALLOCATION_ENTRIES ];

/*-----------------------------------------------------------*/

void TrackAllocation( size_t allocatedSize, void * ptr );
void TraceFree( void * ptr );

/*-----------------------------------------------------------*/

void TrackAllocation( size_t allocatedSize, void * ptr )
{
    size_t i;
    TaskHandle_t allocatingTask;
    AllocationEntry_t * pAllocationEntry = NULL;
    PerTaskAllocationEntry_t * pTaskAllocationEntry = NULL;

    if( xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED )
    {
        allocatingTask = xTaskGetCurrentTaskHandle();

        for( i = 0; i < NUM_ALLOCATION_ENTRIES; i++ )
        {
            if( allocationEntries[ i ].inUse == pdFALSE )
            {
                pAllocationEntry = &( allocationEntries[ i ] );
                break;
            }
        }

        /* Do we already have an entry in the per task table? */
        for( i = 0; i < NUM_PER_TASK_ALLOCATION_ENTRIES; i++ )
        {
            if( perTaskAllocationEntries[ i ].task == allocatingTask )
            {
                pTaskAllocationEntry = &( perTaskAllocationEntries[ i ] );
                break;
            }
        }

        /* We do not have an entry in the per task table. Find an empty slot. */
        if( pTaskAllocationEntry == NULL )
        {
            for( i = 0; i < NUM_PER_TASK_ALLOCATION_ENTRIES; i++ )
            {
                if( perTaskAllocationEntries[ i ].task == NULL )
                {
                    pTaskAllocationEntry = &( perTaskAllocationEntries[ i ] );
                    break;
                }
            }
        }

        /* Ensure that we have space in both the tables. */
        configASSERT( pAllocationEntry != NULL );
        configASSERT( pTaskAllocationEntry != NULL );

        pAllocationEntry->allocatingTask = allocatingTask;
        pAllocationEntry->inUse = pdTRUE;
        pAllocationEntry->allocatedSize = allocatedSize;
        pAllocationEntry->allocatedPtr = ptr;

        pTaskAllocationEntry->task = allocatingTask;
        pTaskAllocationEntry->memoryCurrentlyHeld += allocatedSize;
        if( pTaskAllocationEntry->maxMemoryEverHeld < pTaskAllocationEntry->memoryCurrentlyHeld )
        {
            pTaskAllocationEntry->maxMemoryEverHeld = pTaskAllocationEntry->memoryCurrentlyHeld;
        }
    }
}

/*-----------------------------------------------------------*/

void TraceFree( void * ptr )
{
    size_t i;
    AllocationEntry_t * pAllocationEntry = NULL;
    PerTaskAllocationEntry_t * pTaskAllocationEntry = NULL;

    for( i = 0; i < NUM_ALLOCATION_ENTRIES; i++ )
    {
        if( ( allocationEntries[ i ].inUse == pdTRUE ) &&
            ( allocationEntries[ i ].allocatedPtr == ptr ) )
        {
            pAllocationEntry = &( allocationEntries [ i ] );
            break;
        }
    }

    /* Attempt to free a block that was never allocated. */
    configASSERT( pAllocationEntry != NULL );

    for( i = 0; i < NUM_PER_TASK_ALLOCATION_ENTRIES; i++ )
    {
        if( perTaskAllocationEntries[ i ].task == pAllocationEntry->allocatingTask )
        {
            pTaskAllocationEntry = &( perTaskAllocationEntries[ i ] );
            break;
        }
    }

    /* An entry must exist in the per task table. */
    configASSERT( pTaskAllocationEntry != NULL );

    pTaskAllocationEntry->memoryCurrentlyHeld -= pAllocationEntry->allocatedSize;

    pAllocationEntry->inUse = pdFALSE;
    pAllocationEntry->allocatingTask = NULL;
    pAllocationEntry->allocatedSize = 0;
    pAllocationEntry->allocatedPtr = NULL;
}

/*-----------------------------------------------------------*/

The following goes in FreeRTOSConfig.h:

void TrackAllocation( size_t allocatedSize, void * ptr );
void TraceFree( void * ptr );

#define traceMALLOC( pvReturn, xAllocatedBlockSize ) \
TrackAllocation( xAllocatedBlockSize, pvReturn )

#define traceFREE( pv, xAllocatedBlockSize ) \
TraceFree( pv )

@aggarg aggarg closed this Sep 17, 2024
@Panometric
Copy link

@aggarg Will these macros be defined in a future release? Seems odd to close a PR with a workaround only documented in a closed PR. I think there is a strong case for having this functionality in the released version.

@aggarg
Copy link
Member

aggarg commented Oct 3, 2024

@Panometric, the purpose of the above example code is to show the malloc tracking can be implemented in the application using trace macros. We will consider providing a sample with these definitions. I am interested to know your use case. Would you please be kind enough to share what you want to track and for what purpose?

@Panometric
Copy link

@aggarg Thanks, I do believe there is a general need since finding the uses of the heap can be quite daunting. In our case we are kind of forced to share the heap with a binary library for BLE that only provides hooks for malloc/free that are routed back through FreeRTOS. Allocated buffers are passed in that should be released by the library once sent. It only leaks under certain conditions, like when comms were in progress when the connection drops. So it's difficult problem to recreate. Therefore, we would use this do a post mortem, proving the the library vendor that they are causing the leak.

@aggarg
Copy link
Member

aggarg commented Oct 9, 2024

Thank you for sharing your use case. Just one more question - do you keep this enabled in your production application or do you use it during development only?

@Panometric
Copy link

Hard to say for sure, probably only QA versions, since it introduces some load and memory usage. But we would likely preserve the tracking data in for reporting after reset caused a failed malloc.

@aggarg
Copy link
Member

aggarg commented Oct 11, 2024

Thank you for sharing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants