blob: 51618fda768588a706eea16745917b8b26330008 [file] [log] [blame] [view]
# Track events (Tracing SDK)
Track events are part of the [Perfetto Tracing SDK](tracing-sdk.md).
*Track events* are application specific, time bounded events recorded into a
*trace* while the application is running. Track events are always associated
with a *track*, which is a timeline of monotonically increasing time. A track
corresponds to an independent sequence of execution, such as a single thread
in a process.
![Track events shown in the Perfetto UI](
/docs/images/track-events.png "Track events in the Perfetto UI")
See the [Getting started](/docs/instrumentation/tracing-sdk#getting-started)
section of the Tracing SDK page for instructions on how to check out and
build the SDK.
TIP: The code from these examples is also available [in the
repository](/examples/sdk/README.md).
There are a few main types of track events:
- **Slices**, which represent nested, time bounded operations. For example,
a slice could cover the time period from when a function begins executing
to when it returns, the time spent loading a file from the network or the
time to complete a user journey.
- **Counters**, which are snapshots of time-varying numeric values. For
example, a track event can record instantaneous the memory usage of a
process during its execution.
- **Flows**, which are used to connect related slices that span different
tracks together. For example, if an image file is first loaded from
the network and then decoded on a thread pool, a flow event can be used to
highlight its path through the system.
The [Perfetto UI](https://ui.perfetto.dev) has built in support for track
events, which provides a useful way to quickly visualize the internal
processing of an app. For example, the [Chrome
browser](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool)
is deeply instrumented with track events to assist in debugging, development
and performance analysis.
To start using track events, first define the set of categories that your events
will fall into. Each category can be separately enabled or disabled for tracing
(see [Category configuration](#category-configuration)).
Add the list of categories into a header file (e.g.,
`my_app_tracing_categories.h`) like this:
```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"));
```
Then, declare static storage for the categories in a cc file (e.g.,
`my_app_tracing_categories.cc`):
```C++
#include "my_app_tracing_categories.h"
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
```
Finally, initialize track events after the client library is brought up:
```C++
int main(int argc, char** argv) {
...
perfetto::Tracing::Initialize(args);
perfetto::TrackEvent::Register(); // Add this.
}
```
Now you can add track events to existing functions like this:
```C++
#include "my_app_tracing_categories.h"
void DrawPlayer() {
TRACE_EVENT("rendering", "DrawPlayer"); // Begin "DrawPlayer" slice.
...
// End "DrawPlayer" slice.
}
```
This type of trace event is scoped, under the hood it uses C++ [RAII]. The
event will cover the time from when the `TRACE_EVENT` annotation is encountered
to the end of the block (in the example above, until the function returns).
For events that don't follow function scoping, use `TRACE_EVENT_BEGIN` and
`TRACE_EVENT_END`:
```C++
void LoadGame() {
DisplayLoadingScreen();
TRACE_EVENT_BEGIN("io", "Loading"); // Begin "Loading" slice.
LoadCollectibles();
LoadVehicles();
LoadPlayers();
TRACE_EVENT_END("io"); // End "Loading" slice.
StartGame();
}
```
Note that you don't need to give a name for `TRACE_EVENT_END`, since it
automatically closes the most recent event that began on the same thread. In
other words, all events on a given thread share the same stack. This means
that it's not recommended to have a matching pair of `TRACE_EVENT_BEGIN` and
`TRACE_EVENT_END` markers in separate functions, since an unrelated event
might terminate the original event unexpectedly; for events that cross
function boundaries it's usually best to emit them on a [separate
track](#tracks).
You can also supply (up to two) debug annotations together with the event.
```C++
int player_number = 1;
TRACE_EVENT("rendering", "DrawPlayer", "player_number", player_number);
```
See [below](#track-event-arguments) for the other types of supported track
event arguments. For more complex arguments, you can define [your own
protobuf messages](/protos/perfetto/trace/track_event/track_event.proto) and
emit them as a parameter for the event.
NOTE: Currently custom protobuf messages need to be added directly to the
Perfetto repository under `protos/perfetto/trace`, and Perfetto itself
must also be rebuilt. We are working
[to lift this limitation](https://github.com/google/perfetto/issues/11).
As an example of a custom track event argument type, save the following as
`protos/perfetto/trace/track_event/player_info.proto`:
```protobuf
message PlayerInfo {
optional string name = 1;
optional uint64 score = 2;
}
```
This new file should also be added to
`protos/perfetto/trace/track_event/BUILD.gn`:
```json
sources = [
...
"player_info.proto"
]
```
Also, a matching argument should be added to the track event message
definition in
`protos/perfetto/trace/track_event/track_event.proto`:
```protobuf
import "protos/perfetto/trace/track_event/player_info.proto";
...
message TrackEvent {
...
// New argument types go here.
optional PlayerInfo player_info = 1000;
}
```
The corresponding trace point could look like this:
```C++
Player my_player;
TRACE_EVENT("category", "MyEvent", [&](perfetto::EventContext ctx) {
auto player = ctx.event()->set_player_info();
player->set_name(my_player.name());
player->set_player_score(my_player.score());
});
```
The lambda function passed to the macro is only called if tracing is enabled for
the given category. It is always called synchronously and possibly multiple
times if multiple concurrent tracing sessions are active.
Now that you have instrumented your app with track events, you are ready to
start [recording traces](tracing-sdk.md#recording).
## Category configuration
All track events are assigned to one more trace categories. For example:
```C++
TRACE_EVENT("rendering", ...); // Event in the "rendering" category.
```
By default, all non-debug and non-slow track event categories are enabled for
tracing. *Debug* and *slow* categories are categories with special tags:
- `"debug"` categories can give more verbose debugging output for a particular
subsystem.
- `"slow"` categories record enough data that they can affect the interactive
performance of your app.
Category tags can be defined like this:
```C++
perfetto::Category("rendering.debug")
.SetDescription("Debug events from the graphics subsystem")
.SetTags("debug", "my_custom_tag")
```
A single trace event can also belong to multiple categories:
```C++
// Event in the "rendering" and "benchmark" categories.
TRACE_EVENT("rendering,benchmark", ...);
```
A corresponding category group entry must be added to the category registry:
```C++
perfetto::Category::Group("rendering,benchmark")
```
It's also possible to efficiently query whether a given category is enabled
for tracing:
```C++
if (TRACE_EVENT_CATEGORY_ENABLED("rendering")) {
// ...
}
```
The `TrackEventConfig` field in Perfetto's `TraceConfig` can be used to
select which categories are enabled for tracing:
```protobuf
message TrackEventConfig {
// Each list item is a glob. Each category is matched against the lists
// as explained below.
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
repeated string disabled_tags = 3; // Default: [“slow”, “debug”]
repeated string enabled_tags = 4; // Default: []
}
```
To determine if a category is enabled, it is checked against the filters in the
following order:
1. Exact matches in enabled categories.
2. Exact matches in enabled tags.
3. Exact matches in disabled categories.
4. Exact matches in disabled tags.
5. Pattern matches in enabled categories.
6. Pattern matches in enabled tags.
7. Pattern matches in disabled categories.
8. Pattern matches in disabled tags.
If none of the steps produced a match, the category is enabled by default. In
other words, every category is implicitly enabled unless specifically disabled.
For example:
| Setting | Needed configuration |
| ------------------------------- | -------------------------------------------- |
| Enable just specific categories | `enabled_categories = [“foo”, “bar”, “baz”]` |
| | `disabled_categories = [“*”]` |
| Enable all non-slow categories | (Happens by default.) |
| Enable specific tags | `disabled_tags = [“*”]` |
| | `enabled_tags = [“foo”, “bar”]` |
## Dynamic and test-only categories
Ideally all trace categories should be defined at compile time as shown
above, as this ensures trace points will have minimal runtime and binary size
overhead. However, in some cases trace categories can only be determined at
runtime (e.g., they come from instrumentation in a dynamically loaded JavaScript
running in a WebView or in a NodeJS engine). These can be used by trace points
as follows:
```C++
perfetto::DynamicCategory dynamic_category{"nodejs.something"};
TRACE_EVENT_BEGIN(dynamic_category, "SomeEvent", ...);
```
TIP: It's also possible to use dynamic event names by passing `nullptr` as
the name and filling in the `TrackEvent::name` field manually.
Some trace categories are only useful for testing, and they should not make
it into a production binary. These types of categories can be defined with a
list of prefix strings:
```C++
PERFETTO_DEFINE_TEST_CATEGORY_PREFIXES(
"test", // Applies to test.*
"dontship" // Applies to dontship.*.
);
```
## Dynamic event names
Ideally all event name should be compile time string constants. For example:
```C++
TRACE_EVENT_BEGIN("rendering", "DrawGame");
```
Here `"DrawGame"` is a compile time string. If we pass a dynamic string here,
we will get compile time static_assert failure. For example :
```C++
const char* name = "DrawGame";
TRACE_EVENT_BEGIN("rendering", name); // Error. Event name is not static.
```
There are two ways to use dynamic event name:
1) If the event name is actually dynamic (e.g., std::string), write it using
`perfetto::DynamicString`:
```C++
TRACE_EVENT("category", perfetto::DynamicString{dynamic_name});
```
Note: Below is the old way of using dynamic event names. It's not recommended
anymore.
```C++
TRACE_EVENT("category", nullptr, [&](perfetto::EventContext ctx) {
ctx.event()->set_name(dynamic_name);
});
```
2) If the name is static, but the pointer is computed at runtime, wrap it
with perfetto::StaticString:
```C++
TRACE_EVENT("category", perfetto::StaticString{name});
TRACE_EVENT("category", perfetto::StaticString{i % 2 == 0 ? "A" : "B"});
```
DANGER: Using perfetto::StaticString with strings whose contents change
dynamically can cause silent trace data corruption.
## Performance
Perfetto's trace points are designed to have minimal overhead when tracing is
disabled while providing high throughput for data intensive tracing use
cases. While exact timings will depend on your system, there is a
[microbenchmark](/src/tracing/api_benchmark.cc) which gives some ballpark
figures:
| Scenario | Runtime on Pixel 3 XL | Runtime on ThinkStation P920 |
| -------- | --------------------- | ---------------------------- |
| `TRACE_EVENT(...)` (disabled) | 2 ns | 1 ns |
| `TRACE_EVENT("cat", "name")` | 285 ns | 630 ns |
| `TRACE_EVENT("cat", "name", <lambda>)` | 304 ns | 663 ns |
| `TRACE_EVENT("cat", "name", "key", value)` | 354 ns | 664 ns |
| `DataSource::Trace(<lambda>)` (disabled) | 2 ns | 1 ns |
| `DataSource::Trace(<lambda>)` | 133 ns | 58 ns |
## Advanced topics
### Track event arguments
The following optional arguments can be passed to `TRACE_EVENT` to add extra
information to events:
```C++
TRACE_EVENT("cat", "name"[, track][, timestamp]
(, "debug_name", debug_value |, TrackEvent::kFieldName, value)*
[, lambda]);
```
Some examples of valid combinations:
1. A lambda for writing custom TrackEvent fields:
```C++
TRACE_EVENT("category", "Name", [&](perfetto::EventContext ctx) {
ctx.event()->set_custom_value(...);
});
```
2. A timestamp and a lambda:
```C++
TRACE_EVENT("category", "Name", time_in_nanoseconds,
[&](perfetto::EventContext ctx) {
ctx.event()->set_custom_value(...);
});
```
|time_in_nanoseconds| should be an uint64_t by default. To support custom
timestamp types,
|perfetto::TraceTimestampTraits<MyTimestamp>::ConvertTimestampToTraceTimeNs|
should be defined. See |ConvertTimestampToTraceTimeNs| for more details.
3. Arbitrary number of debug annotations:
```C++
TRACE_EVENT("category", "Name", "arg", value);
TRACE_EVENT("category", "Name", "arg", value, "arg2", value2);
TRACE_EVENT("category", "Name", "arg", value, "arg2", value2,
"arg3", value3);
```
See |TracedValue| for recording custom types as debug annotations.
4. Arbitrary number of TrackEvent fields (including extensions):
```C++
TRACE_EVENT("category", "Name",
perfetto::protos::pbzero::TrackEvent::kFieldName, value);
```
5. Arbitrary combination of debug annotations and TrackEvent fields:
```C++
TRACE_EVENT("category", "Name",
perfetto::protos::pbzero::TrackEvent::kFieldName, value1,
"arg", value2);
```
6. Arbitrary combination of debug annotations / TrackEvent fields and a lambda:
```C++
TRACE_EVENT("category", "Name", "arg", value1,
pbzero::TrackEvent::kFieldName, value2,
[&](perfetto::EventContext ctx) {
ctx.event()->set_custom_value(...);
});
```
7. An overridden track:
```C++
TRACE_EVENT("category", "Name", perfetto::Track(1234));
```
See |Track| for other types of tracks which may be used.
8. A track and a lambda:
```C++
TRACE_EVENT("category", "Name", perfetto::Track(1234),
[&](perfetto::EventContext ctx) {
ctx.event()->set_custom_value(...);
});
```
9. A track and a timestamp:
```C++
TRACE_EVENT("category", "Name", perfetto::Track(1234),
time_in_nanoseconds);
```
10. A track, a timestamp and a lambda:
```C++
TRACE_EVENT("category", "Name", perfetto::Track(1234),
time_in_nanoseconds, [&](perfetto::EventContext ctx) {
ctx.event()->set_custom_value(...);
});
```
11. A track and any combination of debug annotions and TrackEvent fields:
```C++
TRACE_EVENT("category", "Name", perfetto::Track(1234),
"arg", value);
TRACE_EVENT("category", "Name", perfetto::Track(1234),
"arg", value, "arg2", value2);
TRACE_EVENT("category", "Name", perfetto::Track(1234),
"arg", value, "arg2", value2,
pbzero::TrackEvent::kFieldName, value3);
```
### Tracks
Every track event is associated with a track, which specifies the timeline
the event belongs to. In most cases, a track corresponds to a visual
horizontal track in the Perfetto UI like this:
![Track timelines shown in the Perfetto UI](
/docs/images/track-timeline.png "Track timelines in the Perfetto UI")
Events that describe parallel sequences (e.g., separate
threads) should use separate tracks, while sequential events (e.g., nested
function calls) generally belong on the same track.
Perfetto supports three kinds of tracks:
- `Track` – a basic timeline.
- `ProcessTrack` – a timeline that represents a single process in the system.
- `ThreadTrack` – a timeline that represents a single thread in the system.
Tracks can have a parent track, which is used to group related tracks
together. For example, the parent of a `ThreadTrack` is the `ProcessTrack` of
the process the thread belongs to. By default, tracks are grouped under the
current process's `ProcessTrack`.
A track is identified by a uuid, which must be unique across the entire
recorded trace. To minimize the chances of accidental collisions, the uuids
of child tracks are combined with those of their parents, with each
`ProcessTrack` having a random, per-process uuid.
By default, track events (e.g., `TRACE_EVENT`) use the `ThreadTrack` for the
calling thread. This can be overridden, for example, to mark events that
begin and end on a different thread:
```C++
void OnNewRequest(size_t request_id) {
// Open a slice when the request came in.
TRACE_EVENT_BEGIN("category", "HandleRequest", perfetto::Track(request_id));
// Start a thread to handle the request.
std::thread worker_thread([=] {
// ... produce response ...
// Close the slice for the request now that we finished handling it.
TRACE_EVENT_END("category", perfetto::Track(request_id));
});
```
Tracks can also optionally be annotated with metadata:
```C++
auto desc = track.Serialize();
desc.set_name("MyTrack");
perfetto::TrackEvent::SetTrackDescriptor(track, desc);
```
Threads and processes can also be named in a similar way, e.g.:
```C++
auto desc = perfetto::ProcessTrack::Current().Serialize();
desc.mutable_process()->set_process_name("MyProcess");
perfetto::TrackEvent::SetTrackDescriptor(
perfetto::ProcessTrack::Current(), desc);
```
The metadata remains valid between tracing sessions. To free up data for a
track, call EraseTrackDescriptor:
```C++
perfetto::TrackEvent::EraseTrackDescriptor(track);
```
### Flows
Flows can be used to link two (or more) events (slices or instants), to mark
them as related.
The link is displayed as an arrow in the UI, when one of the events is selected:
![A flow between two slices in the Perfetto UI](
/docs/images/flows.png "A flow between two slices in the Perfetto UI")
```C++
// The same identifier is used in both the related slices.
uint64_t request_id = GetRequestId();
{
TRACE_EVENT("rendering", "HandleRequestPhase1",
perfetto::Flow::ProcessScoped(request_id));
//...
}
std::thread t1([&] {
TRACE_EVENT("rendering", "HandleRequestPhase2",
perfetto::TerminatingFlow::ProcessScoped(request_id));
//...
});
```
### Counters
Time-varying numeric data can be recorded with the `TRACE_COUNTER` macro:
```C++
TRACE_COUNTER("category", "MyCounter", 1234.5);
```
This data is displayed as a counter track in the Perfetto UI:
![A counter track shown in the Perfetto UI](
/docs/images/counter-events.png "A counter track shown in the Perfetto UI")
Both integer and floating point counter values are supported. Counters can
also be annotated with additional information such as units, for example, for
tracking the rendering framerate in terms of frames per second or "fps":
```C++
TRACE_COUNTER("category", perfetto::CounterTrack("Framerate", "fps"), 120);
```
As another example, a memory counter that records bytes but accepts samples
as kilobytes (to reduce trace binary size) can be defined like this:
```C++
perfetto::CounterTrack memory_track = perfetto::CounterTrack("Memory")
.set_unit("bytes")
.set_multiplier(1024);
TRACE_COUNTER("category", memory_track, 4 /* = 4096 bytes */);
```
See
[counter_descriptor.proto](
/protos/perfetto/trace/track_event/counter_descriptor.proto) for the full set
of attributes for a counter track.
To record a counter value at a specific point in time (instead of the current
time), you can pass in a custom timestamp:
```C++
// First record the current time and counter value.
uint64_t timestamp = perfetto::TrackEvent::GetTraceTimeNs();
int64_t value = 1234;
// Later, emit a sample at that point in time.
TRACE_COUNTER("category", "MyCounter", timestamp, value);
```
### Interning
Interning can be used to avoid repeating the same constant data (e.g., event
names) throughout the trace. Perfetto automatically performs interning for
most strings passed to `TRACE_EVENT`, but it's also possible to also define
your own types of interned data.
First, define an interning index for your type. It should map to a specific
field of
[interned_data.proto](/protos/perfetto/trace/interned_data/interned_data.proto)
and specify how the interned data is written into that message when seen for
the first time.
```C++
struct MyInternedData
: public perfetto::TrackEventInternedDataIndex<
MyInternedData,
perfetto::protos::pbzero::InternedData::kMyInternedDataFieldNumber,
const char*> {
static void Add(perfetto::protos::pbzero::InternedData* interned_data,
size_t iid,
const char* value) {
auto my_data = interned_data->add_my_interned_data();
my_data->set_iid(iid);
my_data->set_value(value);
}
};
```
Next, use your interned data in a trace point as shown below. The interned
string will only be emitted the first time the trace point is hit (unless the
trace buffer has wrapped around).
```C++
TRACE_EVENT(
"category", "Event", [&](perfetto::EventContext ctx) {
auto my_message = ctx.event()->set_my_message();
size_t iid = MyInternedData::Get(&ctx, "Repeated data to be interned");
my_message->set_iid(iid);
});
```
Note that interned data is strongly typed, i.e., each class of interned data
uses a separate namespace for identifiers.
### Tracing session observers
The session observer interface allows applications to be notified when track
event tracing starts and stops:
```C++
class Observer : public perfetto::TrackEventSessionObserver {
public:
~Observer() override = default;
void OnSetup(const perfetto::DataSourceBase::SetupArgs&) override {
// Called when tracing session is configured. Note tracing isn't active yet,
// so track events emitted here won't be recorded.
}
void OnStart(const perfetto::DataSourceBase::StartArgs&) override {
// Called when a tracing session is started. It is possible to emit track
// events from this callback.
}
void OnStop(const perfetto::DataSourceBase::StopArgs&) override {
// Called when a tracing session is stopped. It is still possible to emit
// track events from this callback.
}
};
```
Note that all methods of the interface are called on an internal Perfetto
thread.
For example, here's how to wait for any tracing session to start:
```C++
class Observer : public perfetto::TrackEventSessionObserver {
public:
Observer() { perfetto::TrackEvent::AddSessionObserver(this); }
~Observer() { perfetto::TrackEvent::RemoveSessionObserver(this); }
void OnStart(const perfetto::DataSourceBase::StartArgs&) override {
std::unique_lock<std::mutex> lock(mutex);
cv.notify_one();
}
void WaitForTracingStart() {
printf("Waiting for tracing to start...\n");
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [] { return perfetto::TrackEvent::IsEnabled(); });
printf("Tracing started\n");
}
std::mutex mutex;
std::condition_variable cv;
};
Observer observer;
observer.WaitForTracingToStart();
```
[RAII]: https://en.cppreference.com/w/cpp/language/raii