blob: 8b09c564fabdaf453317e83f2f17bb4aed071456 [file] [log] [blame] [view]
# Trace Event Best Practices
This document outlines best practices for emitting trace events in Chrome,
focusing on clarity, performance, and effective debugging. It draws from the
current state of Chrome's tracing infrastructure and the [Perfetto
library](https://perfetto.dev/docs).
## Trace Category
Trace events are grouped into categories, which are essential for filtering and
organizing trace data. Proper categorization is crucial for effective trace
analysis.
### Built-in Categories
* Refer to [`base/trace_event/builtin_categories.h`](https://cs.chromium.org/chromium/src/base/trace_event/builtin_categories.h) for a list of predefined
categories.
* Familiarize yourself with existing categories before creating new ones to
avoid duplication.
### Naming Convention
* Follow the `namespace.category(.sub_category)(.debug)` naming convention
for new categories.
* Example: `base.scheduling`, `renderer.compositor`.
* Avoid generic categories like `toplevel`. These become "junk drawers" and
are too noisy.
* Use `.debug` suffix for debug categories instead of `TRACE_DISABLED_BY_DEFAULT()`.
### Category Descriptions
* Document new categories with `.SetDescription()` to clarify their purpose.
* Identify an owner (e.g., a team or individual) for each category in
comments.
* Add the `"debug"` tag for debug categories.
### Avoid Category Groups
* Avoid emitting events to multiple categories. Category groups need to be
defined for each combination that’s used in chrome, which can lead to
combinatorial explosion.
* Prefer leveraging tags to group a set of categories under a common tag
instead.
## Trace Event Usage
### Trace Event Macros
* Most use cases are served by `#include "base/trace_event/track_event.h"`
* **Use Perfetto Macros:** Employ the modern macros documented in
[`perfetto/include/perfetto/tracing/track_event.h`](https://cs.chromium.org/chromium/src/third_party/perfetto/include/perfetto/tracing/track_event.h).
* `TRACE_EVENT()`
* `TRACE_EVENT_BEGIN()`
* `TRACE_EVENT_END()`
* See
[`third_party/perfetto/docs/instrumentation/track-events.md`](https://cs.chromium.org/chromium/src/third_party/perfetto/docs/instrumentation/track-events.md) for more details.
* **Avoid Legacy Macros:** Do not use legacy macros defined in
`third_party/perfetto/include/perfetto/tracing/track_event_legacy.h`, such
as `TRACE_EVENT0/1/2`, `TRACE_EVENT_ASYNC_BEGIN0/1/2` or any other macro
that has 0/1/2 suffix.
* These macros are deprecated and should not be used in new code.
* Do not emit synchronous events when a thread is idle. This yields misleading
process activity summary shown by perfetto UI.
### Static Strings
* **Always Use Static Strings:** For event names, *always* use static strings.
* Dynamic strings are filtered out due to privacy concerns and appear as
"filtered" in field traces.
* Use
[`perfetto::StaticString`](https://cs.chromium.org/chromium/src/third_party/perfetto/include/perfetto/tracing/string_helpers.h)
to mark strings that are not known to be static at compile time but are in
fact static.
* If you need to add sensitive data in the trace event, prefer emitting it as
arguments or proto fields, and keep the event name a static string.
## Asynchronous Events
Asynchronous events are emitted outside of the current thread's execution flow;
they are not bound to the thread where the `TRACE_EVENT_BEGIN` or
`TRACE_EVENT_END` macros are invoked. Use the `perfetto::Track` API from
[`third_party/perfetto/include/perfetto/tracing/track.h`](https://cs.chromium.org/chromium/src/third_party/perfetto/include/perfetto/tracing/track.h)
to specify which track they belong to, which can span across threads or even
processes. Asynchronous events are best suited for representing high-level,
long-lived states or operations that are conceptually independent of a single
thread's execution.
### Careful Usage
* **Reserve for High-Level State:** Use asynchronous events sparingly,
primarily for representing high-level, user-perceptible states or
significant, long-lived operations.
* Examples: First Contentful Paint (FCP), page visibility changes.
* **Avoid for Debugging:** Avoid using async events for debugging object
lifetimes or short-lived events. Async tracks take a lot of vertical space
and nesting can be visually misleading. Alternatively
* Put the event in a category with the `.debug` suffix (e.g., `mycomponent.debug`). This allows for easy filtering when collecting traces.
* Use instant events with flows to retain threading context.
```cpp
TRACE_EVENT_INSTANT("my_component.debug.lifetime", "MyObject::Constructor",
perfetto::Flow::FromPointer(this));
TRACE_EVENT_INSTANT("my_component.debug.lifetime", "MyObject::Destructor",
perfetto::TerminatingFlow::FromPointer(this));
```
### Named Tracks
* **Use Named Tracks:** Instead of emitting async events directly to a global
track, emit them to a `NamedTrack`. To organize events logically, you can
create a track hierarchy.
```cpp
#include "base/trace_event/track_event.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
const perfetto::NamedTrack CreateFrameParentTrack() {
perfetto::NamedTrack track("Frames", 0, perfetto::Track());
if (perfetto::Tracing::IsInitialized()) {
// Because the track doesn't get any events of its own it must manually
// emit the track descriptor. This is done conditionally to avoid
// crashing in unit tests where tracing isn't initialized.
base::TrackEvent::SetTrackDescriptor(track, track.Serialize());
}
return track;
}
perfetto::NamedTrack GetFrameVisibleTrack(int64_t frame_id) {
static const perfetto::NamedTrack parent_track = CreateFrameParentTrack();
return perfetto::NamedTrack("Frame Visibility", frame_id, parent_track);
}
void MyFunction(int64_t frame_id, int64_t start_ts, int64_t end_ts) {
TRACE_EVENT_BEGIN("renderer", "Visible",
GetFrameVisibleTrack(frame_id), start_ts);
TRACE_EVENT_END("renderer", "Visible",
GetFrameVisibleTrack(frame_id), end_ts);
}
```
## 4. Testing
Use `base::test::TracingEnvironment` to enable tracing in unittests and `base::test::TestTraceProcessor` to control a tracing session and read events.
Avoid using legacy base::trace_event::TraceLog in new code.
```cpp
#include "base/test/test_trace_processor.h"
#include "base/test/trace_test_utils.h"
#include "base/trace_event/track_event.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(MyComponentTest, TestTracing) {
base::test::TracingEnvironment tracing_env;
base::test::TestTraceProcessor ttp;
ttp.StartTrace("my_category");
// Code that emits trace events
TRACE_EVENT("my_category", "MyScopedEvent");
TRACE_EVENT_BEGIN("my_category", "MyScopedEvent");
// ... some work ...
TRACE_EVENT_END("my_category", "MyScopedEvent");
absl::Status status = ttp.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
auto result = ttp.RunQuery(R"sql(
SELECT count(*) as cnt from slice where name = 'MyScopedEvent'
)sql");
ASSERT_TRUE(result.has_value()) << result.error();
// verify that the expected events are present in result.
EXPECT_THAT(result.value(),
::testing::ElementsAre(std::vector<std::string>{"cnt"},
std::vector<std::string>{"2"}));
}
```