| # Recording In-App Traces with Perfetto | |
| In this guide, you'll learn how to: | |
| - Use the Perfetto SDK to add custom trace points to a C++ application. | |
| - Record a trace containing your custom events. | |
| - Visualize the trace in the Perfetto UI. | |
| - Programmatically analyze the trace using PerfettoSQL. | |
| The Perfetto SDK is a C++ library that allows you to instrument your application | |
| to record trace events. These events can then be visualized and analyzed with | |
| the Perfetto UI and Trace Processor. | |
| ## Adding your first instrumentation | |
| ### Setup | |
| Download `perfetto-cpp-sdk-src.zip` from the latest Perfetto release at | |
| https://github.com/google/perfetto/releases/latest and extract it. | |
| The SDK consists of two files, `perfetto.h` and `perfetto.cc`. These are an | |
| amalgamation of the Client API designed to easy to integrate to existing build | |
| systems. The sources are self-contained and require only a C++17 compliant | |
| standard library. | |
| Copy them in your project. The next steps assume they're in the `perfetto/sdk` | |
| folder. Assuming your build looks like this: | |
| <?tabs> | |
| TAB: CMake | |
| ``` | |
| cmake_minimum_required(VERSION 3.13) | |
| project(Example) | |
| # Main executable | |
| add_executable(example example.cc) | |
| ``` | |
| </tabs?> | |
| You can add the perfetto static library like this: | |
| <?tabs> | |
| TAB: CMake | |
| ``` | |
| # It's recommended to use a recent version of CMake. | |
| cmake_minimum_required(VERSION 3.13) | |
| # Name of the project. | |
| project(Example) | |
| # Find the thread library, which is a dependency of Perfetto. | |
| find_package(Threads) | |
| # Add the Perfetto SDK source files to a static library. | |
| include_directories(perfetto/sdk) | |
| add_library(perfetto STATIC perfetto/sdk/perfetto.cc) | |
| # Add your application's source files to an executable. | |
| add_executable(example example.cc) | |
| # Link the Perfetto library and the thread library to your executable. | |
| target_link_libraries(example perfetto ${CMAKE_THREAD_LIBS_INIT}) | |
| # Windows-specific settings. | |
| if (WIN32) | |
| # The Perfetto library contains many symbols, so it needs the "big object" | |
| # format. | |
| target_compile_options(perfetto PRIVATE "/bigobj") | |
| # Disable legacy features in windows.h. | |
| add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX) | |
| # On Windows, we need to link to the WinSock2 library. | |
| target_link_libraries(example ws2_32) | |
| endif (WIN32) | |
| # Enable standards-compliant mode when using the Visual Studio compiler. | |
| if (MSVC) | |
| target_compile_options(example PRIVATE "/permissive-") | |
| endif (MSVC) | |
| ``` | |
| </tabs?> | |
| Initialize perfetto in your program and define your tracing categories: | |
| <?tabs> | |
| TAB: C++ | |
| ``` | |
| #include <perfetto.h> | |
| PERFETTO_DEFINE_CATEGORIES( | |
| perfetto::Category("rendering") | |
| .SetDescription("Events from the graphics subsystem"), | |
| perfetto::Category("network") | |
| .SetDescription("Network upload and download statistics")); | |
| PERFETTO_TRACK_EVENT_STATIC_STORAGE(); | |
| int main(int argc, char** argv) { | |
| perfetto::TracingInitArgs args; | |
| args.backends |= perfetto::kInProcessBackend; | |
| perfetto::Tracing::Initialize(args); | |
| perfetto::TrackEvent::Register(); | |
| //... | |
| } | |
| ``` | |
| </tabs?> | |
| You can now add instrumentation points to your code. They will emit events when | |
| tracing is enabled. | |
| The `TRACE_EVENT` macro records a scoped event. The event starts when the macro | |
| is called and ends at the end of the current scope (e.g., when the function | |
| returns). This is the most common type of event and is useful for tracing the | |
| duration of a function. | |
| The `TRACE_EVENT_BEGIN` and `TRACE_EVENT_END` macros can be used to record | |
| events that don't follow function scoping. `TRACE_EVENT_BEGIN` starts an event, | |
| and `TRACE_EVENT_END` ends the most recent event started on the same thread by | |
| default, but can be configured to work across threads and even across processes | |
| (see the | |
| [Track Event documentation](/docs/instrumentation/track-events.md#tracks) for | |
| more details). This is useful for tracing operations that span multiple | |
| functions. | |
| The `TRACE_COUNTER` macro records the value of a counter at a specific point in | |
| time. This is useful for tracking things like memory usage or the number of | |
| items in a queue. | |
| <?tabs> | |
| TAB: C++ | |
| ``` | |
| void DrawPlayer(int player_number) { | |
| TRACE_EVENT("rendering", "DrawPlayer", "player_number", player_number); | |
| // ... | |
| } | |
| void DrawGame() { | |
| TRACE_EVENT_BEGIN("rendering", "DrawGame"); | |
| DrawPlayer(1); | |
| DrawPlayer(2); | |
| TRACE_EVENT_END("rendering"); | |
| // ... | |
| TRACE_COUNTER("rendering", "Framerate", 120); | |
| } | |
| ``` | |
| </tabs?> | |
| ## Collecting your first app trace | |
| You can start collecting events with: | |
| <?tabs> | |
| TAB: C++ | |
| ``` | |
| // Create a trace configuration object. This is used to define the buffers, | |
| // data sources, and other settings for the trace. | |
| perfetto::TraceConfig cfg; | |
| // Add a buffer to the config. Traces are written to this buffer in memory. | |
| cfg.add_buffers()->set_size_kb(1024); // 1 MB | |
| // Add a data source to the config. This specifies what kind of data to | |
| // collect. In this case, we're collecting track events. | |
| auto* ds_cfg = cfg.add_data_sources()->mutable_config(); | |
| ds_cfg->set_name("track_event"); | |
| // Configure the track event data source. We can specify which categories of | |
| // events to enable or disable. | |
| perfetto::protos::gen::TrackEventConfig te_cfg; | |
| te_cfg.add_disabled_categories("*"); // Disable all categories by default. | |
| te_cfg.add_enabled_categories("rendering"); // Enable our "rendering" category. | |
| ds_cfg->set_track_event_config_raw(te_cfg.SerializeAsString()); | |
| // Create a new tracing session. | |
| std::unique_ptr<perfetto::TracingSession> tracing_session = | |
| perfetto::Tracing::NewTrace(); | |
| // Set up the tracing session with the configuration. | |
| tracing_session->Setup(cfg); | |
| // Start tracing. This will block until the trace is stopped. | |
| tracing_session->StartBlocking(); | |
| // The tracing_session object must be kept alive for the duration of the | |
| // trace. | |
| // ... | |
| ``` | |
| </tabs?> | |
| And you can stop and save them into a file with: | |
| <?tabs> | |
| TAB: C++ | |
| ``` | |
| // Stop the tracing session. This will block until all tracing data has been | |
| // flushed. | |
| tracing_session->StopBlocking(); | |
| // Read the trace data from the session. | |
| std::vector<char> trace_data(tracing_session->ReadTraceBlocking()); | |
| // Write the trace data to a file. | |
| std::ofstream output; | |
| output.open("example.pftrace", std::ios::out | std::ios::binary); | |
| output.write(trace_data.data(), std::streamsize(trace_data.size())); | |
| output.close(); | |
| ``` | |
| </tabs?> | |
| ## Visualizing your first app trace | |
| You can now open the `example.pftrace` file with https://ui.perfetto.dev/ | |
| It will show the events captured by the execution of your instrumentation | |
| points: | |
|  | |
| ## Querying your first app trace | |
| As well as visualizing traces on a timeline, Perfetto has support for querying | |
| traces using SQL. The easiest way to do this is using the query engine available | |
| directly in the UI. | |
| 1. In the Perfetto UI, click on the "Query (SQL)" tab in the left-hand menu. | |
|  | |
| 2. This will open a two-part window. You can write your PerfettoSQL query in | |
| the top section and view the results in the bottom section. | |
|  | |
| 3. You can then execute queries Ctrl/Cmd + Enter: | |
| For example, by running: | |
| ``` | |
| SELECT | |
| dur AS duration_ns, | |
| EXTRACT_ARG(slice.arg_set_id, 'debug.player_number') AS player_number | |
| FROM slice | |
| WHERE slice.name = 'DrawPlayer'; | |
| ``` | |
| you can see how many times the `DrawPlayer` instrumentation point has been hit, | |
| how long each execution took and its `player_number` annotation. | |
|  | |
| The framerate counter is available in the `counter` table: | |
| ```sql | |
| SELECT ts AS timestamp_ns, value AS frame_rate | |
| FROM counter | |
| JOIN track ON track.id = counter.track_id | |
| WHERE name = 'Framerate'; | |
| ``` | |
| ## Combined In-App and System Tracing | |
| While in-app tracing is useful for understanding your application's behavior in | |
| isolation, its real power comes from combining it with a system-wide trace. This | |
| allows you to see how your app's events correlate with system events like CPU | |
| scheduling, memory usage, and I/O, providing a complete picture of your app's | |
| performance in the context of the entire system. | |
| To enable combined tracing, you need to change your application to connect to | |
| the system-wide tracing service and then use the standard system tracing tools | |
| to record a trace. | |
| 1. **Modify your application code**: | |
| - Change the initialization to connect to the system backend | |
| (`kSystemBackend`). This tells the Perfetto SDK to send trace events to | |
| the central system tracing service instead of collecting them within the | |
| app. | |
| - Remove all the code related to managing the tracing session | |
| (`perfetto::Tracing::NewTrace()`, `tracing_session->Setup()`, | |
| `tracing_session->StartBlocking()`, etc.). Your application now only acts | |
| as a producer of trace data, and the system tracing service will control | |
| when tracing starts and stops. | |
| Your `main` function should now look like this: | |
| ```cpp | |
| #include <perfetto.h> | |
| // Define your categories as before. | |
| PERFETTO_DEFINE_CATEGORIES( | |
| perfetto::Category("rendering") | |
| .SetDescription("Events from the graphics subsystem"), | |
| perfetto::Category("network") | |
| .SetDescription("Network upload and download statistics")); | |
| PERFETTO_TRACK_EVENT_STATIC_STORAGE(); | |
| int main(int argc, char** argv) { | |
| // Connect to the system tracing service. | |
| perfetto::TracingInitArgs args; | |
| args.backends |= perfetto::kSystemBackend; | |
| perfetto::Tracing::Initialize(args); | |
| // Register your track event data source. | |
| perfetto::TrackEvent::Register(); | |
| // Your application logic goes here. | |
| // The TRACE_EVENT macros will now write to the system trace buffer | |
| // when tracing is enabled externally. | |
| // ... | |
| } | |
| ``` | |
| 2. **Record a system trace**: | |
| With your application running, you can now record a combined trace using the | |
| methods described in the | |
| [Recording system traces](/docs/getting-started/system-tracing.md) guide. | |
| When you configure your trace, you need to enable the `track_event` data | |
| source in addition to any system data sources you want to collect (e.g., | |
| `linux.ftrace`). This will ensure that your application's custom events are | |
| included in the trace. | |
| When you open the resulting trace file in the Perfetto UI, you will see your | |
| application's custom tracks alongside the system-level tracks. | |
| ## Next steps | |
| Now that you've recorded your first in-app trace, you can learn more about | |
| instrumenting your code: | |
| - **[Tracing SDK](/docs/instrumentation/tracing-sdk.md)**: A deep dive into the | |
| SDK's features. | |
| - **[Track Events](/docs/instrumentation/track-events.md)**: Learn more about | |
| the different types of track events and how to use them. |