Tracing Architecture
This document explains the high-level design of the ESP-IDF tracing system.
Overview
Applications use esp_trace to collect runtime information from the target and send it to a host tool for analysis. This supports use cases such as FreeRTOS task and ISR analysis with SEGGER SystemView, source code coverage with Gcov, and application-specific data collection over apptrace.
ESP-IDF provides common tracing formats and transports, and the same framework can be extended for new ones, such as a custom trace format or a transport over SPI or UDP.
The ESP-IDF tracing system follows a Port & Adapter design. Applications call the public esp_trace API, while the tracing core connects the selected encoder with the selected transport.
This design provides:
A stable application-facing API.
Independent selection of trace format and host link.
Adapter-specific details kept outside the core tracing code.
Startup and panic handling owned by the tracing system.
flowchart TB
app["Application<br/>FreeRTOS tasks, ISRs, esp_trace_write(), trace macros"]
api["Public interface<br/>esp_trace API"]
core["Core tracing code<br/>esp_trace component<br/>session, multi-core init, adapter coordination"]
registry["Runtime registry<br/>maps configured names to adapters"]
host["Host link<br/>OpenOCD over JTAG, UART, or USB Serial JTAG"]
subgraph PORTS["Ports"]
direction LR
enc_port["Encoder port"]
transport_port["Transport port"]
end
subgraph ADAPTERS["Adapters"]
direction LR
encoder["Encoder adapter<br/>external component, for example espressif/esp_sysview<br/>formats recorder events"]
transport["Transport adapter<br/>esp_trace component<br/>apptrace over JTAG/UART, USB Serial JTAG"]
end
app --> api --> core
core --- registry
core --> enc_port
core --> transport_port
enc_port --> encoder
transport_port --> transport
encoder -.-> transport
transport --> host
Components
Core Tracing Code
The esp_trace component contains the public API and maintains the active trace session. It creates the encoder/transport pair during startup, coordinates multi-core initialization, and forwards API calls to the selected adapters.
Encoder Port
The encoder port interface (esp_trace_port_encoder.h ) defines how a trace library plugs into esp_trace. An encoder receives trace writes or trace-hook events and converts them to a recorder-specific format, such as the SystemView protocol. esp_trace defines this interface but does not ship an encoder; encoders are provided by external components, for example espressif/esp_sysview. See Integrating a Custom Trace Library.
Transport Port
The transport port interface (esp_trace_port_transport.h ) defines how encoded trace data leaves the target. A transport writes bytes to a host-facing link and handles link-specific operations such as flushing, host-connection checks, and panic-time output. esp_trace provides built-in apptrace over JTAG/UART and USB Serial JTAG transport adapters. See Application Level Tracing Transport (apptrace).
Each trace session pairs one encoder with one transport. The encoder can pass encoded trace data to the transport selected for the session, and runtime trace writes do not allocate memory.
Initialization
esp_trace initializes automatically during system startup based on the encoder and transport selected in project configuration. Applications normally do not need to call a separate initialization function before using the public tracing API. Adapter-specific initialization requirements are covered in Integrating a Custom Trace Library.
Data Flow
A typical write flows from the public API to the encoder, then to the transport:
flowchart TD
write["esp_trace_write(handle, data, size, tmo)"]
validate["Core validates the handle"]
encode["Encoder writes formatted data<br/>for example SystemView protocol"]
send["Transport sends encoded bytes<br/>JTAG, UART, or USB Serial JTAG"]
status["Status returns to the caller"]
write --> validate --> encode --> send --> status
Panic Handling
During a panic, interrupts are disabled and normal locking cannot be used. The core calls optional panic callbacks on the active encoder and transport. Each adapter can then flush its own buffers without taking normal locks. Panic flushing can still drop data because it must not block or use normal locking.
Registry
Adapters register themselves at link time with ESP_TRACE_REGISTER_ENCODER() and ESP_TRACE_REGISTER_TRANSPORT(). During initialization, the core looks up the configured encoder and transport by name. Only adapters linked into the application are available, and adding a new adapter does not require changes to the core.