| # 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]; |
| } |
| ... |
| } |
| }; |
| ``` |