blob: 13aba1c14aef4c417691b7092d879682af9a6811 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
#include <limits>
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_reader.h"
#include "base/memory/ref_counted_memory.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/trace_event/trace_buffer.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "services/tracing/public/cpp/buildflags.h"
#include "services/tracing/public/cpp/perfetto/producer_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/perfetto/include/perfetto/ext/base/utils.h"
#include "third_party/perfetto/include/perfetto/protozero/scattered_stream_null_delegate.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pb.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
#include "base/test/trace_event_analyzer.h"
#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_win.h"
#endif
namespace tracing {
namespace {
using base::trace_event::TraceLog;
using ::testing::Invoke;
using ::testing::Return;
class MockTraceWriter : public perfetto::TraceWriter {
public:
MockTraceWriter(
const base::RepeatingCallback<void(
std::unique_ptr<perfetto::protos::TracePacket>)>& on_packet_callback)
: delegate_(perfetto::base::kPageSize),
stream_(&delegate_),
on_packet_callback_(std::move(on_packet_callback)) {
trace_packet_.Reset(&stream_);
}
void FlushPacketIfPossible() {
// GetNewBuffer() in ScatteredStreamWriterNullDelegate doesn't
// actually return a new buffer, but rather lets us access the buffer
// buffer already used by protozero to write the TracePacket into.
protozero::ContiguousMemoryRange buffer = delegate_.GetNewBuffer();
uint32_t message_size = trace_packet_.Finalize();
if (message_size) {
EXPECT_GE(buffer.size(), message_size);
auto proto = std::make_unique<perfetto::protos::TracePacket>();
EXPECT_TRUE(proto->ParseFromArray(buffer.begin, message_size));
on_packet_callback_.Run(std::move(proto));
}
stream_.Reset(buffer);
trace_packet_.Reset(&stream_);
}
perfetto::TraceWriter::TracePacketHandle NewTracePacket() override {
FlushPacketIfPossible();
return perfetto::TraceWriter::TracePacketHandle(&trace_packet_);
}
void Flush(std::function<void()> callback = {}) override {}
perfetto::WriterID writer_id() const override {
return perfetto::WriterID(0);
}
uint64_t written() const override { return 0u; }
private:
perfetto::protos::pbzero::TracePacket trace_packet_;
protozero::ScatteredStreamWriterNullDelegate delegate_;
protozero::ScatteredStreamWriter stream_;
base::RepeatingCallback<void(std::unique_ptr<perfetto::protos::TracePacket>)>
on_packet_callback_;
DISALLOW_COPY_AND_ASSIGN(MockTraceWriter);
};
class MockPerfettoProducer : public ProducerClient {
public:
explicit MockPerfettoProducer(std::unique_ptr<PerfettoTaskRunner> task_runner)
: ProducerClient(task_runner.get()),
task_runner_(std::move(task_runner)) {}
std::unique_ptr<perfetto::TraceWriter> CreateTraceWriter(
perfetto::BufferID target_buffer,
perfetto::BufferExhaustedPolicy =
perfetto::BufferExhaustedPolicy::kDefault) override {
auto packet_callback = base::BindRepeating(
[](base::WeakPtr<MockPerfettoProducer> weak_self,
scoped_refptr<base::SequencedTaskRunner> task_runner,
std::unique_ptr<perfetto::protos::TracePacket> packet) {
task_runner->PostTask(
FROM_HERE, base::BindOnce(&MockPerfettoProducer::ReceivePacket,
weak_self, std::move(packet)));
},
weak_ptr_factory_.GetWeakPtr(), base::SequencedTaskRunnerHandle::Get());
return std::make_unique<MockTraceWriter>(packet_callback);
}
void ReceivePacket(std::unique_ptr<perfetto::protos::TracePacket> packet) {
base::AutoLock lock(lock_);
finalized_packets_.push_back(std::move(packet));
}
const perfetto::protos::TracePacket* GetFinalizedPacket(
size_t packet_index = 0) {
base::AutoLock lock(lock_);
EXPECT_GT(finalized_packets_.size(), packet_index);
return finalized_packets_[packet_index].get();
}
const std::vector<std::unique_ptr<perfetto::protos::TracePacket>>&
finalized_packets() const {
return finalized_packets_;
}
private:
base::Lock lock_; // protects finalized_packets_
std::vector<std::unique_ptr<perfetto::protos::TracePacket>>
finalized_packets_;
std::unique_ptr<PerfettoTaskRunner> task_runner_;
base::WeakPtrFactory<MockPerfettoProducer> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(MockPerfettoProducer);
};
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
class MockLoaderLockSampler : public TracingSamplerProfiler::LoaderLockSampler {
public:
MockLoaderLockSampler() = default;
~MockLoaderLockSampler() override = default;
MOCK_METHOD(bool, IsLoaderLockHeld, (), (const, override));
};
class LoaderLockEventAnalyzer {
public:
LoaderLockEventAnalyzer() {
trace_analyzer::Start(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"));
}
size_t CountEvents() {
std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer =
trace_analyzer::Stop();
trace_analyzer::TraceEventVector events;
return analyzer->FindEvents(
trace_analyzer::Query::EventName() ==
trace_analyzer::Query::String(
TracingSamplerProfiler::kLoaderLockHeldEventName),
&events);
}
};
#endif // BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
class TracingSampleProfilerTest : public testing::Test {
public:
TracingSampleProfilerTest() = default;
~TracingSampleProfilerTest() override = default;
void SetUp() override {
events_stack_received_count_ = 0u;
PerfettoTracedProcess::ResetTaskRunnerForTesting();
PerfettoTracedProcess::GetTaskRunner()->GetOrCreateTaskRunner();
auto perfetto_wrapper = std::make_unique<PerfettoTaskRunner>(
task_environment_.GetMainThreadTaskRunner());
producer_ =
std::make_unique<MockPerfettoProducer>(std::move(perfetto_wrapper));
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
ON_CALL(mock_loader_lock_sampler_, IsLoaderLockHeld())
.WillByDefault(Return(false));
TracingSamplerProfiler::SetLoaderLockSamplerForTesting(
&mock_loader_lock_sampler_);
#endif
}
void TearDown() override {
// Be sure there is no pending/running tasks.
task_environment_.RunUntilIdle();
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
TracingSamplerProfiler::SetLoaderLockSamplerForTesting(nullptr);
#endif
}
void BeginTrace() {
TracingSamplerProfiler::StartTracingForTesting(producer_.get());
}
void WaitForEvents() {
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
}
void EndTracing() {
TracingSamplerProfiler::StopTracingForTesting();
base::RunLoop().RunUntilIdle();
auto& packets = producer_->finalized_packets();
for (auto& packet : packets) {
if (packet->has_streaming_profile_packet()) {
events_stack_received_count_++;
}
}
}
void ValidateReceivedEvents() {
if (TracingSamplerProfiler::IsStackUnwindingSupported()) {
EXPECT_GT(events_stack_received_count_, 0U);
} else {
EXPECT_EQ(events_stack_received_count_, 0U);
}
}
uint32_t FindProfilerSequenceId() {
uint32_t profile_sequence_id = std::numeric_limits<uint32_t>::max();
auto& packets = producer_->finalized_packets();
for (auto& packet : packets) {
if (packet->has_streaming_profile_packet()) {
profile_sequence_id = packet->trusted_packet_sequence_id();
break;
}
}
EXPECT_NE(profile_sequence_id, std::numeric_limits<uint32_t>::max());
return profile_sequence_id;
}
const MockPerfettoProducer* producer() const { return producer_.get(); }
protected:
base::test::TaskEnvironment task_environment_;
// We want our singleton torn down after each test.
base::ShadowingAtExitManager at_exit_manager_;
base::trace_event::TraceResultBuffer trace_buffer_;
std::unique_ptr<MockPerfettoProducer> producer_;
// Number of stack sampling events received.
size_t events_stack_received_count_ = 0;
#if BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
MockLoaderLockSampler mock_loader_lock_sampler_;
#endif
private:
DISALLOW_COPY_AND_ASSIGN(TracingSampleProfilerTest);
};
// Stub module for testing.
class TestModule : public base::ModuleCache::Module {
public:
TestModule() = default;
TestModule(const TestModule&) = delete;
TestModule& operator=(const TestModule&) = delete;
void set_id(const std::string& id) { id_ = id; }
uintptr_t GetBaseAddress() const override { return 0; }
std::string GetId() const override { return id_; }
base::FilePath GetDebugBasename() const override { return base::FilePath(); }
size_t GetSize() const override { return 0; }
bool IsNative() const override { return true; }
private:
std::string id_;
};
} // namespace
TEST_F(TracingSampleProfilerTest, OnSampleCompleted) {
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
BeginTrace();
base::RunLoop().RunUntilIdle();
WaitForEvents();
EndTracing();
base::RunLoop().RunUntilIdle();
ValidateReceivedEvents();
}
TEST_F(TracingSampleProfilerTest, JoinRunningTracing) {
BeginTrace();
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
base::RunLoop().RunUntilIdle();
WaitForEvents();
EndTracing();
base::RunLoop().RunUntilIdle();
ValidateReceivedEvents();
}
TEST_F(TracingSampleProfilerTest, TestStartupTracing) {
auto profiler = TracingSamplerProfiler::CreateOnMainThread();
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::IsStackUnwindingSupported()) {
uint32_t seq_id = FindProfilerSequenceId();
auto& packets = producer()->finalized_packets();
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();
WaitForEvents();
auto start_tracing_ts = TRACE_TIME_TICKS_NOW();
BeginTrace();
base::RunLoop().RunUntilIdle();
WaitForEvents();
EndTracing();
base::RunLoop().RunUntilIdle();
if (TracingSamplerProfiler::IsStackUnwindingSupported()) {
uint32_t seq_id = FindProfilerSequenceId();
auto& packets = producer()->finalized_packets();
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, SamplingChildThread) {
base::Thread sampled_thread("sampling_profiler_test");
sampled_thread.Start();
sampled_thread.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&TracingSamplerProfiler::CreateOnChildThread));
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)
TEST_F(TracingSampleProfilerTest, SampleLoaderLockOnMainThread) {
LoaderLockEventAnalyzer event_analyzer;
bool lock_held = false;
size_t call_count = 0;
EXPECT_CALL(mock_loader_lock_sampler_, IsLoaderLockHeld())
.WillRepeatedly(Invoke([&lock_held, &call_count]() {
++call_count;
lock_held = !lock_held;
return lock_held;
}));
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.
EXPECT_EQ(event_analyzer.CountEvents(), call_count);
// Loader lock should have been sampled every time the stack is sampled,
// although not every stack sample generates a stack event.
EXPECT_GE(call_count, events_stack_received_count_);
}
TEST_F(TracingSampleProfilerTest, SampleLoaderLockAlwaysHeld) {
LoaderLockEventAnalyzer event_analyzer;
EXPECT_CALL(mock_loader_lock_sampler_, IsLoaderLockHeld())
.WillRepeatedly(Return(true));
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.
EXPECT_EQ(event_analyzer.CountEvents(), 1U);
}
TEST_F(TracingSampleProfilerTest, SampleLoaderLockNeverHeld) {
LoaderLockEventAnalyzer event_analyzer;
EXPECT_CALL(mock_loader_lock_sampler_, IsLoaderLockHeld())
.WillRepeatedly(Return(false));
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);
}
TEST_F(TracingSampleProfilerTest, SampleLoaderLockOnChildThread) {
LoaderLockEventAnalyzer event_analyzer;
// Loader lock should only be sampled on main thread.
EXPECT_CALL(mock_loader_lock_sampler_, IsLoaderLockHeld()).Times(0);
base::Thread sampled_thread("sampling_profiler_test");
sampled_thread.Start();
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);
}
#endif // BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
// Crashes on Windows. https://crbug.com/1076768
#if defined(OS_WIN)
#define MAYBE_ValidModule DISABLED_ValidModule
#else
#define MAYBE_ValidModule ValidModule
#endif
TEST(TracingProfileBuilderTest, MAYBE_ValidModule) {
TestModule module;
TracingSamplerProfiler::TracingProfileBuilder profile_builder(
base::PlatformThreadId(),
std::make_unique<MockTraceWriter>(base::DoNothing()), false);
profile_builder.OnSampleCompleted({base::Frame(0x1010, &module)},
base::TimeTicks());
}
// Crashes on Win7 only. http://crbug.com/1076768
#if defined(OS_WIN)
#define MAYBE_InvalidModule DISABLED_InvalidModule
#else
#define MAYBE_InvalidModule InvalidModule
#endif
TEST(TracingProfileBuilderTest, MAYBE_InvalidModule) {
TracingSamplerProfiler::TracingProfileBuilder profile_builder(
base::PlatformThreadId(),
std::make_unique<MockTraceWriter>(base::DoNothing()), false);
profile_builder.OnSampleCompleted({base::Frame(0x1010, nullptr)},
base::TimeTicks());
}
#if defined(OS_ANDROID) || defined(OS_LINUX)
TEST(TracingProfileBuilderTest, MangleELFModuleID) {
TestModule module;
// See explanation for the module_id mangling in
// TracingSamplerProfiler::TracingProfileBuilder::GetCallstackIDAndMaybeEmit.
module.set_id("7F0715C286F8B16C10E4AD349CDA3B9B56C7A773");
bool found_build_id = false;
auto on_packet_callback = base::BindLambdaForTesting(
[&found_build_id](std::unique_ptr<perfetto::protos::TracePacket> packet) {
if (!packet->has_interned_data() ||
packet->interned_data().build_ids_size() == 0) {
return;
}
found_build_id = true;
EXPECT_EQ(packet->interned_data().build_ids(0).str(),
"C215077FF8866CB110E4AD349CDA3B9B0");
});
auto trace_writer = std::make_unique<MockTraceWriter>(on_packet_callback);
auto* raw_trace_writer = trace_writer.get();
TracingSamplerProfiler::TracingProfileBuilder profile_builder(
base::PlatformThreadId(), std::move(trace_writer), false);
profile_builder.OnSampleCompleted({base::Frame(0x1010, &module)},
base::TimeTicks());
raw_trace_writer->FlushPacketIfPossible();
EXPECT_TRUE(found_build_id);
}
#endif
} // namespace tracing