/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef INCLUDE_PERFETTO_TRACING_INTERCEPTOR_H_ #define INCLUDE_PERFETTO_TRACING_INTERCEPTOR_H_ // An 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: // // class MyInterceptor : public perfetto::Interceptor { // 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 // as shown below. // // perfetto::InterceptorDescriptor desc; // desc.set_name("my_interceptor"); // MyInterceptor::Register(desc); // // Finally, an interceptor is enabled through the trace config like this: // // 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: // // class MyInterceptor : public perfetto::Interceptor { // 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: // // class MyInterceptor : public perfetto::Interceptor { // ... // 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: // // class MyInterceptor : public perfetto::Interceptor { // public: // struct ThreadLocalState // : public perfetto::InterceptorBase::ThreadLocalState { // ThreadLocalState(ThreadLocalStateArgs&) override = default; // ~ThreadLocalState() override = default; // // std::map event_names; // }; // }; // // This per-thread state can then be accessed and maintained in // |OnTracePacket| like this: // // class MyInterceptor : public perfetto::Interceptor { // ... // 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]; // } // ... // } // }; // #include #include "perfetto/protozero/field.h" #include "perfetto/tracing/core/forward_decls.h" #include "perfetto/tracing/internal/basic_types.h" #include "perfetto/tracing/internal/data_source_internal.h" #include "perfetto/tracing/locked_handle.h" namespace { class MockTracingMuxer; } namespace perfetto { namespace protos { namespace gen { class DataSourceConfig; class InterceptorDescriptor; } // namespace gen } // namespace protos using protos::gen::InterceptorDescriptor; namespace internal { class InterceptorTraceWriter; class TracingMuxer; class TracingMuxerFake; class TracingMuxerImpl; } // namespace internal // A virtual base class for interceptors. Users should derive from the templated // subclass below instead of this one. class PERFETTO_EXPORT InterceptorBase { public: virtual ~InterceptorBase(); // A virtual base class for thread-local state needed by the interceptor. // To define your own state, subclass this with the same name in the // interceptor class. A reference to the state can then be looked up through // context.GetThreadLocalState() in the trace packet interceptor function. class ThreadLocalState { public: virtual ~ThreadLocalState(); }; struct SetupArgs { const DataSourceConfig& config; }; struct StartArgs {}; struct StopArgs {}; // Called when an intercepted data source is set up. Both the interceptor's // and the data source's configuration is available in // |SetupArgs|. Called on an internal Perfetto service thread, but not // concurrently. virtual void OnSetup(const SetupArgs&) {} // Called when an intercepted data source starts. Called on an internal // Perfetto service thread, but not concurrently. virtual void OnStart(const StartArgs&) {} // Called when an intercepted data source stops. Called on an internal // Perfetto service thread, but not concurrently. virtual void OnStop(const StopArgs&) {} private: friend class internal::InterceptorTraceWriter; friend class internal::TracingMuxer; friend class internal::TracingMuxerFake; friend class internal::TracingMuxerImpl; friend MockTracingMuxer; template friend class Interceptor; // Data passed from DataSource::Trace() into the interceptor. struct TracePacketCallbackArgs { internal::DataSourceStaticState* static_state; uint32_t instance_index; protozero::ConstBytes packet_data; ThreadLocalState* tls; }; // These callback functions are defined as stateless to avoid accidentally // introducing cross-thread data races. using TLSFactory = std::unique_ptr (*)( internal::DataSourceStaticState*, uint32_t data_source_instance_index); using TracePacketCallback = void (*)(TracePacketCallbackArgs); static void RegisterImpl( const InterceptorDescriptor& descriptor, std::function()> factory, InterceptorBase::TLSFactory tls_factory, InterceptorBase::TracePacketCallback on_trace_packet); }; // Templated interceptor instantiation. See above for usage. template class PERFETTO_EXPORT Interceptor : public InterceptorBase { public: // A context object provided to the ThreadLocalState constructor. Provides // access to the per-instance interceptor object. class ThreadLocalStateArgs { public: ~ThreadLocalStateArgs() = default; // Return a locked reference to the interceptor session. The session object // will remain valid as long as the returned handle is in scope. LockedHandle GetInterceptorLocked() { auto* internal_state = static_state_->TryGet(data_source_instance_index_); if (!internal_state) return LockedHandle(); return LockedHandle( &internal_state->lock, static_cast(internal_state->interceptor.get())); } private: friend class Interceptor; friend class InterceptorContext; friend class TracingMuxerImpl; ThreadLocalStateArgs(internal::DataSourceStaticState* static_state, uint32_t data_source_instance_index) : static_state_(static_state), data_source_instance_index_(data_source_instance_index) {} internal::DataSourceStaticState* const static_state_; const uint32_t data_source_instance_index_; }; // A context object provided to each call into |OnTracePacket|. Contains the // intercepted serialized trace packet data. class InterceptorContext { public: InterceptorContext(InterceptorContext&&) noexcept = default; ~InterceptorContext() = default; // Return a locked reference to the interceptor session. The session object // will remain valid as long as the returned handle is in scope. LockedHandle GetInterceptorLocked() { return tls_args_.GetInterceptorLocked(); } // Return the thread-local state for this interceptor. See // InterceptorBase::ThreadLocalState. typename InterceptorType::ThreadLocalState& GetThreadLocalState() { return static_cast(*tls_); } // A buffer containing the serialized TracePacket protocol buffer message. // This memory is only valid during the call to OnTracePacket. protozero::ConstBytes packet_data; private: friend class Interceptor; InterceptorContext(TracePacketCallbackArgs args) : packet_data(args.packet_data), tls_args_(args.static_state, args.instance_index), tls_(args.tls) {} InterceptorContext(const InterceptorContext&) = delete; InterceptorContext& operator=(const InterceptorContext&) = delete; ThreadLocalStateArgs tls_args_; InterceptorBase::ThreadLocalState* const tls_; }; // Register the interceptor for use in tracing sessions. // The optional |constructor_args| will be passed to the interceptor when it // is constructed. template static void Register(const InterceptorDescriptor& descriptor, const Args&... constructor_args) { auto factory = [constructor_args...]() { return std::unique_ptr( new InterceptorType(constructor_args...)); }; auto tls_factory = [](internal::DataSourceStaticState* static_state, uint32_t data_source_instance_index) { // Don't bother allocating TLS state unless the interceptor is actually // using it. if (std::is_same::value) { return std::unique_ptr(nullptr); } ThreadLocalStateArgs args(static_state, data_source_instance_index); return std::unique_ptr( new typename InterceptorType::ThreadLocalState(args)); }; auto on_trace_packet = [](TracePacketCallbackArgs args) { InterceptorType::OnTracePacket(InterceptorContext(std::move(args))); }; RegisterImpl(descriptor, std::move(factory), std::move(tls_factory), std::move(on_trace_packet)); } }; } // namespace perfetto #endif // INCLUDE_PERFETTO_TRACING_INTERCEPTOR_H_