docs: Add documentation for interception API
Bug: 170628040
Change-Id: I8a98d6e4f35464b7992c6744ffe9e6b5aaf89a2a
diff --git a/CHANGELOG b/CHANGELOG
index 326e1e8..c6781fd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
Unreleased:
Tracing service and probes:
- *
+ * Added trace packet interceptor API for rerouting trace data into
+ non-Perfetto systems.
Trace Processor:
*
UI:
diff --git a/docs/instrumentation/interceptors.md b/docs/instrumentation/interceptors.md
new file mode 100644
index 0000000..f61c469
--- /dev/null
+++ b/docs/instrumentation/interceptors.md
@@ -0,0 +1,143 @@
+# Trace packet interceptors (Tracing SDK)
+
+A trace packet interceptor is used to redirect trace packets written by a
+data source into a custom backend instead of the normal Perfetto tracing
+service. For example, the console interceptor prints all trace packets to the
+console as they are generated. Another potential use is exporting trace data
+to another tracing service such as Android ATrace or Windows ETW.
+
+An interceptor is defined by subclassing the `perfetto::Interceptor` template:
+
+```C++
+class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+ public:
+ ~MyInterceptor() override = default;
+
+ // This function is called for each intercepted trace packet. |context|
+ // contains information about the trace packet as well as other state
+ // tracked by the interceptor (e.g., see ThreadLocalState).
+ //
+ // Intercepted trace data is provided in the form of serialized protobuf
+ // bytes, accessed through the |context.packet_data| field.
+ //
+ // Warning: this function can be called on any thread at any time. See
+ // below for how to safely access shared interceptor data from here.
+ static void OnTracePacket(InterceptorContext context) {
+ perfetto::protos::pbzero::TracePacket::Decoder packet(
+ context.packet_data.data, context.packet_data.size);
+ // ... Write |packet| to the desired destination ...
+ }
+};
+```
+
+An interceptor should be registered before any tracing sessions are started.
+Note that the interceptor also needs to be activated through the trace config
+shown below.
+
+```C++
+perfetto::InterceptorDescriptor desc;
+desc.set_name("my_interceptor");
+MyInterceptor::Register(desc);
+```
+
+Finally, an interceptor is enabled through the trace config like this:
+
+```C++
+perfetto::TraceConfig cfg;
+auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ds_cfg->set_name("data_source_to_intercept"); // e.g. "track_event"
+ds_cfg->mutable_interceptor_config()->set_name("my_interceptor");
+```
+
+Once an interceptor is enabled, all data from the affected data sources is
+sent to the interceptor instead of the main tracing buffer.
+
+## Interceptor state
+
+Besides the serialized trace packet data, the `OnTracePacket` interceptor
+function can access three other types of state:
+
+1. **Global state:** this is no different from a normal static function, but
+ care must be taken because |OnTracePacket| can be called concurrently on
+ any thread at any time.
+
+2. **Per-data source instance state:** since the interceptor class is
+ automatically instantiated for each intercepted data source, its fields
+ can be used to store per-instance data such as the trace config. This data
+ can be maintained through the OnSetup/OnStart/OnStop callbacks:
+
+ ```C++
+ class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+ public:
+ void OnSetup(const SetupArgs& args) override {
+ enable_foo_ = args.config.interceptor_config().enable_foo();
+ }
+
+ bool enable_foo_{};
+ };
+ ```
+
+ In the interceptor function this data must be accessed through a scoped
+ lock for safety:
+
+ ```C++
+ class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+ ...
+ static void OnTracePacket(InterceptorContext context) {
+ auto my_interceptor = context.GetInterceptorLocked();
+ if (my_interceptor) {
+ // Access fields of MyInterceptor here.
+ if (my_interceptor->enable_foo_) { ... }
+ }
+ ...
+ }
+ };
+ ```
+
+ Since accessing this data involves holding a lock, it should be done
+ sparingly.
+
+3. **Per-thread/TraceWriter state:** many data sources use interning to avoid
+ repeating common data in the trace. Since the interning dictionaries are
+ typically kept individually for each TraceWriter sequence (i.e., per
+ thread), an interceptor can declare a data structure with lifetime
+ matching the TraceWriter:
+
+ ```C++
+ class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+ public:
+ struct ThreadLocalState
+ : public perfetto::InterceptorBase::ThreadLocalState {
+ ThreadLocalState(ThreadLocalStateArgs&) override = default;
+ ~ThreadLocalState() override = default;
+
+ std::map<size_t, std::string> event_names;
+ };
+ };
+ ```
+
+ This per-thread state can then be accessed and maintained in
+ `OnTracePacket` like this:
+
+ ```C++
+ class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+ ...
+ static void OnTracePacket(InterceptorContext context) {
+ // Updating interned data.
+ auto& tls = context.GetThreadLocalState();
+ if (parsed_packet.sequence_flags() & perfetto::protos::pbzero::
+ TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+ tls.event_names.clear();
+ }
+ for (const auto& entry : parsed_packet.interned_data().event_names())
+ tls.event_names[entry.iid()] = entry.name();
+
+ // Looking up interned data.
+ if (parsed_packet.has_track_event()) {
+ size_t name_iid = parsed_packet.track_event().name_iid();
+ const std::string& event_name = tls.event_names[name_iid];
+ }
+ ...
+ }
+ };
+ ```
\ No newline at end of file
diff --git a/docs/toc.md b/docs/toc.md
index b3b87bc..ac1500b 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -28,6 +28,7 @@
* [App Instrumentation](#)
* [Tracing SDK](instrumentation/tracing-sdk.md)
* [Track events](instrumentation/track-events.md)
+ * [Interceptors](instrumentation/interceptors.md)
* [Trace analysis](#)
* [Trace Processor (SQL)](analysis/trace-processor.md)