blob: b800cb32b0ee66b27823efd2eabcfacf15df925b [file] [log] [blame]
/*
* 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<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
// 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<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:
//
// 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:
//
// 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:
//
// 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];
// }
// ...
// }
// };
//
#include <functional>
#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 InterceptorTraceWriterTest;
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_COMPONENT 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::InterceptorTraceWriterTest;
friend class internal::TracingMuxer;
friend class internal::TracingMuxerFake;
friend class internal::TracingMuxerImpl;
friend MockTracingMuxer;
template <class T>
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<ThreadLocalState> (*)(
internal::DataSourceStaticState*,
uint32_t data_source_instance_index);
using TracePacketCallback = void (*)(TracePacketCallbackArgs);
static void RegisterImpl(
const InterceptorDescriptor& descriptor,
std::function<std::unique_ptr<InterceptorBase>()> factory,
InterceptorBase::TLSFactory tls_factory,
InterceptorBase::TracePacketCallback on_trace_packet);
};
// Templated interceptor instantiation. See above for usage.
template <class InterceptorType>
class PERFETTO_EXPORT_COMPONENT 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;
ThreadLocalStateArgs(const ThreadLocalStateArgs&) = delete;
ThreadLocalStateArgs& operator=(const ThreadLocalStateArgs&) = delete;
ThreadLocalStateArgs(ThreadLocalStateArgs&&) noexcept = default;
ThreadLocalStateArgs& operator=(ThreadLocalStateArgs&&) noexcept = 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<InterceptorType> GetInterceptorLocked() {
auto* internal_state = static_state_->TryGet(data_source_instance_index_);
if (!internal_state)
return LockedHandle<InterceptorType>();
std::unique_lock<std::recursive_mutex> lock(internal_state->lock);
return LockedHandle<InterceptorType>(
std::move(lock),
static_cast<InterceptorType*>(internal_state->interceptor.get()));
}
private:
friend class Interceptor<InterceptorType>;
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<InterceptorType> GetInterceptorLocked() {
return tls_args_.GetInterceptorLocked();
}
// Return the thread-local state for this interceptor. See
// InterceptorBase::ThreadLocalState.
typename InterceptorType::ThreadLocalState& GetThreadLocalState() {
return static_cast<typename InterceptorType::ThreadLocalState&>(*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<InterceptorType>;
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 <class... Args>
static void Register(const InterceptorDescriptor& descriptor,
const Args&... constructor_args) {
auto factory = [constructor_args...]() {
return std::unique_ptr<InterceptorBase>(
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<typename InterceptorType::ThreadLocalState,
InterceptorBase::ThreadLocalState>::value) {
return std::unique_ptr<InterceptorBase::ThreadLocalState>(nullptr);
}
ThreadLocalStateArgs args(static_state, data_source_instance_index);
return std::unique_ptr<InterceptorBase::ThreadLocalState>(
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_