blob: b96bbeb379542354c46898dea434f3e6271fb2a6 [file] [log] [blame]
// Copyright 2019 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 "base/profiler/stack_sampling_profiler_test_util.h"
#include <utility>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/profiler/stack_sampling_profiler.h"
#include "base/profiler/unwinder.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
// A profile builder for test use that expects to receive exactly one sample.
class TestProfileBuilder : public ProfileBuilder {
public:
// The callback is passed the last sample recorded.
using CompletedCallback = OnceCallback<void(std::vector<Frame>)>;
TestProfileBuilder(ModuleCache* module_cache, CompletedCallback callback)
: module_cache_(module_cache), callback_(std::move(callback)) {}
~TestProfileBuilder() override = default;
TestProfileBuilder(const TestProfileBuilder&) = delete;
TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;
// ProfileBuilder:
ModuleCache* GetModuleCache() override { return module_cache_; }
void RecordMetadata(MetadataProvider* metadata_provider) override {}
void OnSampleCompleted(std::vector<Frame> sample) override {
EXPECT_TRUE(sample_.empty());
sample_ = std::move(sample);
}
void OnProfileCompleted(TimeDelta profile_duration,
TimeDelta sampling_period) override {
EXPECT_FALSE(sample_.empty());
std::move(callback_).Run(std::move(sample_));
}
private:
ModuleCache* const module_cache_;
CompletedCallback callback_;
std::vector<Frame> sample_;
};
} // namespace
TargetThread::TargetThread(OnceClosure to_run) : to_run_(std::move(to_run)) {}
TargetThread::~TargetThread() = default;
void TargetThread::ThreadMain() {
id_ = PlatformThread::CurrentId();
std::move(to_run_).Run();
}
UnwindScenario::UnwindScenario(const SetupFunction& setup_function)
: setup_function_(setup_function) {}
UnwindScenario::~UnwindScenario() = default;
FunctionAddressRange UnwindScenario::GetWaitForSampleAddressRange() const {
return WaitForSample(nullptr);
}
FunctionAddressRange UnwindScenario::GetSetupFunctionAddressRange() const {
return setup_function_.Run(OnceClosure());
}
FunctionAddressRange UnwindScenario::GetOuterFunctionAddressRange() const {
return InvokeSetupFunction(SetupFunction(), nullptr);
}
void UnwindScenario::Execute(SampleEvents* events) {
InvokeSetupFunction(setup_function_, events);
}
// static
// Disable inlining for this function so that it gets its own stack frame.
NOINLINE FunctionAddressRange
UnwindScenario::InvokeSetupFunction(const SetupFunction& setup_function,
SampleEvents* events) {
const void* start_program_counter = GetProgramCounter();
if (!setup_function.is_null()) {
const auto wait_for_sample_closure =
BindLambdaForTesting([&]() { UnwindScenario::WaitForSample(events); });
setup_function.Run(wait_for_sample_closure);
}
// Volatile to prevent a tail call to GetProgramCounter().
const void* volatile end_program_counter = GetProgramCounter();
return {start_program_counter, end_program_counter};
}
// static
// Disable inlining for this function so that it gets its own stack frame.
NOINLINE FunctionAddressRange
UnwindScenario::WaitForSample(SampleEvents* events) {
const void* start_program_counter = GetProgramCounter();
if (events) {
events->ready_for_sample.Signal();
events->sample_finished.Wait();
}
// Volatile to prevent a tail call to GetProgramCounter().
const void* volatile end_program_counter = GetProgramCounter();
return {start_program_counter, end_program_counter};
}
// Disable inlining for this function so that it gets its own stack frame.
NOINLINE FunctionAddressRange
CallWithPlainFunction(OnceClosure wait_for_sample) {
const void* start_program_counter = GetProgramCounter();
if (!wait_for_sample.is_null())
std::move(wait_for_sample).Run();
// Volatile to prevent a tail call to GetProgramCounter().
const void* volatile end_program_counter = GetProgramCounter();
return {start_program_counter, end_program_counter};
}
void WithTargetThread(UnwindScenario* scenario,
ProfileCallback profile_callback) {
UnwindScenario::SampleEvents events;
TargetThread target_thread(
BindLambdaForTesting([&]() { scenario->Execute(&events); }));
PlatformThreadHandle target_thread_handle;
EXPECT_TRUE(PlatformThread::Create(0, &target_thread, &target_thread_handle));
events.ready_for_sample.Wait();
std::move(profile_callback).Run(target_thread.id());
events.sample_finished.Signal();
PlatformThread::Join(target_thread_handle);
}
std::vector<Frame> SampleScenario(UnwindScenario* scenario,
ModuleCache* module_cache,
UnwinderFactory aux_unwinder_factory) {
StackSamplingProfiler::SamplingParams params;
params.sampling_interval = TimeDelta::FromMilliseconds(0);
params.samples_per_profile = 1;
std::vector<Frame> sample;
WithTargetThread(
scenario, BindLambdaForTesting([&](PlatformThreadId target_thread_id) {
WaitableEvent sampling_thread_completed(
WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
StackSamplingProfiler profiler(
target_thread_id, params,
std::make_unique<TestProfileBuilder>(
module_cache,
BindLambdaForTesting([&sample, &sampling_thread_completed](
std::vector<Frame> result_sample) {
sample = std::move(result_sample);
sampling_thread_completed.Signal();
})));
if (aux_unwinder_factory)
profiler.AddAuxUnwinder(std::move(aux_unwinder_factory).Run());
profiler.Start();
sampling_thread_completed.Wait();
}));
return sample;
}
std::string FormatSampleForDiagnosticOutput(const std::vector<Frame>& sample) {
std::string output;
for (const auto& frame : sample) {
output += StringPrintf(
"0x%p %s\n", reinterpret_cast<const void*>(frame.instruction_pointer),
frame.module ? frame.module->GetDebugBasename().AsUTF8Unsafe().c_str()
: "null module");
}
return output;
}
void ExpectStackContains(const std::vector<Frame>& stack,
const std::vector<FunctionAddressRange>& functions) {
auto frame_it = stack.begin();
auto function_it = functions.begin();
for (; frame_it != stack.end() && function_it != functions.end();
++frame_it) {
if (frame_it->instruction_pointer >=
reinterpret_cast<uintptr_t>(function_it->start) &&
frame_it->instruction_pointer <=
reinterpret_cast<uintptr_t>(function_it->end)) {
++function_it;
}
}
EXPECT_EQ(function_it, functions.end())
<< "Function in position " << function_it - functions.begin() << " at "
<< function_it->start << " was not found in stack "
<< "(or did not appear in the expected order):\n"
<< FormatSampleForDiagnosticOutput(stack);
}
void ExpectStackDoesNotContain(
const std::vector<Frame>& stack,
const std::vector<FunctionAddressRange>& functions) {
struct FunctionAddressRangeCompare {
bool operator()(const FunctionAddressRange& a,
const FunctionAddressRange& b) const {
return std::make_pair(a.start, a.end) < std::make_pair(b.start, b.end);
}
};
std::set<FunctionAddressRange, FunctionAddressRangeCompare> seen_functions;
for (const auto frame : stack) {
for (const auto function : functions) {
if (frame.instruction_pointer >=
reinterpret_cast<uintptr_t>(function.start) &&
frame.instruction_pointer <=
reinterpret_cast<uintptr_t>(function.end)) {
seen_functions.insert(function);
}
}
}
for (const auto function : seen_functions) {
ADD_FAILURE() << "Function at " << function.start
<< " was unexpectedly found in stack:\n"
<< FormatSampleForDiagnosticOutput(stack);
}
}
} // namespace base