[tracing] Modernize TracingSamplerProfiler
This CL gets rid of DataSourceProxy for TracingSamplerProfiler. It also removes dead code around legacy StartupTracing. Change-Id: I9d687d681d26cd092e3afed263edf2c7543a9a51 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6203461 Reviewed-by: Mikhail Khokhlov <khokhlov@google.com> Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org> Cr-Commit-Position: refs/heads/main@{#1416309}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
31a4514e78
commit
7638e654a0
content/browser/tracing
services/tracing/public/cpp/stack_sampling
@ -18,6 +18,7 @@
|
||||
#include "content/public/test/content_browser_test_utils.h"
|
||||
#include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer_proto.h"
|
||||
#include "services/tracing/public/cpp/perfetto/metadata_data_source.h"
|
||||
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "third_party/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h"
|
||||
|
||||
@ -92,6 +93,17 @@ perfetto::protos::gen::TraceConfig TraceConfigWithMetadata(
|
||||
return perfetto_config;
|
||||
}
|
||||
|
||||
perfetto::protos::gen::TraceConfig TraceConfigWithSamplerProfiler() {
|
||||
auto perfetto_config = base::test::DefaultTraceConfig(
|
||||
"-*,disabled-by-default-cpu_profiler", false);
|
||||
|
||||
auto* data_source = perfetto_config.add_data_sources();
|
||||
auto* source_config = data_source->mutable_config();
|
||||
source_config->set_name("org.chromium.sampler_profiler");
|
||||
|
||||
return perfetto_config;
|
||||
}
|
||||
|
||||
perfetto::protos::gen::TraceConfig TraceConfigWithMetadataMultisession(
|
||||
const std::string& category_filter_string) {
|
||||
auto perfetto_config =
|
||||
@ -336,6 +348,69 @@ IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest,
|
||||
std::vector<std::string>{"1"}));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeUnwinder : public base::Unwinder {
|
||||
public:
|
||||
bool CanUnwindFrom(const base::Frame& current_frame) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
base::UnwindResult TryUnwind(base::UnwinderStateCapture* capture_state,
|
||||
base::RegisterContext* thread_context,
|
||||
uintptr_t stack_top,
|
||||
std::vector<base::Frame>* stack) override {
|
||||
return base::UnwindResult::kCompleted;
|
||||
}
|
||||
};
|
||||
|
||||
// Note that this is relevant only for Android, since TracingSamplingProfiler
|
||||
// ignores any provided unwinder factory for non-Android platforms:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.cc;l=905-908;drc=70d839a3b8bcf1ef43c42a54a4b27f14ee149750
|
||||
base::StackSamplingProfiler::UnwindersFactory MakeFakeUnwinder() {
|
||||
return base::BindOnce([] {
|
||||
auto fake_unwinder = std::make_unique<FakeUnwinder>();
|
||||
std::vector<std::unique_ptr<base::Unwinder>> unwinders;
|
||||
unwinders.push_back(std::move(fake_unwinder));
|
||||
return unwinders;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest, CpuProfiler) {
|
||||
// In the browser process, the tracing sampler profiler gets constructed by
|
||||
// the chrome/ layer, so we need to do the same manually for testing purposes.
|
||||
std::unique_ptr<tracing::TracingSamplerProfiler> tracing_sampler_profiler;
|
||||
{
|
||||
base::ScopedAllowBlockingForTesting allow_blocking;
|
||||
tracing_sampler_profiler =
|
||||
tracing::TracingSamplerProfiler::CreateOnMainThread(
|
||||
base::BindRepeating(&MakeFakeUnwinder));
|
||||
}
|
||||
|
||||
// There won't be any samples if stack unwinding isn't supported.
|
||||
if (!tracing::TracingSamplerProfiler::IsStackUnwindingSupportedForTesting()) {
|
||||
GTEST_SKIP() << "Stack unwinding not supported on this platform";
|
||||
}
|
||||
|
||||
base::RunLoop wait_for_sample;
|
||||
tracing_sampler_profiler->SetSampleCallbackForTesting(
|
||||
wait_for_sample.QuitClosure());
|
||||
|
||||
base::test::TestTraceProcessor ttp;
|
||||
ttp.StartTrace(TraceConfigWithSamplerProfiler());
|
||||
|
||||
wait_for_sample.Run();
|
||||
|
||||
absl::Status status = ttp.StopAndParseTrace();
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
auto result = ttp.RunQuery("SELECT * FROM cpu_profile_stack_sample");
|
||||
ASSERT_TRUE(result.has_value()) << result.error();
|
||||
EXPECT_GT(result.value().size(), 1U);
|
||||
}
|
||||
|
||||
IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest,
|
||||
MemoryInstrumentationDetailed) {
|
||||
base::WaitableEvent dump_completed;
|
||||
|
@ -60,7 +60,6 @@
|
||||
|
||||
using StreamingProfilePacketHandle =
|
||||
protozero::MessageHandle<perfetto::protos::pbzero::StreamingProfilePacket>;
|
||||
using TracePacketHandle = perfetto::TraceWriter::TracePacketHandle;
|
||||
|
||||
namespace tracing {
|
||||
|
||||
@ -88,172 +87,75 @@ uintptr_t executable_start_addr() {
|
||||
// Pointer to the main thread instance, if any.
|
||||
TracingSamplerProfiler* g_main_thread_instance = nullptr;
|
||||
|
||||
class TracingSamplerProfilerDataSource;
|
||||
|
||||
TracingSamplerProfilerDataSource* g_sampler_profiler_ds_for_test = nullptr;
|
||||
|
||||
class TracingSamplerProfilerDataSource
|
||||
: public PerfettoTracedProcess::DataSourceBase {
|
||||
class TracingSamplerProfilerManager {
|
||||
public:
|
||||
static TracingSamplerProfilerDataSource* Get() {
|
||||
static base::NoDestructor<TracingSamplerProfilerDataSource> instance;
|
||||
static TracingSamplerProfilerManager* Get() {
|
||||
static base::NoDestructor<TracingSamplerProfilerManager> instance;
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
void OnDataSourceStart(TracingSamplerProfiler::DataSource* data_source) {
|
||||
base::AutoLock lock(lock_);
|
||||
DCHECK_EQ(data_source_, nullptr);
|
||||
data_source_ = data_source;
|
||||
for (TracingSamplerProfiler* profiler : profilers_) {
|
||||
profiler->StartTracing(data_source_->CreateTraceWriter(),
|
||||
data_source_->privacy_filtering_enabled());
|
||||
}
|
||||
}
|
||||
void OnDataSourceStop(TracingSamplerProfiler::DataSource* data_source) {
|
||||
base::AutoLock lock(lock_);
|
||||
DCHECK_EQ(data_source_, data_source);
|
||||
data_source_ = nullptr;
|
||||
for (TracingSamplerProfiler* profiler : profilers_) {
|
||||
profiler->StopTracing();
|
||||
}
|
||||
}
|
||||
|
||||
void WillClearIncrementalState() {
|
||||
incremental_state_reset_id_.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint32_t GetIncrementalStateResetID() {
|
||||
return incremental_state_reset_id_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RegisterProfiler(TracingSamplerProfiler* profiler) {
|
||||
base::AutoLock lock(lock_);
|
||||
if (!profilers_.insert(profiler).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_started_) {
|
||||
profiler->StartTracing(
|
||||
CreateTraceWriter(),
|
||||
data_source_config_.chrome_config().privacy_filtering_enabled());
|
||||
} else if (is_startup_tracing_) {
|
||||
profiler->StartTracing(
|
||||
nullptr,
|
||||
/*should_enable_filtering=*/true);
|
||||
if (data_source_) {
|
||||
profiler->StartTracing(data_source_->CreateTraceWriter(),
|
||||
data_source_->privacy_filtering_enabled());
|
||||
}
|
||||
}
|
||||
|
||||
void UnregisterProfiler(TracingSamplerProfiler* profiler) {
|
||||
base::AutoLock lock(lock_);
|
||||
if (!profilers_.erase(profiler) || !(is_started_ || is_startup_tracing_)) {
|
||||
if (!profilers_.erase(profiler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
profiler->StopTracing();
|
||||
}
|
||||
|
||||
// PerfettoTracedProcess::DataSourceBase implementation, called by
|
||||
// ProducerClient.
|
||||
void StartTracingImpl(
|
||||
const perfetto::DataSourceConfig& data_source_config) override {
|
||||
base::AutoLock lock(lock_);
|
||||
DCHECK(!is_started_);
|
||||
is_started_ = true;
|
||||
is_startup_tracing_ = false;
|
||||
data_source_config_ = data_source_config;
|
||||
|
||||
bool should_enable_filtering =
|
||||
data_source_config.chrome_config().privacy_filtering_enabled();
|
||||
|
||||
for (TracingSamplerProfiler* profiler : profilers_) {
|
||||
profiler->StartTracing(CreateTraceWriter(), should_enable_filtering);
|
||||
}
|
||||
}
|
||||
|
||||
void StopTracingImpl(base::OnceClosure stop_complete_callback) override {
|
||||
base::AutoLock lock(lock_);
|
||||
DCHECK(is_started_);
|
||||
is_started_ = false;
|
||||
is_startup_tracing_ = false;
|
||||
|
||||
for (TracingSamplerProfiler* profiler : profilers_) {
|
||||
if (data_source_) {
|
||||
profiler->StopTracing();
|
||||
}
|
||||
|
||||
std::move(stop_complete_callback).Run();
|
||||
}
|
||||
|
||||
void Flush(base::RepeatingClosure flush_complete_callback) override {
|
||||
flush_complete_callback.Run();
|
||||
}
|
||||
|
||||
void SetupStartupTracing(const base::trace_event::TraceConfig& trace_config,
|
||||
bool privacy_filtering_enabled) override {
|
||||
bool enable_sampler_profiler = trace_config.IsCategoryGroupEnabled(
|
||||
TRACE_DISABLED_BY_DEFAULT("cpu_profiler"));
|
||||
if (!enable_sampler_profiler)
|
||||
return;
|
||||
|
||||
base::AutoLock lock(lock_);
|
||||
if (is_started_) {
|
||||
return;
|
||||
}
|
||||
is_startup_tracing_ = true;
|
||||
for (TracingSamplerProfiler* profiler : profilers_) {
|
||||
// Enable filtering for startup tracing always to be safe.
|
||||
profiler->StartTracing(
|
||||
nullptr,
|
||||
/*should_enable_filtering=*/true);
|
||||
}
|
||||
}
|
||||
|
||||
void AbortStartupTracing() override {
|
||||
base::AutoLock lock(lock_);
|
||||
if (!is_startup_tracing_) {
|
||||
return;
|
||||
}
|
||||
for (TracingSamplerProfiler* profiler : profilers_) {
|
||||
// Enable filtering for startup tracing always to be safe.
|
||||
profiler->StartTracing(
|
||||
nullptr,
|
||||
/*should_enable_filtering=*/true);
|
||||
}
|
||||
is_startup_tracing_ = false;
|
||||
}
|
||||
|
||||
void ClearIncrementalState() override {
|
||||
incremental_state_reset_id_.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static uint32_t GetIncrementalStateResetID() {
|
||||
return incremental_state_reset_id_.load(std::memory_order_relaxed);
|
||||
}
|
||||
using DataSourceProxy =
|
||||
PerfettoTracedProcess::DataSourceProxy<TracingSamplerProfilerDataSource>;
|
||||
|
||||
static void ResetForTesting() {
|
||||
if (!g_sampler_profiler_ds_for_test)
|
||||
return;
|
||||
g_sampler_profiler_ds_for_test->~TracingSamplerProfilerDataSource();
|
||||
new (g_sampler_profiler_ds_for_test) TracingSamplerProfilerDataSource;
|
||||
}
|
||||
|
||||
void RegisterDataSource() {
|
||||
perfetto::DataSourceDescriptor dsd;
|
||||
dsd.set_name(mojom::kSamplerProfilerSourceName);
|
||||
DataSourceProxy::Register(dsd, this);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class base::NoDestructor<TracingSamplerProfilerDataSource>;
|
||||
friend class base::NoDestructor<TracingSamplerProfilerManager>;
|
||||
|
||||
TracingSamplerProfilerDataSource()
|
||||
: DataSourceBase(mojom::kSamplerProfilerSourceName) {
|
||||
g_sampler_profiler_ds_for_test = this;
|
||||
}
|
||||
TracingSamplerProfilerManager() = default;
|
||||
~TracingSamplerProfilerManager() = default;
|
||||
|
||||
~TracingSamplerProfilerDataSource() override {
|
||||
// Unreachable because of static instance of type `base::NoDestructor<>`
|
||||
// and private ctr.
|
||||
// Reachable only in case of test mode. See `ResetForTesting()`.
|
||||
}
|
||||
|
||||
// We create one trace writer per profiled thread both in SDK and non-SDK
|
||||
// build. This is necessary because each profiler keeps its own interned data
|
||||
// index, so to avoid collisions interned data should go into different
|
||||
// writer sequences.
|
||||
std::unique_ptr<perfetto::TraceWriterBase> CreateTraceWriter();
|
||||
|
||||
// TODO(eseckler): Use GUARDED_BY annotations for all members below.
|
||||
base::Lock lock_; // Protects subsequent members.
|
||||
std::set<raw_ptr<TracingSamplerProfiler, SetExperimental>> profilers_;
|
||||
bool is_startup_tracing_ = false;
|
||||
bool is_started_ = false;
|
||||
perfetto::DataSourceConfig data_source_config_;
|
||||
|
||||
static std::atomic<uint32_t> incremental_state_reset_id_;
|
||||
std::set<raw_ptr<TracingSamplerProfiler, SetExperimental>> profilers_
|
||||
GUARDED_BY(lock_);
|
||||
raw_ptr<TracingSamplerProfiler::DataSource> data_source_ GUARDED_BY(lock_);
|
||||
std::atomic<uint32_t> incremental_state_reset_id_{1};
|
||||
};
|
||||
|
||||
using DataSourceProxy = TracingSamplerProfilerDataSource::DataSourceProxy;
|
||||
|
||||
// static
|
||||
std::atomic<uint32_t>
|
||||
TracingSamplerProfilerDataSource::incremental_state_reset_id_{0};
|
||||
|
||||
base::SequenceLocalStorageSlot<TracingSamplerProfiler>&
|
||||
GetSequenceLocalStorageProfilerSlot() {
|
||||
static base::SequenceLocalStorageSlot<TracingSamplerProfiler> storage;
|
||||
@ -376,17 +278,25 @@ perfetto::StaticString UnwinderTypeToString(
|
||||
|
||||
} // namespace
|
||||
|
||||
TracingSamplerProfiler::TracingProfileBuilder::BufferedSample::BufferedSample(
|
||||
base::TimeTicks ts,
|
||||
std::vector<base::Frame>&& s)
|
||||
: timestamp(ts), sample(std::move(s)) {}
|
||||
TracingSamplerProfiler::DataSource::~DataSource() = default;
|
||||
|
||||
TracingSamplerProfiler::TracingProfileBuilder::BufferedSample::
|
||||
~BufferedSample() = default;
|
||||
void TracingSamplerProfiler::DataSource::OnSetup(const SetupArgs& args) {
|
||||
privacy_filtering_enabled_ =
|
||||
args.config->chrome_config().privacy_filtering_enabled();
|
||||
}
|
||||
|
||||
TracingSamplerProfiler::TracingProfileBuilder::BufferedSample::BufferedSample(
|
||||
TracingSamplerProfiler::TracingProfileBuilder::BufferedSample&& other)
|
||||
: BufferedSample(other.timestamp, std::move(other.sample)) {}
|
||||
void TracingSamplerProfiler::DataSource::OnStart(const StartArgs&) {
|
||||
TracingSamplerProfilerManager::Get()->OnDataSourceStart(this);
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::DataSource::OnStop(const StopArgs&) {
|
||||
TracingSamplerProfilerManager::Get()->OnDataSourceStop(this);
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::DataSource::WillClearIncrementalState(
|
||||
const ClearIncrementalStateArgs& args) {
|
||||
TracingSamplerProfilerManager::Get()->WillClearIncrementalState();
|
||||
}
|
||||
|
||||
TracingSamplerProfiler::TracingProfileBuilder::TracingProfileBuilder(
|
||||
base::PlatformThreadId sampled_thread_id,
|
||||
@ -425,41 +335,22 @@ using SampleDebugProto =
|
||||
void TracingSamplerProfiler::TracingProfileBuilder::OnSampleCompleted(
|
||||
std::vector<base::Frame> frames,
|
||||
base::TimeTicks sample_timestamp) {
|
||||
base::AutoLock l(trace_writer_lock_);
|
||||
bool is_startup_tracing = (trace_writer_ == nullptr);
|
||||
|
||||
if (is_startup_tracing) {
|
||||
if (buffered_samples_.size() < kMaxBufferedSamples) {
|
||||
buffered_samples_.emplace_back(
|
||||
BufferedSample(sample_timestamp, std::move(frames)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!buffered_samples_.empty()) {
|
||||
for (auto& sample : buffered_samples_) {
|
||||
WriteSampleToTrace(std::move(sample));
|
||||
}
|
||||
buffered_samples_.clear();
|
||||
}
|
||||
|
||||
BufferedSample sample(sample_timestamp, std::move(frames));
|
||||
WriteSampleToTrace(std::move(sample));
|
||||
WriteSampleToTrace(std::move(frames), sample_timestamp);
|
||||
if (sample_callback_for_testing_) {
|
||||
sample_callback_for_testing_.Run();
|
||||
}
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::TracingProfileBuilder::WriteSampleToTrace(
|
||||
TracingSamplerProfiler::TracingProfileBuilder::BufferedSample sample) {
|
||||
auto& frames = sample.sample;
|
||||
auto reset_id =
|
||||
TracingSamplerProfilerDataSource::GetIncrementalStateResetID();
|
||||
if (reset_id != last_incremental_state_reset_id_) {
|
||||
reset_incremental_state_ = true;
|
||||
last_incremental_state_reset_id_ = reset_id;
|
||||
}
|
||||
std::vector<base::Frame> frames,
|
||||
base::TimeTicks sample_timestamp) {
|
||||
uint32_t previous_incremental_state_reset_id = std::exchange(
|
||||
last_incremental_state_reset_id_,
|
||||
TracingSamplerProfilerManager::Get()->GetIncrementalStateResetID());
|
||||
bool reset_incremental_state =
|
||||
(previous_incremental_state_reset_id != last_incremental_state_reset_id_);
|
||||
|
||||
if (reset_incremental_state_) {
|
||||
if (reset_incremental_state) {
|
||||
stack_profile_writer_.ResetEmittedState();
|
||||
|
||||
TracePacketHandle trace_packet = trace_writer_->NewTracePacket();
|
||||
@ -473,13 +364,12 @@ void TracingSamplerProfiler::TracingProfileBuilder::WriteSampleToTrace(
|
||||
thread_descriptor->set_pid(
|
||||
base::trace_event::TraceLog::GetInstance()->process_id());
|
||||
thread_descriptor->set_tid(sampled_thread_id_);
|
||||
last_timestamp_ = sample.timestamp;
|
||||
last_timestamp_ = sample_timestamp;
|
||||
thread_descriptor->set_reference_timestamp_us(
|
||||
last_timestamp_.since_origin().InMicroseconds());
|
||||
|
||||
TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
|
||||
UnwinderTypeToString(unwinder_type_));
|
||||
reset_incremental_state_ = false;
|
||||
}
|
||||
|
||||
TracePacketHandle trace_packet = trace_writer_->NewTracePacket();
|
||||
@ -497,14 +387,8 @@ void TracingSamplerProfiler::TracingProfileBuilder::WriteSampleToTrace(
|
||||
}
|
||||
|
||||
streaming_profile_packet->add_timestamp_delta_us(
|
||||
(sample.timestamp - last_timestamp_).InMicroseconds());
|
||||
last_timestamp_ = sample.timestamp;
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::TracingProfileBuilder::SetTraceWriter(
|
||||
std::unique_ptr<perfetto::TraceWriterBase> writer) {
|
||||
base::AutoLock l(trace_writer_lock_);
|
||||
trace_writer_ = std::move(writer);
|
||||
(sample_timestamp - last_timestamp_).InMicroseconds());
|
||||
last_timestamp_ = sample_timestamp;
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::TracingProfileBuilder::SetUnwinderType(
|
||||
@ -520,7 +404,7 @@ TracingSamplerProfiler::StackProfileWriter::~StackProfileWriter() = default;
|
||||
InterningID
|
||||
TracingSamplerProfiler::StackProfileWriter::GetCallstackIDAndMaybeEmit(
|
||||
std::vector<base::Frame>& frames,
|
||||
perfetto::TraceWriter::TracePacketHandle* trace_packet) {
|
||||
TracePacketHandle* trace_packet) {
|
||||
size_t ip_hash = 0;
|
||||
for (const auto& frame : frames) {
|
||||
ip_hash = base::HashInts(ip_hash, frame.instruction_pointer);
|
||||
@ -733,15 +617,11 @@ void TracingSamplerProfiler::DeleteOnChildThreadForTesting() {
|
||||
GetSequenceLocalStorageProfilerSlot().reset();
|
||||
}
|
||||
|
||||
// static
|
||||
void TracingSamplerProfiler::ResetDataSourceForTesting() {
|
||||
TracingSamplerProfilerDataSource::Get()->ResetForTesting();
|
||||
RegisterDataSource();
|
||||
}
|
||||
|
||||
// static
|
||||
void TracingSamplerProfiler::RegisterDataSource() {
|
||||
TracingSamplerProfilerDataSource::Get()->RegisterDataSource();
|
||||
perfetto::DataSourceDescriptor dsd;
|
||||
dsd.set_name(mojom::kSamplerProfilerSourceName);
|
||||
perfetto::DataSource<TracingSamplerProfiler::DataSource>::Register(dsd);
|
||||
}
|
||||
|
||||
// static
|
||||
@ -763,26 +643,10 @@ void TracingSamplerProfiler::SetAuxUnwinderFactoryOnMainThread(
|
||||
g_main_thread_instance->SetAuxUnwinderFactory(factory);
|
||||
}
|
||||
|
||||
// TODO(b/336718643): Remove unused code after removing use_perfetto_client_library build
|
||||
// flag.
|
||||
// static
|
||||
void TracingSamplerProfiler::StartTracingForTesting() {
|
||||
TracingSamplerProfilerDataSource::Get()->StartTracing(
|
||||
1, perfetto::DataSourceConfig());
|
||||
}
|
||||
|
||||
// static
|
||||
void TracingSamplerProfiler::SetupStartupTracingForTesting() {
|
||||
base::trace_event::TraceConfig config(
|
||||
TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
|
||||
base::trace_event::TraceRecordMode::RECORD_UNTIL_FULL);
|
||||
TracingSamplerProfilerDataSource::Get()->SetupStartupTracing(
|
||||
config, /*privacy_filtering_enabled=*/false);
|
||||
}
|
||||
|
||||
// static
|
||||
void TracingSamplerProfiler::StopTracingForTesting() {
|
||||
TracingSamplerProfilerDataSource::Get()->StopTracing(base::DoNothing());
|
||||
void TracingSamplerProfiler::SetSampleCallbackForTesting(
|
||||
const base::RepeatingClosure& sample_callback_for_testing) {
|
||||
base::AutoLock lock(lock_);
|
||||
sample_callback_for_testing_ = sample_callback_for_testing;
|
||||
}
|
||||
|
||||
TracingSamplerProfiler::TracingSamplerProfiler(
|
||||
@ -801,11 +665,11 @@ TracingSamplerProfiler::TracingSamplerProfiler(
|
||||
// created on the profiled thread. See crbug.com/1392158#c26 for details.
|
||||
base::ThreadDelegatePosix::Create(sampled_thread_token_);
|
||||
#endif // INITIALIZE_THREAD_DELEGATE_POSIX
|
||||
TracingSamplerProfilerDataSource::Get()->RegisterProfiler(this);
|
||||
TracingSamplerProfilerManager::Get()->RegisterProfiler(this);
|
||||
}
|
||||
|
||||
TracingSamplerProfiler::~TracingSamplerProfiler() {
|
||||
TracingSamplerProfilerDataSource::Get()->UnregisterProfiler(this);
|
||||
TracingSamplerProfilerManager::Get()->UnregisterProfiler(this);
|
||||
if (g_main_thread_instance == this)
|
||||
g_main_thread_instance = nullptr;
|
||||
}
|
||||
@ -818,22 +682,11 @@ void TracingSamplerProfiler::SetAuxUnwinderFactory(
|
||||
profiler_->AddAuxUnwinder(aux_unwinder_factory_.Run());
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::SetSampleCallbackForTesting(
|
||||
const base::RepeatingClosure& sample_callback_for_testing) {
|
||||
base::AutoLock lock(lock_);
|
||||
sample_callback_for_testing_ = sample_callback_for_testing;
|
||||
}
|
||||
|
||||
void TracingSamplerProfiler::StartTracing(
|
||||
std::unique_ptr<perfetto::TraceWriterBase> trace_writer,
|
||||
bool should_enable_filtering) {
|
||||
base::AutoLock lock(lock_);
|
||||
if (profiler_) {
|
||||
if (trace_writer) {
|
||||
profile_builder_->SetTraceWriter(std::move(trace_writer));
|
||||
}
|
||||
return;
|
||||
}
|
||||
DCHECK_EQ(profiler_, nullptr);
|
||||
|
||||
if (!base::StackSamplingProfiler::IsSupportedForCurrentPlatform()) {
|
||||
return;
|
||||
@ -844,8 +697,7 @@ void TracingSamplerProfiler::StartTracing(
|
||||
params.sampling_interval = base::Milliseconds(50);
|
||||
|
||||
auto profile_builder = std::make_unique<TracingProfileBuilder>(
|
||||
sampled_thread_token_.id,
|
||||
std::move(trace_writer),
|
||||
sampled_thread_token_.id, std::move(trace_writer),
|
||||
should_enable_filtering, sample_callback_for_testing_);
|
||||
|
||||
profile_builder_ = profile_builder.get();
|
||||
@ -910,19 +762,19 @@ void TracingSamplerProfiler::StopTracing() {
|
||||
|
||||
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS(
|
||||
COMPONENT_EXPORT(TRACING_CPP),
|
||||
tracing::TracingSamplerProfilerDataSource::DataSourceProxy);
|
||||
tracing::TracingSamplerProfiler::DataSource);
|
||||
|
||||
// This should go after PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS
|
||||
// to avoid instantiation of type() template method before specialization.
|
||||
std::unique_ptr<perfetto::TraceWriterBase>
|
||||
tracing::TracingSamplerProfilerDataSource::CreateTraceWriter() {
|
||||
tracing::TracingSamplerProfiler::DataSource::CreateTraceWriter() {
|
||||
perfetto::internal::DataSourceStaticState* static_state =
|
||||
perfetto::DataSourceHelper<DataSourceProxy>::type().static_state();
|
||||
perfetto::DataSourceHelper<TracingSamplerProfiler::DataSource>::type()
|
||||
.static_state();
|
||||
// DataSourceProxy disallows multiple instances, so our instance will always
|
||||
// have index 0.
|
||||
perfetto::internal::DataSourceState* instance_state = static_state->TryGet(0);
|
||||
CHECK(instance_state);
|
||||
return perfetto::internal::TracingMuxer::Get()->CreateTraceWriter(
|
||||
static_state, data_source_config_.target_buffer(), instance_state,
|
||||
perfetto::BufferExhaustedPolicy::kDrop);
|
||||
static_state, 0, instance_state, perfetto::BufferExhaustedPolicy::kDrop);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "services/tracing/public/cpp/buildflags.h"
|
||||
#include "services/tracing/public/cpp/perfetto/interning_index.h"
|
||||
#include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_writer.h"
|
||||
#include "third_party/perfetto/include/perfetto/tracing/data_source.h"
|
||||
|
||||
#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_ARM64) && \
|
||||
BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
@ -57,6 +58,35 @@ class LoaderLockSamplingThread;
|
||||
// field |profiler_| to be thread-safe.
|
||||
class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
|
||||
public:
|
||||
class COMPONENT_EXPORT(TRACING_CPP) DataSource
|
||||
: public perfetto::DataSource<DataSource> {
|
||||
public:
|
||||
static constexpr bool kSupportsMultipleInstances = false;
|
||||
using TraceContext = perfetto::DataSource<DataSource>::TraceContext;
|
||||
|
||||
DataSource() = default;
|
||||
~DataSource() override;
|
||||
|
||||
void OnSetup(const SetupArgs& args) override;
|
||||
void OnStart(const StartArgs&) override;
|
||||
void OnStop(const StopArgs&) override;
|
||||
void WillClearIncrementalState(
|
||||
const ClearIncrementalStateArgs& args) override;
|
||||
|
||||
// We create one trace writer per profiled thread. This is necessary because
|
||||
// each profiler keeps its own interned data index, so to avoid collisions
|
||||
// interned data should go into different writer sequences.
|
||||
std::unique_ptr<perfetto::TraceWriterBase> CreateTraceWriter();
|
||||
|
||||
bool privacy_filtering_enabled() const {
|
||||
return privacy_filtering_enabled_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool privacy_filtering_enabled_ = false;
|
||||
};
|
||||
using TracePacketHandle = DataSource::TraceContext::TracePacketHandle;
|
||||
|
||||
class COMPONENT_EXPORT(TRACING_CPP) StackProfileWriter {
|
||||
public:
|
||||
explicit StackProfileWriter(bool should_enable_filtering);
|
||||
@ -69,9 +99,8 @@ class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
|
||||
// corresponding to the callstack. Meanwhile it could emit extra entries
|
||||
// to intern data. |function_name| member in Frame could be std::move(ed) by
|
||||
// this method to reduce number of copies we have for function names.
|
||||
InterningID GetCallstackIDAndMaybeEmit(
|
||||
std::vector<base::Frame>& frames,
|
||||
perfetto::TraceWriter::TracePacketHandle* trace_packet);
|
||||
InterningID GetCallstackIDAndMaybeEmit(std::vector<base::Frame>& frames,
|
||||
TracePacketHandle* trace_packet);
|
||||
|
||||
void ResetEmittedState();
|
||||
|
||||
@ -119,48 +148,16 @@ class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
|
||||
void OnProfileCompleted(base::TimeDelta profile_duration,
|
||||
base::TimeDelta sampling_period) override {}
|
||||
|
||||
void SetTraceWriter(
|
||||
std::unique_ptr<perfetto::TraceWriterBase> trace_writer);
|
||||
|
||||
void SetUnwinderType(TracingSamplerProfiler::UnwinderType unwinder_type);
|
||||
|
||||
private:
|
||||
struct BufferedSample {
|
||||
BufferedSample(base::TimeTicks, std::vector<base::Frame>&&);
|
||||
|
||||
BufferedSample(const BufferedSample&) = delete;
|
||||
BufferedSample& operator=(const BufferedSample&) = delete;
|
||||
|
||||
BufferedSample(BufferedSample&& other);
|
||||
|
||||
~BufferedSample();
|
||||
|
||||
base::TimeTicks timestamp;
|
||||
std::vector<base::Frame> sample;
|
||||
};
|
||||
|
||||
void WriteSampleToTrace(BufferedSample sample);
|
||||
|
||||
// TODO(ssid): Consider using an interning scheme to reduce memory usage
|
||||
// and increase the sample size.
|
||||
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
|
||||
// We usually sample at 50ms, and expect that tracing should have started in
|
||||
// 10s (5s for 2 threads). Approximately 100 frames and 200 samples would use
|
||||
// 300KiB.
|
||||
constexpr static size_t kMaxBufferedSamples = 200;
|
||||
#else
|
||||
// 2000 samples are enough to store samples for 100 seconds (50s for 2
|
||||
// threads), and consumes about 3MiB of memory.
|
||||
constexpr static size_t kMaxBufferedSamples = 2000;
|
||||
#endif
|
||||
std::vector<BufferedSample> buffered_samples_;
|
||||
void WriteSampleToTrace(std::vector<base::Frame> frames,
|
||||
base::TimeTicks sample_timestamp);
|
||||
|
||||
base::ModuleCache module_cache_;
|
||||
const base::PlatformThreadId sampled_thread_id_;
|
||||
base::Lock trace_writer_lock_;
|
||||
std::unique_ptr<perfetto::TraceWriterBase> trace_writer_;
|
||||
StackProfileWriter stack_profile_writer_;
|
||||
bool reset_incremental_state_ = true;
|
||||
uint32_t last_incremental_state_reset_id_ = 0;
|
||||
base::TimeTicks last_timestamp_;
|
||||
base::RepeatingClosure sample_callback_for_testing_;
|
||||
@ -202,11 +199,7 @@ class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
|
||||
factory);
|
||||
|
||||
// For tests.
|
||||
static void SetupStartupTracingForTesting();
|
||||
static void DeleteOnChildThreadForTesting();
|
||||
static void StartTracingForTesting();
|
||||
static void StopTracingForTesting();
|
||||
static void ResetDataSourceForTesting();
|
||||
// Returns whether of not the sampler profiling is able to unwind the stack
|
||||
// on this platform, ignoring any CoreUnwindersCallback provided.
|
||||
static bool IsStackUnwindingSupportedForTesting();
|
||||
@ -247,7 +240,7 @@ class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
|
||||
UnwinderType unwinder_type_;
|
||||
|
||||
base::Lock lock_;
|
||||
std::unique_ptr<base::StackSamplingProfiler> profiler_; // under |lock_|
|
||||
std::unique_ptr<base::StackSamplingProfiler> profiler_ GUARDED_BY(lock_);
|
||||
// This dangling raw_ptr occurred in:
|
||||
// services_unittests: TracingSampleProfilerTest.SamplingChildThread
|
||||
// https://ci.chromium.org/ui/p/chromium/builders/try/win-rel/237204/test-results?q=ExactID%3Aninja%3A%2F%2Fservices%3Aservices_unittests%2FTracingSampleProfilerTest.SamplingChildThread+VHash%3A83af393c6a76b581
|
||||
|
@ -66,8 +66,6 @@ using ::testing::Return;
|
||||
using PacketVector =
|
||||
std::vector<std::unique_ptr<perfetto::protos::TracePacket>>;
|
||||
|
||||
std::unique_ptr<perfetto::TracingSession> g_tracing_session;
|
||||
|
||||
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
|
||||
|
||||
class MockLoaderLockSampler : public LoaderLockSampler {
|
||||
@ -122,9 +120,7 @@ class TracingSampleProfilerTest : public testing::Test {
|
||||
|
||||
events_stack_received_count_ = 0u;
|
||||
|
||||
tracing::PerfettoTracedProcess::DataSourceBase::ResetTaskRunnerForTesting(
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault());
|
||||
TracingSamplerProfiler::ResetDataSourceForTesting();
|
||||
TracingSamplerProfiler::RegisterDataSource();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@ -141,29 +137,27 @@ class TracingSampleProfilerTest : public testing::Test {
|
||||
ds_cfg = trace_config.add_data_sources()->mutable_config();
|
||||
ds_cfg->set_name("track_event");
|
||||
|
||||
g_tracing_session = perfetto::Tracing::NewTrace();
|
||||
g_tracing_session->Setup(trace_config);
|
||||
g_tracing_session->StartBlocking();
|
||||
// Make sure TraceEventMetadataSource::StartTracingImpl gets run.
|
||||
base::RunLoop().RunUntilIdle();
|
||||
tracing_session_ = perfetto::Tracing::NewTrace();
|
||||
tracing_session_->Setup(trace_config);
|
||||
tracing_session_->StartBlocking();
|
||||
}
|
||||
|
||||
void WaitForEvents() { base::PlatformThread::Sleep(base::Milliseconds(200)); }
|
||||
|
||||
void EnsureTraceStopped() {
|
||||
if (!g_tracing_session)
|
||||
if (!tracing_session_) {
|
||||
return;
|
||||
}
|
||||
|
||||
base::TrackEvent::Flush();
|
||||
|
||||
base::RunLoop wait_for_stop;
|
||||
g_tracing_session->SetOnStopCallback(
|
||||
tracing_session_->SetOnStopCallback(
|
||||
[&wait_for_stop] { wait_for_stop.Quit(); });
|
||||
g_tracing_session->Stop();
|
||||
tracing_session_->Stop();
|
||||
wait_for_stop.Run();
|
||||
|
||||
std::vector<char> serialized_data = g_tracing_session->ReadTraceBlocking();
|
||||
g_tracing_session.reset();
|
||||
std::vector<char> serialized_data = tracing_session_->ReadTraceBlocking();
|
||||
tracing_session_.reset();
|
||||
|
||||
perfetto::protos::Trace trace;
|
||||
EXPECT_TRUE(
|
||||
@ -212,8 +206,6 @@ class TracingSampleProfilerTest : public testing::Test {
|
||||
}
|
||||
|
||||
protected:
|
||||
// We want our singleton torn down after each test.
|
||||
base::ShadowingAtExitManager at_exit_manager_;
|
||||
base::trace_event::TraceResultBuffer trace_buffer_;
|
||||
|
||||
base::test::TaskEnvironment task_environment_;
|
||||
@ -221,6 +213,8 @@ class TracingSampleProfilerTest : public testing::Test {
|
||||
std::vector<std::unique_ptr<perfetto::protos::TracePacket>>
|
||||
finalized_packets_;
|
||||
|
||||
std::unique_ptr<perfetto::TracingSession> tracing_session_;
|
||||
|
||||
// Number of stack sampling events received.
|
||||
size_t events_stack_received_count_ = 0;
|
||||
|
||||
@ -273,10 +267,8 @@ TEST_F(TracingSampleProfilerTest, OnSampleCompleted) {
|
||||
TracingSamplerProfiler::CreateOnMainThread(base::BindRepeating(
|
||||
[] { return MakeMockUnwinderFactoryWithExpectations(); }));
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
ValidateReceivedEvents();
|
||||
}
|
||||
|
||||
@ -296,85 +288,11 @@ TEST_F(TracingSampleProfilerTest, MAYBE_JoinRunningTracing) {
|
||||
auto profiler =
|
||||
TracingSamplerProfiler::CreateOnMainThread(base::BindRepeating(
|
||||
[] { return MakeMockUnwinderFactoryWithExpectations(); }));
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
ValidateReceivedEvents();
|
||||
}
|
||||
|
||||
TEST_F(TracingSampleProfilerTest, TestStartupTracing) {
|
||||
auto profiler =
|
||||
TracingSamplerProfiler::CreateOnMainThread(base::BindRepeating(
|
||||
[] { return MakeMockUnwinderFactoryWithExpectations(); }));
|
||||
TracingSamplerProfiler::SetupStartupTracingForTesting();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
auto start_tracing_ts = TRACE_TIME_TICKS_NOW();
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
if (TracingSamplerProfiler::IsStackUnwindingSupportedForTesting()) {
|
||||
uint32_t seq_id = FindProfilerSequenceId();
|
||||
auto& packets = GetFinalizedPackets();
|
||||
int64_t reference_ts = 0;
|
||||
int64_t first_profile_ts = 0;
|
||||
for (auto& packet : packets) {
|
||||
if (packet->trusted_packet_sequence_id() == seq_id) {
|
||||
if (packet->has_thread_descriptor()) {
|
||||
reference_ts = packet->thread_descriptor().reference_timestamp_us();
|
||||
} else if (packet->has_streaming_profile_packet()) {
|
||||
first_profile_ts =
|
||||
reference_ts +
|
||||
packet->streaming_profile_packet().timestamp_delta_us(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Expect first sample before tracing started.
|
||||
EXPECT_LT(first_profile_ts,
|
||||
start_tracing_ts.since_origin().InMicroseconds());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TracingSampleProfilerTest, JoinStartupTracing) {
|
||||
TracingSamplerProfiler::SetupStartupTracingForTesting();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
auto profiler =
|
||||
TracingSamplerProfiler::CreateOnMainThread(base::BindRepeating(
|
||||
[] { return MakeMockUnwinderFactoryWithExpectations(); }));
|
||||
WaitForEvents();
|
||||
auto start_tracing_ts = TRACE_TIME_TICKS_NOW();
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
if (TracingSamplerProfiler::IsStackUnwindingSupportedForTesting()) {
|
||||
uint32_t seq_id = FindProfilerSequenceId();
|
||||
auto& packets = GetFinalizedPackets();
|
||||
int64_t reference_ts = 0;
|
||||
int64_t first_profile_ts = 0;
|
||||
for (auto& packet : packets) {
|
||||
if (packet->trusted_packet_sequence_id() == seq_id) {
|
||||
if (packet->has_thread_descriptor()) {
|
||||
reference_ts = packet->thread_descriptor().reference_timestamp_us();
|
||||
} else if (packet->has_streaming_profile_packet()) {
|
||||
first_profile_ts =
|
||||
reference_ts +
|
||||
packet->streaming_profile_packet().timestamp_delta_us(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Expect first sample before tracing started.
|
||||
EXPECT_LT(first_profile_ts,
|
||||
start_tracing_ts.since_origin().InMicroseconds());
|
||||
}
|
||||
}
|
||||
|
||||
// This is needed because this code is racy (example:
|
||||
// https://crbug.com/338398659#comment1) by design: tracing needs to have
|
||||
// minimal runtime overhead, so tracing code assumes certain things are already
|
||||
@ -396,14 +314,12 @@ TEST_F(TracingSampleProfilerTest, MAYBE_SamplingChildThread) {
|
||||
base::BindRepeating(
|
||||
[] { return MakeMockUnwinderFactoryWithExpectations(); })));
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
ValidateReceivedEvents();
|
||||
sampled_thread.task_runner()->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&TracingSamplerProfiler::DeleteOnChildThreadForTesting));
|
||||
base::RunLoop().RunUntilIdle();
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
|
||||
@ -422,10 +338,8 @@ TEST_F(TracingSampleProfilerTest, SampleLoaderLockOnMainThread) {
|
||||
|
||||
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
|
||||
// Since the loader lock state changed each time it was sampled an event
|
||||
// should be emitted each time.
|
||||
@ -441,10 +355,8 @@ TEST_F(TracingSampleProfilerTest, SampleLoaderLockAlwaysHeld) {
|
||||
|
||||
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
|
||||
// An event should be emitted at the first sample when the loader lock was
|
||||
// held, and then not again since the state never changed.
|
||||
@ -459,10 +371,8 @@ TEST_F(TracingSampleProfilerTest, SampleLoaderLockNeverHeld) {
|
||||
|
||||
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
|
||||
// No events should be emitted since the lock is never held.
|
||||
EXPECT_EQ(event_analyzer.CountEvents(), 0U);
|
||||
@ -479,13 +389,11 @@ TEST_F(TracingSampleProfilerTest, SampleLoaderLockOnChildThread) {
|
||||
sampled_thread.task_runner()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&TracingSamplerProfiler::CreateOnChildThread));
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
sampled_thread.task_runner()->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&TracingSamplerProfiler::DeleteOnChildThreadForTesting));
|
||||
base::RunLoop().RunUntilIdle();
|
||||
|
||||
EXPECT_EQ(event_analyzer.CountEvents(), 0U);
|
||||
}
|
||||
@ -499,10 +407,8 @@ TEST_F(TracingSampleProfilerTest, SampleLoaderLockWithoutMock) {
|
||||
// test process.
|
||||
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
|
||||
BeginTrace();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
WaitForEvents();
|
||||
EndTracing();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
|
||||
// The loader lock may or may not be held during the test, so there's no
|
||||
// output to test. The test passes if it reaches the end without crashing.
|
||||
|
Reference in New Issue
Block a user