mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-01 01:36:09 +03:00
891 lines
33 KiB
C++
891 lines
33 KiB
C++
|
// 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 <map>
|
||
|
#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/lazy_instance.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 {
|
||
|
|
||
|
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 NULL_PROFILER_ID = -1;
|
||
|
|
||
|
void ChangeAtomicFlags(subtle::Atomic32* flags,
|
||
|
subtle::Atomic32 set,
|
||
|
subtle::Atomic32 clear) {
|
||
|
DCHECK(set != 0 || clear != 0);
|
||
|
DCHECK_EQ(0, set & clear);
|
||
|
|
||
|
subtle::Atomic32 bits = subtle::NoBarrier_Load(flags);
|
||
|
while (true) {
|
||
|
subtle::Atomic32 existing =
|
||
|
subtle::NoBarrier_CompareAndSwap(flags, bits, (bits | set) & ~clear);
|
||
|
if (existing == bits)
|
||
|
break;
|
||
|
bits = existing;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // 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::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::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(int profiler_id,
|
||
|
PlatformThreadId target,
|
||
|
const SamplingParams& params,
|
||
|
const CompletedCallback& callback,
|
||
|
WaitableEvent* finished,
|
||
|
std::unique_ptr<NativeStackSampler> sampler)
|
||
|
: profiler_id(profiler_id),
|
||
|
target(target),
|
||
|
params(params),
|
||
|
callback(callback),
|
||
|
finished(finished),
|
||
|
native_sampler(std::move(sampler)) {}
|
||
|
~CollectionContext() = default;
|
||
|
|
||
|
// An identifier for the profiler associated with this collection, used to
|
||
|
// uniquely identify the collection to outside interests.
|
||
|
const int profiler_id;
|
||
|
|
||
|
const PlatformThreadId target; // ID of The thread being sampled.
|
||
|
const SamplingParams params; // Information about how to sample.
|
||
|
const CompletedCallback callback; // Callback made when sampling complete.
|
||
|
WaitableEvent* const finished; // Signaled when all sampling complete.
|
||
|
|
||
|
// Platform-specific module that does the actual sampling.
|
||
|
std::unique_ptr<NativeStackSampler> native_sampler;
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
// Counters that indicate the current position along the acquisition.
|
||
|
int burst = 0;
|
||
|
int sample = 0;
|
||
|
|
||
|
// The collected stack samples. The active profile is always at the back().
|
||
|
CallStackProfiles profiles;
|
||
|
|
||
|
// Sequence number for generating new profiler ids.
|
||
|
static AtomicSequenceNumber next_profiler_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 an ID that can later be used to stop
|
||
|
// the sampling.
|
||
|
int Add(std::unique_ptr<CollectionContext> collection);
|
||
|
|
||
|
// Removes an active collection based on its ID, forcing it to run its
|
||
|
// callback if any data has been collected. This can be called externally
|
||
|
// from any thread.
|
||
|
void Remove(int 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 and reports collected data via callback. 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 flakyness in unit tests.
|
||
|
void FinishCollection(CollectionContext* collection);
|
||
|
|
||
|
// Records a single sample of a collection.
|
||
|
void RecordSample(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 id);
|
||
|
void PerformCollectionTask(int id);
|
||
|
void ShutdownTask(int add_events);
|
||
|
|
||
|
// Updates the |next_sample_time| time based on configured parameters.
|
||
|
// Returns true if there is a next sample or false if sampling is complete.
|
||
|
bool UpdateNextSampleTime(CollectionContext* collection);
|
||
|
|
||
|
// 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 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
|
||
|
// samething 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_profiler_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 id = collection->profiler_id;
|
||
|
scoped_refptr<SingleThreadTaskRunner> task_runner =
|
||
|
GetOrCreateTaskRunnerForAdd();
|
||
|
|
||
|
task_runner->PostTask(
|
||
|
FROM_HERE, BindOnce(&SamplingThread::AddCollectionTask, Unretained(this),
|
||
|
std::move(collection)));
|
||
|
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
void StackSamplingProfiler::SamplingThread::Remove(int 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), 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->profiler_id));
|
||
|
|
||
|
// If there is no duration for the final profile (because it was stopped),
|
||
|
// calculate it now.
|
||
|
if (!collection->profiles.empty() &&
|
||
|
collection->profiles.back().profile_duration == TimeDelta()) {
|
||
|
collection->profiles.back().profile_duration =
|
||
|
Time::Now() - collection->profile_start_time +
|
||
|
collection->params.sampling_interval;
|
||
|
}
|
||
|
|
||
|
// Extract some information so callback and event-signalling can still be
|
||
|
// done after the collection has been removed from the list of "active" ones.
|
||
|
// This allows the the controlling object (and tests using it) to be confident
|
||
|
// that collection is fully finished when those things occur.
|
||
|
const CompletedCallback callback = collection->callback;
|
||
|
CallStackProfiles profiles = std::move(collection->profiles);
|
||
|
WaitableEvent* finished = collection->finished;
|
||
|
|
||
|
// Run the associated callback, passing the collected profiles.
|
||
|
callback.Run(std::move(profiles));
|
||
|
|
||
|
// Signal that this collection is finished.
|
||
|
finished->Signal();
|
||
|
}
|
||
|
|
||
|
void StackSamplingProfiler::SamplingThread::RecordSample(
|
||
|
CollectionContext* collection) {
|
||
|
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
|
||
|
DCHECK(collection->native_sampler);
|
||
|
|
||
|
// If this is the first sample of a burst, a new Profile needs to be created
|
||
|
// and filled.
|
||
|
if (collection->sample == 0) {
|
||
|
collection->profiles.push_back(CallStackProfile());
|
||
|
CallStackProfile& profile = collection->profiles.back();
|
||
|
profile.sampling_period = collection->params.sampling_interval;
|
||
|
collection->profile_start_time = Time::Now();
|
||
|
collection->native_sampler->ProfileRecordingStarting(&profile.modules);
|
||
|
}
|
||
|
|
||
|
// The currently active profile being captured.
|
||
|
CallStackProfile& profile = collection->profiles.back();
|
||
|
|
||
|
// Record a single sample.
|
||
|
profile.samples.push_back(Sample());
|
||
|
collection->native_sampler->RecordStackSample(stack_buffer_.get(),
|
||
|
&profile.samples.back());
|
||
|
|
||
|
// If this is the last sample of a burst, record the total time.
|
||
|
if (collection->sample == collection->params.samples_per_burst - 1) {
|
||
|
profile.profile_duration = Time::Now() - collection->profile_start_time +
|
||
|
collection->params.sampling_interval;
|
||
|
collection->native_sampler->ProfileRecordingStopped(stack_buffer_.get());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 profiler_id = collection->profiler_id;
|
||
|
const TimeDelta initial_delay = collection->params.initial_delay;
|
||
|
|
||
|
active_collections_.insert(
|
||
|
std::make_pair(profiler_id, std::move(collection)));
|
||
|
|
||
|
GetTaskRunnerOnSamplingThread()->PostDelayedTask(
|
||
|
FROM_HERE,
|
||
|
BindOnce(&SamplingThread::PerformCollectionTask, Unretained(this),
|
||
|
profiler_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 id) {
|
||
|
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
|
||
|
|
||
|
auto found = active_collections_.find(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(id);
|
||
|
DCHECK_EQ(1U, count);
|
||
|
|
||
|
FinishCollection(collection.get());
|
||
|
ScheduleShutdownIfIdle();
|
||
|
}
|
||
|
|
||
|
void StackSamplingProfiler::SamplingThread::PerformCollectionTask(int id) {
|
||
|
DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
|
||
|
|
||
|
auto found = active_collections_.find(id);
|
||
|
|
||
|
// The task won't be found if it has been stopped.
|
||
|
if (found == active_collections_.end())
|
||
|
return;
|
||
|
|
||
|
CollectionContext* collection = found->second.get();
|
||
|
|
||
|
// Handle first-run with no "next time".
|
||
|
if (collection->next_sample_time == Time())
|
||
|
collection->next_sample_time = Time::Now();
|
||
|
|
||
|
// Do the collection of a single sample.
|
||
|
RecordSample(collection);
|
||
|
|
||
|
// Update the time of the next sample recording.
|
||
|
const bool collection_finished = !UpdateNextSampleTime(collection);
|
||
|
if (!collection_finished) {
|
||
|
bool success = GetTaskRunnerOnSamplingThread()->PostDelayedTask(
|
||
|
FROM_HERE,
|
||
|
BindOnce(&SamplingThread::PerformCollectionTask, Unretained(this), id),
|
||
|
std::max(collection->next_sample_time - Time::Now(), TimeDelta()));
|
||
|
DCHECK(success);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Take ownership of |collection| and remove it from the map. If collection is
|
||
|
// to be restarted, a new collection task will be added below.
|
||
|
std::unique_ptr<CollectionContext> owned_collection =
|
||
|
std::move(found->second);
|
||
|
size_t count = active_collections_.erase(id);
|
||
|
DCHECK_EQ(1U, count);
|
||
|
|
||
|
// All capturing has completed so finish the collection.
|
||
|
FinishCollection(collection);
|
||
|
ScheduleShutdownIfIdle();
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
bool StackSamplingProfiler::SamplingThread::UpdateNextSampleTime(
|
||
|
CollectionContext* collection) {
|
||
|
// 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.
|
||
|
if (++collection->sample < collection->params.samples_per_burst) {
|
||
|
collection->next_sample_time += collection->params.sampling_interval;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (++collection->burst < collection->params.bursts) {
|
||
|
collection->sample = 0;
|
||
|
collection->next_sample_time += collection->params.burst_interval;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
ResetAnnotations();
|
||
|
}
|
||
|
|
||
|
// static
|
||
|
void StackSamplingProfiler::TestAPI::ResetAnnotations() {
|
||
|
subtle::NoBarrier_Store(&process_milestones_, 0u);
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
subtle::Atomic32 StackSamplingProfiler::process_milestones_ = 0;
|
||
|
|
||
|
StackSamplingProfiler::StackSamplingProfiler(
|
||
|
const SamplingParams& params,
|
||
|
const CompletedCallback& callback,
|
||
|
NativeStackSamplerTestDelegate* test_delegate)
|
||
|
: StackSamplingProfiler(base::PlatformThread::CurrentId(),
|
||
|
params,
|
||
|
callback,
|
||
|
test_delegate) {}
|
||
|
|
||
|
StackSamplingProfiler::StackSamplingProfiler(
|
||
|
PlatformThreadId thread_id,
|
||
|
const SamplingParams& params,
|
||
|
const CompletedCallback& callback,
|
||
|
NativeStackSamplerTestDelegate* test_delegate)
|
||
|
: thread_id_(thread_id),
|
||
|
params_(params),
|
||
|
completed_callback_(callback),
|
||
|
// 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_(NULL_PROFILER_ID),
|
||
|
test_delegate_(test_delegate) {}
|
||
|
|
||
|
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() {
|
||
|
if (completed_callback_.is_null())
|
||
|
return;
|
||
|
|
||
|
std::unique_ptr<NativeStackSampler> native_sampler =
|
||
|
NativeStackSampler::Create(thread_id_, &RecordAnnotations,
|
||
|
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(NULL_PROFILER_ID, profiler_id_);
|
||
|
profiler_id_ = SamplingThread::GetInstance()->Add(
|
||
|
std::make_unique<SamplingThread::CollectionContext>(
|
||
|
SamplingThread::CollectionContext::next_profiler_id.GetNext(),
|
||
|
thread_id_, params_, completed_callback_, &profiling_inactive_,
|
||
|
std::move(native_sampler)));
|
||
|
DCHECK_NE(NULL_PROFILER_ID, profiler_id_);
|
||
|
}
|
||
|
|
||
|
void StackSamplingProfiler::Stop() {
|
||
|
SamplingThread::GetInstance()->Remove(profiler_id_);
|
||
|
profiler_id_ = NULL_PROFILER_ID;
|
||
|
}
|
||
|
|
||
|
// static
|
||
|
void StackSamplingProfiler::SetProcessMilestone(int milestone) {
|
||
|
DCHECK_LE(0, milestone);
|
||
|
DCHECK_GT(static_cast<int>(sizeof(process_milestones_) * 8), milestone);
|
||
|
DCHECK_EQ(0, subtle::NoBarrier_Load(&process_milestones_) & (1 << milestone));
|
||
|
ChangeAtomicFlags(&process_milestones_, 1 << milestone, 0);
|
||
|
}
|
||
|
|
||
|
// static
|
||
|
void StackSamplingProfiler::RecordAnnotations(Sample* sample) {
|
||
|
// The code inside this method must not do anything that could acquire a
|
||
|
// mutex, including allocating memory (which includes LOG messages) because
|
||
|
// that mutex could be held by a stopped thread, thus resulting in deadlock.
|
||
|
sample->process_milestones = subtle::NoBarrier_Load(&process_milestones_);
|
||
|
}
|
||
|
|
||
|
// 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 true;
|
||
|
if (a.process_milestones > b.process_milestones)
|
||
|
return false;
|
||
|
|
||
|
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) {
|
||
|
return (a.module_index < b.module_index) ||
|
||
|
(a.module_index == b.module_index &&
|
||
|
a.instruction_pointer < b.instruction_pointer);
|
||
|
}
|
||
|
|
||
|
} // namespace base
|