1523 lines
57 KiB
1523 lines
57 KiB
// Copyright 2015 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 <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/bind.h"
|
|
#include "base/callback.h"
|
|
#include "base/compiler_specific.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/macros.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/native_library.h"
|
|
#include "base/path_service.h"
|
|
#include "base/profiler/native_stack_sampler.h"
|
|
#include "base/profiler/stack_sampling_profiler.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/scoped_native_library.h"
|
|
#include "base/stl_util.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/synchronization/lock.h"
|
|
#include "base/synchronization/waitable_event.h"
|
|
#include "base/test/bind_test_util.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/threading/simple_thread.h"
|
|
#include "base/time/time.h"
|
|
#include "build/build_config.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
#if defined(OS_WIN)
|
|
#include <intrin.h>
|
|
#include <malloc.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <alloca.h>
|
|
#endif
|
|
|
|
// STACK_SAMPLING_PROFILER_SUPPORTED is used to conditionally enable the tests
|
|
// below for supported platforms (currently Win x64 and Mac x64).
|
|
#if defined(_WIN64) || (defined(OS_MACOSX) && !defined(OS_IOS))
|
|
#define STACK_SAMPLING_PROFILER_SUPPORTED 1
|
|
#endif
|
|
|
|
#if defined(OS_WIN)
|
|
#pragma intrinsic(_ReturnAddress)
|
|
#endif
|
|
|
|
namespace base {
|
|
|
|
#if defined(STACK_SAMPLING_PROFILER_SUPPORTED)
|
|
#define PROFILER_TEST_F(TestClass, TestName) TEST_F(TestClass, TestName)
|
|
#else
|
|
#define PROFILER_TEST_F(TestClass, TestName) \
|
|
TEST_F(TestClass, DISABLED_##TestName)
|
|
#endif
|
|
|
|
using SamplingParams = StackSamplingProfiler::SamplingParams;
|
|
using Frame = StackSamplingProfiler::Frame;
|
|
using Frames = std::vector<Frame>;
|
|
using InternalFrame = StackSamplingProfiler::InternalFrame;
|
|
using InternalFrames = std::vector<InternalFrame>;
|
|
using InternalFrameSets = std::vector<std::vector<InternalFrame>>;
|
|
using Module = StackSamplingProfiler::Module;
|
|
using InternalModule = StackSamplingProfiler::InternalModule;
|
|
using Sample = StackSamplingProfiler::Sample;
|
|
|
|
namespace {
|
|
|
|
// Configuration for the frames that appear on the stack.
|
|
struct StackConfiguration {
|
|
enum Config { NORMAL, WITH_ALLOCA, WITH_OTHER_LIBRARY };
|
|
|
|
explicit StackConfiguration(Config config)
|
|
: StackConfiguration(config, nullptr) {
|
|
EXPECT_NE(config, WITH_OTHER_LIBRARY);
|
|
}
|
|
|
|
StackConfiguration(Config config, NativeLibrary library)
|
|
: config(config), library(library) {
|
|
EXPECT_TRUE(config != WITH_OTHER_LIBRARY || library);
|
|
}
|
|
|
|
Config config;
|
|
|
|
// Only used if config == WITH_OTHER_LIBRARY.
|
|
NativeLibrary library;
|
|
};
|
|
|
|
// Signature for a target function that is expected to appear in the stack. See
|
|
// SignalAndWaitUntilSignaled() below. The return value should be a program
|
|
// counter pointer near the end of the function.
|
|
using TargetFunction = const void* (*)(WaitableEvent*,
|
|
WaitableEvent*,
|
|
const StackConfiguration*);
|
|
|
|
// A thread to target for profiling, whose stack is guaranteed to contain
|
|
// SignalAndWaitUntilSignaled() when coordinated with the main thread.
|
|
class TargetThread : public PlatformThread::Delegate {
|
|
public:
|
|
explicit TargetThread(const StackConfiguration& stack_config);
|
|
|
|
// PlatformThread::Delegate:
|
|
void ThreadMain() override;
|
|
|
|
// Waits for the thread to have started and be executing in
|
|
// SignalAndWaitUntilSignaled().
|
|
void WaitForThreadStart();
|
|
|
|
// Allows the thread to return from SignalAndWaitUntilSignaled() and finish
|
|
// execution.
|
|
void SignalThreadToFinish();
|
|
|
|
// This function is guaranteed to be executing between calls to
|
|
// WaitForThreadStart() and SignalThreadToFinish() when invoked with
|
|
// |thread_started_event_| and |finish_event_|. Returns a program counter
|
|
// value near the end of the function. May be invoked with null WaitableEvents
|
|
// to just return the program counter.
|
|
//
|
|
// This function is static so that we can get a straightforward address
|
|
// for it in one of the tests below, rather than dealing with the complexity
|
|
// of a member function pointer representation.
|
|
static const void* SignalAndWaitUntilSignaled(
|
|
WaitableEvent* thread_started_event,
|
|
WaitableEvent* finish_event,
|
|
const StackConfiguration* stack_config);
|
|
|
|
// Calls into SignalAndWaitUntilSignaled() after allocating memory on the
|
|
// stack with alloca.
|
|
static const void* CallWithAlloca(WaitableEvent* thread_started_event,
|
|
WaitableEvent* finish_event,
|
|
const StackConfiguration* stack_config);
|
|
|
|
// Calls into SignalAndWaitUntilSignaled() via a function in
|
|
// base_profiler_test_support_library.
|
|
static const void* CallThroughOtherLibrary(
|
|
WaitableEvent* thread_started_event,
|
|
WaitableEvent* finish_event,
|
|
const StackConfiguration* stack_config);
|
|
|
|
PlatformThreadId id() const { return id_; }
|
|
|
|
private:
|
|
struct TargetFunctionArgs {
|
|
WaitableEvent* thread_started_event;
|
|
WaitableEvent* finish_event;
|
|
const StackConfiguration* stack_config;
|
|
};
|
|
|
|
// Callback function to be provided when calling through the other library.
|
|
static void OtherLibraryCallback(void* arg);
|
|
|
|
// Returns the current program counter, or a value very close to it.
|
|
static const void* GetProgramCounter();
|
|
|
|
WaitableEvent thread_started_event_;
|
|
WaitableEvent finish_event_;
|
|
PlatformThreadId id_;
|
|
const StackConfiguration stack_config_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TargetThread);
|
|
};
|
|
|
|
TargetThread::TargetThread(const StackConfiguration& stack_config)
|
|
: thread_started_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
|
|
WaitableEvent::InitialState::NOT_SIGNALED),
|
|
finish_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
|
|
WaitableEvent::InitialState::NOT_SIGNALED),
|
|
id_(0),
|
|
stack_config_(stack_config) {}
|
|
|
|
void TargetThread::ThreadMain() {
|
|
id_ = PlatformThread::CurrentId();
|
|
switch (stack_config_.config) {
|
|
case StackConfiguration::NORMAL:
|
|
SignalAndWaitUntilSignaled(&thread_started_event_, &finish_event_,
|
|
&stack_config_);
|
|
break;
|
|
|
|
case StackConfiguration::WITH_ALLOCA:
|
|
CallWithAlloca(&thread_started_event_, &finish_event_, &stack_config_);
|
|
break;
|
|
|
|
case StackConfiguration::WITH_OTHER_LIBRARY:
|
|
CallThroughOtherLibrary(&thread_started_event_, &finish_event_,
|
|
&stack_config_);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TargetThread::WaitForThreadStart() {
|
|
thread_started_event_.Wait();
|
|
}
|
|
|
|
void TargetThread::SignalThreadToFinish() {
|
|
finish_event_.Signal();
|
|
}
|
|
|
|
// static
|
|
// Disable inlining for this function so that it gets its own stack frame.
|
|
NOINLINE const void* TargetThread::SignalAndWaitUntilSignaled(
|
|
WaitableEvent* thread_started_event,
|
|
WaitableEvent* finish_event,
|
|
const StackConfiguration* stack_config) {
|
|
if (thread_started_event && finish_event) {
|
|
thread_started_event->Signal();
|
|
finish_event->Wait();
|
|
}
|
|
|
|
// Volatile to prevent a tail call to GetProgramCounter().
|
|
const void* volatile program_counter = GetProgramCounter();
|
|
return program_counter;
|
|
}
|
|
|
|
// static
|
|
// Disable inlining for this function so that it gets its own stack frame.
|
|
NOINLINE const void* TargetThread::CallWithAlloca(
|
|
WaitableEvent* thread_started_event,
|
|
WaitableEvent* finish_event,
|
|
const StackConfiguration* stack_config) {
|
|
const size_t alloca_size = 100;
|
|
// Memset to 0 to generate a clean failure.
|
|
std::memset(alloca(alloca_size), 0, alloca_size);
|
|
|
|
SignalAndWaitUntilSignaled(thread_started_event, finish_event, stack_config);
|
|
|
|
// Volatile to prevent a tail call to GetProgramCounter().
|
|
const void* volatile program_counter = GetProgramCounter();
|
|
return program_counter;
|
|
}
|
|
|
|
// static
|
|
NOINLINE const void* TargetThread::CallThroughOtherLibrary(
|
|
WaitableEvent* thread_started_event,
|
|
WaitableEvent* finish_event,
|
|
const StackConfiguration* stack_config) {
|
|
if (stack_config) {
|
|
// A function whose arguments are a function accepting void*, and a void*.
|
|
using InvokeCallbackFunction = void (*)(void (*)(void*), void*);
|
|
EXPECT_TRUE(stack_config->library);
|
|
InvokeCallbackFunction function = reinterpret_cast<InvokeCallbackFunction>(
|
|
GetFunctionPointerFromNativeLibrary(stack_config->library,
|
|
"InvokeCallbackFunction"));
|
|
EXPECT_TRUE(function);
|
|
|
|
TargetFunctionArgs args = {thread_started_event, finish_event,
|
|
stack_config};
|
|
(*function)(&OtherLibraryCallback, &args);
|
|
}
|
|
|
|
// Volatile to prevent a tail call to GetProgramCounter().
|
|
const void* volatile program_counter = GetProgramCounter();
|
|
return program_counter;
|
|
}
|
|
|
|
// static
|
|
void TargetThread::OtherLibraryCallback(void* arg) {
|
|
const TargetFunctionArgs* args = static_cast<TargetFunctionArgs*>(arg);
|
|
SignalAndWaitUntilSignaled(args->thread_started_event, args->finish_event,
|
|
args->stack_config);
|
|
// Prevent tail call.
|
|
volatile int i = 0;
|
|
ALLOW_UNUSED_LOCAL(i);
|
|
}
|
|
|
|
// static
|
|
// Disable inlining for this function so that it gets its own stack frame.
|
|
NOINLINE const void* TargetThread::GetProgramCounter() {
|
|
#if defined(OS_WIN)
|
|
return _ReturnAddress();
|
|
#else
|
|
return __builtin_return_address(0);
|
|
#endif
|
|
}
|
|
|
|
// Profile consists of a set of internal frame sets and other sampling
|
|
// information.
|
|
struct Profile {
|
|
Profile() = default;
|
|
Profile(Profile&& other) = default;
|
|
Profile(const InternalFrameSets& frame_sets,
|
|
int annotation_count,
|
|
TimeDelta profile_duration,
|
|
TimeDelta sampling_period);
|
|
|
|
~Profile() = default;
|
|
|
|
Profile& operator=(Profile&& other) = default;
|
|
|
|
// The collected internal frame sets.
|
|
InternalFrameSets frame_sets;
|
|
|
|
// The number of invocations of RecordAnnotations().
|
|
int annotation_count;
|
|
|
|
// Duration of this profile.
|
|
TimeDelta profile_duration;
|
|
|
|
// Time between samples.
|
|
TimeDelta sampling_period;
|
|
};
|
|
|
|
Profile::Profile(const InternalFrameSets& frame_sets,
|
|
int annotation_count,
|
|
TimeDelta profile_duration,
|
|
TimeDelta sampling_period)
|
|
: frame_sets(frame_sets),
|
|
annotation_count(annotation_count),
|
|
profile_duration(profile_duration),
|
|
sampling_period(sampling_period) {}
|
|
|
|
// The callback type used to collect a profile. The passed Profile is move-only.
|
|
// Other threads, including the UI thread, may block on callback completion so
|
|
// this should run as quickly as possible.
|
|
using ProfileCompletedCallback = Callback<void(Profile)>;
|
|
|
|
// TestProfileBuilder collects internal frames produced by the profiler.
|
|
class TestProfileBuilder : public StackSamplingProfiler::ProfileBuilder {
|
|
public:
|
|
TestProfileBuilder(const ProfileCompletedCallback& callback);
|
|
|
|
~TestProfileBuilder() override;
|
|
|
|
// StackSamplingProfiler::ProfileBuilder:
|
|
void RecordAnnotations() override;
|
|
void OnSampleCompleted(InternalFrames internal_frames) override;
|
|
void OnProfileCompleted(TimeDelta profile_duration,
|
|
TimeDelta sampling_period) override;
|
|
|
|
private:
|
|
// The sets of internal frames recorded.
|
|
std::vector<InternalFrames> frame_sets_;
|
|
|
|
// The number of invocations of RecordAnnotations().
|
|
int annotation_count_ = 0;
|
|
|
|
// Callback made when sampling a profile completes.
|
|
const ProfileCompletedCallback callback_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestProfileBuilder);
|
|
};
|
|
|
|
TestProfileBuilder::TestProfileBuilder(const ProfileCompletedCallback& callback)
|
|
: callback_(callback) {}
|
|
|
|
TestProfileBuilder::~TestProfileBuilder() = default;
|
|
|
|
void TestProfileBuilder::RecordAnnotations() {
|
|
++annotation_count_;
|
|
}
|
|
|
|
void TestProfileBuilder::OnSampleCompleted(InternalFrames internal_frames) {
|
|
frame_sets_.push_back(std::move(internal_frames));
|
|
}
|
|
|
|
void TestProfileBuilder::OnProfileCompleted(TimeDelta profile_duration,
|
|
TimeDelta sampling_period) {
|
|
callback_.Run(Profile(frame_sets_, annotation_count_, profile_duration,
|
|
sampling_period));
|
|
}
|
|
|
|
// Loads the other library, which defines a function to be called in the
|
|
// WITH_OTHER_LIBRARY configuration.
|
|
NativeLibrary LoadOtherLibrary() {
|
|
// The lambda gymnastics works around the fact that we can't use ASSERT_*
|
|
// macros in a function returning non-null.
|
|
const auto load = [](NativeLibrary* library) {
|
|
FilePath other_library_path;
|
|
ASSERT_TRUE(PathService::Get(DIR_EXE, &other_library_path));
|
|
other_library_path = other_library_path.AppendASCII(
|
|
GetNativeLibraryName("base_profiler_test_support_library"));
|
|
NativeLibraryLoadError load_error;
|
|
*library = LoadNativeLibrary(other_library_path, &load_error);
|
|
ASSERT_TRUE(*library) << "error loading " << other_library_path.value()
|
|
<< ": " << load_error.ToString();
|
|
};
|
|
|
|
NativeLibrary library = nullptr;
|
|
load(&library);
|
|
return library;
|
|
}
|
|
|
|
// Unloads |library| and returns when it has completed unloading. Unloading a
|
|
// library is asynchronous on Windows, so simply calling UnloadNativeLibrary()
|
|
// is insufficient to ensure it's been unloaded.
|
|
void SynchronousUnloadNativeLibrary(NativeLibrary library) {
|
|
UnloadNativeLibrary(library);
|
|
#if defined(OS_WIN)
|
|
// NativeLibrary is a typedef for HMODULE, which is actually the base address
|
|
// of the module.
|
|
uintptr_t module_base_address = reinterpret_cast<uintptr_t>(library);
|
|
HMODULE module_handle;
|
|
// Keep trying to get the module handle until the call fails.
|
|
while (::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
|
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
reinterpret_cast<LPCTSTR>(module_base_address),
|
|
&module_handle) ||
|
|
::GetLastError() != ERROR_MOD_NOT_FOUND) {
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
|
|
}
|
|
#elif defined(OS_MACOSX)
|
|
// Unloading a library on the Mac is synchronous.
|
|
#else
|
|
NOTIMPLEMENTED();
|
|
#endif
|
|
}
|
|
|
|
// Executes the function with the target thread running and executing within
|
|
// SignalAndWaitUntilSignaled(). Performs all necessary target thread startup
|
|
// and shutdown work before and afterward.
|
|
template <class Function>
|
|
void WithTargetThread(Function function,
|
|
const StackConfiguration& stack_config) {
|
|
TargetThread target_thread(stack_config);
|
|
PlatformThreadHandle target_thread_handle;
|
|
EXPECT_TRUE(PlatformThread::Create(0, &target_thread, &target_thread_handle));
|
|
|
|
target_thread.WaitForThreadStart();
|
|
|
|
function(target_thread.id());
|
|
|
|
target_thread.SignalThreadToFinish();
|
|
|
|
PlatformThread::Join(target_thread_handle);
|
|
}
|
|
|
|
template <class Function>
|
|
void WithTargetThread(Function function) {
|
|
WithTargetThread(function, StackConfiguration(StackConfiguration::NORMAL));
|
|
}
|
|
|
|
struct TestProfilerInfo {
|
|
TestProfilerInfo(PlatformThreadId thread_id,
|
|
const SamplingParams& params,
|
|
NativeStackSamplerTestDelegate* delegate = nullptr)
|
|
: completed(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED),
|
|
profiler(thread_id,
|
|
params,
|
|
std::make_unique<TestProfileBuilder>(
|
|
BindLambdaForTesting([this](Profile result_profile) {
|
|
profile = std::move(result_profile);
|
|
completed.Signal();
|
|
})),
|
|
delegate) {}
|
|
|
|
// The order here is important to ensure objects being referenced don't get
|
|
// destructed until after the objects referencing them.
|
|
Profile profile;
|
|
WaitableEvent completed;
|
|
StackSamplingProfiler profiler;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(TestProfilerInfo);
|
|
};
|
|
|
|
// Creates multiple profilers based on a vector of parameters.
|
|
std::vector<std::unique_ptr<TestProfilerInfo>> CreateProfilers(
|
|
PlatformThreadId target_thread_id,
|
|
const std::vector<SamplingParams>& params) {
|
|
DCHECK(!params.empty());
|
|
|
|
std::vector<std::unique_ptr<TestProfilerInfo>> profilers;
|
|
for (size_t i = 0; i < params.size(); ++i) {
|
|
profilers.push_back(
|
|
std::make_unique<TestProfilerInfo>(target_thread_id, params[i]));
|
|
}
|
|
|
|
return profilers;
|
|
}
|
|
|
|
// Captures internal frames as specified by |params| on the TargetThread, and
|
|
// returns them. Waits up to |profiler_wait_time| for the profiler to complete.
|
|
InternalFrameSets CaptureFrameSets(const SamplingParams& params,
|
|
TimeDelta profiler_wait_time) {
|
|
InternalFrameSets frame_sets;
|
|
WithTargetThread([¶ms, &frame_sets,
|
|
profiler_wait_time](PlatformThreadId target_thread_id) {
|
|
TestProfilerInfo info(target_thread_id, params);
|
|
info.profiler.Start();
|
|
info.completed.TimedWait(profiler_wait_time);
|
|
info.profiler.Stop();
|
|
info.completed.Wait();
|
|
frame_sets = std::move(info.profile.frame_sets);
|
|
});
|
|
|
|
return frame_sets;
|
|
}
|
|
|
|
// Waits for one of multiple samplings to complete.
|
|
size_t WaitForSamplingComplete(
|
|
const std::vector<std::unique_ptr<TestProfilerInfo>>& infos) {
|
|
// Map unique_ptrs to something that WaitMany can accept.
|
|
std::vector<WaitableEvent*> sampling_completed_rawptrs(infos.size());
|
|
std::transform(infos.begin(), infos.end(), sampling_completed_rawptrs.begin(),
|
|
[](const std::unique_ptr<TestProfilerInfo>& info) {
|
|
return &info.get()->completed;
|
|
});
|
|
// Wait for one profiler to finish.
|
|
return WaitableEvent::WaitMany(sampling_completed_rawptrs.data(),
|
|
sampling_completed_rawptrs.size());
|
|
}
|
|
|
|
// If this executable was linked with /INCREMENTAL (the default for non-official
|
|
// debug and release builds on Windows), function addresses do not correspond to
|
|
// function code itself, but instead to instructions in the Incremental Link
|
|
// Table that jump to the functions. Checks for a jump instruction and if
|
|
// present does a little decompilation to find the function's actual starting
|
|
// address.
|
|
const void* MaybeFixupFunctionAddressForILT(const void* function_address) {
|
|
#if defined(_WIN64)
|
|
const unsigned char* opcode =
|
|
reinterpret_cast<const unsigned char*>(function_address);
|
|
if (*opcode == 0xe9) {
|
|
// This is a relative jump instruction. Assume we're in the ILT and compute
|
|
// the function start address from the instruction offset.
|
|
const int32_t* offset = reinterpret_cast<const int32_t*>(opcode + 1);
|
|
const unsigned char* next_instruction =
|
|
reinterpret_cast<const unsigned char*>(offset + 1);
|
|
return next_instruction + *offset;
|
|
}
|
|
#endif
|
|
return function_address;
|
|
}
|
|
|
|
// Searches through the frames in |sample|, returning an iterator to the first
|
|
// frame that has an instruction pointer within |target_function|. Returns
|
|
// sample.end() if no such frames are found.
|
|
InternalFrames::const_iterator FindFirstFrameWithinFunction(
|
|
const InternalFrames& frames,
|
|
TargetFunction target_function) {
|
|
uintptr_t function_start =
|
|
reinterpret_cast<uintptr_t>(MaybeFixupFunctionAddressForILT(
|
|
reinterpret_cast<const void*>(target_function)));
|
|
uintptr_t function_end =
|
|
reinterpret_cast<uintptr_t>(target_function(nullptr, nullptr, nullptr));
|
|
for (auto it = frames.begin(); it != frames.end(); ++it) {
|
|
if (it->instruction_pointer >= function_start &&
|
|
it->instruction_pointer <= function_end) {
|
|
return it;
|
|
}
|
|
}
|
|
return frames.end();
|
|
}
|
|
|
|
// Formats a sample into a string that can be output for test diagnostics.
|
|
std::string FormatSampleForDiagnosticOutput(const InternalFrames& frames) {
|
|
std::string output;
|
|
for (const auto& frame : frames) {
|
|
output += StringPrintf(
|
|
"0x%p %s\n", reinterpret_cast<const void*>(frame.instruction_pointer),
|
|
frame.internal_module.filename.AsUTF8Unsafe().c_str());
|
|
}
|
|
return output;
|
|
}
|
|
|
|
// Returns a duration that is longer than the test timeout. We would use
|
|
// TimeDelta::Max() but https://crbug.com/465948.
|
|
TimeDelta AVeryLongTimeDelta() {
|
|
return TimeDelta::FromDays(1);
|
|
}
|
|
|
|
// Tests the scenario where the library is unloaded after copying the stack, but
|
|
// before walking it. If |wait_until_unloaded| is true, ensures that the
|
|
// asynchronous library loading has completed before walking the stack. If
|
|
// false, the unloading may still be occurring during the stack walk.
|
|
void TestLibraryUnload(bool wait_until_unloaded) {
|
|
// Test delegate that supports intervening between the copying of the stack
|
|
// and the walking of the stack.
|
|
class StackCopiedSignaler : public NativeStackSamplerTestDelegate {
|
|
public:
|
|
StackCopiedSignaler(WaitableEvent* stack_copied,
|
|
WaitableEvent* start_stack_walk,
|
|
bool wait_to_walk_stack)
|
|
: stack_copied_(stack_copied),
|
|
start_stack_walk_(start_stack_walk),
|
|
wait_to_walk_stack_(wait_to_walk_stack) {}
|
|
|
|
void OnPreStackWalk() override {
|
|
stack_copied_->Signal();
|
|
if (wait_to_walk_stack_)
|
|
start_stack_walk_->Wait();
|
|
}
|
|
|
|
private:
|
|
WaitableEvent* const stack_copied_;
|
|
WaitableEvent* const start_stack_walk_;
|
|
const bool wait_to_walk_stack_;
|
|
};
|
|
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
NativeLibrary other_library = LoadOtherLibrary();
|
|
TargetThread target_thread(StackConfiguration(
|
|
StackConfiguration::WITH_OTHER_LIBRARY, other_library));
|
|
|
|
PlatformThreadHandle target_thread_handle;
|
|
EXPECT_TRUE(PlatformThread::Create(0, &target_thread, &target_thread_handle));
|
|
|
|
target_thread.WaitForThreadStart();
|
|
|
|
WaitableEvent sampling_thread_completed(
|
|
WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED);
|
|
Profile profile;
|
|
|
|
WaitableEvent stack_copied(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED);
|
|
WaitableEvent start_stack_walk(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED);
|
|
StackCopiedSignaler test_delegate(&stack_copied, &start_stack_walk,
|
|
wait_until_unloaded);
|
|
StackSamplingProfiler profiler(
|
|
target_thread.id(), params,
|
|
std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
|
|
[&profile, &sampling_thread_completed](Profile result_profile) {
|
|
profile = std::move(result_profile);
|
|
sampling_thread_completed.Signal();
|
|
})),
|
|
&test_delegate);
|
|
|
|
profiler.Start();
|
|
|
|
// Wait for the stack to be copied and the target thread to be resumed.
|
|
stack_copied.Wait();
|
|
|
|
// Cause the target thread to finish, so that it's no longer executing code in
|
|
// the library we're about to unload.
|
|
target_thread.SignalThreadToFinish();
|
|
PlatformThread::Join(target_thread_handle);
|
|
|
|
// Unload the library now that it's not being used.
|
|
if (wait_until_unloaded)
|
|
SynchronousUnloadNativeLibrary(other_library);
|
|
else
|
|
UnloadNativeLibrary(other_library);
|
|
|
|
// Let the stack walk commence after unloading the library, if we're waiting
|
|
// on that event.
|
|
start_stack_walk.Signal();
|
|
|
|
// Wait for the sampling thread to complete and fill out |profile|.
|
|
sampling_thread_completed.Wait();
|
|
|
|
// Look up the frames.
|
|
ASSERT_EQ(1u, profile.frame_sets.size());
|
|
const InternalFrames& frames = profile.frame_sets[0];
|
|
|
|
// Check that the stack contains a frame for
|
|
// TargetThread::SignalAndWaitUntilSignaled().
|
|
InternalFrames::const_iterator end_frame = FindFirstFrameWithinFunction(
|
|
frames, &TargetThread::SignalAndWaitUntilSignaled);
|
|
ASSERT_TRUE(end_frame != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
|
|
&TargetThread::SignalAndWaitUntilSignaled))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
|
|
if (wait_until_unloaded) {
|
|
// The stack should look like this, resulting one frame after
|
|
// SignalAndWaitUntilSignaled. The frame in the now-unloaded library is
|
|
// not recorded since we can't get module information.
|
|
//
|
|
// ... WaitableEvent and system frames ...
|
|
// TargetThread::SignalAndWaitUntilSignaled
|
|
// TargetThread::OtherLibraryCallback
|
|
EXPECT_EQ(2, frames.end() - end_frame)
|
|
<< "Stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
} else {
|
|
// We didn't wait for the asynchronous unloading to complete, so the results
|
|
// are non-deterministic: if the library finished unloading we should have
|
|
// the same stack as |wait_until_unloaded|, if not we should have the full
|
|
// stack. The important thing is that we should not crash.
|
|
|
|
if (frames.end() - end_frame == 2) {
|
|
// This is the same case as |wait_until_unloaded|.
|
|
return;
|
|
}
|
|
|
|
// Check that the stack contains a frame for
|
|
// TargetThread::CallThroughOtherLibrary().
|
|
InternalFrames::const_iterator other_library_frame =
|
|
FindFirstFrameWithinFunction(frames,
|
|
&TargetThread::CallThroughOtherLibrary);
|
|
ASSERT_TRUE(other_library_frame != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
|
|
&TargetThread::CallThroughOtherLibrary))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
|
|
// The stack should look like this, resulting in three frames between
|
|
// SignalAndWaitUntilSignaled and CallThroughOtherLibrary:
|
|
//
|
|
// ... WaitableEvent and system frames ...
|
|
// TargetThread::SignalAndWaitUntilSignaled
|
|
// TargetThread::OtherLibraryCallback
|
|
// InvokeCallbackFunction (in other library)
|
|
// TargetThread::CallThroughOtherLibrary
|
|
EXPECT_EQ(3, other_library_frame - end_frame)
|
|
<< "Stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
}
|
|
}
|
|
|
|
// Provide a suitable (and clean) environment for the tests below. All tests
|
|
// must use this class to ensure that proper clean-up is done and thus be
|
|
// usable in a later test.
|
|
class StackSamplingProfilerTest : public testing::Test {
|
|
public:
|
|
void SetUp() override {
|
|
// The idle-shutdown time is too long for convenient (and accurate) testing.
|
|
// That behavior is checked instead by artificially triggering it through
|
|
// the TestAPI.
|
|
StackSamplingProfiler::TestAPI::DisableIdleShutdown();
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Be a good citizen and clean up after ourselves. This also re-enables the
|
|
// idle-shutdown behavior.
|
|
StackSamplingProfiler::TestAPI::Reset();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Checks that the basic expected information is present in sampled internal
|
|
// frames.
|
|
//
|
|
// macOS ASAN is not yet supported - crbug.com/718628.
|
|
#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
|
|
#define MAYBE_Basic Basic
|
|
#else
|
|
#define MAYBE_Basic DISABLED_Basic
|
|
#endif
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_Basic) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
|
|
|
|
// Check that the size of the frame sets are correct.
|
|
ASSERT_EQ(1u, frame_sets.size());
|
|
const InternalFrames& frames = frame_sets[0];
|
|
|
|
// Check that all the modules are valid.
|
|
for (const auto& frame : frames)
|
|
EXPECT_TRUE(frame.internal_module.is_valid);
|
|
|
|
// Check that the stack contains a frame for
|
|
// TargetThread::SignalAndWaitUntilSignaled().
|
|
InternalFrames::const_iterator loc = FindFirstFrameWithinFunction(
|
|
frames, &TargetThread::SignalAndWaitUntilSignaled);
|
|
ASSERT_TRUE(loc != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
|
|
&TargetThread::SignalAndWaitUntilSignaled))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
}
|
|
|
|
// Checks that the profiler handles stacks containing dynamically-allocated
|
|
// stack memory.
|
|
// macOS ASAN is not yet supported - crbug.com/718628.
|
|
#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
|
|
#define MAYBE_Alloca Alloca
|
|
#else
|
|
#define MAYBE_Alloca DISABLED_Alloca
|
|
#endif
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_Alloca) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
Profile profile;
|
|
WithTargetThread(
|
|
[¶ms, &profile](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>(BindLambdaForTesting(
|
|
[&profile, &sampling_thread_completed](Profile result_profile) {
|
|
profile = std::move(result_profile);
|
|
sampling_thread_completed.Signal();
|
|
})));
|
|
profiler.Start();
|
|
sampling_thread_completed.Wait();
|
|
},
|
|
StackConfiguration(StackConfiguration::WITH_ALLOCA));
|
|
|
|
// Look up the frames.
|
|
ASSERT_EQ(1u, profile.frame_sets.size());
|
|
const InternalFrames& frames = profile.frame_sets[0];
|
|
|
|
// Check that the stack contains a frame for
|
|
// TargetThread::SignalAndWaitUntilSignaled().
|
|
InternalFrames::const_iterator end_frame = FindFirstFrameWithinFunction(
|
|
frames, &TargetThread::SignalAndWaitUntilSignaled);
|
|
ASSERT_TRUE(end_frame != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
|
|
&TargetThread::SignalAndWaitUntilSignaled))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
|
|
// Check that the stack contains a frame for TargetThread::CallWithAlloca().
|
|
InternalFrames::const_iterator alloca_frame =
|
|
FindFirstFrameWithinFunction(frames, &TargetThread::CallWithAlloca);
|
|
ASSERT_TRUE(alloca_frame != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(
|
|
reinterpret_cast<const void*>(&TargetThread::CallWithAlloca))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
|
|
// These frames should be adjacent on the stack.
|
|
EXPECT_EQ(1, alloca_frame - end_frame)
|
|
<< "Stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
}
|
|
|
|
// Checks that a profiler can stop/destruct without ever having started.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, StopWithoutStarting) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
Profile profile;
|
|
WaitableEvent sampling_completed(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED);
|
|
|
|
StackSamplingProfiler profiler(
|
|
target_thread_id, params,
|
|
std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
|
|
[&profile, &sampling_completed](Profile result_profile) {
|
|
profile = std::move(result_profile);
|
|
sampling_completed.Signal();
|
|
})));
|
|
|
|
profiler.Stop(); // Constructed but never started.
|
|
EXPECT_FALSE(sampling_completed.IsSignaled());
|
|
});
|
|
}
|
|
|
|
// Checks that its okay to stop a profiler before it finishes even when the
|
|
// sampling thread continues to run.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, StopSafely) {
|
|
// Test delegate that counts samples.
|
|
class SampleRecordedCounter : public NativeStackSamplerTestDelegate {
|
|
public:
|
|
SampleRecordedCounter() = default;
|
|
|
|
void OnPreStackWalk() override {
|
|
AutoLock lock(lock_);
|
|
++count_;
|
|
}
|
|
|
|
size_t Get() {
|
|
AutoLock lock(lock_);
|
|
return count_;
|
|
}
|
|
|
|
private:
|
|
Lock lock_;
|
|
size_t count_ = 0;
|
|
};
|
|
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
SamplingParams params[2];
|
|
|
|
// Providing an initial delay makes it more likely that both will be
|
|
// scheduled before either starts to run. Once started, samples will
|
|
// run ordered by their scheduled, interleaved times regardless of
|
|
// whatever interval the thread wakes up.
|
|
params[0].initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params[0].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[0].samples_per_profile = 100000;
|
|
|
|
params[1].initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[1].samples_per_profile = 100000;
|
|
|
|
SampleRecordedCounter samples_recorded[size(params)];
|
|
|
|
TestProfilerInfo profiler_info0(target_thread_id, params[0],
|
|
&samples_recorded[0]);
|
|
TestProfilerInfo profiler_info1(target_thread_id, params[1],
|
|
&samples_recorded[1]);
|
|
|
|
profiler_info0.profiler.Start();
|
|
profiler_info1.profiler.Start();
|
|
|
|
// Wait for both to start accumulating samples. Using a WaitableEvent is
|
|
// possible but gets complicated later on because there's no way of knowing
|
|
// if 0 or 1 additional sample will be taken after Stop() and thus no way
|
|
// of knowing how many Wait() calls to make on it.
|
|
while (samples_recorded[0].Get() == 0 || samples_recorded[1].Get() == 0)
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
|
|
|
|
// Ensure that the first sampler can be safely stopped while the second
|
|
// continues to run. The stopped first profiler will still have a
|
|
// RecordSampleTask pending that will do nothing when executed because the
|
|
// collection will have been removed by Stop().
|
|
profiler_info0.profiler.Stop();
|
|
profiler_info0.completed.Wait();
|
|
size_t count0 = samples_recorded[0].Get();
|
|
size_t count1 = samples_recorded[1].Get();
|
|
|
|
// Waiting for the second sampler to collect a couple samples ensures that
|
|
// the pending RecordSampleTask for the first has executed because tasks are
|
|
// always ordered by their next scheduled time.
|
|
while (samples_recorded[1].Get() < count1 + 2)
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
|
|
|
|
// Ensure that the first profiler didn't do anything since it was stopped.
|
|
EXPECT_EQ(count0, samples_recorded[0].Get());
|
|
});
|
|
}
|
|
|
|
// Checks that no internal frames are captured if the profiling is stopped
|
|
// during the initial delay.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, StopDuringInitialDelay) {
|
|
SamplingParams params;
|
|
params.initial_delay = TimeDelta::FromSeconds(60);
|
|
|
|
InternalFrameSets frame_sets =
|
|
CaptureFrameSets(params, TimeDelta::FromMilliseconds(0));
|
|
|
|
EXPECT_TRUE(frame_sets.empty());
|
|
}
|
|
|
|
// Checks that tasks can be stopped before completion and incomplete internal
|
|
// frames are captured.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, StopDuringInterSampleInterval) {
|
|
// Test delegate that counts samples.
|
|
class SampleRecordedEvent : public NativeStackSamplerTestDelegate {
|
|
public:
|
|
SampleRecordedEvent()
|
|
: sample_recorded_(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED) {}
|
|
|
|
void OnPreStackWalk() override { sample_recorded_.Signal(); }
|
|
|
|
void WaitForSample() { sample_recorded_.Wait(); }
|
|
|
|
private:
|
|
WaitableEvent sample_recorded_;
|
|
};
|
|
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
SamplingParams params;
|
|
|
|
params.sampling_interval = AVeryLongTimeDelta();
|
|
params.samples_per_profile = 2;
|
|
|
|
SampleRecordedEvent samples_recorded;
|
|
TestProfilerInfo profiler_info(target_thread_id, params, &samples_recorded);
|
|
|
|
profiler_info.profiler.Start();
|
|
|
|
// Wait for profiler to start accumulating samples.
|
|
samples_recorded.WaitForSample();
|
|
|
|
// Ensure that it can stop safely.
|
|
profiler_info.profiler.Stop();
|
|
profiler_info.completed.Wait();
|
|
|
|
EXPECT_EQ(1u, profiler_info.profile.frame_sets.size());
|
|
});
|
|
}
|
|
|
|
// Checks that we can destroy the profiler while profiling.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, DestroyProfilerWhileProfiling) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(10);
|
|
|
|
Profile profile;
|
|
WithTargetThread([¶ms, &profile](PlatformThreadId target_thread_id) {
|
|
std::unique_ptr<StackSamplingProfiler> profiler;
|
|
auto profile_builder = std::make_unique<TestProfileBuilder>(
|
|
BindLambdaForTesting([&profile](Profile result_profile) {
|
|
profile = std::move(result_profile);
|
|
}));
|
|
profiler.reset(new StackSamplingProfiler(target_thread_id, params,
|
|
std::move(profile_builder)));
|
|
profiler->Start();
|
|
profiler.reset();
|
|
|
|
// Wait longer than a sample interval to catch any use-after-free actions by
|
|
// the profiler thread.
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(50));
|
|
});
|
|
}
|
|
|
|
// Checks that the different profilers may be run.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, CanRunMultipleProfilers) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
|
|
ASSERT_EQ(1u, frame_sets.size());
|
|
|
|
frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
|
|
ASSERT_EQ(1u, frame_sets.size());
|
|
}
|
|
|
|
// Checks that a sampler can be started while another is running.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MultipleStart) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
std::vector<SamplingParams> params(2);
|
|
|
|
params[0].initial_delay = AVeryLongTimeDelta();
|
|
params[0].samples_per_profile = 1;
|
|
|
|
params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[1].samples_per_profile = 1;
|
|
|
|
std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
|
|
CreateProfilers(target_thread_id, params);
|
|
|
|
profiler_infos[0]->profiler.Start();
|
|
profiler_infos[1]->profiler.Start();
|
|
profiler_infos[1]->completed.Wait();
|
|
EXPECT_EQ(1u, profiler_infos[1]->profile.frame_sets.size());
|
|
});
|
|
}
|
|
|
|
// Checks that the profile duration and the sampling interval are calculated
|
|
// correctly. Also checks that RecordAnnotations() is invoked each time a sample
|
|
// is recorded.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, ProfileGeneralInfo) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params.samples_per_profile = 3;
|
|
|
|
TestProfilerInfo profiler_info(target_thread_id, params);
|
|
|
|
profiler_info.profiler.Start();
|
|
profiler_info.completed.Wait();
|
|
EXPECT_EQ(3u, profiler_info.profile.frame_sets.size());
|
|
|
|
// The profile duration should be greater than the total sampling intervals.
|
|
EXPECT_GT(profiler_info.profile.profile_duration,
|
|
profiler_info.profile.sampling_period * 3);
|
|
|
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1),
|
|
profiler_info.profile.sampling_period);
|
|
|
|
// The number of invocations of RecordAnnotations() should be equal to the
|
|
// number of samples recorded.
|
|
EXPECT_EQ(3, profiler_info.profile.annotation_count);
|
|
});
|
|
}
|
|
|
|
// Checks that the sampling thread can shut down.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, SamplerIdleShutdown) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
|
|
ASSERT_EQ(1u, frame_sets.size());
|
|
|
|
// Capture thread should still be running at this point.
|
|
ASSERT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
|
|
// Initiate an "idle" shutdown and ensure it happens. Idle-shutdown was
|
|
// disabled by the test fixture so the test will fail due to a timeout if
|
|
// it does not exit.
|
|
StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(false);
|
|
|
|
// While the shutdown has been initiated, the actual exit of the thread still
|
|
// happens asynchronously. Watch until the thread actually exits. This test
|
|
// will time-out in the case of failure.
|
|
while (StackSamplingProfiler::TestAPI::IsSamplingThreadRunning())
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
|
|
}
|
|
|
|
// Checks that additional requests will restart a stopped profiler.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest,
|
|
WillRestartSamplerAfterIdleShutdown) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
|
|
ASSERT_EQ(1u, frame_sets.size());
|
|
|
|
// Capture thread should still be running at this point.
|
|
ASSERT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
|
|
// Post a ShutdownTask on the sampling thread which, when executed, will
|
|
// mark the thread as EXITING and begin shut down of the thread.
|
|
StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(false);
|
|
|
|
// Ensure another capture will start the sampling thread and run.
|
|
frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
|
|
ASSERT_EQ(1u, frame_sets.size());
|
|
EXPECT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
}
|
|
|
|
// Checks that it's safe to stop a task after it's completed and the sampling
|
|
// thread has shut-down for being idle.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, StopAfterIdleShutdown) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
SamplingParams params;
|
|
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params.samples_per_profile = 1;
|
|
|
|
TestProfilerInfo profiler_info(target_thread_id, params);
|
|
|
|
profiler_info.profiler.Start();
|
|
profiler_info.completed.Wait();
|
|
|
|
// Capture thread should still be running at this point.
|
|
ASSERT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
|
|
// Perform an idle shutdown.
|
|
StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(false);
|
|
|
|
// Stop should be safe though its impossible to know at this moment if the
|
|
// sampling thread has completely exited or will just "stop soon".
|
|
profiler_info.profiler.Stop();
|
|
});
|
|
}
|
|
|
|
// Checks that profilers can run both before and after the sampling thread has
|
|
// started.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest,
|
|
ProfileBeforeAndAfterSamplingThreadRunning) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
std::vector<SamplingParams> params(2);
|
|
|
|
params[0].initial_delay = AVeryLongTimeDelta();
|
|
params[0].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[0].samples_per_profile = 1;
|
|
|
|
params[1].initial_delay = TimeDelta::FromMilliseconds(0);
|
|
params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[1].samples_per_profile = 1;
|
|
|
|
std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
|
|
CreateProfilers(target_thread_id, params);
|
|
|
|
// First profiler is started when there has never been a sampling thread.
|
|
EXPECT_FALSE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
profiler_infos[0]->profiler.Start();
|
|
// Second profiler is started when sampling thread is already running.
|
|
EXPECT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
profiler_infos[1]->profiler.Start();
|
|
|
|
// Only the second profiler should finish before test times out.
|
|
size_t completed_profiler = WaitForSamplingComplete(profiler_infos);
|
|
EXPECT_EQ(1U, completed_profiler);
|
|
});
|
|
}
|
|
|
|
// Checks that an idle-shutdown task will abort if a new profiler starts
|
|
// between when it was posted and when it runs.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, IdleShutdownAbort) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
SamplingParams params;
|
|
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params.samples_per_profile = 1;
|
|
|
|
TestProfilerInfo profiler_info(target_thread_id, params);
|
|
|
|
profiler_info.profiler.Start();
|
|
profiler_info.completed.Wait();
|
|
EXPECT_EQ(1u, profiler_info.profile.frame_sets.size());
|
|
|
|
// Perform an idle shutdown but simulate that a new capture is started
|
|
// before it can actually run.
|
|
StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(true);
|
|
|
|
// Though the shutdown-task has been executed, any actual exit of the
|
|
// thread is asynchronous so there is no way to detect that *didn't* exit
|
|
// except to wait a reasonable amount of time and then check. Since the
|
|
// thread was just running ("perform" blocked until it was), it should
|
|
// finish almost immediately and without any waiting for tasks or events.
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(200));
|
|
EXPECT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
|
|
|
|
// Ensure that it's still possible to run another sampler.
|
|
TestProfilerInfo another_info(target_thread_id, params);
|
|
another_info.profiler.Start();
|
|
another_info.completed.Wait();
|
|
EXPECT_EQ(1u, another_info.profile.frame_sets.size());
|
|
});
|
|
}
|
|
|
|
// Checks that synchronized multiple sampling requests execute in parallel.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, ConcurrentProfiling_InSync) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
std::vector<SamplingParams> params(2);
|
|
|
|
// Providing an initial delay makes it more likely that both will be
|
|
// scheduled before either starts to run. Once started, samples will
|
|
// run ordered by their scheduled, interleaved times regardless of
|
|
// whatever interval the thread wakes up. Thus, total execution time
|
|
// will be 10ms (delay) + 10x1ms (sampling) + 1/2 timer minimum interval.
|
|
params[0].initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params[0].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[0].samples_per_profile = 9;
|
|
|
|
params[1].initial_delay = TimeDelta::FromMilliseconds(11);
|
|
params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params[1].samples_per_profile = 8;
|
|
|
|
std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
|
|
CreateProfilers(target_thread_id, params);
|
|
|
|
profiler_infos[0]->profiler.Start();
|
|
profiler_infos[1]->profiler.Start();
|
|
|
|
// Wait for one profiler to finish.
|
|
size_t completed_profiler = WaitForSamplingComplete(profiler_infos);
|
|
|
|
size_t other_profiler = 1 - completed_profiler;
|
|
// Wait for the other profiler to finish.
|
|
profiler_infos[other_profiler]->completed.Wait();
|
|
|
|
// Ensure each got the correct number of frame sets.
|
|
EXPECT_EQ(9u, profiler_infos[0]->profile.frame_sets.size());
|
|
EXPECT_EQ(8u, profiler_infos[1]->profile.frame_sets.size());
|
|
});
|
|
}
|
|
|
|
// Checks that several mixed sampling requests execute in parallel.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, ConcurrentProfiling_Mixed) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
std::vector<SamplingParams> params(3);
|
|
|
|
params[0].initial_delay = TimeDelta::FromMilliseconds(8);
|
|
params[0].sampling_interval = TimeDelta::FromMilliseconds(4);
|
|
params[0].samples_per_profile = 10;
|
|
|
|
params[1].initial_delay = TimeDelta::FromMilliseconds(9);
|
|
params[1].sampling_interval = TimeDelta::FromMilliseconds(3);
|
|
params[1].samples_per_profile = 10;
|
|
|
|
params[2].initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params[2].sampling_interval = TimeDelta::FromMilliseconds(2);
|
|
params[2].samples_per_profile = 10;
|
|
|
|
std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
|
|
CreateProfilers(target_thread_id, params);
|
|
|
|
for (size_t i = 0; i < profiler_infos.size(); ++i)
|
|
profiler_infos[i]->profiler.Start();
|
|
|
|
// Wait for one profiler to finish.
|
|
size_t completed_profiler = WaitForSamplingComplete(profiler_infos);
|
|
EXPECT_EQ(10u,
|
|
profiler_infos[completed_profiler]->profile.frame_sets.size());
|
|
// Stop and destroy all profilers, always in the same order. Don't crash.
|
|
for (size_t i = 0; i < profiler_infos.size(); ++i)
|
|
profiler_infos[i]->profiler.Stop();
|
|
for (size_t i = 0; i < profiler_infos.size(); ++i)
|
|
profiler_infos[i].reset();
|
|
});
|
|
}
|
|
|
|
// Checks that a stack that runs through another library produces a stack with
|
|
// the expected functions.
|
|
// macOS ASAN is not yet supported - crbug.com/718628.
|
|
#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
|
|
#define MAYBE_OtherLibrary OtherLibrary
|
|
#else
|
|
#define MAYBE_OtherLibrary DISABLED_OtherLibrary
|
|
#endif
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_OtherLibrary) {
|
|
SamplingParams params;
|
|
params.sampling_interval = TimeDelta::FromMilliseconds(0);
|
|
params.samples_per_profile = 1;
|
|
|
|
Profile profile;
|
|
{
|
|
ScopedNativeLibrary other_library(LoadOtherLibrary());
|
|
WithTargetThread(
|
|
[¶ms, &profile](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>(
|
|
BindLambdaForTesting([&profile, &sampling_thread_completed](
|
|
Profile result_profile) {
|
|
profile = std::move(result_profile);
|
|
sampling_thread_completed.Signal();
|
|
})));
|
|
profiler.Start();
|
|
sampling_thread_completed.Wait();
|
|
},
|
|
StackConfiguration(StackConfiguration::WITH_OTHER_LIBRARY,
|
|
other_library.get()));
|
|
}
|
|
|
|
// Look up the frames.
|
|
ASSERT_EQ(1u, profile.frame_sets.size());
|
|
const InternalFrames& frames = profile.frame_sets[0];
|
|
|
|
// Check that the stack contains a frame for
|
|
// TargetThread::CallThroughOtherLibrary().
|
|
InternalFrames::const_iterator other_library_frame =
|
|
FindFirstFrameWithinFunction(frames,
|
|
&TargetThread::CallThroughOtherLibrary);
|
|
ASSERT_TRUE(other_library_frame != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
|
|
&TargetThread::CallThroughOtherLibrary))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
|
|
// Check that the stack contains a frame for
|
|
// TargetThread::SignalAndWaitUntilSignaled().
|
|
InternalFrames::const_iterator end_frame = FindFirstFrameWithinFunction(
|
|
frames, &TargetThread::SignalAndWaitUntilSignaled);
|
|
ASSERT_TRUE(end_frame != frames.end())
|
|
<< "Function at "
|
|
<< MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
|
|
&TargetThread::SignalAndWaitUntilSignaled))
|
|
<< " was not found in stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
|
|
// The stack should look like this, resulting in three frames between
|
|
// SignalAndWaitUntilSignaled and CallThroughOtherLibrary:
|
|
//
|
|
// ... WaitableEvent and system frames ...
|
|
// TargetThread::SignalAndWaitUntilSignaled
|
|
// TargetThread::OtherLibraryCallback
|
|
// InvokeCallbackFunction (in other library)
|
|
// TargetThread::CallThroughOtherLibrary
|
|
EXPECT_EQ(3, other_library_frame - end_frame)
|
|
<< "Stack:\n"
|
|
<< FormatSampleForDiagnosticOutput(frames);
|
|
}
|
|
|
|
// Checks that a stack that runs through a library that is unloading produces a
|
|
// stack, and doesn't crash.
|
|
// Unloading is synchronous on the Mac, so this test is inapplicable.
|
|
#if !defined(OS_MACOSX)
|
|
#define MAYBE_UnloadingLibrary UnloadingLibrary
|
|
#else
|
|
#define MAYBE_UnloadingLibrary DISABLED_UnloadingLibrary
|
|
#endif
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_UnloadingLibrary) {
|
|
TestLibraryUnload(false);
|
|
}
|
|
|
|
// Checks that a stack that runs through a library that has been unloaded
|
|
// produces a stack, and doesn't crash.
|
|
// macOS ASAN is not yet supported - crbug.com/718628.
|
|
#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
|
|
#define MAYBE_UnloadedLibrary UnloadedLibrary
|
|
#else
|
|
#define MAYBE_UnloadedLibrary DISABLED_UnloadedLibrary
|
|
#endif
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_UnloadedLibrary) {
|
|
TestLibraryUnload(true);
|
|
}
|
|
|
|
// Checks that different threads can be sampled in parallel.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MultipleSampledThreads) {
|
|
// Create target threads. The extra parethesis around the StackConfiguration
|
|
// call are to avoid the most-vexing-parse problem.
|
|
TargetThread target_thread1((StackConfiguration(StackConfiguration::NORMAL)));
|
|
TargetThread target_thread2((StackConfiguration(StackConfiguration::NORMAL)));
|
|
PlatformThreadHandle target_thread_handle1, target_thread_handle2;
|
|
EXPECT_TRUE(
|
|
PlatformThread::Create(0, &target_thread1, &target_thread_handle1));
|
|
EXPECT_TRUE(
|
|
PlatformThread::Create(0, &target_thread2, &target_thread_handle2));
|
|
target_thread1.WaitForThreadStart();
|
|
target_thread2.WaitForThreadStart();
|
|
|
|
// Providing an initial delay makes it more likely that both will be
|
|
// scheduled before either starts to run. Once started, samples will
|
|
// run ordered by their scheduled, interleaved times regardless of
|
|
// whatever interval the thread wakes up.
|
|
SamplingParams params1, params2;
|
|
params1.initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params1.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params1.samples_per_profile = 9;
|
|
params2.initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params2.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params2.samples_per_profile = 8;
|
|
|
|
Profile profile1, profile2;
|
|
|
|
WaitableEvent sampling_thread_completed1(
|
|
WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED);
|
|
StackSamplingProfiler profiler1(
|
|
target_thread1.id(), params1,
|
|
std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
|
|
[&profile1, &sampling_thread_completed1](Profile result_profile) {
|
|
profile1 = std::move(result_profile);
|
|
sampling_thread_completed1.Signal();
|
|
})));
|
|
|
|
WaitableEvent sampling_thread_completed2(
|
|
WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED);
|
|
StackSamplingProfiler profiler2(
|
|
target_thread2.id(), params2,
|
|
std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
|
|
[&profile2, &sampling_thread_completed2](Profile result_profile) {
|
|
profile2 = std::move(result_profile);
|
|
sampling_thread_completed2.Signal();
|
|
})));
|
|
|
|
// Finally the real work.
|
|
profiler1.Start();
|
|
profiler2.Start();
|
|
sampling_thread_completed1.Wait();
|
|
sampling_thread_completed2.Wait();
|
|
EXPECT_EQ(9u, profile1.frame_sets.size());
|
|
EXPECT_EQ(8u, profile2.frame_sets.size());
|
|
|
|
target_thread1.SignalThreadToFinish();
|
|
target_thread2.SignalThreadToFinish();
|
|
PlatformThread::Join(target_thread_handle1);
|
|
PlatformThread::Join(target_thread_handle2);
|
|
}
|
|
|
|
// A simple thread that runs a profiler on another thread.
|
|
class ProfilerThread : public SimpleThread {
|
|
public:
|
|
ProfilerThread(const std::string& name,
|
|
PlatformThreadId thread_id,
|
|
const SamplingParams& params)
|
|
: SimpleThread(name, Options()),
|
|
run_(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED),
|
|
completed_(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED),
|
|
profiler_(thread_id,
|
|
params,
|
|
std::make_unique<TestProfileBuilder>(
|
|
BindLambdaForTesting([this](Profile result_profile) {
|
|
profile_ = std::move(result_profile);
|
|
completed_.Signal();
|
|
}))) {}
|
|
|
|
void Run() override {
|
|
run_.Wait();
|
|
profiler_.Start();
|
|
}
|
|
|
|
void Go() { run_.Signal(); }
|
|
|
|
void Wait() { completed_.Wait(); }
|
|
|
|
Profile& profile() { return profile_; }
|
|
|
|
private:
|
|
WaitableEvent run_;
|
|
|
|
Profile profile_;
|
|
WaitableEvent completed_;
|
|
StackSamplingProfiler profiler_;
|
|
};
|
|
|
|
// Checks that different threads can run samplers in parallel.
|
|
PROFILER_TEST_F(StackSamplingProfilerTest, MultipleProfilerThreads) {
|
|
WithTargetThread([](PlatformThreadId target_thread_id) {
|
|
// Providing an initial delay makes it more likely that both will be
|
|
// scheduled before either starts to run. Once started, samples will
|
|
// run ordered by their scheduled, interleaved times regardless of
|
|
// whatever interval the thread wakes up.
|
|
SamplingParams params1, params2;
|
|
params1.initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params1.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params1.samples_per_profile = 9;
|
|
params2.initial_delay = TimeDelta::FromMilliseconds(10);
|
|
params2.sampling_interval = TimeDelta::FromMilliseconds(1);
|
|
params2.samples_per_profile = 8;
|
|
|
|
// Start the profiler threads and give them a moment to get going.
|
|
ProfilerThread profiler_thread1("profiler1", target_thread_id, params1);
|
|
ProfilerThread profiler_thread2("profiler2", target_thread_id, params2);
|
|
profiler_thread1.Start();
|
|
profiler_thread2.Start();
|
|
PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
|
|
|
|
// This will (approximately) synchronize the two threads.
|
|
profiler_thread1.Go();
|
|
profiler_thread2.Go();
|
|
|
|
// Wait for them both to finish and validate collection.
|
|
profiler_thread1.Wait();
|
|
profiler_thread2.Wait();
|
|
EXPECT_EQ(9u, profiler_thread1.profile().frame_sets.size());
|
|
EXPECT_EQ(8u, profiler_thread2.profile().frame_sets.size());
|
|
|
|
profiler_thread1.Join();
|
|
profiler_thread2.Join();
|
|
});
|
|
}
|
|
|
|
} // namespace base
|