# MabuTrace 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](https://ui.perfetto.dev) 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**. ## Key Features - **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. ## Getting Started ### Installation #### Arduino (IDE) **1. Using Library Manager** 1. In the Arduino IDE, go to `Tools` -> `Manage Libraries...`. 2. Enter "mabutrace" in the search field 3. Click `INSTALL` **2. Manual Install** Alternatively, download and install this repository manually: 1. Download this repository as a ZIP file. 2. In the Arduino IDE, go to `Sketch` -> `Include Library` -> `Add .ZIP Library...`. 3. Select the downloaded ZIP file. #### ESP-IDF **1. Using Component Manager (Recommended)** Add the following to your project's `idf_component.yml` manifest: ```yaml dependencies: mabuware/mabutrace: version: "*" ``` **2. Manual Clone** Alternatively, clone the repository into your `components` directory: ```sh cd your-project/components git clone https://github.com/mabuware/MabuTrace.git ``` #### PlatformIO **if framework = espidf** add a `idf_component.yml` manifest file to your src directory with the following content: ```yaml 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`: ```ini lib_deps = mabuware/mabutrace ``` Or, place the library in your project's `lib/` directory. ### Basic Usage 1. **Include the library:** ```cpp #include <mabutrace.h> ``` 2. **Initialize the tracer:** In your `setup()` function (Arduino) or `app_main()` (ESP-IDF), initialize the library. It's also recommended to start the web server for easy trace retrieval. ```cpp #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."); } ``` 3. **Add tracepoints to your code:** Use the macros to instrument your application logic. (See API examples below). 4. **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.json` file locally. Then open `chrome://tracing` and load the file. (Chrome only) - **"Open Trace in Perfetto"** to automatically open a new tab and load the trace data for analysis. (Any browser) ## API by Example 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. ### Scoped Duration Events 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. ```cpp 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. ```cpp void my_worker_function() { TRC(); // Equivalent to TRACE_SCOPE("my_worker_function"); // ... } ``` ### Instant Events Use `TRACE_INSTANT` to mark a single point in time, such as an error condition or an important event. ```cpp if (xQueueSend(myQueue, &data, 0) == errQUEUE_FULL) { // Mark that the queue was full. TRACE_INSTANT("Queue Full", COLOR_DARK_RED); } ``` ### Flow Events 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). 1. Create an outbound flow event with `TRACE_FLOW_OUT`, passing a pointer to a `uint16_t` variable which will be filled with a link ID. 2. Pass this link ID along with your data (e.g., in a queue message). 3. In the receiving task, create an inbound flow event with `TRACE_FLOW_IN` using the received ID. Perfetto and chrome://tracing will draw a connecting arrow from the outbound event to the inbound one. ```cpp // 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 ... } } ``` ### Counter Events 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. ```cpp 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)); } } ``` ## How It Works 1. **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. 2. **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. 3. **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. 4. **Task Naming:** The library keeps track of FreeRTOS `TaskHandle_t`s and automatically associates them with task names for clear labeling in the trace viewer. ## License 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.
idf.py add-dependency "mabuware/mabutrace^1.0.2"