diff options
Diffstat (limited to 'src/tracing/test/api_integrationtest.cc')
-rw-r--r-- | src/tracing/test/api_integrationtest.cc | 2471 |
1 files changed, 2471 insertions, 0 deletions
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc new file mode 100644 index 000000000..6eaa39c85 --- /dev/null +++ b/src/tracing/test/api_integrationtest.cc @@ -0,0 +1,2471 @@ +/* + * Copyright (C) 2019 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. + */ + +#include <fcntl.h> + +#include <chrono> +#include <condition_variable> +#include <functional> +#include <list> +#include <mutex> +#include <thread> +#include <vector> + +// We also want to test legacy trace events. +#define PERFETTO_ENABLE_LEGACY_TRACE_EVENTS 1 + +#include "perfetto/tracing.h" +#include "test/gtest_and_gmock.h" + +// Deliberately not pulling any non-public perfetto header to spot accidental +// header public -> non-public dependency while building this file. + +// These two are the only headers allowed here, see comments in +// api_test_support.h. +#include "src/tracing/test/api_test_support.h" +#include "src/tracing/test/tracing_module.h" + +#include "perfetto/tracing/core/data_source_descriptor.h" +#include "perfetto/tracing/core/trace_config.h" + +// xxx.pbzero.h includes are for the writing path (the code that pretends to be +// production code). +// yyy.gen.h includes are for the test readback path (the code in the test that +// checks that the results are valid). +#include "protos/perfetto/common/track_event_descriptor.gen.h" +#include "protos/perfetto/config/track_event/track_event_config.gen.h" +#include "protos/perfetto/trace/clock_snapshot.pbzero.h" +#include "protos/perfetto/trace/interned_data/interned_data.gen.h" +#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h" +#include "protos/perfetto/trace/profiling/profile_common.gen.h" +#include "protos/perfetto/trace/test_event.gen.h" +#include "protos/perfetto/trace/test_event.pbzero.h" +#include "protos/perfetto/trace/trace.gen.h" +#include "protos/perfetto/trace/trace_packet.gen.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" +#include "protos/perfetto/trace/track_event/chrome_process_descriptor.gen.h" +#include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h" +#include "protos/perfetto/trace/track_event/debug_annotation.gen.h" +#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h" +#include "protos/perfetto/trace/track_event/log_message.gen.h" +#include "protos/perfetto/trace/track_event/log_message.pbzero.h" +#include "protos/perfetto/trace/track_event/process_descriptor.gen.h" +#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h" +#include "protos/perfetto/trace/track_event/source_location.gen.h" +#include "protos/perfetto/trace/track_event/source_location.pbzero.h" +#include "protos/perfetto/trace/track_event/thread_descriptor.gen.h" +#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" +#include "protos/perfetto/trace/track_event/track_descriptor.gen.h" +#include "protos/perfetto/trace/track_event/track_event.gen.h" + +// Events in categories starting with "dynamic" will use dynamic category +// lookup. +PERFETTO_DEFINE_TEST_CATEGORY_PREFIXES("dynamic"); + +// Trace categories used in the tests. +PERFETTO_DEFINE_CATEGORIES( + perfetto::Category("test") + .SetDescription("This is a test category") + .SetTags("tag"), + perfetto::Category("foo"), + perfetto::Category("bar"), + perfetto::Category("cat").SetTags("slow"), + perfetto::Category("cat.verbose").SetTags("debug"), + perfetto::Category::Group("foo,bar"), + perfetto::Category::Group("baz,bar,quux"), + perfetto::Category::Group("red,green,blue,foo"), + perfetto::Category::Group("red,green,blue,yellow"), + perfetto::Category(TRACE_DISABLED_BY_DEFAULT("cat"))); +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + +// For testing interning of complex objects. +using SourceLocation = std::tuple<const char* /* file_name */, + const char* /* function_name */, + uint32_t /* line_number */>; + +namespace std { +template <> +struct hash<SourceLocation> { + size_t operator()(const SourceLocation& value) const { + auto hasher = hash<size_t>(); + return hasher(reinterpret_cast<size_t>(get<0>(value))) ^ + hasher(reinterpret_cast<size_t>(get<1>(value))) ^ + hasher(get<2>(value)); + } +}; +} // namespace std + +// Represents an opaque (from Perfetto's point of view) thread identifier (e.g., +// base::PlatformThreadId in Chromium). +struct MyThreadId { + MyThreadId(int pid_, int tid_) : pid(pid_), tid(tid_) {} + + const int pid = 0; + const int tid = 0; +}; + +// Represents an opaque timestamp (e.g., base::TimeTicks in Chromium). +class MyTimestamp { + public: + explicit MyTimestamp(uint64_t ts_) : ts(ts_) {} + + const uint64_t ts; +}; + +namespace perfetto { +namespace legacy { + +template <> +bool ConvertThreadId(const MyThreadId& thread, + uint64_t* track_uuid_out, + int32_t* pid_override_out, + int32_t* tid_override_out) { + if (!thread.pid && !thread.tid) + return false; + if (!thread.pid) { + // Thread in current process. + *track_uuid_out = perfetto::ThreadTrack::ForThread( + static_cast<base::PlatformThreadId>(thread.tid)) + .uuid; + } else { + // Thread in another process. + *pid_override_out = thread.pid; + *tid_override_out = thread.tid; + } + return true; +} + +template <> +uint64_t ConvertTimestampToTraceTimeNs(const MyTimestamp& timestamp) { + return timestamp.ts; +} + +} // namespace legacy +} // namespace perfetto + +namespace { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::HasSubstr; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::NiceMock; +using ::testing::Not; +using ::testing::Property; +using ::testing::StrEq; + +// ------------------------------ +// Declarations of helper classes +// ------------------------------ +static constexpr auto kWaitEventTimeout = std::chrono::seconds(5); + +struct WaitableTestEvent { + std::mutex mutex_; + std::condition_variable cv_; + bool notified_ = false; + + bool notified() { + std::unique_lock<std::mutex> lock(mutex_); + return notified_; + } + + void Wait() { + std::unique_lock<std::mutex> lock(mutex_); + if (!cv_.wait_for(lock, kWaitEventTimeout, [this] { return notified_; })) { + fprintf(stderr, "Timed out while waiting for event\n"); + abort(); + } + } + + void Notify() { + std::unique_lock<std::mutex> lock(mutex_); + notified_ = true; + cv_.notify_one(); + } +}; + +class MockDataSource; + +// We can't easily use gmock here because instances of data sources are lazily +// created by the service and are not owned by the test fixture. +struct TestDataSourceHandle { + WaitableTestEvent on_create; + WaitableTestEvent on_setup; + WaitableTestEvent on_start; + WaitableTestEvent on_stop; + MockDataSource* instance; + perfetto::DataSourceConfig config; + bool handle_stop_asynchronously = false; + std::function<void()> on_start_callback; + std::function<void()> on_stop_callback; + std::function<void()> async_stop_closure; +}; + +class MockDataSource : public perfetto::DataSource<MockDataSource> { + public: + void OnSetup(const SetupArgs&) override; + void OnStart(const StartArgs&) override; + void OnStop(const StopArgs&) override; + TestDataSourceHandle* handle_ = nullptr; +}; + +class MockDataSource2 : public perfetto::DataSource<MockDataSource2> { + public: + void OnSetup(const SetupArgs&) override {} + void OnStart(const StartArgs&) override {} + void OnStop(const StopArgs&) override {} +}; + +// Used to verify that track event data sources in different namespaces register +// themselves correctly in the muxer. +class MockTracingMuxer : public perfetto::internal::TracingMuxer { + public: + struct DataSource { + const perfetto::DataSourceDescriptor dsd; + perfetto::internal::DataSourceStaticState* static_state; + }; + + MockTracingMuxer() : TracingMuxer(nullptr), prev_instance_(instance_) { + instance_ = this; + } + ~MockTracingMuxer() override { instance_ = prev_instance_; } + + bool RegisterDataSource( + const perfetto::DataSourceDescriptor& dsd, + DataSourceFactory, + perfetto::internal::DataSourceStaticState* static_state) override { + data_sources.emplace_back(DataSource{dsd, static_state}); + return true; + } + + std::unique_ptr<perfetto::TraceWriterBase> CreateTraceWriter( + perfetto::internal::DataSourceState*, + perfetto::BufferExhaustedPolicy) override { + return nullptr; + } + + void DestroyStoppedTraceWritersForCurrentThread() override {} + + std::vector<DataSource> data_sources; + + private: + TracingMuxer* prev_instance_; +}; + +struct TestIncrementalState { + TestIncrementalState() { constructed = true; } + // Note: a virtual destructor is not required for incremental state. + ~TestIncrementalState() { destroyed = true; } + + int count = 100; + static bool constructed; + static bool destroyed; +}; + +bool TestIncrementalState::constructed; +bool TestIncrementalState::destroyed; + +struct TestIncrementalDataSourceTraits + : public perfetto::DefaultDataSourceTraits { + using IncrementalStateType = TestIncrementalState; +}; + +class TestIncrementalDataSource + : public perfetto::DataSource<TestIncrementalDataSource, + TestIncrementalDataSourceTraits> { + public: + void OnSetup(const SetupArgs&) override {} + void OnStart(const StartArgs&) override {} + void OnStop(const StopArgs&) override {} +}; + +// A convenience wrapper around TracingSession that allows to do block on +// +struct TestTracingSessionHandle { + perfetto::TracingSession* get() { return session.get(); } + std::unique_ptr<perfetto::TracingSession> session; + WaitableTestEvent on_stop; +}; + +class MyDebugAnnotation : public perfetto::DebugAnnotation { + public: + ~MyDebugAnnotation() override = default; + + void Add( + perfetto::protos::pbzero::DebugAnnotation* annotation) const override { + annotation->set_legacy_json_value(R"({"key": 123})"); + } +}; + +// ------------------------- +// Declaration of test class +// ------------------------- +class PerfettoApiTest : public ::testing::Test { + public: + static PerfettoApiTest* instance; + + void SetUp() override { + instance = this; + + perfetto::TracingInitArgs args; + args.backends = perfetto::kInProcessBackend; + perfetto::Tracing::Initialize(args); + RegisterDataSource<MockDataSource>("my_data_source"); + perfetto::TrackEvent::Register(); + + // Make sure our data source always has a valid handle. + data_sources_["my_data_source"]; + } + + void TearDown() override { instance = nullptr; } + + template <typename DataSourceType> + TestDataSourceHandle* RegisterDataSource(std::string name) { + EXPECT_EQ(data_sources_.count(name), 0u); + TestDataSourceHandle* handle = &data_sources_[name]; + perfetto::DataSourceDescriptor dsd; + dsd.set_name(name); + DataSourceType::Register(dsd); + return handle; + } + + TestTracingSessionHandle* NewTrace(const perfetto::TraceConfig& cfg, + int fd = -1) { + sessions_.emplace_back(); + TestTracingSessionHandle* handle = &sessions_.back(); + handle->session = + perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend); + handle->session->SetOnStopCallback([handle] { handle->on_stop.Notify(); }); + handle->session->Setup(cfg, fd); + return handle; + } + + TestTracingSessionHandle* NewTraceWithCategories( + std::vector<std::string> categories) { + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_disabled_categories("*"); + for (const auto& category : categories) + te_cfg.add_enabled_categories(category); + ds_cfg->set_track_event_config_raw(te_cfg.SerializeAsString()); + + return NewTrace(cfg); + } + + std::vector<std::string> ReadLogMessagesFromTrace( + perfetto::TracingSession* tracing_session) { + std::vector<char> raw_trace = tracing_session->ReadTraceBlocking(); + EXPECT_GE(raw_trace.size(), 0u); + + // Read back the trace, maintaining interning tables as we go. + std::vector<std::string> log_messages; + std::map<uint64_t, std::string> log_message_bodies; + std::map<uint64_t, perfetto::protos::gen::SourceLocation> source_locations; + perfetto::protos::gen::Trace parsed_trace; + EXPECT_TRUE( + parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + for (const auto& packet : parsed_trace.packet()) { + if (!packet.has_track_event()) + continue; + + if (packet.has_interned_data()) { + const auto& interned_data = packet.interned_data(); + for (const auto& it : interned_data.log_message_body()) { + EXPECT_GE(it.iid(), 1u); + EXPECT_EQ(log_message_bodies.find(it.iid()), + log_message_bodies.end()); + log_message_bodies[it.iid()] = it.body(); + } + for (const auto& it : interned_data.source_locations()) { + EXPECT_GE(it.iid(), 1u); + EXPECT_EQ(source_locations.find(it.iid()), source_locations.end()); + source_locations[it.iid()] = it; + } + } + const auto& track_event = packet.track_event(); + if (track_event.type() != + perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) + continue; + + EXPECT_TRUE(track_event.has_log_message()); + const auto& log = track_event.log_message(); + if (log.source_location_iid()) { + std::stringstream msg; + const auto& source_location = + source_locations[log.source_location_iid()]; + msg << source_location.function_name() << "(" + << source_location.file_name() << ":" + << source_location.line_number() + << "): " << log_message_bodies[log.body_iid()]; + log_messages.emplace_back(msg.str()); + } else { + log_messages.emplace_back(log_message_bodies[log.body_iid()]); + } + } + return log_messages; + } + + std::vector<std::string> ReadSlicesFromTrace( + perfetto::TracingSession* tracing_session) { + std::vector<char> raw_trace = tracing_session->ReadTraceBlocking(); + EXPECT_GE(raw_trace.size(), 0u); + + // Read back the trace, maintaining interning tables as we go. + std::vector<std::string> slices; + std::map<uint64_t, std::string> categories; + std::map<uint64_t, std::string> event_names; + std::map<uint64_t, std::string> debug_annotation_names; + perfetto::protos::gen::Trace parsed_trace; + EXPECT_TRUE( + parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + bool incremental_state_was_cleared = false; + uint32_t sequence_id = 0; + for (const auto& packet : parsed_trace.packet()) { + if (packet.sequence_flags() & perfetto::protos::pbzero::TracePacket:: + SEQ_INCREMENTAL_STATE_CLEARED) { + incremental_state_was_cleared = true; + categories.clear(); + event_names.clear(); + debug_annotation_names.clear(); + } + + if (!packet.has_track_event()) + continue; + + // Make sure we only see track events on one sequence. + if (packet.trusted_packet_sequence_id()) { + if (!sequence_id) + sequence_id = packet.trusted_packet_sequence_id(); + EXPECT_EQ(sequence_id, packet.trusted_packet_sequence_id()); + } + + // Update incremental state. + if (packet.has_interned_data()) { + const auto& interned_data = packet.interned_data(); + for (const auto& it : interned_data.event_categories()) { + EXPECT_EQ(categories.find(it.iid()), categories.end()); + categories[it.iid()] = it.name(); + } + for (const auto& it : interned_data.event_names()) { + EXPECT_EQ(event_names.find(it.iid()), event_names.end()); + event_names[it.iid()] = it.name(); + } + for (const auto& it : interned_data.debug_annotation_names()) { + EXPECT_EQ(debug_annotation_names.find(it.iid()), + debug_annotation_names.end()); + debug_annotation_names[it.iid()] = it.name(); + } + } + const auto& track_event = packet.track_event(); + std::string slice; + switch (track_event.type()) { + case perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN: + slice += "B"; + break; + case perfetto::protos::gen::TrackEvent::TYPE_SLICE_END: + slice += "E"; + break; + case perfetto::protos::gen::TrackEvent::TYPE_INSTANT: + slice += "I"; + break; + case perfetto::protos::gen::TrackEvent::TYPE_UNSPECIFIED: { + EXPECT_TRUE(track_event.has_legacy_event()); + EXPECT_FALSE(track_event.type()); + auto legacy_event = track_event.legacy_event(); + slice += "Legacy_" + + std::string(1, static_cast<char>(legacy_event.phase())); + break; + } + case perfetto::protos::gen::TrackEvent::TYPE_COUNTER: + slice += "C"; + break; + default: + ADD_FAILURE(); + } + if (track_event.has_legacy_event()) { + auto legacy_event = track_event.legacy_event(); + std::stringstream id; + if (legacy_event.has_unscoped_id()) { + id << "(unscoped_id=" << legacy_event.unscoped_id() << ")"; + } else if (legacy_event.has_local_id()) { + id << "(local_id=" << legacy_event.local_id() << ")"; + } else if (legacy_event.has_global_id()) { + id << "(global_id=" << legacy_event.global_id() << ")"; + } else if (legacy_event.has_bind_id()) { + id << "(bind_id=" << legacy_event.bind_id() << ")"; + } + if (legacy_event.has_id_scope()) + id << "(id_scope=\"" << legacy_event.id_scope() << "\")"; + if (legacy_event.use_async_tts()) + id << "(use_async_tts)"; + if (legacy_event.bind_to_enclosing()) + id << "(bind_to_enclosing)"; + if (legacy_event.has_flow_direction()) + id << "(flow_direction=" << legacy_event.flow_direction() << ")"; + if (legacy_event.has_pid_override()) + id << "(pid_override=" << legacy_event.pid_override() << ")"; + if (legacy_event.has_tid_override()) + id << "(tid_override=" << legacy_event.tid_override() << ")"; + slice += id.str(); + } + size_t category_count = 0; + for (const auto& it : track_event.category_iids()) + slice += (category_count++ ? "," : ":") + categories[it]; + for (const auto& it : track_event.categories()) + slice += (category_count++ ? ",$" : ":$") + it; + if (track_event.has_name_iid()) + slice += "." + event_names[track_event.name_iid()]; + + if (track_event.debug_annotations_size()) { + slice += "("; + bool first_annotation = true; + for (const auto& it : track_event.debug_annotations()) { + if (!first_annotation) { + slice += ","; + } + slice += debug_annotation_names[it.name_iid()] + "="; + std::stringstream value; + if (it.has_bool_value()) { + value << "(bool)" << it.bool_value(); + } else if (it.has_uint_value()) { + value << "(uint)" << it.uint_value(); + } else if (it.has_int_value()) { + value << "(int)" << it.int_value(); + } else if (it.has_double_value()) { + value << "(double)" << it.double_value(); + } else if (it.has_string_value()) { + value << "(string)" << it.string_value(); + } else if (it.has_pointer_value()) { + value << "(pointer)" << std::hex << it.pointer_value(); + } else if (it.has_legacy_json_value()) { + value << "(json)" << it.legacy_json_value(); + } else if (it.has_nested_value()) { + value << "(nested)" << it.nested_value().string_value(); + } + slice += value.str(); + first_annotation = false; + } + slice += ")"; + } + + slices.push_back(slice); + } + EXPECT_TRUE(incremental_state_was_cleared); + return slices; + } + + std::map<std::string, TestDataSourceHandle> data_sources_; + std::list<TestTracingSessionHandle> sessions_; // Needs stable pointers. +}; + +// --------------------------------------------- +// Definitions for non-inlineable helper methods +// --------------------------------------------- +PerfettoApiTest* PerfettoApiTest::instance; + +void MockDataSource::OnSetup(const SetupArgs& args) { + EXPECT_EQ(handle_, nullptr); + auto it = PerfettoApiTest::instance->data_sources_.find(args.config->name()); + + // We should not see an OnSetup for a data source that we didn't register + // before via PerfettoApiTest::RegisterDataSource(). + EXPECT_NE(it, PerfettoApiTest::instance->data_sources_.end()); + handle_ = &it->second; + handle_->config = *args.config; // Deliberate copy. + handle_->on_setup.Notify(); +} + +void MockDataSource::OnStart(const StartArgs&) { + EXPECT_NE(handle_, nullptr); + if (handle_->on_start_callback) + handle_->on_start_callback(); + handle_->on_start.Notify(); +} + +void MockDataSource::OnStop(const StopArgs& args) { + EXPECT_NE(handle_, nullptr); + if (handle_->handle_stop_asynchronously) + handle_->async_stop_closure = args.HandleStopAsynchronously(); + if (handle_->on_stop_callback) + handle_->on_stop_callback(); + handle_->on_stop.Notify(); +} + +// ------------- +// Test fixtures +// ------------- + +TEST_F(PerfettoApiTest, TrackEventStartStopAndDestroy) { + // This test used to cause a use after free as the tracing session got + // destroyed. It needed to be run approximately 2000 times to catch it so test + // with --gtest_repeat=3000 (less if running under GDB). + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + // Create five new trace sessions. + std::vector<std::unique_ptr<perfetto::TracingSession>> sessions; + for (size_t i = 0; i < 5; ++i) { + sessions.push_back( + perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend)); + sessions[i]->Setup(cfg); + sessions[i]->Start(); + sessions[i]->Stop(); + } +} + +TEST_F(PerfettoApiTest, TrackEventStartStopAndStopBlocking) { + // This test used to cause a deadlock (due to StopBlocking() after the session + // already stopped). This usually occurred within 1 or 2 runs of the test so + // use --gtest_repeat=10 + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + // Create five new trace sessions. + std::vector<std::unique_ptr<perfetto::TracingSession>> sessions; + for (size_t i = 0; i < 5; ++i) { + sessions.push_back( + perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend)); + sessions[i]->Setup(cfg); + sessions[i]->Start(); + sessions[i]->Stop(); + } + for (auto& session : sessions) { + session->StopBlocking(); + } +} + +// This is a build-only regression test that checks you can have a track event +// inside a template. +template <typename T> +void TestTrackEventInsideTemplate(T) { + TRACE_EVENT_BEGIN("cat", "Name"); +} + +TEST_F(PerfettoApiTest, TrackEvent) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"test"}); + tracing_session->get()->StartBlocking(); + + // Emit one complete track event. + TRACE_EVENT_BEGIN("test", "TestEvent"); + TRACE_EVENT_END("test"); + perfetto::TrackEvent::Flush(); + + tracing_session->on_stop.Wait(); + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + ASSERT_GE(raw_trace.size(), 0u); + + // Read back the trace, maintaining interning tables as we go. + perfetto::protos::gen::Trace trace; + std::map<uint64_t, std::string> categories; + std::map<uint64_t, std::string> event_names; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + auto now = perfetto::TrackEvent::GetTraceTimeNs(); +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + auto clock_id = perfetto::protos::pbzero::ClockSnapshot::Clock::BOOTTIME; +#else + auto clock_id = perfetto::protos::pbzero::ClockSnapshot::Clock::MONOTONIC; +#endif + EXPECT_EQ(clock_id, perfetto::TrackEvent::GetTraceClockId()); + + bool incremental_state_was_cleared = false; + bool begin_found = false; + bool end_found = false; + bool process_descriptor_found = false; + uint32_t sequence_id = 0; + int32_t cur_pid = perfetto::test::GetCurrentProcessId(); + for (const auto& packet : trace.packet()) { + if (packet.has_track_descriptor()) { + const auto& desc = packet.track_descriptor(); + if (desc.has_process()) { + EXPECT_FALSE(process_descriptor_found); + const auto& pd = desc.process(); + EXPECT_EQ(cur_pid, pd.pid()); + process_descriptor_found = true; + } + } + if (packet.sequence_flags() & + perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) { + EXPECT_TRUE(packet.has_trace_packet_defaults()); + incremental_state_was_cleared = true; + categories.clear(); + event_names.clear(); + } + + if (!packet.has_track_event()) + continue; + EXPECT_TRUE( + packet.sequence_flags() & + (perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED | + perfetto::protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE)); + const auto& track_event = packet.track_event(); + + // Make sure we only see track events on one sequence. + if (packet.trusted_packet_sequence_id()) { + if (!sequence_id) + sequence_id = packet.trusted_packet_sequence_id(); + EXPECT_EQ(sequence_id, packet.trusted_packet_sequence_id()); + } + + // Update incremental state. + if (packet.has_interned_data()) { + const auto& interned_data = packet.interned_data(); + for (const auto& it : interned_data.event_categories()) { + EXPECT_EQ(categories.find(it.iid()), categories.end()); + categories[it.iid()] = it.name(); + } + for (const auto& it : interned_data.event_names()) { + EXPECT_EQ(event_names.find(it.iid()), event_names.end()); + event_names[it.iid()] = it.name(); + } + } + + EXPECT_GT(packet.timestamp(), 0u); + EXPECT_LE(packet.timestamp(), now); +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + EXPECT_FALSE(packet.has_timestamp_clock_id()); +#else + constexpr auto kClockMonotonic = + perfetto::protos::pbzero::ClockSnapshot::Clock::MONOTONIC; + EXPECT_EQ(packet.timestamp_clock_id(), + static_cast<uint32_t>(kClockMonotonic)); +#endif + if (track_event.type() == + perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) { + EXPECT_FALSE(begin_found); + EXPECT_EQ(track_event.category_iids().size(), 1u); + EXPECT_GE(track_event.category_iids()[0], 1u); + EXPECT_EQ("test", categories[track_event.category_iids()[0]]); + EXPECT_EQ("TestEvent", event_names[track_event.name_iid()]); + begin_found = true; + } else if (track_event.type() == + perfetto::protos::gen::TrackEvent::TYPE_SLICE_END) { + EXPECT_FALSE(end_found); + EXPECT_EQ(track_event.category_iids().size(), 0u); + EXPECT_EQ(0u, track_event.name_iid()); + end_found = true; + } + } + EXPECT_TRUE(incremental_state_was_cleared); + EXPECT_TRUE(process_descriptor_found); + EXPECT_TRUE(begin_found); + EXPECT_TRUE(end_found); + + // Dummy instantiation of test template. + TestTrackEventInsideTemplate(true); +} + +TEST_F(PerfettoApiTest, TrackEventCategories) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"bar"}); + tracing_session->get()->StartBlocking(); + + // Emit some track events. + TRACE_EVENT_BEGIN("foo", "NotEnabled"); + TRACE_EVENT_END("foo"); + TRACE_EVENT_BEGIN("bar", "Enabled"); + TRACE_EVENT_END("bar"); + + tracing_session->get()->StopBlocking(); + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + std::string trace(raw_trace.data(), raw_trace.size()); + // TODO(skyostil): Come up with a nicer way to verify trace contents. + EXPECT_THAT(trace, HasSubstr("Enabled")); + EXPECT_THAT(trace, Not(HasSubstr("NotEnabled"))); +} + +TEST_F(PerfettoApiTest, TrackEventRegistrationWithModule) { + MockTracingMuxer muxer; + + // Each track event namespace registers its own data source. + perfetto::TrackEvent::Register(); + EXPECT_EQ(1u, muxer.data_sources.size()); + + tracing_module::InitializeCategories(); + EXPECT_EQ(2u, muxer.data_sources.size()); + + // Both data sources have the same name but distinct static data (i.e., + // individual instance states). + EXPECT_EQ("track_event", muxer.data_sources[0].dsd.name()); + EXPECT_EQ("track_event", muxer.data_sources[1].dsd.name()); + EXPECT_NE(muxer.data_sources[0].static_state, + muxer.data_sources[1].static_state); +} + +TEST_F(PerfettoApiTest, TrackEventDescriptor) { + MockTracingMuxer muxer; + + perfetto::TrackEvent::Register(); + EXPECT_EQ(1u, muxer.data_sources.size()); + EXPECT_EQ("track_event", muxer.data_sources[0].dsd.name()); + + perfetto::protos::gen::TrackEventDescriptor desc; + auto desc_raw = muxer.data_sources[0].dsd.track_event_descriptor_raw(); + EXPECT_TRUE(desc.ParseFromArray(desc_raw.data(), desc_raw.size())); + + // Check that the advertised categories match PERFETTO_DEFINE_CATEGORIES (see + // above). + EXPECT_EQ(6, desc.available_categories_size()); + EXPECT_EQ("test", desc.available_categories()[0].name()); + EXPECT_EQ("This is a test category", + desc.available_categories()[0].description()); + EXPECT_EQ("tag", desc.available_categories()[0].tags()[0]); + EXPECT_EQ("foo", desc.available_categories()[1].name()); + EXPECT_EQ("bar", desc.available_categories()[2].name()); + EXPECT_EQ("cat", desc.available_categories()[3].name()); + EXPECT_EQ("slow", desc.available_categories()[3].tags()[0]); + EXPECT_EQ("cat.verbose", desc.available_categories()[4].name()); + EXPECT_EQ("debug", desc.available_categories()[4].tags()[0]); + EXPECT_EQ("disabled-by-default-cat", desc.available_categories()[5].name()); + EXPECT_EQ("slow", desc.available_categories()[5].tags()[0]); +} + +TEST_F(PerfettoApiTest, TrackEventSharedIncrementalState) { + tracing_module::InitializeCategories(); + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + + perfetto::internal::TrackEventIncrementalState* main_state = nullptr; + perfetto::TrackEvent::Trace( + [&main_state](perfetto::TrackEvent::TraceContext ctx) { + main_state = ctx.GetIncrementalState(); + }); + perfetto::internal::TrackEventIncrementalState* module_state = + tracing_module::GetIncrementalState(); + + // Both track event data sources should use the same incremental state (thanks + // to sharing TLS). + EXPECT_NE(nullptr, main_state); + EXPECT_EQ(main_state, module_state); + tracing_session->get()->StopBlocking(); +} + +TEST_F(PerfettoApiTest, TrackEventCategoriesWithModule) { + // Check that categories defined in two different category registries are + // enabled and disabled correctly. + tracing_module::InitializeCategories(); + + // Create a new trace session. Only the "foo" category is enabled. It also + // exists both locally and in the existing module. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + // Emit some track events locally and from the test module. + TRACE_EVENT_BEGIN("foo", "FooEventFromMain"); + TRACE_EVENT_END("foo"); + tracing_module::EmitTrackEvents(); + tracing_module::EmitTrackEvents2(); + TRACE_EVENT_BEGIN("bar", "DisabledEventFromMain"); + TRACE_EVENT_END("bar"); + + tracing_session->get()->StopBlocking(); + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + std::string trace(raw_trace.data(), raw_trace.size()); + EXPECT_THAT(trace, HasSubstr("FooEventFromMain")); + EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromMain"))); + EXPECT_THAT(trace, HasSubstr("FooEventFromModule")); + EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromModule"))); + EXPECT_THAT(trace, HasSubstr("FooEventFromModule2")); + EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromModule2"))); + + perfetto::protos::gen::Trace parsed_trace; + ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + uint32_t sequence_id = 0; + for (const auto& packet : parsed_trace.packet()) { + if (!packet.has_track_event()) + continue; + + // Make sure we only see track events on one sequence. This means all track + // event modules are sharing the same trace writer (by using the same TLS + // index). + if (packet.trusted_packet_sequence_id()) { + if (!sequence_id) + sequence_id = packet.trusted_packet_sequence_id(); + EXPECT_EQ(sequence_id, packet.trusted_packet_sequence_id()); + } + } +} + +TEST_F(PerfettoApiTest, TrackEventDynamicCategories) { + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + // Session #1 enabled the "dynamic" category. + auto* tracing_session = NewTraceWithCategories({"dynamic"}); + tracing_session->get()->StartBlocking(); + + // Session #2 enables "dynamic_2". + auto* tracing_session2 = NewTraceWithCategories({"dynamic_2"}); + tracing_session2->get()->StartBlocking(); + + // Test naming dynamic categories with std::string. + perfetto::DynamicCategory dynamic{"dynamic"}; + TRACE_EVENT_BEGIN(dynamic, "EventInDynamicCategory"); + perfetto::DynamicCategory dynamic_disabled{"dynamic_disabled"}; + TRACE_EVENT_BEGIN(dynamic_disabled, "EventInDisabledDynamicCategory"); + + // Test naming dynamic categories statically. + TRACE_EVENT_BEGIN("dynamic", "EventInStaticallyNamedDynamicCategory"); + + perfetto::DynamicCategory dynamic_2{"dynamic_2"}; + TRACE_EVENT_BEGIN(dynamic_2, "EventInSecondDynamicCategory"); + TRACE_EVENT_BEGIN("dynamic_2", "EventInSecondStaticallyNamedDynamicCategory"); + + std::thread thread([] { + // Make sure the category name can actually be computed at runtime. + std::string name = "dyn"; + if (perfetto::base::GetThreadId()) + name += "amic"; + perfetto::DynamicCategory cat{name}; + TRACE_EVENT_BEGIN(cat, "DynamicFromOtherThread"); + perfetto::DynamicCategory cat2{"dynamic_disabled"}; + TRACE_EVENT_BEGIN(cat2, "EventInDisabledDynamicCategory"); + }); + thread.join(); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + std::string trace(raw_trace.data(), raw_trace.size()); + EXPECT_THAT(trace, HasSubstr("EventInDynamicCategory")); + EXPECT_THAT(trace, Not(HasSubstr("EventInDisabledDynamicCategory"))); + EXPECT_THAT(trace, HasSubstr("DynamicFromOtherThread")); + EXPECT_THAT(trace, Not(HasSubstr("EventInSecondDynamicCategory"))); + EXPECT_THAT(trace, HasSubstr("EventInStaticallyNamedDynamicCategory")); + EXPECT_THAT(trace, + Not(HasSubstr("EventInSecondStaticallyNamedDynamicCategory"))); + + tracing_session2->get()->StopBlocking(); + raw_trace = tracing_session2->get()->ReadTraceBlocking(); + trace = std::string(raw_trace.data(), raw_trace.size()); + EXPECT_THAT(trace, Not(HasSubstr("EventInDynamicCategory"))); + EXPECT_THAT(trace, Not(HasSubstr("EventInDisabledDynamicCategory"))); + EXPECT_THAT(trace, Not(HasSubstr("DynamicFromOtherThread"))); + EXPECT_THAT(trace, HasSubstr("EventInSecondDynamicCategory")); + EXPECT_THAT(trace, Not(HasSubstr("EventInStaticallyNamedDynamicCategory"))); + EXPECT_THAT(trace, HasSubstr("EventInSecondStaticallyNamedDynamicCategory")); +} + +TEST_F(PerfettoApiTest, TrackEventConcurrentSessions) { + // Check that categories that are enabled and disabled in two parallel tracing + // sessions don't interfere. + + // Session #1 enables the "foo" category. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + // Session #2 enables the "bar" category. + auto* tracing_session2 = NewTraceWithCategories({"bar"}); + tracing_session2->get()->StartBlocking(); + + // Emit some track events under both categories. + TRACE_EVENT_BEGIN("foo", "Session1_First"); + TRACE_EVENT_END("foo"); + TRACE_EVENT_BEGIN("bar", "Session2_First"); + TRACE_EVENT_END("bar"); + + tracing_session->get()->StopBlocking(); + TRACE_EVENT_BEGIN("foo", "Session1_Second"); + TRACE_EVENT_END("foo"); + TRACE_EVENT_BEGIN("bar", "Session2_Second"); + TRACE_EVENT_END("bar"); + + tracing_session2->get()->StopBlocking(); + TRACE_EVENT_BEGIN("foo", "Session1_Third"); + TRACE_EVENT_END("foo"); + TRACE_EVENT_BEGIN("bar", "Session2_Third"); + TRACE_EVENT_END("bar"); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + std::string trace(raw_trace.data(), raw_trace.size()); + EXPECT_THAT(trace, HasSubstr("Session1_First")); + EXPECT_THAT(trace, Not(HasSubstr("Session1_Second"))); + EXPECT_THAT(trace, Not(HasSubstr("Session1_Third"))); + EXPECT_THAT(trace, Not(HasSubstr("Session2_First"))); + EXPECT_THAT(trace, Not(HasSubstr("Session2_Second"))); + EXPECT_THAT(trace, Not(HasSubstr("Session2_Third"))); + + std::vector<char> raw_trace2 = tracing_session2->get()->ReadTraceBlocking(); + std::string trace2(raw_trace2.data(), raw_trace2.size()); + EXPECT_THAT(trace2, Not(HasSubstr("Session1_First"))); + EXPECT_THAT(trace2, Not(HasSubstr("Session1_Second"))); + EXPECT_THAT(trace2, Not(HasSubstr("Session1_Third"))); + EXPECT_THAT(trace2, HasSubstr("Session2_First")); + EXPECT_THAT(trace2, HasSubstr("Session2_Second")); + EXPECT_THAT(trace2, Not(HasSubstr("Session2_Third"))); +} + +TEST_F(PerfettoApiTest, TrackEventProcessAndThreadDescriptors) { + // Thread and process descriptors can be set before tracing is enabled. + perfetto::TrackEvent::SetProcessDescriptor( + [](perfetto::protos::pbzero::TrackDescriptor* desc) { + desc->set_name("hello.exe"); + desc->set_chrome_process()->set_process_priority(1); + }); + + // Erased tracks shouldn't show up anywhere. + perfetto::Track erased(1234u); + perfetto::TrackEvent::SetTrackDescriptor( + erased, [](perfetto::protos::pbzero::TrackDescriptor* desc) { + desc->set_name("ErasedTrack"); + }); + perfetto::TrackEvent::EraseTrackDescriptor(erased); + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + TRACE_EVENT_INSTANT("test", "MainThreadEvent"); + + std::thread thread([&] { + perfetto::TrackEvent::SetThreadDescriptor( + [](perfetto::protos::pbzero::TrackDescriptor* desc) { + desc->set_name("TestThread"); + }); + TRACE_EVENT_INSTANT("test", "ThreadEvent"); + }); + thread.join(); + + // Update the process descriptor while tracing is enabled. It should be + // immediately reflected in the trace. + perfetto::TrackEvent::SetProcessDescriptor( + [](perfetto::protos::pbzero::TrackDescriptor* desc) { + desc->set_name("goodbye.exe"); + }); + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + + // After tracing ends, setting the descriptor has no immediate effect. + perfetto::TrackEvent::SetProcessDescriptor( + [](perfetto::protos::pbzero::TrackDescriptor* desc) { + desc->set_name("noop.exe"); + }); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + std::vector<perfetto::protos::gen::TrackDescriptor> descs; + std::vector<perfetto::protos::gen::TrackDescriptor> thread_descs; + constexpr uint32_t kMainThreadSequence = 2; + for (const auto& packet : trace.packet()) { + if (packet.has_track_descriptor()) { + if (packet.trusted_packet_sequence_id() == kMainThreadSequence) { + descs.push_back(packet.track_descriptor()); + } else { + thread_descs.push_back(packet.track_descriptor()); + } + } + } + + // The main thread records the initial process name as well as the one that's + // set during tracing. Additionally it records a thread descriptor for the + // main thread. + + EXPECT_EQ(3u, descs.size()); + + // Default track for the main thread. + EXPECT_EQ(0, descs[0].process().pid()); + EXPECT_NE(0, descs[0].thread().pid()); + + // First process descriptor. + EXPECT_NE(0, descs[1].process().pid()); + EXPECT_EQ("hello.exe", descs[1].name()); + + // Second process descriptor. + EXPECT_NE(0, descs[2].process().pid()); + EXPECT_EQ("goodbye.exe", descs[2].name()); + + // The child thread records only its own thread descriptor (twice, since it + // was mutated). + EXPECT_EQ(2u, thread_descs.size()); + EXPECT_EQ("TestThread", thread_descs[0].name()); + EXPECT_NE(0, thread_descs[0].thread().pid()); + EXPECT_NE(0, thread_descs[0].thread().tid()); + EXPECT_EQ("TestThread", thread_descs[1].name()); + EXPECT_NE(0, thread_descs[1].thread().pid()); + EXPECT_NE(0, thread_descs[1].thread().tid()); +} + +TEST_F(PerfettoApiTest, TrackEventCustomTrack) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"bar"}); + tracing_session->get()->StartBlocking(); + + // Declare a custom track and give it a name. + uint64_t async_id = 123; + perfetto::TrackEvent::SetTrackDescriptor( + perfetto::Track(async_id), + [](perfetto::protos::pbzero::TrackDescriptor* desc) { + desc->set_name("MyCustomTrack"); + }); + + // Start events on one thread and end them on another. + TRACE_EVENT_BEGIN("bar", "AsyncEvent", perfetto::Track(async_id), "debug_arg", + 123); + + TRACE_EVENT_BEGIN("bar", "SubEvent", perfetto::Track(async_id), + [](perfetto::EventContext) {}); + const auto main_thread_track = + perfetto::Track(async_id, perfetto::ThreadTrack::Current()); + std::thread thread([&] { + TRACE_EVENT_END("bar", perfetto::Track(async_id)); + TRACE_EVENT_END("bar", perfetto::Track(async_id), "arg1", false, "arg2", + true); + const auto thread_track = + perfetto::Track(async_id, perfetto::ThreadTrack::Current()); + // Thread-scoped tracks will have different uuids on different thread even + // if the id matches. + ASSERT_NE(main_thread_track.uuid, thread_track.uuid); + }); + thread.join(); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + // Check that the track uuids match on the begin and end events. + const auto track = perfetto::Track(async_id); + constexpr uint32_t kMainThreadSequence = 2; + int event_count = 0; + bool found_descriptor = false; + for (const auto& packet : trace.packet()) { + if (packet.has_track_descriptor() && + !packet.track_descriptor().has_process() && + !packet.track_descriptor().has_thread()) { + auto td = packet.track_descriptor(); + EXPECT_EQ("MyCustomTrack", td.name()); + EXPECT_EQ(track.uuid, td.uuid()); + EXPECT_EQ(perfetto::ProcessTrack::Current().uuid, td.parent_uuid()); + found_descriptor = true; + continue; + } + + if (!packet.has_track_event()) + continue; + auto track_event = packet.track_event(); + if (track_event.type() == + perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) { + EXPECT_EQ(kMainThreadSequence, packet.trusted_packet_sequence_id()); + EXPECT_EQ(track.uuid, track_event.track_uuid()); + } else { + EXPECT_NE(kMainThreadSequence, packet.trusted_packet_sequence_id()); + EXPECT_EQ(track.uuid, track_event.track_uuid()); + } + event_count++; + } + EXPECT_TRUE(found_descriptor); + EXPECT_EQ(4, event_count); + perfetto::TrackEvent::EraseTrackDescriptor(track); +} + +TEST_F(PerfettoApiTest, TrackEventCustomTrackAndTimestamp) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"bar"}); + tracing_session->get()->StartBlocking(); + + // Custom track. + perfetto::Track track(789); + + auto empty_lambda = [](perfetto::EventContext) {}; + constexpr uint64_t kBeginEventTime = 10; + constexpr uint64_t kEndEventTime = 15; + TRACE_EVENT_BEGIN("bar", "Event", track, kBeginEventTime, empty_lambda); + TRACE_EVENT_END("bar", track, kEndEventTime, empty_lambda); + + constexpr uint64_t kInstantEventTime = 1; + TRACE_EVENT_INSTANT("bar", "InstantEvent", track, kInstantEventTime, + empty_lambda); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + int event_count = 0; + for (const auto& packet : trace.packet()) { + if (!packet.has_track_event()) + continue; + event_count++; + switch (packet.track_event().type()) { + case perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN: + EXPECT_EQ(packet.timestamp(), kBeginEventTime); + break; + case perfetto::protos::gen::TrackEvent::TYPE_SLICE_END: + EXPECT_EQ(packet.timestamp(), kEndEventTime); + break; + case perfetto::protos::gen::TrackEvent::TYPE_INSTANT: + EXPECT_EQ(packet.timestamp(), kInstantEventTime); + break; + case perfetto::protos::gen::TrackEvent::TYPE_COUNTER: + case perfetto::protos::gen::TrackEvent::TYPE_UNSPECIFIED: + ADD_FAILURE(); + } + } + EXPECT_EQ(event_count, 3); + perfetto::TrackEvent::EraseTrackDescriptor(track); +} + +TEST_F(PerfettoApiTest, TrackEventAnonymousCustomTrack) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"bar"}); + tracing_session->get()->StartBlocking(); + + // Emit an async event without giving it an explicit descriptor. + uint64_t async_id = 4004; + auto track = perfetto::Track(async_id, perfetto::ThreadTrack::Current()); + TRACE_EVENT_BEGIN("bar", "AsyncEvent", track); + std::thread thread([&] { TRACE_EVENT_END("bar", track); }); + thread.join(); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + // Check that a descriptor for the track was emitted. + bool found_descriptor = false; + for (const auto& packet : trace.packet()) { + if (packet.has_track_descriptor() && + !packet.track_descriptor().has_process() && + !packet.track_descriptor().has_thread()) { + auto td = packet.track_descriptor(); + EXPECT_EQ(track.uuid, td.uuid()); + EXPECT_EQ(perfetto::ThreadTrack::Current().uuid, td.parent_uuid()); + found_descriptor = true; + } + } + EXPECT_TRUE(found_descriptor); +} + +TEST_F(PerfettoApiTest, TrackEventTypedArgs) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + auto random_value = random(); + TRACE_EVENT_BEGIN("foo", "EventWithTypedArg", + [random_value](perfetto::EventContext ctx) { + auto* log = ctx.event()->set_log_message(); + log->set_source_location_iid(1); + log->set_body_iid(2); + auto* dbg = ctx.event()->add_debug_annotations(); + dbg->set_name("random"); + dbg->set_int_value(random_value); + }); + TRACE_EVENT_END("foo"); + + tracing_session->get()->StopBlocking(); + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + std::string trace(raw_trace.data(), raw_trace.size()); + + perfetto::protos::gen::Trace parsed_trace; + ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + + bool found_args = false; + for (const auto& packet : parsed_trace.packet()) { + if (!packet.has_track_event()) + continue; + const auto& track_event = packet.track_event(); + if (track_event.type() != + perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) + continue; + + EXPECT_TRUE(track_event.has_log_message()); + const auto& log = track_event.log_message(); + EXPECT_EQ(1u, log.source_location_iid()); + EXPECT_EQ(2u, log.body_iid()); + + const auto& dbg = track_event.debug_annotations()[0]; + EXPECT_EQ("random", dbg.name()); + EXPECT_EQ(random_value, dbg.int_value()); + + found_args = true; + } + EXPECT_TRUE(found_args); +} + +struct InternedLogMessageBody + : public perfetto::TrackEventInternedDataIndex< + InternedLogMessageBody, + perfetto::protos::pbzero::InternedData::kLogMessageBodyFieldNumber, + std::string> { + static void Add(perfetto::protos::pbzero::InternedData* interned_data, + size_t iid, + const std::string& value) { + auto l = interned_data->add_log_message_body(); + l->set_iid(iid); + l->set_body(value.data(), value.size()); + commit_count++; + } + + static int commit_count; +}; + +int InternedLogMessageBody::commit_count = 0; + +TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterning) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + std::stringstream large_message; + for (size_t i = 0; i < 512; i++) + large_message << i << ". Something wicked this way comes. "; + + size_t body_iid; + InternedLogMessageBody::commit_count = 0; + TRACE_EVENT_BEGIN("foo", "EventWithState", [&](perfetto::EventContext ctx) { + EXPECT_EQ(0, InternedLogMessageBody::commit_count); + body_iid = InternedLogMessageBody::Get(&ctx, "Alas, poor Yorick!"); + auto log = ctx.event()->set_log_message(); + log->set_body_iid(body_iid); + EXPECT_EQ(1, InternedLogMessageBody::commit_count); + + auto body_iid2 = InternedLogMessageBody::Get(&ctx, "Alas, poor Yorick!"); + EXPECT_EQ(body_iid, body_iid2); + EXPECT_EQ(1, InternedLogMessageBody::commit_count); + }); + TRACE_EVENT_END("foo"); + + TRACE_EVENT_BEGIN("foo", "EventWithState", [&](perfetto::EventContext ctx) { + // Check that very large amounts of interned data works. + auto log = ctx.event()->set_log_message(); + log->set_body_iid(InternedLogMessageBody::Get(&ctx, large_message.str())); + EXPECT_EQ(2, InternedLogMessageBody::commit_count); + }); + TRACE_EVENT_END("foo"); + + // Make sure interned data persists across trace points. + TRACE_EVENT_BEGIN("foo", "EventWithState", [&](perfetto::EventContext ctx) { + auto body_iid2 = InternedLogMessageBody::Get(&ctx, "Alas, poor Yorick!"); + EXPECT_EQ(body_iid, body_iid2); + + auto body_iid3 = InternedLogMessageBody::Get(&ctx, "I knew him, Horatio"); + EXPECT_NE(body_iid, body_iid3); + auto log = ctx.event()->set_log_message(); + log->set_body_iid(body_iid3); + EXPECT_EQ(3, InternedLogMessageBody::commit_count); + }); + TRACE_EVENT_END("foo"); + + tracing_session->get()->StopBlocking(); + auto log_messages = ReadLogMessagesFromTrace(tracing_session->get()); + EXPECT_THAT(log_messages, + ElementsAre("Alas, poor Yorick!", large_message.str(), + "I knew him, Horatio")); +} + +struct InternedLogMessageBodySmall + : public perfetto::TrackEventInternedDataIndex< + InternedLogMessageBodySmall, + perfetto::protos::pbzero::InternedData::kLogMessageBodyFieldNumber, + const char*, + perfetto::SmallInternedDataTraits> { + static void Add(perfetto::protos::pbzero::InternedData* interned_data, + size_t iid, + const char* value) { + auto l = interned_data->add_log_message_body(); + l->set_iid(iid); + l->set_body(value); + } +}; + +TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterningByValue) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + size_t body_iid; + TRACE_EVENT_BEGIN("foo", "EventWithState", [&](perfetto::EventContext ctx) { + body_iid = InternedLogMessageBodySmall::Get(&ctx, "This above all:"); + auto log = ctx.event()->set_log_message(); + log->set_body_iid(body_iid); + + auto body_iid2 = InternedLogMessageBodySmall::Get(&ctx, "This above all:"); + EXPECT_EQ(body_iid, body_iid2); + + auto body_iid3 = + InternedLogMessageBodySmall::Get(&ctx, "to thine own self be true"); + EXPECT_NE(body_iid, body_iid3); + }); + TRACE_EVENT_END("foo"); + + tracing_session->get()->StopBlocking(); + auto log_messages = ReadLogMessagesFromTrace(tracing_session->get()); + EXPECT_THAT(log_messages, ElementsAre("This above all:")); +} + +struct InternedLogMessageBodyHashed + : public perfetto::TrackEventInternedDataIndex< + InternedLogMessageBodyHashed, + perfetto::protos::pbzero::InternedData::kLogMessageBodyFieldNumber, + std::string, + perfetto::HashedInternedDataTraits> { + static void Add(perfetto::protos::pbzero::InternedData* interned_data, + size_t iid, + const std::string& value) { + auto l = interned_data->add_log_message_body(); + l->set_iid(iid); + l->set_body(value.data(), value.size()); + } +}; + +TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterningByHashing) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + size_t body_iid; + TRACE_EVENT_BEGIN("foo", "EventWithState", [&](perfetto::EventContext ctx) { + // Test using a dynamically created interned value. + body_iid = InternedLogMessageBodyHashed::Get( + &ctx, std::string("Though this ") + "be madness,"); + auto log = ctx.event()->set_log_message(); + log->set_body_iid(body_iid); + + auto body_iid2 = + InternedLogMessageBodyHashed::Get(&ctx, "Though this be madness,"); + EXPECT_EQ(body_iid, body_iid2); + + auto body_iid3 = + InternedLogMessageBodyHashed::Get(&ctx, "yet there is method in’t"); + EXPECT_NE(body_iid, body_iid3); + }); + TRACE_EVENT_END("foo"); + + tracing_session->get()->StopBlocking(); + auto log_messages = ReadLogMessagesFromTrace(tracing_session->get()); + EXPECT_THAT(log_messages, ElementsAre("Though this be madness,")); +} + +struct InternedSourceLocation + : public perfetto::TrackEventInternedDataIndex< + InternedSourceLocation, + perfetto::protos::pbzero::InternedData::kSourceLocationsFieldNumber, + SourceLocation> { + static void Add(perfetto::protos::pbzero::InternedData* interned_data, + size_t iid, + const SourceLocation& value) { + auto l = interned_data->add_source_locations(); + auto file_name = std::get<0>(value); + auto function_name = std::get<1>(value); + auto line_number = std::get<2>(value); + l->set_iid(iid); + l->set_file_name(file_name); + l->set_function_name(function_name); + l->set_line_number(line_number); + } +}; + +TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterningComplexValue) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + TRACE_EVENT_BEGIN("foo", "EventWithState", [&](perfetto::EventContext ctx) { + const SourceLocation location{"file.cc", "SomeFunction", 123}; + auto location_iid = InternedSourceLocation::Get(&ctx, location); + auto body_iid = InternedLogMessageBody::Get(&ctx, "To be, or not to be"); + auto log = ctx.event()->set_log_message(); + log->set_source_location_iid(location_iid); + log->set_body_iid(body_iid); + + auto location_iid2 = InternedSourceLocation::Get(&ctx, location); + EXPECT_EQ(location_iid, location_iid2); + + const SourceLocation location2{"file.cc", "SomeFunction", 456}; + auto location_iid3 = InternedSourceLocation::Get(&ctx, location2); + EXPECT_NE(location_iid, location_iid3); + }); + TRACE_EVENT_END("foo"); + + tracing_session->get()->StopBlocking(); + auto log_messages = ReadLogMessagesFromTrace(tracing_session->get()); + EXPECT_THAT(log_messages, + ElementsAre("SomeFunction(file.cc:123): To be, or not to be")); +} + +TEST_F(PerfettoApiTest, TrackEventScoped) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"test"}); + tracing_session->get()->StartBlocking(); + + { + uint64_t arg = 123; + TRACE_EVENT("test", "TestEventWithArgs", [&](perfetto::EventContext ctx) { + ctx.event()->set_log_message()->set_body_iid(arg); + }); + } + + // Ensure a single line if statement counts as a valid scope for the macro. + if (true) + TRACE_EVENT("test", "SingleLineTestEvent"); + + { + // Make sure you can have multiple scoped events in the same scope. + TRACE_EVENT("test", "TestEvent"); + TRACE_EVENT("test", "AnotherEvent"); + TRACE_EVENT("foo", "DisabledEvent"); + } + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT( + slices, + ElementsAre("B:test.TestEventWithArgs", "E", "B:test.SingleLineTestEvent", + "E", "B:test.TestEvent", "B:test.AnotherEvent", "E", "E")); +} + +TEST_F(PerfettoApiTest, TrackEventInstant) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"test"}); + tracing_session->get()->StartBlocking(); + + TRACE_EVENT_INSTANT("test", "TestEvent"); + TRACE_EVENT_INSTANT("test", "AnotherEvent"); + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT(slices, ElementsAre("I:test.TestEvent", "I:test.AnotherEvent")); +} + +TEST_F(PerfettoApiTest, TrackEventDebugAnnotations) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"test"}); + tracing_session->get()->StartBlocking(); + + enum MyEnum { ENUM_FOO, ENUM_BAR }; + enum MySignedEnum { SIGNED_ENUM_FOO = -1, SIGNED_ENUM_BAR }; + + TRACE_EVENT_BEGIN("test", "E", "bool_arg", false); + TRACE_EVENT_BEGIN("test", "E", "int_arg", -123); + TRACE_EVENT_BEGIN("test", "E", "uint_arg", 456u); + TRACE_EVENT_BEGIN("test", "E", "float_arg", 3.14159262f); + TRACE_EVENT_BEGIN("test", "E", "double_arg", 6.22); + TRACE_EVENT_BEGIN("test", "E", "str_arg", "hello", "str_arg2", + std::string("tracing")); + TRACE_EVENT_BEGIN("test", "E", "ptr_arg", + reinterpret_cast<void*>(0xbaadf00d)); + TRACE_EVENT_BEGIN("test", "E", "size_t_arg", size_t{42}); + TRACE_EVENT_BEGIN("test", "E", "ptrdiff_t_arg", ptrdiff_t{-7}); + TRACE_EVENT_BEGIN("test", "E", "enum_arg", ENUM_BAR); + TRACE_EVENT_BEGIN("test", "E", "signed_enum_arg", SIGNED_ENUM_FOO); + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT( + slices, + ElementsAre( + "B:test.E(bool_arg=(bool)0)", "B:test.E(int_arg=(int)-123)", + "B:test.E(uint_arg=(uint)456)", "B:test.E(float_arg=(double)3.14159)", + "B:test.E(double_arg=(double)6.22)", + "B:test.E(str_arg=(string)hello,str_arg2=(string)tracing)", + "B:test.E(ptr_arg=(pointer)baadf00d)", + "B:test.E(size_t_arg=(uint)42)", "B:test.E(ptrdiff_t_arg=(int)-7)", + "B:test.E(enum_arg=(uint)1)", "B:test.E(signed_enum_arg=(int)-1)")); +} + +TEST_F(PerfettoApiTest, TrackEventCustomDebugAnnotations) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"test"}); + tracing_session->get()->StartBlocking(); + + std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation()); + + TRACE_EVENT_BEGIN("test", "E", "custom_arg", MyDebugAnnotation()); + TRACE_EVENT_BEGIN("test", "E", "normal_arg", "x", "custom_arg", + std::move(owned_annotation)); + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT( + slices, + ElementsAre( + R"(B:test.E(custom_arg=(json){"key": 123}))", + R"(B:test.E(normal_arg=(string)x,custom_arg=(json){"key": 123}))")); +} + +TEST_F(PerfettoApiTest, TrackEventCustomRawDebugAnnotations) { + // Note: this class is also testing a non-moveable and non-copiable argument. + class MyRawDebugAnnotation : public perfetto::DebugAnnotation { + public: + MyRawDebugAnnotation() { msg_->set_string_value("nested_value"); } + ~MyRawDebugAnnotation() = default; + + // |msg_| already deletes these implicitly, but let's be explicit for safety + // against future changes. + MyRawDebugAnnotation(const MyRawDebugAnnotation&) = delete; + MyRawDebugAnnotation(MyRawDebugAnnotation&&) = delete; + + void Add(perfetto::protos::pbzero::DebugAnnotation* annotation) const { + auto ranges = msg_.GetRanges(); + annotation->AppendScatteredBytes( + perfetto::protos::pbzero::DebugAnnotation::kNestedValueFieldNumber, + &ranges[0], ranges.size()); + } + + private: + mutable protozero::HeapBuffered< + perfetto::protos::pbzero::DebugAnnotation::NestedValue> + msg_; + }; + + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"test"}); + tracing_session->get()->StartBlocking(); + + TRACE_EVENT_BEGIN("test", "E", "raw_arg", MyRawDebugAnnotation()); + TRACE_EVENT_BEGIN("test", "E", "plain_arg", 42, "raw_arg", + MyRawDebugAnnotation()); + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT( + slices, + ElementsAre("B:test.E(raw_arg=(nested)nested_value)", + "B:test.E(plain_arg=(int)42,raw_arg=(nested)nested_value)")); +} + +TEST_F(PerfettoApiTest, TrackEventComputedName) { + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + + // New macros require perfetto::StaticString{} annotation. + for (int i = 0; i < 3; i++) + TRACE_EVENT_BEGIN("test", perfetto::StaticString{i % 2 ? "Odd" : "Even"}); + + // Legacy macros assume all arguments are static strings. + for (int i = 0; i < 3; i++) + TRACE_EVENT_BEGIN0("test", i % 2 ? "Odd" : "Even"); + + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT(slices, ElementsAre("B:test.Even", "B:test.Odd", "B:test.Even", + "B:test.Even", "B:test.Odd", "B:test.Even")); +} + +TEST_F(PerfettoApiTest, TrackEventArgumentsNotEvaluatedWhenDisabled) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"foo"}); + tracing_session->get()->StartBlocking(); + + bool called = false; + auto ArgumentFunction = [&] { + called = true; + return 123; + }; + + TRACE_EVENT_BEGIN("test", "DisabledEvent", "arg", ArgumentFunction()); + { TRACE_EVENT("test", "DisabledScopedEvent", "arg", ArgumentFunction()); } + perfetto::TrackEvent::Flush(); + + tracing_session->get()->StopBlocking(); + EXPECT_FALSE(called); + + ArgumentFunction(); + EXPECT_TRUE(called); +} + +TEST_F(PerfettoApiTest, TrackEventConfig) { + auto check_config = [&](perfetto::protos::gen::TrackEventConfig te_cfg) { + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + ds_cfg->set_track_event_config_raw(te_cfg.SerializeAsString()); + + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + + TRACE_EVENT_BEGIN("foo", "FooEvent"); + TRACE_EVENT_BEGIN("bar", "BarEvent"); + TRACE_EVENT_BEGIN("foo,bar", "MultiFooBar"); + TRACE_EVENT_BEGIN("baz,bar,quux", "MultiBar"); + TRACE_EVENT_BEGIN("red,green,blue,foo", "MultiFoo"); + TRACE_EVENT_BEGIN("red,green,blue,yellow", "MultiNone"); + TRACE_EVENT_BEGIN("cat", "SlowEvent"); + TRACE_EVENT_BEGIN("cat.verbose", "DebugEvent"); + TRACE_EVENT_BEGIN("test", "TagEvent"); + TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("cat"), "SlowDisabledEvent"); + TRACE_EVENT_BEGIN("dynamic,foo", "DynamicGroupFooEvent"); + perfetto::DynamicCategory dyn{"dynamic,bar"}; + TRACE_EVENT_BEGIN(dyn, "DynamicGroupBarEvent"); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + tracing_session->session.reset(); + return slices; + }; + + // Empty config should enable all categories except slow ones. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + auto slices = check_config(te_cfg); + EXPECT_THAT( + slices, + ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar", + "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo", + "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent", + "B:$dynamic,$bar.DynamicGroupBarEvent")); + } + + // Enable exactly one category. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_disabled_categories("*"); + te_cfg.add_enabled_categories("foo"); + auto slices = check_config(te_cfg); + EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:foo,bar.MultiFooBar", + "B:red,green,blue,foo.MultiFoo", + "B:$dynamic,$foo.DynamicGroupFooEvent")); + } + + // Enable two categories. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_disabled_categories("*"); + te_cfg.add_enabled_categories("foo"); + te_cfg.add_enabled_categories("baz"); + te_cfg.add_enabled_categories("bar"); + auto slices = check_config(te_cfg); + EXPECT_THAT( + slices, + ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar", + "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo", + "B:$dynamic,$foo.DynamicGroupFooEvent", + "B:$dynamic,$bar.DynamicGroupBarEvent")); + } + + // Enabling all categories with a pattern doesn't enable slow ones. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_enabled_categories("*"); + auto slices = check_config(te_cfg); + EXPECT_THAT( + slices, + ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar", + "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo", + "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent", + "B:$dynamic,$bar.DynamicGroupBarEvent")); + } + + // Enable with a pattern. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_disabled_categories("*"); + te_cfg.add_enabled_categories("fo*"); + auto slices = check_config(te_cfg); + EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:foo,bar.MultiFooBar", + "B:red,green,blue,foo.MultiFoo", + "B:$dynamic,$foo.DynamicGroupFooEvent")); + } + + // Enable with a tag. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_disabled_categories("*"); + te_cfg.add_enabled_tags("tag"); + auto slices = check_config(te_cfg); + EXPECT_THAT(slices, ElementsAre("B:test.TagEvent")); + } + + // Enable just slow categories. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_disabled_categories("*"); + te_cfg.add_enabled_tags("slow"); + auto slices = check_config(te_cfg); + EXPECT_THAT(slices, + ElementsAre("B:cat.SlowEvent", + "B:disabled-by-default-cat.SlowDisabledEvent")); + } + + // Enable everything including slow/debug categories. + { + perfetto::protos::gen::TrackEventConfig te_cfg; + te_cfg.add_enabled_categories("*"); + te_cfg.add_enabled_tags("slow"); + te_cfg.add_enabled_tags("debug"); + auto slices = check_config(te_cfg); + EXPECT_THAT(slices, + ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", + "B:foo,bar.MultiFooBar", "B:baz,bar,quux.MultiBar", + "B:red,green,blue,foo.MultiFoo", "B:cat.SlowEvent", + "B:cat.verbose.DebugEvent", "B:test.TagEvent", + "B:disabled-by-default-cat.SlowDisabledEvent", + "B:$dynamic,$foo.DynamicGroupFooEvent", + "B:$dynamic,$bar.DynamicGroupBarEvent")); + } +} + +TEST_F(PerfettoApiTest, OneDataSourceOneEvent) { + auto* data_source = &data_sources_["my_data_source"]; + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + ds_cfg->set_legacy_config("test config"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + + MockDataSource::Trace([](MockDataSource::TraceContext) { + FAIL() << "Should not be called because the trace was not started"; + }); + MockDataSource::CallIfEnabled([](uint32_t) { + FAIL() << "Should not be called because the trace was not started"; + }); + + tracing_session->get()->Start(); + data_source->on_setup.Wait(); + EXPECT_EQ(data_source->config.legacy_config(), "test config"); + data_source->on_start.Wait(); + + // Emit one trace event. + std::atomic<int> trace_lambda_calls{0}; + MockDataSource::Trace( + [&trace_lambda_calls](MockDataSource::TraceContext ctx) { + auto packet = ctx.NewTracePacket(); + packet->set_timestamp(42); + packet->set_for_testing()->set_str("event 1"); + trace_lambda_calls++; + packet->Finalize(); + + // The SMB scraping logic will skip the last packet because it cannot + // guarantee it's finalized. Create an empty packet so we get the + // previous one and this empty one is ignored. + packet = ctx.NewTracePacket(); + }); + + uint32_t active_instances = 0; + MockDataSource::CallIfEnabled([&active_instances](uint32_t instances) { + active_instances = instances; + }); + EXPECT_EQ(1u, active_instances); + + data_source->on_stop.Wait(); + tracing_session->on_stop.Wait(); + EXPECT_EQ(trace_lambda_calls, 1); + + MockDataSource::Trace([](MockDataSource::TraceContext) { + FAIL() << "Should not be called because the trace is now stopped"; + }); + MockDataSource::CallIfEnabled([](uint32_t) { + FAIL() << "Should not be called because the trace is now stopped"; + }); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + ASSERT_GE(raw_trace.size(), 0u); + + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + bool test_packet_found = false; + for (const auto& packet : trace.packet()) { + if (!packet.has_for_testing()) + continue; + EXPECT_FALSE(test_packet_found); + EXPECT_EQ(packet.timestamp(), 42U); + EXPECT_EQ(packet.for_testing().str(), "event 1"); + test_packet_found = true; + } + EXPECT_TRUE(test_packet_found); +} + +TEST_F(PerfettoApiTest, BlockingStartAndStop) { + auto* data_source = &data_sources_["my_data_source"]; + + // Register a second data source to get a bit more coverage. + perfetto::DataSourceDescriptor dsd; + dsd.set_name("my_data_source2"); + MockDataSource2::Register(dsd); + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source2"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + + tracing_session->get()->StartBlocking(); + EXPECT_TRUE(data_source->on_setup.notified()); + EXPECT_TRUE(data_source->on_start.notified()); + + tracing_session->get()->StopBlocking(); + EXPECT_TRUE(data_source->on_stop.notified()); + EXPECT_TRUE(tracing_session->on_stop.notified()); +} + +TEST_F(PerfettoApiTest, BlockingStartAndStopOnEmptySession) { + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("non_existent_data_source"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + tracing_session->get()->StopBlocking(); + EXPECT_TRUE(tracing_session->on_stop.notified()); +} + +TEST_F(PerfettoApiTest, WriteEventsAfterDeferredStop) { + auto* data_source = &data_sources_["my_data_source"]; + data_source->handle_stop_asynchronously = true; + + // Setup the trace config and start the tracing session. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + + // Stop and wait for the producer to have seen the stop event. + WaitableTestEvent consumer_stop_signal; + tracing_session->get()->SetOnStopCallback( + [&consumer_stop_signal] { consumer_stop_signal.Notify(); }); + tracing_session->get()->Stop(); + data_source->on_stop.Wait(); + + // At this point tracing should be still allowed because of the + // HandleStopAsynchronously() call. + bool lambda_called = false; + + // This usleep is here just to prevent that we accidentally pass the test + // just by virtue of hitting some race. We should be able to trace up until + // 5 seconds after seeing the stop when using the deferred stop mechanism. + usleep(250 * 1000); + + MockDataSource::Trace([&lambda_called](MockDataSource::TraceContext ctx) { + auto packet = ctx.NewTracePacket(); + packet->set_for_testing()->set_str("event written after OnStop"); + packet->Finalize(); + ctx.Flush(); + lambda_called = true; + }); + ASSERT_TRUE(lambda_called); + + // Now call the async stop closure. This acks the stop to the service and + // disallows further Trace() calls. + EXPECT_TRUE(data_source->async_stop_closure); + data_source->async_stop_closure(); + + // Wait that the stop is propagated to the consumer. + consumer_stop_signal.Wait(); + + MockDataSource::Trace([](MockDataSource::TraceContext) { + FAIL() << "Should not be called after the stop is acked"; + }); + + // Check the contents of the trace. + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + ASSERT_GE(raw_trace.size(), 0u); + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + int test_packet_found = 0; + for (const auto& packet : trace.packet()) { + if (!packet.has_for_testing()) + continue; + EXPECT_EQ(packet.for_testing().str(), "event written after OnStop"); + test_packet_found++; + } + EXPECT_EQ(test_packet_found, 1); +} + +TEST_F(PerfettoApiTest, RepeatedStartAndStop) { + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + + for (int i = 0; i < 5; i++) { + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->Start(); + std::atomic<bool> stop_called{false}; + tracing_session->get()->SetOnStopCallback( + [&stop_called] { stop_called = true; }); + tracing_session->get()->StopBlocking(); + EXPECT_TRUE(stop_called); + } +} + +TEST_F(PerfettoApiTest, SetupWithFile) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + char temp_file[] = "/data/local/tmp/perfetto-XXXXXXXX"; +#else + char temp_file[] = "/tmp/perfetto-XXXXXXXX"; +#endif + int fd = mkstemp(temp_file); + ASSERT_TRUE(fd >= 0); + + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + // Write a trace into |fd|. + auto* tracing_session = NewTrace(cfg, fd); + tracing_session->get()->StartBlocking(); + tracing_session->get()->StopBlocking(); + // Check that |fd| didn't get closed. + EXPECT_EQ(0, fcntl(fd, F_GETFD, 0)); + // Check that the trace got written. + EXPECT_GT(lseek(fd, 0, SEEK_END), 0); + EXPECT_EQ(0, close(fd)); + // Clean up. + EXPECT_EQ(0, unlink(temp_file)); +} + +TEST_F(PerfettoApiTest, MultipleRegistrations) { + // Attempt to register the same data source again. + perfetto::DataSourceDescriptor dsd; + dsd.set_name("my_data_source"); + EXPECT_TRUE(MockDataSource::Register(dsd)); + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + + // Emit one trace event. + std::atomic<int> trace_lambda_calls{0}; + MockDataSource::Trace([&trace_lambda_calls](MockDataSource::TraceContext) { + trace_lambda_calls++; + }); + + // Make sure the data source got called only once. + tracing_session->get()->StopBlocking(); + EXPECT_EQ(trace_lambda_calls, 1); +} + +TEST_F(PerfettoApiTest, CustomIncrementalState) { + perfetto::DataSourceDescriptor dsd; + dsd.set_name("incr_data_source"); + TestIncrementalDataSource::Register(dsd); + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("incr_data_source"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + + // First emit a no-op trace event that initializes the incremental state as a + // side effect. + TestIncrementalDataSource::Trace( + [](TestIncrementalDataSource::TraceContext) {}); + EXPECT_TRUE(TestIncrementalState::constructed); + + // Check that the incremental state is carried across trace events. + TestIncrementalDataSource::Trace( + [](TestIncrementalDataSource::TraceContext ctx) { + auto* state = ctx.GetIncrementalState(); + EXPECT_TRUE(state); + EXPECT_EQ(100, state->count); + state->count++; + }); + + TestIncrementalDataSource::Trace( + [](TestIncrementalDataSource::TraceContext ctx) { + auto* state = ctx.GetIncrementalState(); + EXPECT_EQ(101, state->count); + }); + + // Make sure the incremental state gets cleaned up between sessions. + tracing_session->get()->StopBlocking(); + tracing_session = NewTrace(cfg); + tracing_session->get()->StartBlocking(); + TestIncrementalDataSource::Trace( + [](TestIncrementalDataSource::TraceContext ctx) { + auto* state = ctx.GetIncrementalState(); + EXPECT_TRUE(TestIncrementalState::destroyed); + EXPECT_TRUE(state); + EXPECT_EQ(100, state->count); + }); + tracing_session->get()->StopBlocking(); +} + +// Regression test for b/139110180. Checks that GetDataSourceLocked() can be +// called from OnStart() and OnStop() callbacks without deadlocking. +TEST_F(PerfettoApiTest, GetDataSourceLockedFromCallbacks) { + auto* data_source = &data_sources_["my_data_source"]; + + // Setup the trace config. + perfetto::TraceConfig cfg; + cfg.set_duration_ms(1); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("my_data_source"); + + // Create a new trace session. + auto* tracing_session = NewTrace(cfg); + + data_source->on_start_callback = [] { + MockDataSource::Trace([](MockDataSource::TraceContext ctx) { + ctx.NewTracePacket()->set_for_testing()->set_str("on-start"); + auto ds = ctx.GetDataSourceLocked(); + ASSERT_TRUE(!!ds); + ctx.NewTracePacket()->set_for_testing()->set_str("on-start-locked"); + }); + }; + + data_source->on_stop_callback = [] { + MockDataSource::Trace([](MockDataSource::TraceContext ctx) { + ctx.NewTracePacket()->set_for_testing()->set_str("on-stop"); + auto ds = ctx.GetDataSourceLocked(); + ASSERT_TRUE(!!ds); + ctx.NewTracePacket()->set_for_testing()->set_str("on-stop-locked"); + ctx.Flush(); + }); + }; + + tracing_session->get()->Start(); + data_source->on_stop.Wait(); + tracing_session->on_stop.Wait(); + + std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking(); + ASSERT_GE(raw_trace.size(), 0u); + + perfetto::protos::gen::Trace trace; + ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size())); + int packets_found = 0; + for (const auto& packet : trace.packet()) { + if (!packet.has_for_testing()) + continue; + packets_found |= packet.for_testing().str() == "on-start" ? 1 : 0; + packets_found |= packet.for_testing().str() == "on-start-locked" ? 2 : 0; + packets_found |= packet.for_testing().str() == "on-stop" ? 4 : 0; + packets_found |= packet.for_testing().str() == "on-stop-locked" ? 8 : 0; + } + EXPECT_EQ(packets_found, 1 | 2 | 4 | 8); +} + +TEST_F(PerfettoApiTest, LegacyTraceEvents) { + // Create a new trace session. + auto* tracing_session = + NewTraceWithCategories({"cat", TRACE_DISABLED_BY_DEFAULT("cat")}); + tracing_session->get()->StartBlocking(); + + // Basic events. + TRACE_EVENT_INSTANT0("cat", "LegacyEvent", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_BEGIN1("cat", "LegacyEvent", "arg", 123); + TRACE_EVENT_END2("cat", "LegacyEvent", "arg", "string", "arg2", 0.123f); + + // Scoped event. + { TRACE_EVENT0("cat", "ScopedLegacyEvent"); } + + // Event with flow (and disabled category). + TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("cat"), "LegacyFlowEvent", + 0xdadacafe, TRACE_EVENT_FLAG_FLOW_IN); + + // Event with timestamp. + TRACE_EVENT_INSTANT_WITH_TIMESTAMP0("cat", "LegacyInstantEvent", + TRACE_EVENT_SCOPE_GLOBAL, + MyTimestamp{123456789ul}); + + // Event with id, thread id and timestamp (and dynamic name). + TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0( + "cat", std::string("LegacyWithIdTidAndTimestamp").c_str(), 1, + MyThreadId(123, 456), MyTimestamp{3}); + + // Event with id. + TRACE_COUNTER1("cat", "LegacyCounter", 1234); + TRACE_COUNTER_ID1("cat", "LegacyCounterWithId", 1234, 9000); + + // Metadata event. + TRACE_EVENT_METADATA1("cat", "LegacyMetadata", "obsolete", true); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT( + slices, + ElementsAre( + "I:cat.LegacyEvent", "B:cat.LegacyEvent(arg=(int)123)", + "E.LegacyEvent(arg=(string)string,arg2=(double)0.123)", + "B:cat.ScopedLegacyEvent", "E", + "B(bind_id=3671771902)(flow_direction=1):disabled-by-default-cat." + "LegacyFlowEvent", + "I:cat.LegacyInstantEvent", + "Legacy_S(unscoped_id=1)(pid_override=123)(tid_override=456):cat." + "LegacyWithIdTidAndTimestamp", + "Legacy_C:cat.LegacyCounter(value=(int)1234)", + "Legacy_C(unscoped_id=1234):cat.LegacyCounterWithId(value=(int)9000)", + "Legacy_M:cat.LegacyMetadata")); +} + +TEST_F(PerfettoApiTest, LegacyTraceEventsWithCustomAnnotation) { + // Create a new trace session. + auto* tracing_session = NewTraceWithCategories({"cat"}); + tracing_session->get()->StartBlocking(); + + MyDebugAnnotation annotation; + TRACE_EVENT_BEGIN1("cat", "LegacyEvent", "arg", annotation); + + std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation()); + TRACE_EVENT_BEGIN1("cat", "LegacyEvent", "arg", std::move(owned_annotation)); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT(slices, + ElementsAre("B:cat.LegacyEvent(arg=(json){\"key\": 123})", + "B:cat.LegacyEvent(arg=(json){\"key\": 123})")); +} + +TEST_F(PerfettoApiTest, LegacyTraceEventsWithConcurrentSessions) { + // Make sure that a uniquely owned debug annotation can be written into + // multiple concurrent tracing sessions. + + auto* tracing_session = NewTraceWithCategories({"cat"}); + tracing_session->get()->StartBlocking(); + + auto* tracing_session2 = NewTraceWithCategories({"cat"}); + tracing_session2->get()->StartBlocking(); + + std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation()); + TRACE_EVENT_BEGIN1("cat", "LegacyEvent", "arg", std::move(owned_annotation)); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT(slices, + ElementsAre("B:cat.LegacyEvent(arg=(json){\"key\": 123})")); + + tracing_session2->get()->StopBlocking(); + slices = ReadSlicesFromTrace(tracing_session2->get()); + EXPECT_THAT(slices, + ElementsAre("B:cat.LegacyEvent(arg=(json){\"key\": 123})")); +} + +TEST_F(PerfettoApiTest, LegacyTraceEventsWithId) { + auto* tracing_session = NewTraceWithCategories({"cat"}); + tracing_session->get()->StartBlocking(); + + TRACE_EVENT_ASYNC_BEGIN0("cat", "UnscopedId", 0x1000); + TRACE_EVENT_ASYNC_BEGIN0("cat", "LocalId", TRACE_ID_LOCAL(0x2000)); + TRACE_EVENT_ASYNC_BEGIN0("cat", "GlobalId", TRACE_ID_GLOBAL(0x3000)); + TRACE_EVENT_ASYNC_BEGIN0( + "cat", "WithScope", + TRACE_ID_WITH_SCOPE("scope string", TRACE_ID_GLOBAL(0x4000))); + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT(slices, ElementsAre("Legacy_S(unscoped_id=4096):cat.UnscopedId", + "Legacy_S(local_id=8192):cat.LocalId", + "Legacy_S(global_id=12288):cat.GlobalId", + "Legacy_S(global_id=16384)(id_scope=\"scope " + "string\"):cat.WithScope")); +} + +TEST_F(PerfettoApiTest, LegacyTraceEventsWithFlow) { + auto* tracing_session = NewTraceWithCategories({"cat"}); + tracing_session->get()->StartBlocking(); + + const uint64_t flow_id = 1234; + { + TRACE_EVENT_WITH_FLOW1("cat", "LatencyInfo.Flow", TRACE_ID_GLOBAL(flow_id), + TRACE_EVENT_FLAG_FLOW_OUT, "step", "Begin"); + } + + { + TRACE_EVENT_WITH_FLOW2("cat", "LatencyInfo.Flow", TRACE_ID_GLOBAL(flow_id), + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "step", "Middle", "value", false); + } + + { + TRACE_EVENT_WITH_FLOW1("cat", "LatencyInfo.Flow", TRACE_ID_GLOBAL(flow_id), + TRACE_EVENT_FLAG_FLOW_IN, "step", "End"); + } + + perfetto::TrackEvent::Flush(); + tracing_session->get()->StopBlocking(); + auto slices = ReadSlicesFromTrace(tracing_session->get()); + EXPECT_THAT(slices, + ElementsAre("B(bind_id=1234)(flow_direction=2):cat.LatencyInfo." + "Flow(step=(string)Begin)", + "E", + "B(bind_id=1234)(flow_direction=3):cat.LatencyInfo." + "Flow(step=(string)Middle,value=(bool)0)", + "E", + "B(bind_id=1234)(flow_direction=1):cat.LatencyInfo." + "Flow(step=(string)End)", + "E")); +} + +TEST_F(PerfettoApiTest, LegacyCategoryGroupEnabledState) { + bool foo_status; + bool bar_status; + bool dynamic_status; + TRACE_EVENT_CATEGORY_GROUP_ENABLED("foo", &foo_status); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("bar", &bar_status); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("dynamic", &dynamic_status); + EXPECT_FALSE(foo_status); + EXPECT_FALSE(bar_status); + EXPECT_FALSE(dynamic_status); + + const uint8_t* foo_enabled = + TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("foo"); + const uint8_t* bar_enabled = + TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("bar"); + EXPECT_FALSE(*foo_enabled); + EXPECT_FALSE(*bar_enabled); + + auto* tracing_session = NewTraceWithCategories({"foo", "dynamic"}); + tracing_session->get()->StartBlocking(); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("foo", &foo_status); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("bar", &bar_status); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("dynamic", &dynamic_status); + EXPECT_TRUE(foo_status); + EXPECT_FALSE(bar_status); + EXPECT_TRUE(dynamic_status); + + EXPECT_TRUE(*foo_enabled); + EXPECT_FALSE(*bar_enabled); + + tracing_session->get()->StopBlocking(); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("foo", &foo_status); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("bar", &bar_status); + TRACE_EVENT_CATEGORY_GROUP_ENABLED("dynamic", &dynamic_status); + EXPECT_FALSE(foo_status); + EXPECT_FALSE(bar_status); + EXPECT_FALSE(dynamic_status); + EXPECT_FALSE(*foo_enabled); + EXPECT_FALSE(*bar_enabled); +} + +TEST_F(PerfettoApiTest, CategoryEnabledState) { + perfetto::DynamicCategory dynamic{"dynamic"}; + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("foo")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("bar")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("red,green,blue,foo")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("dynamic")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("dynamic_2")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED(dynamic)); + + auto* tracing_session = NewTraceWithCategories({"foo", "dynamic"}); + tracing_session->get()->StartBlocking(); + EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED("foo")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("bar")); + EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED("red,green,blue,foo")); + EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED("dynamic")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("dynamic_2")); + EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED(dynamic)); + + tracing_session->get()->StopBlocking(); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("foo")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("bar")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("red,green,blue,foo")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("dynamic")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("dynamic_2")); + EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED(dynamic)); +} + +} // namespace + +PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MockDataSource); +PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MockDataSource2); +PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(TestIncrementalDataSource, + TestIncrementalDataSourceTraits); |