]> granicus.if.org Git - esp-idf/commitdiff
heap: Add task tracking option for heap usage monitoring
authorStephen Casner <casner@acm.org>
Wed, 10 Jan 2018 09:14:47 +0000 (01:14 -0800)
committerAngus Gratton <gus@projectgus.com>
Mon, 19 Feb 2018 23:32:06 +0000 (10:32 +1100)
Add back a feature that was available in the old heap implementation
in release/v2.1 and earlier: keep track of which task allocates each
block from the heap. The task handle is conditionally added as
another word in the heap poisoning header under this configuration
option CONFIG_HEAP_TASK_TRACKING.

To allow custom monitoring and debugging code to be added, add helper
functions in multi_heap.c and multi_heap_poisoning.c to provide access
to information in the block headers.

Add esp_heap_debug_dump_totals() to monitor heap usage

esp_heap_debug_dump_totals() dumps into a user-provided data structure
a summary of the amound of heap memory in region type that is used by
each task.  Optionally it will also dump into another data structure
the metadata about each allocated block for a given list of tasks or
for all tasks (limited by available space).

Address change requests on PR #1498

This set of changes fixes the files in e3b702c to just add the
CONFIG_HEAP_TASK_TRACKING option without adding the new function
heap_caps_get_per_task_info() in case that is the only portion of the
PR that will be accepted.  Part of the change is to remove the new .c
and .h files containing that function and to remove the line to
compile it from components/heap/component.mk since it should not have
been included in e3b702c.  One or more additional commits to add the
new function will follow.

The other changes here:
- uint32_t get_all_caps() moves to heap_private.h
- replace "void* foo" with "void *foo"
- add braces around single-line "if" blocks
- replace tab characters with spaces

Address change requests on PR #1498, part 2

This set of changes fixes the files in cdf32aa to add the new function
heap_caps_get_per_task_info() with its new name and to add the line to
compile it in components/heap/component.mk.  This does not address all
the suggested changes because there are some needing further
discussion.

This commit does not include the suggested change to move the
declaration of the new function into esp_heap_caps.h because the new
function references TaskHandle_t so esp_heap_caps.h would have to
include freertos/FreeRTOS.h and freertos/task.h, but FreeRTOS.h
includes esp_heap_caps.h through two other header files which results
in compilation errors because not all of FreeRTOS.h has been read yet.

Change heap_caps_get_per_task_info() to take struct of params

In addition to moving the large number of function parameters into a
struct as the single parameter, the following changes were made:

- Clear out the totals for any prepopulated tasks so the app code
  doesn't have to do it.

- Rather than partitioning the per-task totals into a predetermined
  set of heap capabilities, take a list of <caps,mask> pairs to
  compare the caps to the heap capabilities as masked.  This lets the
  caller configure the desired partitioning, or none.

- Allow the totals array pointer or the blocks array pointer to be
  NULL to indicate not to collect that part of the information.

- In addition to returning the total space allocated by each task,
  return the number of blocks allocated by each task.

- Don't need to return the heap capabilities as part of the details
  for each block since the heap region (and therefore its
  capabilities) can be determined from the block address.

- Renamed heap_task_info.h to esp_heap_task_info.h to fit the naming
  convention, and renamed the structs for totals and block details to
  better fit the revised function name.

- Provide full Doxygen commenting for the function and parameter
  structs.

Add copyright header to new files

Merges https://github.com/espressif/esp-idf/pull/1498

components/heap/Kconfig
components/heap/component.mk
components/heap/heap_caps.c
components/heap/heap_private.h
components/heap/heap_task_info.c [new file with mode: 0644]
components/heap/include/esp_heap_task_info.h [new file with mode: 0644]
components/heap/multi_heap.c
components/heap/multi_heap_internal.h
components/heap/multi_heap_platform.h
components/heap/multi_heap_poisoning.c

index 8a8d1325f3be58f4d01bfc5628a1f58fb6fbf006..8745561ed8c1d3fb8850b58ae3c02403addd3b18 100644 (file)
@@ -37,4 +37,13 @@ config HEAP_TRACING_STACK_DEPTH
         More stack frames uses more memory in the heap trace buffer (and slows down allocation), but
         can provide useful information.
 
+config HEAP_TASK_TRACKING
+    bool "Enable heap task tracking"
+    depends on !HEAP_POISONING_DISABLED
+    help
+        Enables tracking the task responsible for each heap allocation.
+
+        This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block
+        allocated.
+
 endmenu
index b5e61aa7282a14c7be637217b9549228788370f6..8c57271d4a25c0946d7e1c827e1b1085f8c545e0 100644 (file)
@@ -6,6 +6,10 @@ COMPONENT_OBJS := heap_caps_init.o heap_caps.o multi_heap.o heap_trace.o
 
 ifndef CONFIG_HEAP_POISONING_DISABLED
 COMPONENT_OBJS += multi_heap_poisoning.o
+
+ifdef CONFIG_HEAP_TASK_TRACKING
+COMPONENT_OBJS += heap_task_info.o
+endif
 endif
 
 ifdef CONFIG_HEAP_TRACING
index 31cfa8d555f0536c09bbfc9ec3cd5bfc20c51dd7..ad971c17643520105f41e76eb0695a13f5a1904c 100644 (file)
@@ -55,19 +55,6 @@ IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len)
     return (void *)(iptr + 1);
 }
 
-/* return all possible capabilities (across all priorities) for a given heap */
-inline static uint32_t get_all_caps(const heap_t *heap)
-{
-    if (heap->heap == NULL) {
-        return 0;
-    }
-    uint32_t all_caps = 0;
-    for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
-        all_caps |= heap->caps[prio];
-    }
-    return all_caps;
-}
-
 bool heap_caps_match(const heap_t *heap, uint32_t caps)
 {
     return heap->heap != NULL && ((get_all_caps(heap) & caps) == caps);
index 47d8cc55f8da391f78f7d0bfb9e5af9e3a14f388..5103cfd1788e0cd502e1a1391b9f9fc21b00312e 100644 (file)
@@ -48,6 +48,18 @@ extern SLIST_HEAD(registered_heap_ll, heap_t_) registered_heaps;
 
 bool heap_caps_match(const heap_t *heap, uint32_t caps);
 
+/* return all possible capabilities (across all priorities) for a given heap */
+inline static uint32_t get_all_caps(const heap_t *heap)
+{
+    if (heap->heap == NULL) {
+        return 0;
+    }
+    uint32_t all_caps = 0;
+    for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
+        all_caps |= heap->caps[prio];
+    }
+    return all_caps;
+}
 
 /*
  Because we don't want to add _another_ known allocation method to the stack of functions to trace wrt memory tracing,
diff --git a/components/heap/heap_task_info.c b/components/heap/heap_task_info.c
new file mode 100644 (file)
index 0000000..5914e66
--- /dev/null
@@ -0,0 +1,129 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// 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 <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <multi_heap.h>
+#include "multi_heap_internal.h"
+#include "heap_private.h"
+#include "esp_heap_task_info.h"
+
+#ifdef CONFIG_HEAP_TASK_TRACKING
+
+/*
+ * Return per-task heap allocation totals and lists of blocks.
+ *
+ * For each task that has allocated memory from the heap, return totals for
+ * allocations within regions matching one or more sets of capabilities.
+ *
+ * Optionally also return an array of structs providing details about each
+ * block allocated by one or more requested tasks, or by all tasks.
+ *
+ * Returns the number of block detail structs returned.
+ */
+size_t heap_caps_get_per_task_info(heap_task_info_params_t *params)
+{
+    heap_t *reg;
+    heap_task_block_t *blocks = params->blocks;
+    size_t count = *params->num_totals;
+    size_t remaining = params->max_blocks;
+
+    // Clear out totals for any prepopulated tasks.
+    if (params->totals) {
+        for (size_t i = 0; i < count; ++i) {
+            for (size_t type = 0; type < NUM_HEAP_TASK_CAPS; ++type) {
+                params->totals[i].size[type] = 0;
+                params->totals[i].count[type] = 0;
+            }
+        }
+    }
+
+    SLIST_FOREACH(reg, &registered_heaps, next) {
+        multi_heap_handle_t heap = reg->heap;
+        if (heap == NULL) {
+            continue;
+        }
+
+        // Find if the capabilities of this heap region match on of the desired
+        // sets of capabilities.
+        uint32_t caps = get_all_caps(reg);
+        uint32_t type;
+        for (type = 0; type < NUM_HEAP_TASK_CAPS; ++type) {
+            if ((caps & params->mask[type]) == params->caps[type]) {
+                break;
+            }
+        }
+        if (type == NUM_HEAP_TASK_CAPS) {
+            continue;
+        }
+
+        multi_heap_block_handle_t b = multi_heap_get_first_block(heap);
+        multi_heap_internal_lock(heap);
+        for ( ; b ; b = multi_heap_get_next_block(heap, b)) {
+            if (multi_heap_is_free(b)) {
+                continue;
+            }
+            void *p = multi_heap_get_block_address(b);  // Safe, only arithmetic
+            size_t bsize = multi_heap_get_allocated_size(heap, p); // Validates
+            TaskHandle_t btask = (TaskHandle_t)multi_heap_get_block_owner(b);
+
+            // Accumulate per-task allocation totals.
+            if (params->totals) {
+                size_t i;
+                for (i = 0; i < count; ++i) {
+                    if (params->totals[i].task == btask) {
+                        break;
+                    }
+                }
+                if (i < count) {
+                    params->totals[i].size[type] += bsize;
+                    params->totals[i].count[type] += 1;
+                }
+                else {
+                    if (count < params->max_totals) {
+                        params->totals[count].task = btask;
+                        params->totals[count].size[type] = bsize;
+                        params->totals[i].count[type] = 1;
+                        ++count;
+                    }
+                }
+            }
+
+            // Return details about allocated blocks for selected tasks.
+            if (blocks && remaining > 0) {
+                if (params->tasks) {
+                    size_t i;
+                    for (i = 0; i < params->num_tasks; ++i) {
+                        if (btask == params->tasks[i]) {
+                            break;
+                        }
+                    }
+                    if (i == params->num_tasks) {
+                        continue;
+                    }
+                }
+                blocks->task = btask;
+                blocks->address = p;
+                blocks->size = bsize;
+                ++blocks;
+                --remaining;
+            }
+        }
+        multi_heap_internal_unlock(heap);
+    }
+    *params->num_totals = count;
+    return params->max_blocks - remaining;
+}
+
+#endif // CONFIG_HEAP_TASK_TRACKING
diff --git a/components/heap/include/esp_heap_task_info.h b/components/heap/include/esp_heap_task_info.h
new file mode 100644 (file)
index 0000000..fca9a43
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// 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.
+#pragma once
+
+#ifdef CONFIG_HEAP_TASK_TRACKING
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This macro controls how much space is provided for partitioning the per-task
+// heap allocation info according to one or more sets of heap capabilities.
+#define NUM_HEAP_TASK_CAPS 4
+
+/** @brief Structure to collect per-task heap allocation totals partitioned by selected caps */
+typedef struct {
+    TaskHandle_t task;                ///< Task to which these totals belong
+    size_t size[NUM_HEAP_TASK_CAPS];  ///< Total allocations partitioned by selected caps
+    size_t count[NUM_HEAP_TASK_CAPS]; ///< Number of blocks partitioned by selected caps
+} heap_task_totals_t;
+
+/** @brief Structure providing details about a block allocated by a task */
+typedef struct {
+    TaskHandle_t task;                ///< Task that allocated the block
+    void *address;                    ///< User address of allocated block
+    uint32_t size;                    ///< Size of the allocated block
+} heap_task_block_t;
+
+/** @brief Structure to provide parameters to heap_caps_get_per_task_info
+ *
+ * The 'caps' and 'mask' arrays allow partitioning the per-task heap allocation
+ * totals by selected sets of heap region capabilities so that totals for
+ * multiple regions can be accumulated in one scan.  The capabilities flags for
+ * each region ANDed with mask[i] are compared to caps[i] in order; the
+ * allocations in that region are added to totals->size[i] and totals->count[i]
+ * for the first i that matches.  To collect the totals without any
+ * partitioning, set mask[0] and caps[0] both to zero.  The allocation totals
+ * are returned in the 'totals' array of heap_task_totals_t structs.  To allow
+ * easily comparing the totals array between consecutive calls, that array can
+ * be left populated from one call to the next so the order of tasks is the
+ * same even if some tasks have freed their blocks or have been deleted.  The
+ * number of blocks prepopulated is given by num_totals, which is updated upon
+ * return.  If there are more tasks with allocations than the capacity of the
+ * totals array (given by max_totals), information for the excess tasks will be
+ * not be collected.  The totals array pointer can be NULL if the totals are
+ * not desired.
+ *
+ * The 'tasks' array holds a list of handles for tasks whose block details are
+ * to be returned in the 'blocks' array of heap_task_block_t structs.  If the
+ * tasks array pointer is NULL, block details for all tasks will be returned up
+ * to the capacity of the buffer array, given by max_blocks.  The function
+ * return value tells the number of blocks filled into the array.  The blocks
+ * array pointer can be NULL if block details are not desired, or max_blocks
+ * can be set to zero.
+ */
+typedef struct {
+    int32_t caps[NUM_HEAP_TASK_CAPS]; ///< Array of caps for partitioning task totals
+    int32_t mask[NUM_HEAP_TASK_CAPS]; ///< Array of masks under which caps must match
+    TaskHandle_t *tasks;              ///< Array of tasks whose block info is returned
+    size_t num_tasks;                 ///< Length of tasks array
+    heap_task_totals_t *totals;       ///< Array of structs to collect task totals
+    size_t *num_totals;               ///< Number of task structs currently in array
+    size_t max_totals;                ///< Capacity of array of task totals structs
+    heap_task_block_t *blocks;        ///< Array of task block details structs
+    size_t max_blocks;                ///< Capacity of array of task block info structs
+} heap_task_info_params_t;
+
+/**
+ * @brief Return per-task heap allocation totals and lists of blocks.
+ *
+ * For each task that has allocated memory from the heap, return totals for
+ * allocations within regions matching one or more sets of capabilities.
+ *
+ * Optionally also return an array of structs providing details about each
+ * block allocated by one or more requested tasks, or by all tasks.
+ *
+ * @param params Structure to hold all the parameters for the function
+ * (@see heap_task_info_params_t).
+ * @return Number of block detail structs returned (@see heap_task_block_t).
+ */
+extern size_t heap_caps_get_per_task_info(heap_task_info_params_t *params);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CONFIG_HEAP_TASK_TRACKING
index 4cd9964b07c8a09f44ca6fa8eb90f6f527a0a720..77dd9d82cc38b413ca62d92f19fbdf03c055e700 100644 (file)
@@ -54,6 +54,14 @@ size_t multi_heap_free_size(multi_heap_handle_t heap)
 size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
     __attribute__((alias("multi_heap_minimum_free_size_impl")));
 
+void *multi_heap_get_block_address(multi_heap_block_handle_t block)
+    __attribute__((alias("multi_heap_get_block_address_impl")));
+
+void *multi_heap_get_block_owner(multi_heap_block_handle_t block)
+{
+    return NULL;
+}
+
 #endif
 
 #define ALIGN(X) ((X) & ~(sizeof(void *)-1))
@@ -279,6 +287,11 @@ static void split_if_necessary(heap_t *heap, heap_block_t *block, size_t size, h
     heap->free_bytes += block_data_size(new_block);
 }
 
+void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block)
+{
+    return ((char *)block + offsetof(heap_block_t, data));
+}
+
 size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p)
 {
     heap_block_t *pb = get_block(p);
@@ -339,6 +352,27 @@ void inline multi_heap_internal_unlock(multi_heap_handle_t heap)
     MULTI_HEAP_UNLOCK(heap->lock);
 }
 
+multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap)
+{
+    return &heap->first_block;
+}
+
+multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block)
+{
+    heap_block_t *next = get_next_block(block);
+    /* check for valid free last block to avoid assert in assert_valid_block */
+    if (next == heap->last_block && is_last_block(next) && is_free(next)) {
+        return NULL;
+    }
+    assert_valid_block(heap, next);
+    return next;
+}
+
+bool multi_heap_is_free(multi_heap_block_handle_t block)
+{
+    return is_free(block);
+}
+
 void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
 {
     heap_block_t *best_block = NULL;
index e9aa38cee84a047f15c01b58f6ecbba6dc4af8f5..a69a052590712be197ef762edd50b40dc2e3ad0c 100644 (file)
@@ -13,6 +13,9 @@
 // limitations under the License.
 #pragma once
 
+/* Opaque handle to a heap block */
+typedef const struct heap_block *multi_heap_block_handle_t;
+
 /* Internal definitions for the "implementation" of the multi_heap API,
    as defined in multi_heap.c.
 
@@ -28,6 +31,7 @@ void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info)
 size_t multi_heap_free_size_impl(multi_heap_handle_t heap);
 size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap);
 size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p);
+void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block);
 
 /* Some internal functions for heap poisoning use */
 
@@ -45,3 +49,20 @@ void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_fr
 void multi_heap_internal_lock(multi_heap_handle_t heap);
 
 void multi_heap_internal_unlock(multi_heap_handle_t heap);
+
+/* Some internal functions for heap debugging code to use */
+
+/* Get the handle to the first (fixed free) block in a heap */
+multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap);
+
+/* Get the handle to the next block in a heap, with validation */
+multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block);
+
+/* Test if a heap block is free */
+bool multi_heap_is_free(const multi_heap_block_handle_t block);
+
+/* Get the data address of a heap block */
+void *multi_heap_get_block_address(multi_heap_block_handle_t block);
+
+/* Get the owner identification for a heap block */
+void *multi_heap_get_block_owner(multi_heap_block_handle_t block);
index 874fd535bd7bf9d8a8019412e8c6c95215715bf4..6a17522b46a1242b736588f715c2a88662e84627 100644 (file)
@@ -63,6 +63,16 @@ inline static void multi_heap_assert(bool condition, const char *format, int lin
     multi_heap_assert((CONDITION), "CORRUPT HEAP: multi_heap.c:%d detected at 0x%08x\n", \
                       __LINE__, (intptr_t)(ADDRESS))
 
+#ifdef CONFIG_HEAP_TASK_TRACKING
+#define MULTI_HEAP_BLOCK_OWNER TaskHandle_t task;
+#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD) (HEAD)->task = xTaskGetCurrentTaskHandle()
+#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) ((HEAD)->task)
+#else
+#define MULTI_HEAP_BLOCK_OWNER
+#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD)
+#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL)
+#endif
+
 #else // ESP_PLATFORM
 
 #include <assert.h>
@@ -73,4 +83,9 @@ inline static void multi_heap_assert(bool condition, const char *format, int lin
 #define MULTI_HEAP_UNLOCK(PLOCK)
 
 #define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) assert((CONDITION) && "Heap corrupt")
+
+#define MULTI_HEAP_BLOCK_OWNER
+#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD)
+#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL)
+
 #endif
index a2be07079572774824d253a752448c73750bedad..24059822207b2415dbb55212b9393bb4723fbae3 100644 (file)
@@ -47,6 +47,7 @@
 
 typedef struct {
     uint32_t head_canary;
+    MULTI_HEAP_BLOCK_OWNER
     size_t alloc_size;
 } poison_head_t;
 
@@ -67,6 +68,7 @@ static uint8_t *poison_allocated_region(poison_head_t *head, size_t alloc_size)
     poison_tail_t *tail = (poison_tail_t *)(data + alloc_size);
     head->alloc_size = alloc_size;
     head->head_canary = HEAD_CANARY_PATTERN;
+    MULTI_HEAP_SET_BLOCK_OWNER(head);
 
     uint32_t tail_canary = TAIL_CANARY_PATTERN;
     if ((intptr_t)tail % sizeof(void *) == 0) {
@@ -258,6 +260,12 @@ void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
     return result;
 }
 
+void *multi_heap_get_block_address(multi_heap_block_handle_t block)
+{
+    char *head = multi_heap_get_block_address_impl(block);
+    return head + sizeof(poison_head_t);
+}
+
 size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
 {
     poison_head_t *head = verify_allocated_region(p, true);
@@ -269,6 +277,11 @@ size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
     return 0;
 }
 
+void *multi_heap_get_block_owner(multi_heap_block_handle_t block)
+{
+    return MULTI_HEAP_GET_BLOCK_OWNER((poison_head_t*)multi_heap_get_block_address_impl(block));
+}
+
 multi_heap_handle_t multi_heap_register(void *start, size_t size)
 {
     if (start != NULL) {