// 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 #include #include #include #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/files/file_util.h" #include "base/location.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/metrics/metrics_hashes.h" #include "base/profiler/profiler_buildflags.h" #include "base/profiler/sample_metadata.h" #include "base/profiler/stack_sampler.h" #include "base/profiler/stack_sampling_profiler.h" #include "base/profiler/stack_sampling_profiler_test_util.h" #include "base/profiler/unwinder.h" #include "base/ranges/algorithm.h" #include "base/run_loop.h" #include "base/scoped_native_library.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "base/test/bind.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 BUILDFLAG(IS_WIN) #include #include #include #else #include #endif // STACK_SAMPLING_PROFILER_SUPPORTED is used to conditionally enable the tests // below for supported platforms (currently Win x64, Mac x64, iOS 64, some // Android, and ChromeOS x64). // ChromeOS: These don't run under MSan because parts of the stack aren't // initialized. #if (BUILDFLAG(IS_WIN) && defined(ARCH_CPU_X86_64)) || \ (BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)) || \ (BUILDFLAG(IS_IOS) && defined(ARCH_CPU_64_BITS)) || \ (BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)) || \ (BUILDFLAG(IS_CHROMEOS) && defined(ARCH_CPU_X86_64) && \ !defined(MEMORY_SANITIZER)) #define STACK_SAMPLING_PROFILER_SUPPORTED 1 #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; namespace { // State provided to the ProfileBuilder's ApplyMetadataRetrospectively function. struct RetrospectiveMetadata { TimeTicks period_start; TimeTicks period_end; MetadataRecorder::Item item; }; // Profile consists of a set of samples and other sampling information. struct Profile { // The collected samples. std::vector> samples; // The number of invocations of RecordMetadata(). int record_metadata_count; // The retrospective metadata requests. std::vector retrospective_metadata; // Duration of this profile. TimeDelta profile_duration; // Time between samples. TimeDelta 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 = OnceCallback; // TestProfileBuilder collects samples produced by the profiler. class TestProfileBuilder : public ProfileBuilder { public: TestProfileBuilder(ModuleCache* module_cache, ProfileCompletedCallback callback); TestProfileBuilder(const TestProfileBuilder&) = delete; TestProfileBuilder& operator=(const TestProfileBuilder&) = delete; ~TestProfileBuilder() override; // ProfileBuilder: ModuleCache* GetModuleCache() override; void RecordMetadata( const MetadataRecorder::MetadataProvider& metadata_provider) override; void ApplyMetadataRetrospectively( TimeTicks period_start, TimeTicks period_end, const MetadataRecorder::Item& item) override; void OnSampleCompleted(std::vector sample, TimeTicks sample_timestamp) override; void OnProfileCompleted(TimeDelta profile_duration, TimeDelta sampling_period) override; private: raw_ptr module_cache_; // The set of recorded samples. std::vector> samples_; // The number of invocations of RecordMetadata(). int record_metadata_count_ = 0; // The retrospective metadata requests. std::vector retrospective_metadata_; // Callback made when sampling a profile completes. ProfileCompletedCallback callback_; }; TestProfileBuilder::TestProfileBuilder(ModuleCache* module_cache, ProfileCompletedCallback callback) : module_cache_(module_cache), callback_(std::move(callback)) {} TestProfileBuilder::~TestProfileBuilder() = default; ModuleCache* TestProfileBuilder::GetModuleCache() { return module_cache_; } void TestProfileBuilder::RecordMetadata( const MetadataRecorder::MetadataProvider& metadata_provider) { ++record_metadata_count_; } void TestProfileBuilder::ApplyMetadataRetrospectively( TimeTicks period_start, TimeTicks period_end, const MetadataRecorder::Item& item) { retrospective_metadata_.push_back( RetrospectiveMetadata{period_start, period_end, item}); } void TestProfileBuilder::OnSampleCompleted(std::vector sample, TimeTicks sample_timestamp) { samples_.push_back(std::move(sample)); } void TestProfileBuilder::OnProfileCompleted(TimeDelta profile_duration, TimeDelta sampling_period) { std::move(callback_).Run(Profile{samples_, record_metadata_count_, retrospective_metadata_, profile_duration, sampling_period}); } // 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 BUILDFLAG(IS_WIN) // NativeLibrary is a typedef for HMODULE, which is actually the base address // of the module. uintptr_t module_base_address = reinterpret_cast(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(module_base_address), &module_handle) || ::GetLastError() != ERROR_MOD_NOT_FOUND) { PlatformThread::Sleep(Milliseconds(1)); } #elif BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) // Unloading a library on Mac and Android is synchronous. #else NOTIMPLEMENTED(); #endif } void WithTargetThread(ProfileCallback profile_callback) { UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); WithTargetThread(&scenario, std::move(profile_callback)); } struct TestProfilerInfo { TestProfilerInfo(SamplingProfilerThreadToken thread_token, const SamplingParams& params, ModuleCache* module_cache, StackSamplerTestDelegate* delegate = nullptr) : completed(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED), profiler(thread_token, params, std::make_unique( module_cache, BindLambdaForTesting([this](Profile result_profile) { profile = std::move(result_profile); completed.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache), RepeatingClosure(), delegate) {} TestProfilerInfo(const TestProfilerInfo&) = delete; TestProfilerInfo& operator=(const TestProfilerInfo&) = delete; // 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; }; // Captures samples as specified by |params| on the TargetThread, and returns // them. Waits up to |profiler_wait_time| for the profiler to complete. std::vector> CaptureSamples(const SamplingParams& params, TimeDelta profiler_wait_time, ModuleCache* module_cache) { std::vector> samples; WithTargetThread(BindLambdaForTesting( [&](SamplingProfilerThreadToken target_thread_token) { TestProfilerInfo info(target_thread_token, params, module_cache); info.profiler.Start(); info.completed.TimedWait(profiler_wait_time); info.profiler.Stop(); info.completed.Wait(); samples = std::move(info.profile.samples); })); return samples; } // Waits for one of multiple samplings to complete. size_t WaitForSamplingComplete( const std::vector>& infos) { // Map unique_ptrs to something that WaitMany can accept. std::vector sampling_completed_rawptrs(infos.size()); ranges::transform(infos, sampling_completed_rawptrs.begin(), [](const std::unique_ptr& info) { return &info.get()->completed; }); // Wait for one profiler to finish. return WaitableEvent::WaitMany(sampling_completed_rawptrs.data(), sampling_completed_rawptrs.size()); } // Returns a duration that is longer than the test timeout. We would use // TimeDelta::Max() but https://crbug.com/465948. TimeDelta AVeryLongTimeDelta() { return Days(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, ModuleCache* module_cache) { // Test delegate that supports intervening between the copying of the stack // and the walking of the stack. class StackCopiedSignaler : public StackSamplerTestDelegate { 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: const raw_ptr stack_copied_; const raw_ptr start_stack_walk_; const bool wait_to_walk_stack_; }; SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; NativeLibrary other_library = LoadOtherLibrary(); UnwindScenario scenario( BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); UnwindScenario::SampleEvents events; TargetThread target_thread( BindLambdaForTesting([&]() { scenario.Execute(&events); })); target_thread.Start(); events.ready_for_sample.Wait(); 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.thread_token(), params, std::make_unique( module_cache, BindLambdaForTesting( [&profile, &sampling_thread_completed](Profile result_profile) { profile = std::move(result_profile); sampling_thread_completed.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache), RepeatingClosure(), &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. events.sample_finished.Signal(); target_thread.Join(); // 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 sample. ASSERT_EQ(1u, profile.samples.size()); const std::vector& sample = profile.samples[0]; if (wait_until_unloaded) { // We expect the stack to look something like this, with the frame in the // now-unloaded library having a null module. // // ... WaitableEvent and system frames ... // WaitForSample() // TargetThread::OtherLibraryCallback // EXPECT_EQ(nullptr, sample.back().module) << "Stack:\n" << FormatSampleForDiagnosticOutput(sample); ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange()}); ExpectStackDoesNotContain(sample, {scenario.GetSetupFunctionAddressRange(), scenario.GetOuterFunctionAddressRange()}); } 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 (!sample.back().module) { // This is the same case as |wait_until_unloaded|. ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange()}); ExpectStackDoesNotContain(sample, {scenario.GetSetupFunctionAddressRange(), scenario.GetOuterFunctionAddressRange()}); return; } ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), scenario.GetSetupFunctionAddressRange(), scenario.GetOuterFunctionAddressRange()}); } } // 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 TestPeer. StackSamplingProfiler::TestPeer::DisableIdleShutdown(); } void TearDown() override { // Be a good citizen and clean up after ourselves. This also re-enables the // idle-shutdown behavior. StackSamplingProfiler::TestPeer::Reset(); } protected: ModuleCache* module_cache() { return &module_cache_; } private: ModuleCache module_cache_; }; } // namespace // Checks that the basic expected information is present in sampled frames. // // macOS ASAN is not yet supported - crbug.com/718628. // // TODO(https://crbug.com/1100175): Enable this test again for Android with // ASAN. This is now disabled because the android-asan bot fails. // // If we're running the ChromeOS unit tests on Linux, this test will never pass // because Ubuntu's libc isn't compiled with frame pointers. Skip if not a real // ChromeOS device. #if (defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_APPLE)) || \ (defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_ANDROID)) || \ (BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_CHROMEOS_DEVICE)) #define MAYBE_Basic DISABLED_Basic #else #define MAYBE_Basic Basic #endif PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_Basic) { UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); const std::vector& sample = SampleScenario(&scenario, module_cache()); // Check that all the modules are valid. for (const auto& frame : sample) EXPECT_NE(nullptr, frame.module); // The stack should contain a full unwind. ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), scenario.GetSetupFunctionAddressRange(), scenario.GetOuterFunctionAddressRange()}); } // A simple unwinder that always generates one frame then aborts the stack walk. class TestAuxUnwinder : public Unwinder { public: TestAuxUnwinder(const Frame& frame_to_report, base::RepeatingClosure add_initial_modules_callback) : frame_to_report_(frame_to_report), add_initial_modules_callback_(std::move(add_initial_modules_callback)) { } TestAuxUnwinder(const TestAuxUnwinder&) = delete; TestAuxUnwinder& operator=(const TestAuxUnwinder&) = delete; void InitializeModules() override { if (add_initial_modules_callback_) add_initial_modules_callback_.Run(); } bool CanUnwindFrom(const Frame& current_frame) const override { return true; } UnwindResult TryUnwind(RegisterContext* thread_context, uintptr_t stack_top, std::vector* stack) const override { stack->push_back(frame_to_report_); return UnwindResult::kAborted; } private: const Frame frame_to_report_; base::RepeatingClosure add_initial_modules_callback_; }; // Checks that the profiler handles stacks containing dynamically-allocated // stack memory. // macOS ASAN is not yet supported - crbug.com/718628. // Android is not supported since Chrome unwind tables don't support dynamic // frames. // If we're running the ChromeOS unit tests on Linux, this test will never pass // because Ubuntu's libc isn't compiled with frame pointers. Skip if not a real // ChromeOS device. #if (defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_APPLE)) || \ BUILDFLAG(IS_ANDROID) || \ (BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_CHROMEOS_DEVICE)) #define MAYBE_Alloca DISABLED_Alloca #else #define MAYBE_Alloca Alloca #endif PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_Alloca) { UnwindScenario scenario(BindRepeating(&CallWithAlloca)); const std::vector& sample = SampleScenario(&scenario, module_cache()); // The stack should contain a full unwind. ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), scenario.GetSetupFunctionAddressRange(), scenario.GetOuterFunctionAddressRange()}); } // 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. // iOS chrome doesn't support loading native libraries. // Android is not supported when EXCLUDE_UNWIND_TABLES |other_library| doesn't // have unwind tables. // TODO(https://crbug.com/1100175): Enable this test again for Android with // ASAN. This is now disabled because the android-asan bot fails. // If we're running the ChromeOS unit tests on Linux, this test will never pass // because Ubuntu's libc isn't compiled with frame pointers. Skip if not a real // ChromeOS device. #if (defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_APPLE)) || \ BUILDFLAG(IS_IOS) || \ (BUILDFLAG(IS_ANDROID) && BUILDFLAG(EXCLUDE_UNWIND_TABLES)) || \ (BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER)) || \ (BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_CHROMEOS_DEVICE)) #define MAYBE_OtherLibrary DISABLED_OtherLibrary #else #define MAYBE_OtherLibrary OtherLibrary #endif PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_OtherLibrary) { ScopedNativeLibrary other_library(LoadOtherLibrary()); UnwindScenario scenario( BindRepeating(&CallThroughOtherLibrary, Unretained(other_library.get()))); const std::vector& sample = SampleScenario(&scenario, module_cache()); // The stack should contain a full unwind. ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), scenario.GetSetupFunctionAddressRange(), scenario.GetOuterFunctionAddressRange()}); } // 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. // Android is not supported when EXCLUDE_UNWIND_TABLES |other_library| doesn't // have unwind tables. // TODO(https://crbug.com/1100175): Enable this test again for Android with // ASAN. This is now disabled because the android-asan bot fails. // If we're running the ChromeOS unit tests on Linux, this test will never pass // because Ubuntu's libc isn't compiled with frame pointers. Skip if not a real // ChromeOS device. #if BUILDFLAG(IS_APPLE) || \ (BUILDFLAG(IS_ANDROID) && BUILDFLAG(EXCLUDE_UNWIND_TABLES)) || \ (BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER)) || \ (BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_CHROMEOS_DEVICE)) #define MAYBE_UnloadingLibrary DISABLED_UnloadingLibrary #else #define MAYBE_UnloadingLibrary UnloadingLibrary #endif PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_UnloadingLibrary) { TestLibraryUnload(false, module_cache()); } // 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. // Android is not supported since modules are found before unwinding. // If we're running the ChromeOS unit tests on Linux, this test will never pass // because Ubuntu's libc isn't compiled with frame pointers. Skip if not a real // ChromeOS device. #if (defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_APPLE)) || \ BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) || \ (BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_CHROMEOS_DEVICE)) #define MAYBE_UnloadedLibrary DISABLED_UnloadedLibrary #else #define MAYBE_UnloadedLibrary UnloadedLibrary #endif PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_UnloadedLibrary) { TestLibraryUnload(true, module_cache()); } // Checks that a profiler can stop/destruct without ever having started. PROFILER_TEST_F(StackSamplingProfilerTest, StopWithoutStarting) { WithTargetThread(BindLambdaForTesting( [this](SamplingProfilerThreadToken target_thread_token) { SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; Profile profile; WaitableEvent sampling_completed( WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED); StackSamplingProfiler profiler( target_thread_token, params, std::make_unique( module_cache(), BindLambdaForTesting( [&profile, &sampling_completed](Profile result_profile) { profile = std::move(result_profile); sampling_completed.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache())); 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 StackSamplerTestDelegate { 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( BindLambdaForTesting([](SamplingProfilerThreadToken target_thread_token) { 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 = Milliseconds(10); params[0].sampling_interval = Milliseconds(1); params[0].samples_per_profile = 100000; params[1].initial_delay = Milliseconds(10); params[1].sampling_interval = Milliseconds(1); params[1].samples_per_profile = 100000; SampleRecordedCounter samples_recorded[std::size(params)]; ModuleCache module_cache1, module_cache2; TestProfilerInfo profiler_info0(target_thread_token, params[0], &module_cache1, &samples_recorded[0]); TestProfilerInfo profiler_info1(target_thread_token, params[1], &module_cache2, &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(Milliseconds(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(Milliseconds(1)); // Ensure that the first profiler didn't do anything since it was // stopped. EXPECT_EQ(count0, samples_recorded[0].Get()); })); } // Checks that no sample are captured if the profiling is stopped during the // initial delay. PROFILER_TEST_F(StackSamplingProfilerTest, StopDuringInitialDelay) { SamplingParams params; params.initial_delay = Seconds(60); std::vector> samples = CaptureSamples(params, Milliseconds(0), module_cache()); EXPECT_TRUE(samples.empty()); } // Checks that tasks can be stopped before completion and incomplete samples are // captured. PROFILER_TEST_F(StackSamplingProfilerTest, StopDuringInterSampleInterval) { // Test delegate that counts samples. class SampleRecordedEvent : public StackSamplerTestDelegate { 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(BindLambdaForTesting( [this](SamplingProfilerThreadToken target_thread_token) { SamplingParams params; params.sampling_interval = AVeryLongTimeDelta(); params.samples_per_profile = 2; SampleRecordedEvent samples_recorded; TestProfilerInfo profiler_info(target_thread_token, params, module_cache(), &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.samples.size()); })); } PROFILER_TEST_F(StackSamplingProfilerTest, GetNextSampleTime_NormalExecution) { const auto& GetNextSampleTime = StackSamplingProfiler::TestPeer::GetNextSampleTime; const TimeTicks scheduled_current_sample_time = TimeTicks::UnixEpoch(); const TimeDelta sampling_interval = Milliseconds(10); // When executing the sample at exactly the scheduled time the next sample // should be one interval later. EXPECT_EQ(scheduled_current_sample_time + sampling_interval, GetNextSampleTime(scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time)); // When executing the sample less than half an interval after the scheduled // time the next sample also should be one interval later. EXPECT_EQ(scheduled_current_sample_time + sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 0.4 * sampling_interval)); // When executing the sample less than half an interval before the scheduled // time the next sample also should be one interval later. This is not // expected to occur in practice since delayed tasks never run early. EXPECT_EQ(scheduled_current_sample_time + sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time - 0.4 * sampling_interval)); } PROFILER_TEST_F(StackSamplingProfilerTest, GetNextSampleTime_DelayedExecution) { const auto& GetNextSampleTime = StackSamplingProfiler::TestPeer::GetNextSampleTime; const TimeTicks scheduled_current_sample_time = TimeTicks::UnixEpoch(); const TimeDelta sampling_interval = Milliseconds(10); // When executing the sample between 0.5 and 1.5 intervals after the scheduled // time the next sample should be two intervals later. EXPECT_EQ(scheduled_current_sample_time + 2 * sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 0.6 * sampling_interval)); EXPECT_EQ(scheduled_current_sample_time + 2 * sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 1.0 * sampling_interval)); EXPECT_EQ(scheduled_current_sample_time + 2 * sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 1.4 * sampling_interval)); // Similarly when executing the sample between 9.5 and 10.5 intervals after // the scheduled time the next sample should be 11 intervals later. EXPECT_EQ(scheduled_current_sample_time + 11 * sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 9.6 * sampling_interval)); EXPECT_EQ(scheduled_current_sample_time + 11 * sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 10.0 * sampling_interval)); EXPECT_EQ(scheduled_current_sample_time + 11 * sampling_interval, GetNextSampleTime( scheduled_current_sample_time, sampling_interval, scheduled_current_sample_time + 10.4 * sampling_interval)); } // Checks that we can destroy the profiler while profiling. PROFILER_TEST_F(StackSamplingProfilerTest, DestroyProfilerWhileProfiling) { SamplingParams params; params.sampling_interval = Milliseconds(10); Profile profile; WithTargetThread(BindLambdaForTesting([&, this](SamplingProfilerThreadToken target_thread_token) { std::unique_ptr profiler; auto profile_builder = std::make_unique( module_cache(), BindLambdaForTesting([&profile](Profile result_profile) { profile = std::move(result_profile); })); profiler = std::make_unique( target_thread_token, params, std::move(profile_builder), CreateCoreUnwindersFactoryForTesting(module_cache())); profiler->Start(); profiler.reset(); // Wait longer than a sample interval to catch any use-after-free actions by // the profiler thread. PlatformThread::Sleep(Milliseconds(50)); })); } // Checks that the different profilers may be run. PROFILER_TEST_F(StackSamplingProfilerTest, CanRunMultipleProfilers) { SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; std::vector> samples = CaptureSamples(params, AVeryLongTimeDelta(), module_cache()); ASSERT_EQ(1u, samples.size()); samples = CaptureSamples(params, AVeryLongTimeDelta(), module_cache()); ASSERT_EQ(1u, samples.size()); } // Checks that a sampler can be started while another is running. PROFILER_TEST_F(StackSamplingProfilerTest, MultipleStart) { WithTargetThread( BindLambdaForTesting([](SamplingProfilerThreadToken target_thread_token) { SamplingParams params1; params1.initial_delay = AVeryLongTimeDelta(); params1.samples_per_profile = 1; ModuleCache module_cache1; TestProfilerInfo profiler_info1(target_thread_token, params1, &module_cache1); SamplingParams params2; params2.sampling_interval = Milliseconds(1); params2.samples_per_profile = 1; ModuleCache module_cache2; TestProfilerInfo profiler_info2(target_thread_token, params2, &module_cache2); profiler_info1.profiler.Start(); profiler_info2.profiler.Start(); profiler_info2.completed.Wait(); EXPECT_EQ(1u, profiler_info2.profile.samples.size()); })); } // Checks that the profile duration and the sampling interval are calculated // correctly. Also checks that RecordMetadata() is invoked each time a sample // is recorded. PROFILER_TEST_F(StackSamplingProfilerTest, ProfileGeneralInfo) { WithTargetThread(BindLambdaForTesting( [this](SamplingProfilerThreadToken target_thread_token) { SamplingParams params; params.sampling_interval = Milliseconds(1); params.samples_per_profile = 3; TestProfilerInfo profiler_info(target_thread_token, params, module_cache()); profiler_info.profiler.Start(); profiler_info.completed.Wait(); EXPECT_EQ(3u, profiler_info.profile.samples.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(Milliseconds(1), profiler_info.profile.sampling_period); // The number of invocations of RecordMetadata() should be equal to the // number of samples recorded. EXPECT_EQ(3, profiler_info.profile.record_metadata_count); })); } // Checks that the sampling thread can shut down. PROFILER_TEST_F(StackSamplingProfilerTest, SamplerIdleShutdown) { SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; std::vector> samples = CaptureSamples(params, AVeryLongTimeDelta(), module_cache()); ASSERT_EQ(1u, samples.size()); // Capture thread should still be running at this point. ASSERT_TRUE(StackSamplingProfiler::TestPeer::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::TestPeer::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::TestPeer::IsSamplingThreadRunning()) PlatformThread::Sleep(Milliseconds(1)); } // Checks that additional requests will restart a stopped profiler. PROFILER_TEST_F(StackSamplingProfilerTest, WillRestartSamplerAfterIdleShutdown) { SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; std::vector> samples = CaptureSamples(params, AVeryLongTimeDelta(), module_cache()); ASSERT_EQ(1u, samples.size()); // Capture thread should still be running at this point. ASSERT_TRUE(StackSamplingProfiler::TestPeer::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::TestPeer::PerformSamplingThreadIdleShutdown(false); // Ensure another capture will start the sampling thread and run. samples = CaptureSamples(params, AVeryLongTimeDelta(), module_cache()); ASSERT_EQ(1u, samples.size()); EXPECT_TRUE(StackSamplingProfiler::TestPeer::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(BindLambdaForTesting( [this](SamplingProfilerThreadToken target_thread_token) { SamplingParams params; params.sampling_interval = Milliseconds(1); params.samples_per_profile = 1; TestProfilerInfo profiler_info(target_thread_token, params, module_cache()); profiler_info.profiler.Start(); profiler_info.completed.Wait(); // Capture thread should still be running at this point. ASSERT_TRUE(StackSamplingProfiler::TestPeer::IsSamplingThreadRunning()); // Perform an idle shutdown. StackSamplingProfiler::TestPeer::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( BindLambdaForTesting([](SamplingProfilerThreadToken target_thread_token) { ModuleCache module_cache1; ModuleCache module_cache2; std::vector> profiler_infos; profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/AVeryLongTimeDelta(), /*samples_per_profile=*/1, /*sampling_interval=*/Milliseconds(1)}, &module_cache1)); profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/Milliseconds(0), /*samples_per_profile=*/1, /*sampling_interval=*/Milliseconds(1)}, &module_cache2)); // First profiler is started when there has never been a sampling // thread. EXPECT_FALSE( StackSamplingProfiler::TestPeer::IsSamplingThreadRunning()); profiler_infos[0]->profiler.Start(); // Second profiler is started when sampling thread is already running. EXPECT_TRUE(StackSamplingProfiler::TestPeer::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(BindLambdaForTesting( [this](SamplingProfilerThreadToken target_thread_token) { SamplingParams params; params.sampling_interval = Milliseconds(1); params.samples_per_profile = 1; TestProfilerInfo profiler_info(target_thread_token, params, module_cache()); profiler_info.profiler.Start(); profiler_info.completed.Wait(); EXPECT_EQ(1u, profiler_info.profile.samples.size()); // Perform an idle shutdown but simulate that a new capture is started // before it can actually run. StackSamplingProfiler::TestPeer::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(Milliseconds(200)); EXPECT_TRUE(StackSamplingProfiler::TestPeer::IsSamplingThreadRunning()); // Ensure that it's still possible to run another sampler. TestProfilerInfo another_info(target_thread_token, params, module_cache()); another_info.profiler.Start(); another_info.completed.Wait(); EXPECT_EQ(1u, another_info.profile.samples.size()); })); } // Checks that synchronized multiple sampling requests execute in parallel. PROFILER_TEST_F(StackSamplingProfilerTest, ConcurrentProfiling_InSync) { WithTargetThread( BindLambdaForTesting([](SamplingProfilerThreadToken target_thread_token) { std::vector module_caches(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. std::vector> profiler_infos; profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/Milliseconds(10), /*samples_per_profile=*/9, /*sampling_interval=*/Milliseconds(1)}, &module_caches[0])); profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/Milliseconds(11), /*samples_per_profile=*/8, /*sampling_interval=*/Milliseconds(1)}, &module_caches[1])); 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 samples. EXPECT_EQ(9u, profiler_infos[0]->profile.samples.size()); EXPECT_EQ(8u, profiler_infos[1]->profile.samples.size()); })); } // Checks that several mixed sampling requests execute in parallel. PROFILER_TEST_F(StackSamplingProfilerTest, ConcurrentProfiling_Mixed) { WithTargetThread(BindLambdaForTesting([](SamplingProfilerThreadToken target_thread_token) { std::vector module_caches(3); std::vector> profiler_infos; profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/Milliseconds(8), /*samples_per_profile=*/10, /*sampling_interval=*/Milliseconds(4)}, &module_caches[0])); profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/Milliseconds(9), /*samples_per_profile=*/10, /*sampling_interval=*/Milliseconds(3)}, &module_caches[1])); profiler_infos.push_back(std::make_unique( target_thread_token, SamplingParams{/*initial_delay=*/Milliseconds(10), /*samples_per_profile=*/10, /*sampling_interval=*/Milliseconds(2)}, &module_caches[2])); for (auto& 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.samples.size()); // Stop and destroy all profilers, always in the same order. Don't // crash. for (auto& i : profiler_infos) i->profiler.Stop(); for (auto& i : profiler_infos) i.reset(); })); } // Checks that different threads can be sampled in parallel. PROFILER_TEST_F(StackSamplingProfilerTest, MultipleSampledThreads) { UnwindScenario scenario1(BindRepeating(&CallWithPlainFunction)); UnwindScenario::SampleEvents events1; TargetThread target_thread1( BindLambdaForTesting([&]() { scenario1.Execute(&events1); })); target_thread1.Start(); events1.ready_for_sample.Wait(); UnwindScenario scenario2(BindRepeating(&CallWithPlainFunction)); UnwindScenario::SampleEvents events2; TargetThread target_thread2( BindLambdaForTesting([&]() { scenario2.Execute(&events2); })); target_thread2.Start(); events2.ready_for_sample.Wait(); // 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 = Milliseconds(10); params1.sampling_interval = Milliseconds(1); params1.samples_per_profile = 9; params2.initial_delay = Milliseconds(10); params2.sampling_interval = Milliseconds(1); params2.samples_per_profile = 8; Profile profile1, profile2; ModuleCache module_cache1, module_cache2; WaitableEvent sampling_thread_completed1( WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED); StackSamplingProfiler profiler1( target_thread1.thread_token(), params1, std::make_unique( &module_cache1, BindLambdaForTesting( [&profile1, &sampling_thread_completed1](Profile result_profile) { profile1 = std::move(result_profile); sampling_thread_completed1.Signal(); })), CreateCoreUnwindersFactoryForTesting(&module_cache1)); WaitableEvent sampling_thread_completed2( WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED); StackSamplingProfiler profiler2( target_thread2.thread_token(), params2, std::make_unique( &module_cache2, BindLambdaForTesting( [&profile2, &sampling_thread_completed2](Profile result_profile) { profile2 = std::move(result_profile); sampling_thread_completed2.Signal(); })), CreateCoreUnwindersFactoryForTesting(&module_cache2)); // Finally the real work. profiler1.Start(); profiler2.Start(); sampling_thread_completed1.Wait(); sampling_thread_completed2.Wait(); EXPECT_EQ(9u, profile1.samples.size()); EXPECT_EQ(8u, profile2.samples.size()); events1.sample_finished.Signal(); events2.sample_finished.Signal(); target_thread1.Join(); target_thread2.Join(); } // A simple thread that runs a profiler on another thread. class ProfilerThread : public SimpleThread { public: ProfilerThread(const std::string& name, SamplingProfilerThreadToken thread_token, const SamplingParams& params, ModuleCache* module_cache) : SimpleThread(name, Options()), run_(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED), completed_(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED), profiler_(thread_token, params, std::make_unique( module_cache, BindLambdaForTesting([this](Profile result_profile) { profile_ = std::move(result_profile); completed_.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache)) {} 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( BindLambdaForTesting([](SamplingProfilerThreadToken target_thread_token) { // 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 = Milliseconds(10); params1.sampling_interval = Milliseconds(1); params1.samples_per_profile = 9; params2.initial_delay = Milliseconds(10); params2.sampling_interval = Milliseconds(1); params2.samples_per_profile = 8; // Start the profiler threads and give them a moment to get going. ModuleCache module_cache1; ProfilerThread profiler_thread1("profiler1", target_thread_token, params1, &module_cache1); ModuleCache module_cache2; ProfilerThread profiler_thread2("profiler2", target_thread_token, params2, &module_cache2); profiler_thread1.Start(); profiler_thread2.Start(); PlatformThread::Sleep(Milliseconds(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().samples.size()); EXPECT_EQ(8u, profiler_thread2.profile().samples.size()); profiler_thread1.Join(); profiler_thread2.Join(); })); } PROFILER_TEST_F(StackSamplingProfilerTest, AddAuxUnwinder_BeforeStart) { SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); int add_initial_modules_invocation_count = 0; const auto add_initial_modules_callback = [&add_initial_modules_invocation_count]() { ++add_initial_modules_invocation_count; }; Profile profile; WithTargetThread( &scenario, BindLambdaForTesting( [&](SamplingProfilerThreadToken target_thread_token) { WaitableEvent sampling_thread_completed( WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED); StackSamplingProfiler profiler( target_thread_token, params, std::make_unique( module_cache(), BindLambdaForTesting([&profile, &sampling_thread_completed]( Profile result_profile) { profile = std::move(result_profile); sampling_thread_completed.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache())); profiler.AddAuxUnwinder(std::make_unique( Frame(23, nullptr), BindLambdaForTesting(add_initial_modules_callback))); profiler.Start(); sampling_thread_completed.Wait(); })); ASSERT_EQ(1, add_initial_modules_invocation_count); // The sample should have one frame from the context values and one from the // TestAuxUnwinder. ASSERT_EQ(1u, profile.samples.size()); const std::vector& frames = profile.samples[0]; ASSERT_EQ(2u, frames.size()); EXPECT_EQ(23u, frames[1].instruction_pointer); EXPECT_EQ(nullptr, frames[1].module); } PROFILER_TEST_F(StackSamplingProfilerTest, AddAuxUnwinder_AfterStart) { SamplingParams params; params.sampling_interval = Milliseconds(10); params.samples_per_profile = 2; UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); int add_initial_modules_invocation_count = 0; const auto add_initial_modules_callback = [&add_initial_modules_invocation_count]() { ++add_initial_modules_invocation_count; }; Profile profile; WithTargetThread( &scenario, BindLambdaForTesting( [&](SamplingProfilerThreadToken target_thread_token) { WaitableEvent sampling_thread_completed( WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED); StackSamplingProfiler profiler( target_thread_token, params, std::make_unique( module_cache(), BindLambdaForTesting([&profile, &sampling_thread_completed]( Profile result_profile) { profile = std::move(result_profile); sampling_thread_completed.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache())); profiler.Start(); profiler.AddAuxUnwinder(std::make_unique( Frame(23, nullptr), BindLambdaForTesting(add_initial_modules_callback))); sampling_thread_completed.Wait(); })); ASSERT_EQ(1, add_initial_modules_invocation_count); // The sample should have one frame from the context values and one from the // TestAuxUnwinder. ASSERT_EQ(2u, profile.samples.size()); // Whether the aux unwinder is available for the first sample is racy, so rely // on the second sample. const std::vector& frames = profile.samples[1]; ASSERT_EQ(2u, frames.size()); EXPECT_EQ(23u, frames[1].instruction_pointer); EXPECT_EQ(nullptr, frames[1].module); } PROFILER_TEST_F(StackSamplingProfilerTest, AddAuxUnwinder_AfterStop) { SamplingParams params; params.sampling_interval = Milliseconds(0); params.samples_per_profile = 1; UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); Profile profile; WithTargetThread( &scenario, BindLambdaForTesting( [&](SamplingProfilerThreadToken target_thread_token) { WaitableEvent sampling_thread_completed( WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED); StackSamplingProfiler profiler( target_thread_token, params, std::make_unique( module_cache(), BindLambdaForTesting([&profile, &sampling_thread_completed]( Profile result_profile) { profile = std::move(result_profile); sampling_thread_completed.Signal(); })), CreateCoreUnwindersFactoryForTesting(module_cache())); profiler.Start(); profiler.Stop(); profiler.AddAuxUnwinder(std::make_unique( Frame(23, nullptr), base::RepeatingClosure())); sampling_thread_completed.Wait(); })); // The AuxUnwinder should be accepted without error. It will have no effect // since the collection has stopped. } // Checks that requests to apply metadata to past samples are passed on to the // profile builder. PROFILER_TEST_F(StackSamplingProfilerTest, ApplyMetadataToPastSamples_PassedToProfileBuilder) { // Runs the passed closure on the profiler thread after a sample is taken. class PostSampleInvoker : public StackSamplerTestDelegate { public: explicit PostSampleInvoker(RepeatingClosure post_sample_closure) : post_sample_closure_(std::move(post_sample_closure)) {} void OnPreStackWalk() override { post_sample_closure_.Run(); } private: RepeatingClosure post_sample_closure_; }; // Thread-safe representation of the times that samples were taken. class SynchronizedSampleTimes { public: void AddNow() { AutoLock lock(lock_); times_.push_back(TimeTicks::Now()); } std::vector GetTimes() { AutoLock lock(lock_); return times_; } private: Lock lock_; std::vector times_; }; SamplingParams params; params.sampling_interval = Milliseconds(10); // 10,000 samples ensures the profiler continues running until manually // stopped, after applying metadata. params.samples_per_profile = 10000; UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); std::vector sample_times; Profile profile; WithTargetThread( &scenario, BindLambdaForTesting( [&](SamplingProfilerThreadToken target_thread_token) { SynchronizedSampleTimes synchronized_sample_times; WaitableEvent sample_seen(WaitableEvent::ResetPolicy::AUTOMATIC); PostSampleInvoker post_sample_invoker(BindLambdaForTesting([&]() { synchronized_sample_times.AddNow(); sample_seen.Signal(); })); StackSamplingProfiler profiler( target_thread_token, params, std::make_unique( module_cache(), BindLambdaForTesting([&profile](Profile result_profile) { profile = std::move(result_profile); })), CreateCoreUnwindersFactoryForTesting(module_cache()), RepeatingClosure(), &post_sample_invoker); profiler.Start(); // Wait for 5 samples to be collected. for (int i = 0; i < 5; ++i) sample_seen.Wait(); sample_times = synchronized_sample_times.GetTimes(); // Record metadata on past samples, with and without a key value. // The range [times[1], times[3]] is guaranteed to include only // samples 2 and 3, and likewise [times[2], times[4]] is guaranteed // to include only samples 3 and 4. ApplyMetadataToPastSamples(sample_times[1], sample_times[3], "TestMetadata1", 10, base::SampleMetadataScope::kProcess); ApplyMetadataToPastSamples(sample_times[2], sample_times[4], "TestMetadata2", 100, 11, base::SampleMetadataScope::kProcess); profiler.Stop(); })); ASSERT_EQ(2u, profile.retrospective_metadata.size()); const RetrospectiveMetadata& metadata1 = profile.retrospective_metadata[0]; EXPECT_EQ(sample_times[1], metadata1.period_start); EXPECT_EQ(sample_times[3], metadata1.period_end); EXPECT_EQ(HashMetricName("TestMetadata1"), metadata1.item.name_hash); EXPECT_FALSE(metadata1.item.key.has_value()); EXPECT_EQ(10, metadata1.item.value); const RetrospectiveMetadata& metadata2 = profile.retrospective_metadata[1]; EXPECT_EQ(sample_times[2], metadata2.period_start); EXPECT_EQ(sample_times[4], metadata2.period_end); EXPECT_EQ(HashMetricName("TestMetadata2"), metadata2.item.name_hash); ASSERT_TRUE(metadata2.item.key.has_value()); EXPECT_EQ(100, *metadata2.item.key); EXPECT_EQ(11, metadata2.item.value); } PROFILER_TEST_F( StackSamplingProfilerTest, ApplyMetadataToPastSamples_PassedToProfileBuilder_MultipleCollections) { SamplingParams params; params.sampling_interval = Milliseconds(10); // 10,000 samples ensures the profiler continues running until manually // stopped, after applying metadata. params.samples_per_profile = 10000; ModuleCache module_cache1, module_cache2; WaitableEvent profiler1_started; WaitableEvent profiler2_started; WaitableEvent profiler1_metadata_applied; WaitableEvent profiler2_metadata_applied; Profile profile1; WaitableEvent sampling_completed1; TargetThread target_thread1(BindLambdaForTesting([&]() { StackSamplingProfiler profiler1( target_thread1.thread_token(), params, std::make_unique( &module_cache1, BindLambdaForTesting([&](Profile result_profile) { profile1 = std::move(result_profile); sampling_completed1.Signal(); })), CreateCoreUnwindersFactoryForTesting(&module_cache1), RepeatingClosure()); profiler1.Start(); profiler1_started.Signal(); profiler2_started.Wait(); // Record metadata on past samples only for this thread. The time range // shouldn't affect the outcome, it should always be passed to the // ProfileBuilder. ApplyMetadataToPastSamples(TimeTicks(), TimeTicks::Now(), "TestMetadata1", 10, 10, SampleMetadataScope::kThread); profiler1_metadata_applied.Signal(); profiler2_metadata_applied.Wait(); profiler1.Stop(); })); target_thread1.Start(); Profile profile2; WaitableEvent sampling_completed2; TargetThread target_thread2(BindLambdaForTesting([&]() { StackSamplingProfiler profiler2( target_thread2.thread_token(), params, std::make_unique( &module_cache2, BindLambdaForTesting([&](Profile result_profile) { profile2 = std::move(result_profile); sampling_completed2.Signal(); })), CreateCoreUnwindersFactoryForTesting(&module_cache2), RepeatingClosure()); profiler2.Start(); profiler2_started.Signal(); profiler1_started.Wait(); // Record metadata on past samples only for this thread. ApplyMetadataToPastSamples(TimeTicks(), TimeTicks::Now(), "TestMetadata2", 20, 20, SampleMetadataScope::kThread); profiler2_metadata_applied.Signal(); profiler1_metadata_applied.Wait(); profiler2.Stop(); })); target_thread2.Start(); target_thread1.Join(); target_thread2.Join(); // Wait for the profile to be captured before checking expectations. sampling_completed1.Wait(); sampling_completed2.Wait(); ASSERT_EQ(1u, profile1.retrospective_metadata.size()); ASSERT_EQ(1u, profile2.retrospective_metadata.size()); { const RetrospectiveMetadata& metadata1 = profile1.retrospective_metadata[0]; EXPECT_EQ(HashMetricName("TestMetadata1"), metadata1.item.name_hash); ASSERT_TRUE(metadata1.item.key.has_value()); EXPECT_EQ(10, *metadata1.item.key); EXPECT_EQ(10, metadata1.item.value); } { const RetrospectiveMetadata& metadata2 = profile2.retrospective_metadata[0]; EXPECT_EQ(HashMetricName("TestMetadata2"), metadata2.item.name_hash); ASSERT_TRUE(metadata2.item.key.has_value()); EXPECT_EQ(20, *metadata2.item.key); EXPECT_EQ(20, metadata2.item.value); } } } // namespace base