Skip to content

Commit 54a412b

Browse files
authored
feat(monitor): Add HeapMonitor class to monitor component (#435)
* feat(monitor): Add HeapMonitor class to `monitor` component * minor update * fix typo * fix sa * fix sa * update doc * actually fix sa
1 parent d8ac162 commit 54a412b

File tree

6 files changed

+316
-2
lines changed

6 files changed

+316
-2
lines changed

components/monitor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
idf_component_register(
22
INCLUDE_DIRS "include"
3+
SRC_DIRS "src"
34
REQUIRES base_component task)

components/monitor/example/main/monitor_example.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <functional>
33
#include <thread>
44

5+
#include "heap_monitor.hpp"
56
#include "task.hpp"
67
#include "task_monitor.hpp"
78

@@ -14,6 +15,52 @@ using namespace std::placeholders;
1415
extern "C" void app_main(void) {
1516
fmt::print("Stating monitor example!\n");
1617
{
18+
//! [HeapMonitor example]
19+
20+
// test a single monitor
21+
{
22+
espp::HeapMonitor hm({});
23+
auto info = hm.get_info();
24+
fmt::print("Heap Monitor Info (single line 's', default):\n");
25+
fmt::print("{}\n", info);
26+
// should be the exact same as the default
27+
fmt::print("{:s}\n", info);
28+
}
29+
30+
std::vector<int> heap_caps = {MALLOC_CAP_DEFAULT, MALLOC_CAP_INTERNAL, MALLOC_CAP_SPIRAM,
31+
MALLOC_CAP_DMA};
32+
33+
// now test multiple monitors
34+
{
35+
std::vector<espp::HeapMonitor> heap_monitors;
36+
for (const auto &heap_cap : heap_caps) {
37+
// cppcheck-suppress useStlAlgorithm
38+
heap_monitors.push_back(espp::HeapMonitor({.heap_flags = heap_cap}));
39+
}
40+
// print a table with all the monitors
41+
fmt::print("Heap Monitor Info (table 't'):\n");
42+
fmt::print("{}\n", espp::HeapMonitor::get_table_header());
43+
for (const auto &hm : heap_monitors) {
44+
auto info = hm.get_info();
45+
fmt::print("{:t}\n", info);
46+
}
47+
// print a csv with all the monitors
48+
fmt::print("Heap Monitor Info (csv 'c'):\n");
49+
fmt::print("{}\n", espp::HeapMonitor::get_csv_header());
50+
for (const auto &hm : heap_monitors) {
51+
auto info = hm.get_info();
52+
fmt::print("{:c}\n", info);
53+
}
54+
}
55+
56+
// Print a table with the static API
57+
fmt::print("Heap monitor table:\n");
58+
fmt::print("{}\n", espp::HeapMonitor::get_table(heap_caps));
59+
// Print a CSV with the static API
60+
fmt::print("Heap monitor CSV:\n");
61+
fmt::print("{}\n", espp::HeapMonitor::get_csv(heap_caps));
62+
//! [HeapMonitor example]
63+
1764
//! [TaskMonitor example]
1865
// create the monitor
1966
espp::TaskMonitor tm({.period = 500ms});
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#pragma once
2+
3+
#include "esp_heap_caps.h"
4+
#include "sdkconfig.h"
5+
6+
#include "base_component.hpp"
7+
8+
namespace espp {
9+
/// HeapMonitor class
10+
/// This class provides functionality to monitor and report heap memory usage
11+
/// in ESP32 systems. It can be used to get information about different heap
12+
/// regions based on their flags (e.g., MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL, etc.).
13+
///
14+
/// It provides methods to retrieve heap information, format it as a string,
15+
/// and log it. The class can be configured with specific heap flags and a
16+
/// name for the monitor.
17+
///
18+
/// \section heap_monitor_ex1 Heap Monitor Example
19+
/// \snippet monitor_example.cpp HeapMonitor Example
20+
class HeapMonitor : public BaseComponent {
21+
public:
22+
/// Info about a heap region
23+
struct HeapInfo {
24+
int heap_flags; ///< Heap region flags bitmask (e.g., MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL,
25+
///< etc.)
26+
size_t free_bytes; ///< Total free heap in bytes
27+
size_t min_free_bytes; ///< Minimum free heap in bytes
28+
size_t largest_free_block; ///< Largest free block in bytes
29+
size_t allocated_bytes; ///< Total allocated heap in bytes
30+
size_t total_size; ///< Total heap size in bytes
31+
};
32+
33+
/// Configuration for HeapMonitor
34+
struct Config {
35+
int heap_flags = MALLOC_CAP_DEFAULT; ///< Heap region flags bitmask (e.g., MALLOC_CAP_8BIT,
36+
///< MALLOC_CAP_INTERNAL, etc.)
37+
std::string_view name = "HeapMonitor"; ///< Name of the heap monitor
38+
espp::Logger::Verbosity log_level = espp::Logger::Verbosity::INFO; ///< Log level for heap info
39+
};
40+
41+
/// @brief Constructor
42+
/// @param config Configuration for heap monitor
43+
explicit HeapMonitor(const Config &config)
44+
: BaseComponent(config.name, config.log_level)
45+
, heap_flags_(config.heap_flags) {}
46+
47+
/// @brief Get the name of the heap region based on the heap flags
48+
/// @param heap_flags Heap region flags bitmask (e.g., MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL, etc.)
49+
/// @return Name of the heap region as a string
50+
static std::string get_region_name(int heap_flags);
51+
52+
/// @brief Get heap info for a specific heap region
53+
/// @param heap_flags Heap region flags bitmask (e.g., MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL, etc.)
54+
/// @return HeapInfo structure with heap information
55+
static HeapInfo get_info(int heap_flags);
56+
57+
/// @brief Get heap info for a specific heap region
58+
/// @param heap_flags Vector of heap region flags bitmasks (e.g., MALLOC_CAP_8BIT,
59+
/// MALLOC_CAP_INTERNAL, etc.)
60+
/// @return Vector of HeapInfo structures with heap information for each region
61+
static std::vector<HeapInfo> get_info(const std::vector<int> &heap_flags);
62+
63+
/// @brief Get heap info for the configured heap region
64+
/// @return HeapInfo structure with heap information
65+
HeapInfo get_info() const { return get_info(heap_flags_); }
66+
67+
/// @brief Get the header for the CSV output
68+
/// @return CSV header string
69+
static const std::string get_csv_header() {
70+
return "heap_flags,free_bytes,min_free_bytes,largest_free_block,allocated_bytes,total_size";
71+
}
72+
73+
/// @brief Get the header for the table output
74+
/// @return Table header string
75+
static const std::string get_table_header() {
76+
return " Min Free / Free / Biggest / Total | Type";
77+
}
78+
79+
/// @brief Get a string representation of the heap info in table format
80+
/// @param heap_flags Heap region flags bitmask(s) (e.g., MALLOC_CAP_8BIT,
81+
/// MALLOC_CAP_INTERNAL, etc.)
82+
/// @return Table string representation of the heap info, each line
83+
/// representing a heap region (entry in heap_flags vector)
84+
static std::string get_table(const std::vector<int> &heap_flags);
85+
86+
/// @brief Get a string representation of the heap info in CSV format
87+
/// @param heap_flags Heap region flags bitmask(s) (e.g., MALLOC_CAP_8BIT,
88+
/// MALLOC_CAP_INTERNAL, etc.)
89+
/// @return CSV string representation of the heap info, each line
90+
/// representing a heap region (entry in heap_flags vector)
91+
static std::string get_csv(const std::vector<int> &heap_flags);
92+
93+
protected:
94+
int heap_flags_;
95+
}; // class HeapMonitor
96+
} // namespace espp
97+
98+
// libfmt formatting for the HeapInfo struct. Supports single line, csv, and
99+
// table outputs using a presentation format specifier
100+
template <> struct fmt::formatter<espp::HeapMonitor::HeapInfo> {
101+
// presentation format specifier: 's' for single line, 'c' for csv, 't' for table
102+
char presentation = 's';
103+
104+
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) {
105+
// Parse the presentation format and store it in the formatter:
106+
auto it = ctx.begin(), end = ctx.end();
107+
if (it != end && (*it == 's' || *it == 'c' || *it == 't')) {
108+
presentation = *it++;
109+
}
110+
111+
// TODO: Check if reached the end of the range:
112+
// if (it != end && *it != '}') throw format_error("invalid format");
113+
114+
// Return the iterator to the end of the parsed range:
115+
return it;
116+
}
117+
118+
template <typename FormatContext>
119+
auto format(espp::HeapMonitor::HeapInfo const &hi, FormatContext &ctx) const {
120+
switch (presentation) {
121+
default:
122+
// intentional fall-through back to default formatting
123+
case 's': // single line
124+
return fmt::format_to(ctx.out(),
125+
"HeapInfo: {{ flags: {}, free: {}, min_free: {}, largest_free_block: "
126+
"{}, allocated: {}, total: {} }}",
127+
hi.heap_flags, hi.free_bytes, hi.min_free_bytes, hi.largest_free_block,
128+
hi.allocated_bytes, hi.total_size);
129+
case 'c': // csv
130+
return fmt::format_to(ctx.out(), "{},{},{},{},{},{}", hi.heap_flags, hi.free_bytes,
131+
hi.min_free_bytes, hi.largest_free_block, hi.allocated_bytes,
132+
hi.total_size);
133+
case 't': // table
134+
// Format as a table with 8-digit width for each field
135+
// [ min free bytes / free bytes / largest free block / total size ] heap flags
136+
return fmt::format_to(ctx.out(), "[{:8d} / {:8d} / {:8d} / {:8d}] {}", hi.min_free_bytes,
137+
hi.free_bytes, hi.largest_free_block, hi.total_size,
138+
espp::HeapMonitor::get_region_name(hi.heap_flags));
139+
}
140+
}
141+
};
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include "heap_monitor.hpp"
2+
3+
using namespace espp;
4+
5+
std::string HeapMonitor::get_region_name(int heap_flags) {
6+
std::string name = "Heap( ";
7+
if (heap_flags & MALLOC_CAP_DEFAULT) {
8+
name += "DEFAULT ";
9+
}
10+
if (heap_flags & MALLOC_CAP_INTERNAL) {
11+
name += "INTERNAL ";
12+
}
13+
if (heap_flags & MALLOC_CAP_SPIRAM) {
14+
name += "SPIRAM ";
15+
}
16+
if (heap_flags & MALLOC_CAP_DMA) {
17+
name += "DMA ";
18+
}
19+
if (heap_flags & MALLOC_CAP_8BIT) {
20+
name += "8BIT ";
21+
}
22+
if (heap_flags & MALLOC_CAP_32BIT) {
23+
name += "32BIT ";
24+
}
25+
if (heap_flags & MALLOC_CAP_RTCRAM) {
26+
name += "RTCRAM ";
27+
}
28+
if (heap_flags & MALLOC_CAP_TCM) {
29+
name += "TCM ";
30+
}
31+
if (heap_flags & MALLOC_CAP_DMA_DESC_AHB) {
32+
name += "DMA AHB ";
33+
}
34+
if (heap_flags & MALLOC_CAP_DMA_DESC_AXI) {
35+
name += "DMA AXI ";
36+
}
37+
if (heap_flags & MALLOC_CAP_CACHE_ALIGNED) {
38+
name += "CACHE_ALIGNED ";
39+
}
40+
if (heap_flags & MALLOC_CAP_SIMD) {
41+
name += "SIMD ";
42+
}
43+
name += ")";
44+
return name;
45+
}
46+
47+
HeapMonitor::HeapInfo HeapMonitor::get_info(int heap_flags) {
48+
multi_heap_info_t info;
49+
heap_caps_get_info(&info, heap_flags);
50+
HeapInfo heap_info;
51+
heap_info.heap_flags = heap_flags;
52+
heap_info.free_bytes = info.total_free_bytes;
53+
heap_info.min_free_bytes = info.minimum_free_bytes;
54+
heap_info.largest_free_block = info.largest_free_block;
55+
heap_info.allocated_bytes = info.total_allocated_bytes;
56+
heap_info.total_size = heap_caps_get_total_size(heap_flags);
57+
return heap_info;
58+
}
59+
60+
std::vector<HeapMonitor::HeapInfo> HeapMonitor::get_info(const std::vector<int> &heap_flags) {
61+
std::vector<HeapInfo> heap_info_list;
62+
for (const auto &flags : heap_flags) {
63+
heap_info_list.push_back(get_info(flags));
64+
}
65+
return heap_info_list;
66+
}
67+
68+
std::string HeapMonitor::get_table(const std::vector<int> &heap_flags) {
69+
std::string table;
70+
table += get_table_header() + "\n";
71+
auto info_list = get_info(heap_flags);
72+
for (const auto &info : info_list) {
73+
table += fmt::format("{:t}\n", info);
74+
}
75+
return table;
76+
}
77+
78+
std::string HeapMonitor::get_csv(const std::vector<int> &heap_flags) {
79+
std::string table;
80+
table += get_csv_header() + "\n";
81+
auto info_list = get_info(heap_flags);
82+
for (const auto &info : info_list) {
83+
table += fmt::format("{:c}\n", info);
84+
}
85+
return table;
86+
}

doc/Doxyfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ INPUT += $(PROJECT_PATH)/components/kts1622/include/kts1622.hpp
183183
INPUT += $(PROJECT_PATH)/components/led/include/led.hpp
184184
INPUT += $(PROJECT_PATH)/components/led_strip/include/led_strip.hpp
185185
INPUT += $(PROJECT_PATH)/components/logger/include/logger.hpp
186+
INPUT += $(PROJECT_PATH)/components/monitor/include/heap_monitor.hpp
186187
INPUT += $(PROJECT_PATH)/components/monitor/include/task_monitor.hpp
187188
INPUT += $(PROJECT_PATH)/components/motorgo-mini/include/motorgo-mini.hpp
188189
INPUT += $(PROJECT_PATH)/components/math/include/bezier.hpp

doc/en/monitor.rst

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,44 @@
11
Monitoring APIs
22
***************
33

4+
Heap Monitor
5+
------------
6+
7+
The heap monitor provides some simple utilities for monitoring and printing out
8+
the state of the heap memory in the system. It uses various `heap_caps_get_*`
9+
functions to provide information about a memory region specified by a bitmask of
10+
capabilities defining the region:
11+
12+
* `minimum free bytes`: The minimum free bytes available in the region over the
13+
lifetime of the region.
14+
* `free bytes`: The current number of free bytes available in the region.
15+
* `allocated bytes`: The current number of allocated bytes in the region.
16+
* `largest free block`: The size of the current largest free block (in bytes) in
17+
the region. Any mallocs over the size will fail.
18+
* `total size`: The size (in bytes) of the memory region.
19+
20+
It provides some utilities for formatting the output as single line output, CSV
21+
output, or a nice table.
22+
23+
Finally, the class provides some static methods for some common use cases to
24+
quickly get the available memory for various regions as well as easily format
25+
them into csv/table output.
26+
27+
Code examples for the monitor API are provided in the `monitor` example folder.
28+
29+
.. ------------------------------- Example -------------------------------------
30+
31+
.. toctree::
32+
33+
monitor_example
34+
35+
.. ---------------------------- API Reference ----------------------------------
36+
37+
Heap Monitor API Reference
38+
--------------------------
39+
40+
.. include-build-file:: inc/heap_monitor.inc
41+
442
Task Monitor
543
------------
644

@@ -22,7 +60,7 @@ Code examples for the monitor API are provided in the `monitor` example folder.
2260

2361
.. ---------------------------- API Reference ----------------------------------
2462
25-
API Reference
26-
-------------
63+
Task Monitor API Reference
64+
--------------------------
2765

2866
.. include-build-file:: inc/task_monitor.inc

0 commit comments

Comments
 (0)