MabuTrace is a lightweight, C and C++ compatible high-performance tracing library for the ESP32, designed for deep performance analysis and debugging of complex, real-time applications. It captures execution data and exports it in a JSON format compatible with standard profiling tools like Perfetto and the legacy chrome://tracing.
The library is written to be highly efficient, using a binary circular buffer to minimize runtime overhead. This makes it safe to use in timing-critical code, including Interrupt Service Routines (ISRs). It works seamlessly with both the ESP-IDF and the Arduino framework.
- Low Overhead: Events are stored in a compact binary format in a pre-allocated circular buffer, ensuring minimal impact on application performance.
- ISR-Safe: Tracepoints can be safely added within Interrupt Service Routines.
- Built-in Web Server: Starts an HTTP server on the ESP32 to serve a web-based UI for capturing, downloading, and directly opening traces in Perfetto.
- Simple API: Provides intuitive macros for instrumenting your code (
TRACE_SCOPE,TRACE_INSTANT,TRACE_FLOW_IN/_OUT,TRACE_COUNTER). - Rich Event Types:
- Scoped Events: Measure the duration of a function or code block.
- Instant Events: Mark a specific point in time.
- Counter Events: Track the value of a variable over time.
- Flow Events: Visualize causal relationships between tasks, even across core and interrupt boundaries.
- Framework Agnostic: Full support for both ESP-IDF and Arduino projects.
1. Using Library Manager
- In the Arduino IDE, go to
Tools->Manage Libraries.... - Enter "mabutrace" in the search field
- Click
INSTALL
2. Manual Install
Alternatively, download and install this repository manually:
- Download this repository as a ZIP file.
- In the Arduino IDE, go to
Sketch->Include Library->Add .ZIP Library.... - Select the downloaded ZIP file.
1. Using Component Manager (Recommended)
Add the following to your project's idf_component.yml manifest:
dependencies:
mabuware/mabutrace:
version: "*"2. Manual Clone
Alternatively, clone the repository into your components directory:
cd your-project/components
git clone https://github.com/mabuware/MabuTrace.gitif framework = espidf
add a idf_component.yml manifest file to your src directory with the following content:
dependencies:
mabuware/mabutrace:
version: "*"Or, place the library in your project's components/ directory.
if framework = Arduino
Add MabuTrace as a library dependency in your platformio.ini:
lib_deps = mabuware/mabutraceOr, place the library in your project's lib/ directory.
-
Include the library:
#include <mabutrace.h>
-
Initialize the tracer: In your
setup()function (Arduino) orapp_main()(ESP-IDF), initialize the library. It's also recommended to start the web server for easy trace retrieval.#include <WiFi.h> // Or your ESP-IDF WiFi equivalent void setup() { // ... connect to WiFi ... WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } // Initialize MabuTrace and start the web server mabutrace_init(); mabutrace_start_server(81); // Use any available port Serial.print("MabuTrace server started. Go to http://"); Serial.print(WiFi.localIP()); Serial.println(":81/ to capture a trace."); }
-
Add tracepoints to your code: Use the macros to instrument your application logic. (See API examples below).
-
Capture a Trace:
- Run the firmware on your ESP32 and connect to its IP address (e.g.,
http://192.168.1.123:81). - You will see the MabuTrace web UI.
- Click "Capture Trace" to download the latest trace data from the ESP32's buffer. Note that clicking will end the trace, as it is already being continuously written to the ring buffer.
- Once complete, you can:
- "Save Trace" to download the
trace.jsonfile locally. Then openchrome://tracingand load the file. (Chrome only) - "Open Trace in Perfetto" to automatically open a new tab and load the trace data for analysis. (Any browser)
- "Save Trace" to download the
- Run the firmware on your ESP32 and connect to its IP address (e.g.,
MabuTrace provides simple macros that make instrumenting your code easy. Note: Event names are passed as const char* and are not copied. For this reason, you should only use string literals for names to ensure the pointers remain valid.
Use TRACE_SCOPE to measure how long a block of code takes. It automatically records the start time when created and the end time when it goes out of scope.
void process_data() {
// This trace will cover the entire duration of the process_data function.
TRACE_SCOPE("process_data", COLOR_GREEN);
{
// You can create nested scopes.
TRACE_SCOPE("sub-process A");
delay(10);
}
delay(5);
}For convenience, TRC() is a shortcut for TRACE_SCOPE(__func__), using the current function's name.
void my_worker_function() {
TRC(); // Equivalent to TRACE_SCOPE("my_worker_function");
// ...
}Use TRACE_INSTANT to mark a single point in time, such as an error condition or an important event.
if (xQueueSend(myQueue, &data, 0) == errQUEUE_FULL) {
// Mark that the queue was full.
TRACE_INSTANT("Queue Full", COLOR_DARK_RED);
}Flow events are used to visualize the cause-and-effect relationship between events in different threads or contexts (e.g., from an ISR to a worker task).
- Create an outbound flow event with
TRACE_FLOW_OUT, passing a pointer to auint16_tvariable which will be filled with a link ID. - Pass this link ID along with your data (e.g., in a queue message).
- In the receiving task, create an inbound flow event with
TRACE_FLOW_INusing the received ID.
Perfetto and chrome://tracing will draw a connecting arrow from the outbound event to the inbound one.
// In an ISR or Task A (Producer)
void onTimer() {
TRC();
message_t message;
// Create a link ID for the flow. MabuTrace will assign a unique ID.
uint16_t link_idx = 0;
TRACE_FLOW_OUT(&link_idx, "New Data");
message.link = link_idx; // Store the ID in the message
xQueueSendFromISR(Queue1, &message, &xHigherPriorityTaskWoken);
}
// In Task B (Consumer)
void workerTask(void *pvParameters) {
for (;;) {
message_t message;
xQueueReceive(Queue1, &message, portMAX_DELAY);
// This links back to the TRACE_FLOW_OUT event in the ISR.
TRACE_FLOW_IN(message.link);
// ... process message ...
}
}Use TRACE_COUNTER to track the value of a variable over time. Perfetto will render this as a graph. Note that counters are stored as 24bit signed integer in the binary buffer, meaning it's possible to trace values between -8388608 and 8388607.
void monitor_task() {
for (;;) {
// Track the number of messages waiting in a queue.
int free_heap = esp_get_free_heap_size();
TRACE_COUNTER("Free Heap", free_heap);
vTaskDelay(pdMS_TO_TICKS(100));
}
}- Binary Logging: The
TRACE_macros are lightweight functions that write event data into a compact binary struct. This struct is then copied into a global circular buffer. Access to the buffer is protected by a critical section (portMUX_TYPE) to ensure thread and ISR safety. - Circular Buffer: When the buffer fills up, it wraps around, overwriting the oldest entries. This ensures the tracer can run indefinitely without ever running out of memory.
- On-the-fly JSON Conversion: The web server does not pre-allocate a massive buffer for the JSON output. Instead, it reads the binary data from the circular buffer and converts each entry to a JSON string one by one, streaming the result to the client. This keeps memory usage low and constant.
- Task Naming: The library keeps track of FreeRTOS
TaskHandle_ts and automatically associates them with task names for clear labeling in the trace viewer.
MabuTrace is free software, distributed under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
