summaryrefslogtreecommitdiff
path: root/base/profiler/stack_sampling_profiler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'base/profiler/stack_sampling_profiler.cc')
-rw-r--r--base/profiler/stack_sampling_profiler.cc808
1 files changed, 808 insertions, 0 deletions
diff --git a/base/profiler/stack_sampling_profiler.cc b/base/profiler/stack_sampling_profiler.cc
new file mode 100644
index 0000000000..02df814f8c
--- /dev/null
+++ b/base/profiler/stack_sampling_profiler.cc
@@ -0,0 +1,808 @@
+// 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 "base/profiler/stack_sampling_profiler.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/singleton.h"
+#include "base/profiler/native_stack_sampler.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/elapsed_timer.h"
+
+namespace base {
+
+const size_t kUnknownModuleIndex = static_cast<size_t>(-1);
+
+namespace {
+
+// This value is used to initialize the WaitableEvent object. This MUST BE set
+// to MANUAL for correct operation of the IsSignaled() call in Start(). See the
+// comment there for why.
+constexpr WaitableEvent::ResetPolicy kResetPolicy =
+ WaitableEvent::ResetPolicy::MANUAL;
+
+// This value is used when there is no collection in progress and thus no ID
+// for referencing the active collection to the SamplingThread.
+const int kNullProfilerId = -1;
+
+} // namespace
+
+// StackSamplingProfiler::Module ----------------------------------------------
+
+StackSamplingProfiler::Module::Module() : base_address(0u) {}
+
+StackSamplingProfiler::Module::Module(uintptr_t base_address,
+ const std::string& id,
+ const FilePath& filename)
+ : base_address(base_address), id(id), filename(filename) {}
+
+StackSamplingProfiler::Module::~Module() = default;
+
+// StackSamplingProfiler::InternalModule --------------------------------------
+
+StackSamplingProfiler::InternalModule::InternalModule() : is_valid(false) {}
+
+StackSamplingProfiler::InternalModule::InternalModule(uintptr_t base_address,
+ const std::string& id,
+ const FilePath& filename)
+ : base_address(base_address), id(id), filename(filename), is_valid(true) {}
+
+StackSamplingProfiler::InternalModule::~InternalModule() = default;
+
+// StackSamplingProfiler::Frame -----------------------------------------------
+
+StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer,
+ size_t module_index)
+ : instruction_pointer(instruction_pointer), module_index(module_index) {}
+
+StackSamplingProfiler::Frame::~Frame() = default;
+
+StackSamplingProfiler::Frame::Frame()
+ : instruction_pointer(0), module_index(kUnknownModuleIndex) {}
+
+// StackSamplingProfiler::InternalFrame -------------------------------------
+
+StackSamplingProfiler::InternalFrame::InternalFrame(
+ uintptr_t instruction_pointer,
+ InternalModule internal_module)
+ : instruction_pointer(instruction_pointer),
+ internal_module(std::move(internal_module)) {}
+
+StackSamplingProfiler::InternalFrame::~InternalFrame() = default;
+
+// StackSamplingProfiler::Sample ----------------------------------------------
+
+StackSamplingProfiler::Sample::Sample() = default;
+
+StackSamplingProfiler::Sample::Sample(const Sample& sample) = default;
+
+StackSamplingProfiler::Sample::~Sample() = default;
+
+StackSamplingProfiler::Sample::Sample(const Frame& frame) {
+ frames.push_back(std::move(frame));
+}
+
+StackSamplingProfiler::Sample::Sample(const std::vector<Frame>& frames)
+ : frames(frames) {}
+
+// StackSamplingProfiler::CallStackProfile ------------------------------------
+
+StackSamplingProfiler::CallStackProfile::CallStackProfile() = default;
+
+StackSamplingProfiler::CallStackProfile::CallStackProfile(
+ CallStackProfile&& other) = default;
+
+StackSamplingProfiler::CallStackProfile::~CallStackProfile() = default;
+
+StackSamplingProfiler::CallStackProfile&
+StackSamplingProfiler::CallStackProfile::operator=(CallStackProfile&& other) =
+ default;
+
+StackSamplingProfiler::CallStackProfile
+StackSamplingProfiler::CallStackProfile::CopyForTesting() const {
+ return CallStackProfile(*this);
+}
+
+StackSamplingProfiler::CallStackProfile::CallStackProfile(
+ const CallStackProfile& other) = default;
+
+// StackSamplingProfiler::SamplingThread --------------------------------------
+
+class StackSamplingProfiler::SamplingThread : public Thread {
+ public:
+ class TestAPI {
+ public:
+ // Reset the existing sampler. This will unfortunately create the object
+ // unnecessarily if it doesn't already exist but there's no way around that.
+ static void Reset();
+
+ // Disables inherent idle-shutdown behavior.
+ static void DisableIdleShutdown();
+
+ // Begins an idle shutdown as if the idle-timer had expired and wait for
+ // it to execute. Since the timer would have only been started at a time
+ // when the sampling thread actually was idle, this must be called only
+ // when it is known that there are no active sampling threads. If
+ // |simulate_intervening_add| is true then, when executed, the shutdown
+ // task will believe that a new collection has been added since it was
+ // posted.
+ static void ShutdownAssumingIdle(bool simulate_intervening_add);
+
+ private:
+ // Calls the sampling threads ShutdownTask and then signals an event.
+ static void ShutdownTaskAndSignalEvent(SamplingThread* sampler,
+ int add_events,
+ WaitableEvent* event);
+ };
+
+ struct CollectionContext {
+ CollectionContext(PlatformThreadId target,
+ const SamplingParams& params,
+ WaitableEvent* finished,
+ std::unique_ptr<NativeStackSampler> sampler,
+ std::unique_ptr<ProfileBuilder> profile_builder)
+ : collection_id(next_collection_id.GetNext()),
+ target(target),
+ params(params),
+ finished(finished),
+ native_sampler(std::move(sampler)),
+ profile_builder(std::move(profile_builder)) {}
+ ~CollectionContext() = default;
+
+ // An identifier for this collection, used to uniquely identify the
+ // collection to outside interests.
+ const int collection_id;
+
+ const PlatformThreadId target; // ID of The thread being sampled.
+ const SamplingParams params; // Information about how to sample.
+ WaitableEvent* const finished; // Signaled when all sampling complete.
+
+ // Platform-specific module that does the actual sampling.
+ std::unique_ptr<NativeStackSampler> native_sampler;
+
+ // Receives the sampling data and builds a CallStackProfile.
+ std::unique_ptr<ProfileBuilder> profile_builder;
+
+ // The absolute time for the next sample.
+ Time next_sample_time;
+
+ // The time that a profile was started, for calculating the total duration.
+ Time profile_start_time;
+
+ // Counter that indicates the current sample position along the acquisition.
+ int sample_count = 0;
+
+ // Sequence number for generating new collection ids.
+ static AtomicSequenceNumber next_collection_id;
+ };
+
+ // Gets the single instance of this class.
+ static SamplingThread* GetInstance();
+
+ // Adds a new CollectionContext to the thread. This can be called externally
+ // from any thread. This returns a collection id that can later be used to
+ // stop the sampling.
+ int Add(std::unique_ptr<CollectionContext> collection);
+
+ // Removes an active collection based on its collection id, forcing it to run
+ // its callback if any data has been collected. This can be called externally
+ // from any thread.
+ void Remove(int collection_id);
+
+ private:
+ friend class TestAPI;
+ friend struct DefaultSingletonTraits<SamplingThread>;
+
+ // The different states in which the sampling-thread can be.
+ enum ThreadExecutionState {
+ // The thread is not running because it has never been started. It will be
+ // started when a sampling request is received.
+ NOT_STARTED,
+
+ // The thread is running and processing tasks. This is the state when any
+ // sampling requests are active and during the "idle" period afterward
+ // before the thread is stopped.
+ RUNNING,
+
+ // Once all sampling requests have finished and the "idle" period has
+ // expired, the thread will be set to this state and its shutdown
+ // initiated. A call to Stop() must be made to ensure the previous thread
+ // has completely exited before calling Start() and moving back to the
+ // RUNNING state.
+ EXITING,
+ };
+
+ SamplingThread();
+ ~SamplingThread() override;
+
+ // Get task runner that is usable from the outside.
+ scoped_refptr<SingleThreadTaskRunner> GetOrCreateTaskRunnerForAdd();
+ scoped_refptr<SingleThreadTaskRunner> GetTaskRunner(
+ ThreadExecutionState* out_state);
+
+ // Get task runner that is usable from the sampling thread itself.
+ scoped_refptr<SingleThreadTaskRunner> GetTaskRunnerOnSamplingThread();
+
+ // Finishes a collection. The collection's |finished| waitable event will be
+ // signalled. The |collection| should already have been removed from
+ // |active_collections_| by the caller, as this is needed to avoid flakiness
+ // in unit tests.
+ void FinishCollection(CollectionContext* collection);
+
+ // Check if the sampling thread is idle and begin a shutdown if it is.
+ void ScheduleShutdownIfIdle();
+
+ // These methods are tasks that get posted to the internal message queue.
+ void AddCollectionTask(std::unique_ptr<CollectionContext> collection);
+ void RemoveCollectionTask(int collection_id);
+ void RecordSampleTask(int collection_id);
+ void ShutdownTask(int add_events);
+
+ // Thread:
+ void CleanUp() override;
+
+ // A stack-buffer used by the native sampler for its work. This buffer can
+ // be re-used for multiple native sampler objects so long as the API calls
+ // that take it are not called concurrently.
+ std::unique_ptr<NativeStackSampler::StackBuffer> stack_buffer_;
+
+ // A map of collection ids to collection contexts. Because this class is a
+ // singleton that is never destroyed, context objects will never be destructed
+ // except by explicit action. Thus, it's acceptable to pass unretained
+ // pointers to these objects when posting tasks.
+ std::map<int, std::unique_ptr<CollectionContext>> active_collections_;
+
+ // State maintained about the current execution (or non-execution) of
+ // the thread. This state must always be accessed while holding the
+ // lock. A copy of the task-runner is maintained here for use by any
+ // calling thread; this is necessary because Thread's accessor for it is
+ // not itself thread-safe. The lock is also used to order calls to the
+ // Thread API (Start, Stop, StopSoon, & DetachFromSequence) so that
+ // multiple threads may make those calls.
+ Lock thread_execution_state_lock_; // Protects all thread_execution_state_*
+ ThreadExecutionState thread_execution_state_ = NOT_STARTED;
+ scoped_refptr<SingleThreadTaskRunner> thread_execution_state_task_runner_;
+ bool thread_execution_state_disable_idle_shutdown_for_testing_ = false;
+
+ // A counter that notes adds of new collection requests. It is incremented
+ // when changes occur so that delayed shutdown tasks are able to detect if
+ // something new has happened while it was waiting. Like all "execution_state"
+ // vars, this must be accessed while holding |thread_execution_state_lock_|.
+ int thread_execution_state_add_events_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(SamplingThread);
+};
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::Reset() {
+ SamplingThread* sampler = SamplingThread::GetInstance();
+
+ ThreadExecutionState state;
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ state = sampler->thread_execution_state_;
+ DCHECK(sampler->active_collections_.empty());
+ }
+
+ // Stop the thread and wait for it to exit. This has to be done through by
+ // the thread itself because it has taken ownership of its own lifetime.
+ if (state == RUNNING) {
+ ShutdownAssumingIdle(false);
+ state = EXITING;
+ }
+ // Make sure thread is cleaned up since state will be reset to NOT_STARTED.
+ if (state == EXITING)
+ sampler->Stop();
+
+ // Reset internal variables to the just-initialized state.
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ sampler->thread_execution_state_ = NOT_STARTED;
+ sampler->thread_execution_state_task_runner_ = nullptr;
+ sampler->thread_execution_state_disable_idle_shutdown_for_testing_ = false;
+ sampler->thread_execution_state_add_events_ = 0;
+ }
+}
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::DisableIdleShutdown() {
+ SamplingThread* sampler = SamplingThread::GetInstance();
+
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ sampler->thread_execution_state_disable_idle_shutdown_for_testing_ = true;
+ }
+}
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::ShutdownAssumingIdle(
+ bool simulate_intervening_add) {
+ SamplingThread* sampler = SamplingThread::GetInstance();
+
+ ThreadExecutionState state;
+ scoped_refptr<SingleThreadTaskRunner> task_runner =
+ sampler->GetTaskRunner(&state);
+ DCHECK_EQ(RUNNING, state);
+ DCHECK(task_runner);
+
+ int add_events;
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ add_events = sampler->thread_execution_state_add_events_;
+ if (simulate_intervening_add)
+ ++sampler->thread_execution_state_add_events_;
+ }
+
+ WaitableEvent executed(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ // PostTaskAndReply won't work because thread and associated message-loop may
+ // be shut down.
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&ShutdownTaskAndSignalEvent, Unretained(sampler),
+ add_events, Unretained(&executed)));
+ executed.Wait();
+}
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::ShutdownTaskAndSignalEvent(
+ SamplingThread* sampler,
+ int add_events,
+ WaitableEvent* event) {
+ sampler->ShutdownTask(add_events);
+ event->Signal();
+}
+
+AtomicSequenceNumber StackSamplingProfiler::SamplingThread::CollectionContext::
+ next_collection_id;
+
+StackSamplingProfiler::SamplingThread::SamplingThread()
+ : Thread("StackSamplingProfiler") {}
+
+StackSamplingProfiler::SamplingThread::~SamplingThread() = default;
+
+StackSamplingProfiler::SamplingThread*
+StackSamplingProfiler::SamplingThread::GetInstance() {
+ return Singleton<SamplingThread, LeakySingletonTraits<SamplingThread>>::get();
+}
+
+int StackSamplingProfiler::SamplingThread::Add(
+ std::unique_ptr<CollectionContext> collection) {
+ // This is not to be run on the sampling thread.
+
+ int collection_id = collection->collection_id;
+ scoped_refptr<SingleThreadTaskRunner> task_runner =
+ GetOrCreateTaskRunnerForAdd();
+
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&SamplingThread::AddCollectionTask, Unretained(this),
+ std::move(collection)));
+
+ return collection_id;
+}
+
+void StackSamplingProfiler::SamplingThread::Remove(int collection_id) {
+ // This is not to be run on the sampling thread.
+
+ ThreadExecutionState state;
+ scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
+ if (state != RUNNING)
+ return;
+ DCHECK(task_runner);
+
+ // This can fail if the thread were to exit between acquisition of the task
+ // runner above and the call below. In that case, however, everything has
+ // stopped so there's no need to try to stop it.
+ task_runner->PostTask(FROM_HERE,
+ BindOnce(&SamplingThread::RemoveCollectionTask,
+ Unretained(this), collection_id));
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+StackSamplingProfiler::SamplingThread::GetOrCreateTaskRunnerForAdd() {
+ AutoLock lock(thread_execution_state_lock_);
+
+ // The increment of the "add events" count is why this method is to be only
+ // called from "add".
+ ++thread_execution_state_add_events_;
+
+ if (thread_execution_state_ == RUNNING) {
+ DCHECK(thread_execution_state_task_runner_);
+ // This shouldn't be called from the sampling thread as it's inefficient.
+ // Use GetTaskRunnerOnSamplingThread() instead.
+ DCHECK_NE(GetThreadId(), PlatformThread::CurrentId());
+ return thread_execution_state_task_runner_;
+ }
+
+ if (thread_execution_state_ == EXITING) {
+ // StopSoon() was previously called to shut down the thread
+ // asynchonously. Stop() must now be called before calling Start() again to
+ // reset the thread state.
+ //
+ // We must allow blocking here to satisfy the Thread implementation, but in
+ // practice the Stop() call is unlikely to actually block. For this to
+ // happen a new profiling request would have to be made within the narrow
+ // window between StopSoon() and thread exit following the end of the 60
+ // second idle period.
+ ScopedAllowBlocking allow_blocking;
+ Stop();
+ }
+
+ DCHECK(!stack_buffer_);
+ stack_buffer_ = NativeStackSampler::CreateStackBuffer();
+
+ // The thread is not running. Start it and get associated runner. The task-
+ // runner has to be saved for future use because though it can be used from
+ // any thread, it can be acquired via task_runner() only on the created
+ // thread and the thread that creates it (i.e. this thread) for thread-safety
+ // reasons which are alleviated in SamplingThread by gating access to it with
+ // the |thread_execution_state_lock_|.
+ Start();
+ thread_execution_state_ = RUNNING;
+ thread_execution_state_task_runner_ = Thread::task_runner();
+
+ // Detach the sampling thread from the "sequence" (i.e. thread) that
+ // started it so that it can be self-managed or stopped by another thread.
+ DetachFromSequence();
+
+ return thread_execution_state_task_runner_;
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+StackSamplingProfiler::SamplingThread::GetTaskRunner(
+ ThreadExecutionState* out_state) {
+ AutoLock lock(thread_execution_state_lock_);
+ if (out_state)
+ *out_state = thread_execution_state_;
+ if (thread_execution_state_ == RUNNING) {
+ // This shouldn't be called from the sampling thread as it's inefficient.
+ // Use GetTaskRunnerOnSamplingThread() instead.
+ DCHECK_NE(GetThreadId(), PlatformThread::CurrentId());
+ DCHECK(thread_execution_state_task_runner_);
+ } else {
+ DCHECK(!thread_execution_state_task_runner_);
+ }
+
+ return thread_execution_state_task_runner_;
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+StackSamplingProfiler::SamplingThread::GetTaskRunnerOnSamplingThread() {
+ // This should be called only from the sampling thread as it has limited
+ // accessibility.
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ return Thread::task_runner();
+}
+
+void StackSamplingProfiler::SamplingThread::FinishCollection(
+ CollectionContext* collection) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+ DCHECK_EQ(0u, active_collections_.count(collection->collection_id));
+
+ TimeDelta profile_duration = Time::Now() - collection->profile_start_time +
+ collection->params.sampling_interval;
+
+ collection->profile_builder->OnProfileCompleted(
+ profile_duration, collection->params.sampling_interval);
+
+ // Signal that this collection is finished.
+ collection->finished->Signal();
+
+ ScheduleShutdownIfIdle();
+}
+
+void StackSamplingProfiler::SamplingThread::ScheduleShutdownIfIdle() {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ if (!active_collections_.empty())
+ return;
+
+ int add_events;
+ {
+ AutoLock lock(thread_execution_state_lock_);
+ if (thread_execution_state_disable_idle_shutdown_for_testing_)
+ return;
+ add_events = thread_execution_state_add_events_;
+ }
+
+ GetTaskRunnerOnSamplingThread()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&SamplingThread::ShutdownTask, Unretained(this), add_events),
+ TimeDelta::FromSeconds(60));
+}
+
+void StackSamplingProfiler::SamplingThread::AddCollectionTask(
+ std::unique_ptr<CollectionContext> collection) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ const int collection_id = collection->collection_id;
+ const TimeDelta initial_delay = collection->params.initial_delay;
+
+ active_collections_.insert(
+ std::make_pair(collection_id, std::move(collection)));
+
+ GetTaskRunnerOnSamplingThread()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&SamplingThread::RecordSampleTask, Unretained(this),
+ collection_id),
+ initial_delay);
+
+ // Another increment of "add events" serves to invalidate any pending
+ // shutdown tasks that may have been initiated between the Add() and this
+ // task running.
+ {
+ AutoLock lock(thread_execution_state_lock_);
+ ++thread_execution_state_add_events_;
+ }
+}
+
+void StackSamplingProfiler::SamplingThread::RemoveCollectionTask(
+ int collection_id) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ auto found = active_collections_.find(collection_id);
+ if (found == active_collections_.end())
+ return;
+
+ // Remove |collection| from |active_collections_|.
+ std::unique_ptr<CollectionContext> collection = std::move(found->second);
+ size_t count = active_collections_.erase(collection_id);
+ DCHECK_EQ(1U, count);
+
+ FinishCollection(collection.get());
+}
+
+void StackSamplingProfiler::SamplingThread::RecordSampleTask(
+ int collection_id) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ auto found = active_collections_.find(collection_id);
+
+ // The task won't be found if it has been stopped.
+ if (found == active_collections_.end())
+ return;
+
+ CollectionContext* collection = found->second.get();
+
+ // If this is the first sample, the collection params need to be filled.
+ if (collection->sample_count == 0) {
+ collection->profile_start_time = Time::Now();
+ collection->next_sample_time = Time::Now();
+ collection->native_sampler->ProfileRecordingStarting();
+ }
+
+ // Record a single sample.
+ collection->profile_builder->OnSampleCompleted(
+ collection->native_sampler->RecordStackFrames(
+ stack_buffer_.get(), collection->profile_builder.get()));
+
+ // Schedule the next sample recording if there is one.
+ if (++collection->sample_count < collection->params.samples_per_profile) {
+ // This will keep a consistent average interval between samples but will
+ // result in constant series of acquisitions, thus nearly locking out the
+ // target thread, if the interval is smaller than the time it takes to
+ // actually acquire the sample. Anything sampling that quickly is going
+ // to be a problem anyway so don't worry about it.
+ collection->next_sample_time += collection->params.sampling_interval;
+ bool success = GetTaskRunnerOnSamplingThread()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&SamplingThread::RecordSampleTask, Unretained(this),
+ collection_id),
+ std::max(collection->next_sample_time - Time::Now(), TimeDelta()));
+ DCHECK(success);
+ return;
+ }
+
+ // Take ownership of |collection| and remove it from the map.
+ std::unique_ptr<CollectionContext> owned_collection =
+ std::move(found->second);
+ size_t count = active_collections_.erase(collection_id);
+ DCHECK_EQ(1U, count);
+
+ // All capturing has completed so finish the collection.
+ FinishCollection(collection);
+}
+
+void StackSamplingProfiler::SamplingThread::ShutdownTask(int add_events) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ // Holding this lock ensures that any attempt to start another job will
+ // get postponed until |thread_execution_state_| is updated, thus eliminating
+ // the race in starting a new thread while the previous one is exiting.
+ AutoLock lock(thread_execution_state_lock_);
+
+ // If the current count of creation requests doesn't match the passed count
+ // then other tasks have been created since this was posted. Abort shutdown.
+ if (thread_execution_state_add_events_ != add_events)
+ return;
+
+ // There can be no new AddCollectionTasks at this point because creating
+ // those always increments "add events". There may be other requests, like
+ // Remove, but it's okay to schedule the thread to stop once they've been
+ // executed (i.e. "soon").
+ DCHECK(active_collections_.empty());
+ StopSoon();
+
+ // StopSoon will have set the owning sequence (again) so it must be detached
+ // (again) in order for Stop/Start to be called (again) should more work
+ // come in. Holding the |thread_execution_state_lock_| ensures the necessary
+ // happens-after with regard to this detach and future Thread API calls.
+ DetachFromSequence();
+
+ // Set the thread_state variable so the thread will be restarted when new
+ // work comes in. Remove the |thread_execution_state_task_runner_| to avoid
+ // confusion.
+ thread_execution_state_ = EXITING;
+ thread_execution_state_task_runner_ = nullptr;
+ stack_buffer_.reset();
+}
+
+void StackSamplingProfiler::SamplingThread::CleanUp() {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ // There should be no collections remaining when the thread stops.
+ DCHECK(active_collections_.empty());
+
+ // Let the parent clean up.
+ Thread::CleanUp();
+}
+
+// StackSamplingProfiler ------------------------------------------------------
+
+// static
+void StackSamplingProfiler::TestAPI::Reset() {
+ SamplingThread::TestAPI::Reset();
+}
+
+// static
+bool StackSamplingProfiler::TestAPI::IsSamplingThreadRunning() {
+ return SamplingThread::GetInstance()->IsRunning();
+}
+
+// static
+void StackSamplingProfiler::TestAPI::DisableIdleShutdown() {
+ SamplingThread::TestAPI::DisableIdleShutdown();
+}
+
+// static
+void StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(
+ bool simulate_intervening_start) {
+ SamplingThread::TestAPI::ShutdownAssumingIdle(simulate_intervening_start);
+}
+
+StackSamplingProfiler::StackSamplingProfiler(
+ const SamplingParams& params,
+ std::unique_ptr<ProfileBuilder> profile_builder,
+ NativeStackSamplerTestDelegate* test_delegate)
+ : StackSamplingProfiler(PlatformThread::CurrentId(),
+ params,
+ std::move(profile_builder),
+ test_delegate) {}
+
+StackSamplingProfiler::StackSamplingProfiler(
+ PlatformThreadId thread_id,
+ const SamplingParams& params,
+ std::unique_ptr<ProfileBuilder> profile_builder,
+ NativeStackSamplerTestDelegate* test_delegate)
+ : thread_id_(thread_id),
+ params_(params),
+ profile_builder_(std::move(profile_builder)),
+ // The event starts "signaled" so code knows it's safe to start thread
+ // and "manual" so that it can be waited in multiple places.
+ profiling_inactive_(kResetPolicy, WaitableEvent::InitialState::SIGNALED),
+ profiler_id_(kNullProfilerId),
+ test_delegate_(test_delegate) {
+ DCHECK(profile_builder_);
+}
+
+StackSamplingProfiler::~StackSamplingProfiler() {
+ // Stop returns immediately but the shutdown runs asynchronously. There is a
+ // non-zero probability that one more sample will be taken after this call
+ // returns.
+ Stop();
+
+ // The behavior of sampling a thread that has exited is undefined and could
+ // cause Bad Things(tm) to occur. The safety model provided by this class is
+ // that an instance of this object is expected to live at least as long as
+ // the thread it is sampling. However, because the sampling is performed
+ // asynchronously by the SamplingThread, there is no way to guarantee this
+ // is true without waiting for it to signal that it has finished.
+ //
+ // The wait time should, at most, be only as long as it takes to collect one
+ // sample (~200us) or none at all if sampling has already completed.
+ ThreadRestrictions::ScopedAllowWait allow_wait;
+ profiling_inactive_.Wait();
+}
+
+void StackSamplingProfiler::Start() {
+ // Multiple calls to Start() for a single StackSamplingProfiler object is not
+ // allowed. If profile_builder_ is nullptr, then Start() has been called
+ // already.
+ DCHECK(profile_builder_);
+
+ std::unique_ptr<NativeStackSampler> native_sampler =
+ NativeStackSampler::Create(thread_id_, test_delegate_);
+
+ if (!native_sampler)
+ return;
+
+ // The IsSignaled() check below requires that the WaitableEvent be manually
+ // reset, to avoid signaling the event in IsSignaled() itself.
+ static_assert(kResetPolicy == WaitableEvent::ResetPolicy::MANUAL,
+ "The reset policy must be set to MANUAL");
+
+ // If a previous profiling phase is still winding down, wait for it to
+ // complete. We can't use task posting for this coordination because the
+ // thread owning the profiler may not have a message loop.
+ if (!profiling_inactive_.IsSignaled())
+ profiling_inactive_.Wait();
+ profiling_inactive_.Reset();
+
+ DCHECK_EQ(kNullProfilerId, profiler_id_);
+ profiler_id_ = SamplingThread::GetInstance()->Add(
+ std::make_unique<SamplingThread::CollectionContext>(
+ thread_id_, params_, &profiling_inactive_, std::move(native_sampler),
+ std::move(profile_builder_)));
+ DCHECK_NE(kNullProfilerId, profiler_id_);
+}
+
+void StackSamplingProfiler::Stop() {
+ SamplingThread::GetInstance()->Remove(profiler_id_);
+ profiler_id_ = kNullProfilerId;
+}
+
+// StackSamplingProfiler::Frame global functions ------------------------------
+
+bool operator==(const StackSamplingProfiler::Module& a,
+ const StackSamplingProfiler::Module& b) {
+ return a.base_address == b.base_address && a.id == b.id &&
+ a.filename == b.filename;
+}
+
+bool operator==(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b) {
+ return a.process_milestones == b.process_milestones && a.frames == b.frames;
+}
+
+bool operator!=(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b) {
+ return !(a == b);
+}
+
+bool operator<(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b) {
+ if (a.process_milestones != b.process_milestones)
+ return a.process_milestones < b.process_milestones;
+
+ return a.frames < b.frames;
+}
+
+bool operator==(const StackSamplingProfiler::Frame& a,
+ const StackSamplingProfiler::Frame& b) {
+ return a.instruction_pointer == b.instruction_pointer &&
+ a.module_index == b.module_index;
+}
+
+bool operator<(const StackSamplingProfiler::Frame& a,
+ const StackSamplingProfiler::Frame& b) {
+ if (a.module_index != b.module_index)
+ return a.module_index < b.module_index;
+
+ return a.instruction_pointer < b.instruction_pointer;
+}
+
+} // namespace base