Commit 69678443 authored by Joe Mason's avatar Joe Mason Committed by Commit Bot

Sample the loader lock when taking stack samples.

Bug: 1065879

Change-Id: I05ca50043d8efbcdc3ae6d4767fb289162d85198
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2127911
Commit-Queue: Joe Mason <joenotcharles@chromium.org>
Reviewed-by: default avatarRobert Liao <robliao@chromium.org>
Reviewed-by: default avataroysteine <oysteine@chromium.org>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#762064}
parent 6b2f4dfd
......@@ -4,6 +4,8 @@
#include "base/test/test_waitable_event.h"
#include <utility>
namespace base {
TestWaitableEvent::TestWaitableEvent(ResetPolicy reset_policy,
......@@ -16,4 +18,11 @@ TestWaitableEvent::TestWaitableEvent(ResetPolicy reset_policy,
declare_only_used_while_idle();
}
#if defined(OS_WIN)
TestWaitableEvent::TestWaitableEvent(win::ScopedHandle event_handle)
: WaitableEvent(std::move(event_handle)) {
declare_only_used_while_idle();
}
#endif
} // namespace base
......@@ -6,6 +6,11 @@
#define BASE_TEST_TEST_WAITABLE_EVENT_H_
#include "base/synchronization/waitable_event.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
#endif
namespace base {
......@@ -20,6 +25,10 @@ class TestWaitableEvent : public WaitableEvent {
public:
TestWaitableEvent(ResetPolicy reset_policy = ResetPolicy::MANUAL,
InitialState initial_state = InitialState::NOT_SIGNALED);
#if defined(OS_WIN)
explicit TestWaitableEvent(win::ScopedHandle event_handle);
#endif
};
static_assert(sizeof(TestWaitableEvent) == sizeof(WaitableEvent),
......
......@@ -68,6 +68,8 @@ typedef LONG_PTR SSIZE_T, *PSSIZE_T;
typedef DWORD ACCESS_MASK;
typedef ACCESS_MASK REGSAM;
typedef LONG NTSTATUS;
// As defined in guiddef.h.
#ifndef _REFGUID_DEFINED
#define _REFGUID_DEFINED
......
......@@ -120,6 +120,14 @@ source_set("tests") {
]
}
}
if (is_win) {
sources +=
[ "public/cpp/stack_sampling/loader_lock_sampler_win_unittest.cc" ]
deps += [ ":loader_lock_sampler_test_strings" ]
data_deps = [ ":loader_lock_sampler_test_dll" ]
}
}
if (is_android) {
......@@ -134,3 +142,22 @@ if (is_android) {
sources = [ "android/test/src/org/chromium/tracing/UnwindTestHelper.java" ]
}
}
if (is_win) {
source_set("loader_lock_sampler_test_strings") {
testonly = true
sources = [
"public/cpp/stack_sampling/loader_lock_sampler_test_strings.cc",
"public/cpp/stack_sampling/loader_lock_sampler_test_strings.h",
]
}
loadable_module("loader_lock_sampler_test_dll") {
testonly = true
sources = [ "public/cpp/stack_sampling/loader_lock_sampler_test_dll.cc" ]
deps = [ ":loader_lock_sampler_test_strings" ]
}
}
......@@ -107,6 +107,13 @@ target(tracing_lib_type, "cpp") {
"tracing_features.h",
]
if (is_win) {
sources += [
"stack_sampling/loader_lock_sampler_win.cc",
"stack_sampling/loader_lock_sampler_win.h",
]
}
public_deps += [
"//components/tracing:startup_tracing",
"//mojo/public/cpp/bindings",
......
// Copyright 2020 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 <windows.h>
#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_test_strings.h"
namespace {
// This class is used instead of base::WaitableEvent to avoid a dependency on
// //base.
class NamedEvent {
public:
// Constructs a NamedEvent from the existing event named |name|.
NamedEvent(const wchar_t* name)
: handle_(::OpenEvent(EVENT_ALL_ACCESS, /*bInheritHandle=*/FALSE, name)) {
}
NamedEvent(const NamedEvent&) = delete;
NamedEvent& operator=(const NamedEvent&) = delete;
~NamedEvent() {
if (IsValid())
::CloseHandle(handle_);
}
bool IsValid() const { return handle_ && handle_ != INVALID_HANDLE_VALUE; }
HANDLE handle() const { return handle_; }
private:
HANDLE handle_ = INVALID_HANDLE_VALUE;
};
} // namespace
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) {
if (reason != DLL_PROCESS_ATTACH)
return TRUE;
NamedEvent wait_for_lock_event(
tracing::loader_lock_sampler_test::kWaitForLockEventName);
NamedEvent drop_lock_event(
tracing::loader_lock_sampler_test::kDropLockEventName);
if (!wait_for_lock_event.IsValid() || !drop_lock_event.IsValid())
return FALSE;
// Inform the main thread that the lock is taken.
if (!SetEvent(wait_for_lock_event.handle()))
return FALSE;
// Wait until the main thread tells us to drop the lock and exit.
WaitForSingleObject(drop_lock_event.handle(), INFINITE);
return TRUE;
}
// Copyright 2020 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/loader_lock_sampler_test_strings.h"
namespace tracing {
namespace loader_lock_sampler_test {
const wchar_t kWaitForLockEventName[] =
L"LoaderLockSamplerTestWaitForLockEvent";
const wchar_t kDropLockEventName[] = L"LoaderLockSamplerTestDropLockEvent";
} // namespace loader_lock_sampler_test
} // namespace tracing
// Copyright 2020 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.
#ifndef SERVICES_TRACING_PUBLIC_CPP_STACK_SAMPLING_LOADER_LOCK_SAMPLER_TEST_STRINGS_H_
#define SERVICES_TRACING_PUBLIC_CPP_STACK_SAMPLING_LOADER_LOCK_SAMPLER_TEST_STRINGS_H_
namespace tracing {
namespace loader_lock_sampler_test {
// wchar_t is used instead of base::char16 because this is included by
// loader_lock_sampler_test_dll.cc, which doesn't depend on base.
extern const wchar_t kWaitForLockEventName[];
extern const wchar_t kDropLockEventName[];
} // namespace loader_lock_sampler_test
} // namespace tracing
#endif // SERVICES_TRACING_PUBLIC_CPP_STACK_SAMPLING_LOADER_LOCK_SAMPLER_TEST_STRINGS_H_
// Copyright 2020 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/loader_lock_sampler_win.h"
#include "base/native_library.h"
#include "base/win/windows_types.h"
namespace tracing {
namespace {
// Function signatures are derived from
// https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrapi/lockloaderlock.htm
// The signature there only works on 32-bit - use of ULONG_PTR for the cookie
// is from https://www.winehq.org/pipermail/wine-cvs/2014-June/102116.html
using LockLoaderLockFunc = NTSTATUS (*)(ULONG flags,
ULONG* state,
ULONG_PTR* cookie);
using UnlockLoaderLockFunc = NTSTATUS (*)(ULONG flags, ULONG_PTR cookie);
LockLoaderLockFunc g_lock_loader_lock = nullptr;
UnlockLoaderLockFunc g_unlock_loader_lock = nullptr;
} // namespace
bool InitializeLoaderLockSampling() {
const static bool initialized = []() {
// The handle to ntdll is intentionally leaked to ensure that the function
// pointers below remain valid for the lifetime of the process. (Note: ntdll
// is always loaded in the process in practice, so this will be the case
// regardless.)
base::NativeLibrary ntdll = base::LoadSystemLibrary(L"ntdll.dll");
DPCHECK(ntdll);
g_lock_loader_lock = reinterpret_cast<LockLoaderLockFunc>(
base::GetFunctionPointerFromNativeLibrary(ntdll, "LdrLockLoaderLock"));
g_unlock_loader_lock = reinterpret_cast<UnlockLoaderLockFunc>(
base::GetFunctionPointerFromNativeLibrary(ntdll,
"LdrUnlockLoaderLock"));
return true;
}();
return initialized;
}
bool IsLoaderLockHeld() {
if (!g_lock_loader_lock || !g_unlock_loader_lock) {
// Loader lock state unknown; default to false.
return false;
}
// All constants are derived from
// https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrapi/lockloaderlock.htm.
constexpr ULONG kDoNotWaitFlag = 0x2;
constexpr ULONG kEnteredLockState = 0x01;
ULONG state = 0;
ULONG_PTR cookie = 0;
NTSTATUS status = (*g_lock_loader_lock)(kDoNotWaitFlag, &state, &cookie);
if (status < 0) {
// Loader lock state unknown; default to false.
return false;
}
if (state == kEnteredLockState) {
// Keeping the loader lock would be very bad, so raise an exception if that
// happens.
constexpr ULONG kRaiseExceptionOnErrorFlag = 0x1;
(*g_unlock_loader_lock)(kRaiseExceptionOnErrorFlag, cookie);
// Since this thread was able to take the loader lock, no other thread held
// it during the sample.
return false;
}
// Since this thread was not able to take the loader lock, another thread
// held it during the sample.
return true;
}
} // namespace tracing
// Copyright 2020 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.
#ifndef SERVICES_TRACING_PUBLIC_CPP_STACK_SAMPLING_LOADER_LOCK_SAMPLER_WIN_H_
#define SERVICES_TRACING_PUBLIC_CPP_STACK_SAMPLING_LOADER_LOCK_SAMPLER_WIN_H_
#include "base/component_export.h"
namespace tracing {
// Ensures the mechanism that samples the loader lock is initialized. This can
// be called multiple times but only the first call in a process has any
// effect. This may take the loader lock itself so it must be called before
// sampling starts.
COMPONENT_EXPORT(TRACING_CPP) bool InitializeLoaderLockSampling();
// Checks if the loader lock is currently held by another thread. Returns false
// if the lock is already held by the calling thread since the lock can be
// taken recursively.
COMPONENT_EXPORT(TRACING_CPP) bool IsLoaderLockHeld();
} // namespace tracing
#endif // SERVICES_TRACING_PUBLIC_CPP_STACK_SAMPLING_LOADER_LOCK_SAMPLER_WIN_H_
// Copyright 2020 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/loader_lock_sampler_win.h"
#include <utility>
#include "base/files/file_path.h"
#include "base/scoped_native_library.h"
#include "base/strings/string16.h"
#include "base/task/thread_pool.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/test/test_waitable_event.h"
#include "base/win/scoped_handle.h"
#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_test_strings.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace tracing {
namespace {
class TestNamedWaitableEvent : public base::TestWaitableEvent {
public:
TestNamedWaitableEvent(const base::char16* name)
: TestWaitableEvent(
base::win::ScopedHandle(::CreateEvent(nullptr,
/*bManualReset=*/TRUE,
/*bInitialState=*/FALSE,
name))) {}
};
} // namespace
TEST(LoaderLockSamplerTest, LockNotHeld) {
ASSERT_TRUE(InitializeLoaderLockSampling());
EXPECT_FALSE(IsLoaderLockHeld());
}
TEST(LoaderLockSamplerTest, LockHeldByOtherThread) {
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::ThreadPoolCOMEnvironment::NONE};
ASSERT_TRUE(InitializeLoaderLockSampling());
// Creating TaskEnvironment initializes the thread pool, which takes the
// loader lock while creating background threads. Wait until it's released.
while (IsLoaderLockHeld()) {
base::TestWaitableEvent delay_event;
delay_event.TimedWait(TestTimeouts::tiny_timeout());
}
// Create events with fixed names that can be accessed from the helper DLL.
TestNamedWaitableEvent wait_for_loader_lock_event(
loader_lock_sampler_test::kWaitForLockEventName);
TestNamedWaitableEvent drop_loader_lock_event(
loader_lock_sampler_test::kDropLockEventName);
base::TestWaitableEvent dll_done_event;
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock()},
base::BindLambdaForTesting([&dll_done_event] {
// This function will block until DllMain returns, or the load fails.
// The helper DLL will signal |wait_for_loader_lock_event| as soon as it
// enters DllMain, while it holds the loader lock. Then it will block
// with the loader lock held until |drop_loader_lock_event| is
// signalled.
base::ScopedNativeLibrary dll(base::FilePath(
FILE_PATH_LITERAL("loader_lock_sampler_test_dll.dll")));
// If the DLL did not load, |wait_for_loader_lock_event| will not be
// signalled and the TimedWait below will return false (timeout).
ASSERT_TRUE(dll.is_valid())
<< "ScopedNativeLibrary error " << dll.GetError()->ToString();
dll_done_event.Signal();
}));
// Wait for the background thread to take the loader lock.
ASSERT_TRUE(
wait_for_loader_lock_event.TimedWait(TestTimeouts::action_timeout()));
EXPECT_TRUE(IsLoaderLockHeld());
// Tell the DLL to drop the loader lock and exit from DllMain.
drop_loader_lock_event.Signal();
// Make sure the DllMain has exited.
ASSERT_TRUE(dll_done_event.TimedWait(TestTimeouts::action_timeout()));
// It takes a moment for the lock to be released.
base::TestWaitableEvent delay_event;
delay_event.TimedWait(TestTimeouts::tiny_timeout());
EXPECT_FALSE(IsLoaderLockHeld());
}
} // namespace tracing
......@@ -42,6 +42,11 @@
#include "services/tracing/public/cpp/stack_sampling/stack_sampler_android.h"
#endif
#if defined(OS_WIN)
#include "base/threading/platform_thread.h"
#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_win.h"
#endif
using StreamingProfilePacketHandle =
protozero::MessageHandle<perfetto::protos::pbzero::StreamingProfilePacket>;
......@@ -186,8 +191,62 @@ GetSequenceLocalStorageProfilerSlot() {
return *storage;
}
#if defined(OS_WIN)
// The loader lock is process-wide so should only be sampled on a single
// thread.
base::PlatformThreadId g_loader_lock_sample_thread_id = base::kInvalidThreadId;
bool g_loader_lock_is_held = false;
TracingSamplerProfiler::LoaderLockSampler* g_test_loader_lock_sampler = nullptr;
void InitializeLoaderLockSamplingForThread(base::PlatformThreadId thread_id) {
InitializeLoaderLockSampling();
g_loader_lock_sample_thread_id = thread_id;
// InitializeLoaderLockSamplingForThread can be called multiple times during
// tests. Make sure each test starts from a known state.
g_loader_lock_is_held = false;
}
// Checks the state of the loader lock while samples are being taken for thread
// |thread_id|. This function should only be called from the profiler thread
// since it updates global data.
void SampleLoaderLockForThread(base::PlatformThreadId thread_id) {
if (thread_id != g_loader_lock_sample_thread_id)
return;
bool loader_lock_now_held =
g_test_loader_lock_sampler
? g_test_loader_lock_sampler->IsLoaderLockHeld()
: IsLoaderLockHeld();
// TODO(crbug.com/1065077): It would be cleaner to save the loader lock state
// alongside buffered_samples_ and then add it to the ProcessDescriptor
// packet in
// TracingSamplerProfiler::TracingProfileBuilder::WriteSampleToTrace. But
// ProcessDescriptor is currently not being collected correctly. See the full
// discussion in the linked crbug.
if (loader_lock_now_held && !g_loader_lock_is_held) {
TRACE_EVENT_ASYNC_BEGIN0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
TracingSamplerProfiler::kLoaderLockHeldEventName,
thread_id);
} else if (!loader_lock_now_held && g_loader_lock_is_held) {
TRACE_EVENT_ASYNC_END0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
TracingSamplerProfiler::kLoaderLockHeldEventName,
thread_id);
}
g_loader_lock_is_held = loader_lock_now_held;
}
#endif // defined(OS_WIN)
} // namespace
#if defined(OS_WIN)
const char TracingSamplerProfiler::kLoaderLockHeldEventName[] =
"LoaderLockHeld (sampled)";
#endif
TracingSamplerProfiler::TracingProfileBuilder::BufferedSample::BufferedSample(
base::TimeTicks ts,
std::vector<base::Frame>&& s)
......@@ -234,6 +293,10 @@ TracingSamplerProfiler::TracingProfileBuilder::GetModuleCache() {
void TracingSamplerProfiler::TracingProfileBuilder::OnSampleCompleted(
std::vector<base::Frame> frames,
base::TimeTicks sample_timestamp) {
#if defined(OS_WIN)
SampleLoaderLockForThread(sampled_thread_id_);
#endif
base::AutoLock l(trace_writer_lock_);
if (!trace_writer_) {
if (buffered_samples_.size() < kMaxBufferedSamples) {
......@@ -494,8 +557,11 @@ void TracingSamplerProfiler::MangleModuleIDIfNeeded(std::string* module_id) {
// static
std::unique_ptr<TracingSamplerProfiler>
TracingSamplerProfiler::CreateOnMainThread() {
return std::make_unique<TracingSamplerProfiler>(
base::GetSamplingProfilerCurrentThreadToken());
auto thread_token = base::GetSamplingProfilerCurrentThreadToken();
#if defined(OS_WIN)
InitializeLoaderLockSamplingForThread(thread_token.id);
#endif
return std::make_unique<TracingSamplerProfiler>(std::move(thread_token));
}
// static
......@@ -540,6 +606,14 @@ void TracingSamplerProfiler::StopTracingForTesting() {
TracingSamplerProfilerDataSource::Get()->StopTracing(base::DoNothing());
}
#if defined(OS_WIN)
// static
void TracingSamplerProfiler::SetLoaderLockSamplerForTesting(
LoaderLockSampler* sampler) {
g_test_loader_lock_sampler = sampler;
}
#endif
TracingSamplerProfiler::TracingSamplerProfiler(
base::SamplingProfilerThreadToken sampled_thread_token)
: sampled_thread_token_(sampled_thread_token) {
......
......@@ -105,6 +105,21 @@ class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
base::RepeatingClosure sample_callback_for_testing_;
};
#if defined(OS_WIN)
// This class can be implemented to check whether the loader lock is held
// whenever stack frames are sampled. Exposed for testing.
class LoaderLockSampler {
public:
virtual ~LoaderLockSampler() = default;
virtual bool IsLoaderLockHeld() const = 0;
};
// The name of a trace event that will be recorded when the loader lock is
// held.
static const char kLoaderLockHeldEventName[];
#endif
// Creates sampling profiler on main thread. The profiler *must* be
// destroyed prior to process shutdown.
static std::unique_ptr<TracingSamplerProfiler> CreateOnMainThread();
......@@ -124,6 +139,12 @@ class COMPONENT_EXPORT(TRACING_CPP) TracingSamplerProfiler {
static void StopTracingForTesting();
static void MangleModuleIDIfNeeded(std::string* module_id);
#if defined(OS_WIN)
// Registers a mock LoaderLockSampler to be called during tests. |sampler| is
// owned by the caller. It must be reset to |nullptr| at the end of the test.
static void SetLoaderLockSamplerForTesting(LoaderLockSampler* sampler);
#endif
// Returns whether of not the sampler profiling is able to unwind the stack
// on this platform.
constexpr static bool IsStackUnwindingSupported() {
......
......@@ -27,10 +27,16 @@
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pb.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
#if defined(OS_WIN)
#include "base/test/trace_event_analyzer.h"
#endif
namespace tracing {
namespace {
using base::trace_event::TraceLog;
using ::testing::Invoke;
using ::testing::Return;
class MockTraceWriter : public perfetto::TraceWriter {
public:
......@@ -137,6 +143,36 @@ class MockPerfettoProducer : public ProducerClient {
DISALLOW_COPY_AND_ASSIGN(MockPerfettoProducer);
};
#if defined(OS_WIN)
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 // defined(OS_WIN)
class TracingSampleProfilerTest : public testing::Test {
public:
TracingSampleProfilerTest() = default;
......@@ -152,11 +188,22 @@ class TracingSampleProfilerTest : public testing::Test {
producer_ =
std::make_unique<MockPerfettoProducer>(std::move(perfetto_wrapper));
#if defined(OS_WIN)
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 defined(OS_WIN)
TracingSamplerProfiler::SetLoaderLockSamplerForTesting(nullptr);
#endif
}
void BeginTrace() {
......@@ -202,7 +249,7 @@ class TracingSampleProfilerTest : public testing::Test {
const MockPerfettoProducer* producer() const { return producer_.get(); }
private:
protected:
base::test::TaskEnvironment task_environment_;
// We want our singleton torn down after each test.
......@@ -214,6 +261,11 @@ class TracingSampleProfilerTest : public testing::Test {
// Number of stack sampling events received.
size_t events_stack_received_count_ = 0;
#if defined(OS_WIN)
MockLoaderLockSampler mock_loader_lock_sampler_;
#endif
private:
DISALLOW_COPY_AND_ASSIGN(TracingSampleProfilerTest);
};
......@@ -342,6 +394,95 @@ TEST_F(TracingSampleProfilerTest, SamplingChildThread) {
base::RunLoop().RunUntilIdle();
}
#if defined(OS_WIN)
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 // defined(OS_WIN)
TEST(TracingProfileBuilderTest, ValidModule) {
TestModule module;
TracingSamplerProfiler::TracingProfileBuilder profile_builder(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment