diff options
author | wittman <wittman@chromium.org> | 2015-04-07 13:33:04 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-07 20:34:37 +0000 |
commit | 8601b54d3c7aab521ec920e04177f1f3f132f924 (patch) | |
tree | fdc19e3dd4c75fe6187917fbfc3809dcb33bb040 | |
parent | a2c7d0df0f3630fe4a91370df2273ebc6b4c8c49 (diff) | |
download | chromium_src-8601b54d3c7aab521ec920e04177f1f3f132f924.zip chromium_src-8601b54d3c7aab521ec920e04177f1f3f132f924.tar.gz chromium_src-8601b54d3c7aab521ec920e04177f1f3f132f924.tar.bz2 |
StackSamplingProfiler clean up
Refine interfaces, implementation, formatting and comments according to the C++
style guide and Chrome coding standards. Behavior is unaltered except for a few
edge cases and Win32 API error handling.
This change is done as part of the readability review process. Original review:
https://crrev.com/1016563004.
Review URL: https://codereview.chromium.org/1030923002
Cr-Commit-Position: refs/heads/master@{#324107}
-rw-r--r-- | base/BUILD.gn | 2 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/profiler/native_stack_sampler.cc | 13 | ||||
-rw-r--r-- | base/profiler/native_stack_sampler.h | 50 | ||||
-rw-r--r-- | base/profiler/stack_sampling_profiler.cc | 182 | ||||
-rw-r--r-- | base/profiler/stack_sampling_profiler.h | 182 | ||||
-rw-r--r-- | base/profiler/stack_sampling_profiler_posix.cc | 6 | ||||
-rw-r--r-- | base/profiler/stack_sampling_profiler_unittest.cc | 122 | ||||
-rw-r--r-- | base/profiler/stack_sampling_profiler_win.cc | 324 | ||||
-rw-r--r-- | components/metrics/call_stack_profile_metrics_provider.cc | 10 | ||||
-rw-r--r-- | components/metrics/call_stack_profile_metrics_provider.h | 6 | ||||
-rw-r--r-- | components/metrics/call_stack_profile_metrics_provider_unittest.cc | 14 |
12 files changed, 503 insertions, 410 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn index df06d24..8a942ee 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -379,6 +379,8 @@ component("base") { "power_monitor/power_observer.h", "profiler/alternate_timer.cc", "profiler/alternate_timer.h", + "profiler/native_stack_sampler.cc", + "profiler/native_stack_sampler.h", "profiler/scoped_profile.cc", "profiler/scoped_profile.h", "profiler/scoped_tracker.cc", diff --git a/base/base.gypi b/base/base.gypi index fd63f6d..d95062c 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -487,6 +487,8 @@ 'process/process_win.cc', 'profiler/alternate_timer.cc', 'profiler/alternate_timer.h', + 'profiler/native_stack_sampler.cc', + 'profiler/native_stack_sampler.h', 'profiler/scoped_profile.cc', 'profiler/scoped_profile.h', 'profiler/scoped_tracker.cc', diff --git a/base/profiler/native_stack_sampler.cc b/base/profiler/native_stack_sampler.cc new file mode 100644 index 0000000..8b4731b --- /dev/null +++ b/base/profiler/native_stack_sampler.cc @@ -0,0 +1,13 @@ +// 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/native_stack_sampler.h" + +namespace base { + +NativeStackSampler::NativeStackSampler() {} + +NativeStackSampler::~NativeStackSampler() {} + +} // namespace base diff --git a/base/profiler/native_stack_sampler.h b/base/profiler/native_stack_sampler.h new file mode 100644 index 0000000..bc170dc --- /dev/null +++ b/base/profiler/native_stack_sampler.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef BASE_PROFILER_NATIVE_STACK_SAMPLER_H_ +#define BASE_PROFILER_NATIVE_STACK_SAMPLER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/profiler/stack_sampling_profiler.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// NativeStackSampler is an implementation detail of StackSamplingProfiler. It +// abstracts the native implementation required to record a stack sample for a +// given thread. +class NativeStackSampler { + public: + virtual ~NativeStackSampler(); + + // Creates a stack sampler that records samples for |thread_handle|. Returns + // null if this platform does not support stack sampling. + static scoped_ptr<NativeStackSampler> Create(PlatformThreadId thread_id); + + // The following functions are all called on the SamplingThread (not the + // thread being sampled). + + // Notifies the sampler that we're starting to record a new profile. Modules + // shared across samples in the profile should be recorded in |modules|. + virtual void ProfileRecordingStarting( + std::vector<StackSamplingProfiler::Module>* modules) = 0; + + // Records a stack sample to |sample|. + virtual void RecordStackSample(StackSamplingProfiler::Sample* sample) = 0; + + // Notifies the sampler that we've stopped recording the current + // profile. + virtual void ProfileRecordingStopped() = 0; + + protected: + NativeStackSampler(); + + private: + DISALLOW_COPY_AND_ASSIGN(NativeStackSampler); +}; + +} // namespace base + +#endif // BASE_PROFILER_NATIVE_STACK_SAMPLER_H_ + diff --git a/base/profiler/stack_sampling_profiler.cc b/base/profiler/stack_sampling_profiler.cc index f760507..317400b 100644 --- a/base/profiler/stack_sampling_profiler.cc +++ b/base/profiler/stack_sampling_profiler.cc @@ -9,33 +9,41 @@ #include "base/bind.h" #include "base/callback.h" #include "base/memory/singleton.h" +#include "base/profiler/native_stack_sampler.h" #include "base/synchronization/lock.h" -#include "base/synchronization/waitable_event.h" #include "base/timer/elapsed_timer.h" -template <typename T> struct DefaultSingletonTraits; - namespace base { +// PendingProfiles ------------------------------------------------------------ + namespace { -// Thread-safe singleton class that stores collected profiles waiting to be -// processed. +// Thread-safe singleton class that stores collected call stack profiles waiting +// to be processed. class PendingProfiles { public: - PendingProfiles(); ~PendingProfiles(); static PendingProfiles* GetInstance(); - // Appends |profiles|. This function is thread safe. - void PutProfiles(const std::vector<StackSamplingProfiler::Profile>& profiles); - // Gets the pending profiles into *|profiles|. This function is thread safe. - void GetProfiles(std::vector<StackSamplingProfiler::Profile>* profiles); + // Appends |profiles| to |profiles_|. This function may be called on any + // thread. + void AppendProfiles( + const std::vector<StackSamplingProfiler::CallStackProfile>& profiles); + + // Copies the pending profiles from |profiles_| into |profiles|, and clears + // |profiles_|. This function may be called on any thread. + void GetAndClearPendingProfiles( + std::vector<StackSamplingProfiler::CallStackProfile>* profiles); private: + friend struct DefaultSingletonTraits<PendingProfiles>; + + PendingProfiles(); + Lock profiles_lock_; - std::vector<StackSamplingProfiler::Profile> profiles_; + std::vector<StackSamplingProfiler::CallStackProfile> profiles_; DISALLOW_COPY_AND_ASSIGN(PendingProfiles); }; @@ -49,21 +57,24 @@ PendingProfiles* PendingProfiles::GetInstance() { return Singleton<PendingProfiles>::get(); } -void PendingProfiles::PutProfiles( - const std::vector<StackSamplingProfiler::Profile>& profiles) { +void PendingProfiles::AppendProfiles( + const std::vector<StackSamplingProfiler::CallStackProfile>& profiles) { AutoLock scoped_lock(profiles_lock_); profiles_.insert(profiles_.end(), profiles.begin(), profiles.end()); } -void PendingProfiles::GetProfiles( - std::vector<StackSamplingProfiler::Profile>* profiles) { +void PendingProfiles::GetAndClearPendingProfiles( + std::vector<StackSamplingProfiler::CallStackProfile>* profiles) { profiles->clear(); AutoLock scoped_lock(profiles_lock_); profiles_.swap(*profiles); } + } // namespace +// StackSamplingProfiler::Module ---------------------------------------------- + StackSamplingProfiler::Module::Module() : base_address(nullptr) {} StackSamplingProfiler::Module::Module(const void* base_address, const std::string& id, @@ -72,60 +83,28 @@ StackSamplingProfiler::Module::Module(const void* base_address, StackSamplingProfiler::Module::~Module() {} -StackSamplingProfiler::Frame::Frame() - : instruction_pointer(nullptr), - module_index(-1) {} +// StackSamplingProfiler::Frame ----------------------------------------------- StackSamplingProfiler::Frame::Frame(const void* instruction_pointer, - int module_index) + size_t module_index) : instruction_pointer(instruction_pointer), module_index(module_index) {} StackSamplingProfiler::Frame::~Frame() {} -StackSamplingProfiler::Profile::Profile() : preserve_sample_ordering(false) {} - -StackSamplingProfiler::Profile::~Profile() {} - -class StackSamplingProfiler::SamplingThread : public PlatformThread::Delegate { - public: - // Samples stacks using |native_sampler|. When complete, invokes - // |profiles_callback| with the collected profiles. |profiles_callback| must - // be thread-safe and may consume the contents of the vector. - SamplingThread( - scoped_ptr<NativeStackSampler> native_sampler, - const SamplingParams& params, - Callback<void(const std::vector<Profile>&)> completed_callback); - ~SamplingThread() override; - - // Implementation of PlatformThread::Delegate: - void ThreadMain() override; - - void Stop(); - - private: - // Collects a profile from a single burst. Returns true if the profile was - // collected, or false if collection was stopped before it completed. - bool CollectProfile(Profile* profile, TimeDelta* elapsed_time); - // Collects profiles from all bursts, or until the sampling is stopped. If - // stopped before complete, |profiles| will contains only full bursts. - void CollectProfiles(std::vector<Profile>* profiles); - - scoped_ptr<NativeStackSampler> native_sampler_; - - const SamplingParams params_; +// StackSamplingProfiler::CallStackProfile ------------------------------------ - WaitableEvent stop_event_; +StackSamplingProfiler::CallStackProfile::CallStackProfile() + : preserve_sample_ordering(false) {} - Callback<void(const std::vector<Profile>&)> completed_callback_; +StackSamplingProfiler::CallStackProfile::~CallStackProfile() {} - DISALLOW_COPY_AND_ASSIGN(SamplingThread); -}; +// StackSamplingProfiler::SamplingThread -------------------------------------- StackSamplingProfiler::SamplingThread::SamplingThread( scoped_ptr<NativeStackSampler> native_sampler, const SamplingParams& params, - Callback<void(const std::vector<Profile>&)> completed_callback) + CompletedCallback completed_callback) : native_sampler_(native_sampler.Pass()), params_(params), stop_event_(false, false), @@ -137,61 +116,77 @@ StackSamplingProfiler::SamplingThread::~SamplingThread() {} void StackSamplingProfiler::SamplingThread::ThreadMain() { PlatformThread::SetName("Chrome_SamplingProfilerThread"); - std::vector<Profile> profiles; + CallStackProfiles profiles; CollectProfiles(&profiles); completed_callback_.Run(profiles); } +// Depending on how long the sampling takes and the length of the sampling +// interval, a burst of samples could take arbitrarily longer than +// samples_per_burst * sampling_interval. In this case, we (somewhat +// arbitrarily) honor the number of samples requested rather than strictly +// adhering to the sampling intervals. Once we have established users for the +// StackSamplingProfiler and the collected data to judge, we may go the other +// way or make this behavior configurable. bool StackSamplingProfiler::SamplingThread::CollectProfile( - Profile* profile, + CallStackProfile* profile, TimeDelta* elapsed_time) { ElapsedTimer profile_timer; - Profile current_profile; - native_sampler_->ProfileRecordingStarting(¤t_profile); + CallStackProfile current_profile; + native_sampler_->ProfileRecordingStarting(¤t_profile.modules); current_profile.sampling_period = params_.sampling_interval; - bool stopped_early = false; + bool burst_completed = true; + TimeDelta previous_elapsed_sample_time; for (int i = 0; i < params_.samples_per_burst; ++i) { - ElapsedTimer sample_timer; - current_profile.samples.push_back(Sample()); - native_sampler_->RecordStackSample(¤t_profile.samples.back()); - TimeDelta elapsed_sample_time = sample_timer.Elapsed(); - if (i != params_.samples_per_burst - 1) { + if (i != 0) { + // Always wait, even if for 0 seconds, so we can observe a signal on + // stop_event_. if (stop_event_.TimedWait( - std::max(params_.sampling_interval - elapsed_sample_time, + std::max(params_.sampling_interval - previous_elapsed_sample_time, TimeDelta()))) { - stopped_early = true; + burst_completed = false; break; } } + ElapsedTimer sample_timer; + current_profile.samples.push_back(Sample()); + native_sampler_->RecordStackSample(¤t_profile.samples.back()); + previous_elapsed_sample_time = sample_timer.Elapsed(); } *elapsed_time = profile_timer.Elapsed(); current_profile.profile_duration = *elapsed_time; native_sampler_->ProfileRecordingStopped(); - if (!stopped_early) + if (burst_completed) *profile = current_profile; - return !stopped_early; + return burst_completed; } +// In an analogous manner to CollectProfile() and samples exceeding the expected +// total sampling time, bursts may also exceed the burst_interval. We adopt the +// same wait-and-see approach here. void StackSamplingProfiler::SamplingThread::CollectProfiles( - std::vector<Profile>* profiles) { + CallStackProfiles* profiles) { if (stop_event_.TimedWait(params_.initial_delay)) return; + TimeDelta previous_elapsed_profile_time; for (int i = 0; i < params_.bursts; ++i) { - Profile profile; - TimeDelta elapsed_profile_time; - if (CollectProfile(&profile, &elapsed_profile_time)) - profiles->push_back(profile); - else - return; + if (i != 0) { + // Always wait, even if for 0 seconds, so we can observe a signal on + // stop_event_. + if (stop_event_.TimedWait( + std::max(params_.burst_interval - previous_elapsed_profile_time, + TimeDelta()))) + return; + } - if (stop_event_.TimedWait( - std::max(params_.burst_interval - elapsed_profile_time, - TimeDelta()))) + CallStackProfile profile; + if (!CollectProfile(&profile, &previous_elapsed_profile_time)) return; + profiles->push_back(profile); } } @@ -199,14 +194,7 @@ void StackSamplingProfiler::SamplingThread::Stop() { stop_event_.Signal(); } -void StackSamplingProfiler::SamplingThreadDeleter::operator()( - SamplingThread* thread) const { - delete thread; -} - -StackSamplingProfiler::NativeStackSampler::NativeStackSampler() {} - -StackSamplingProfiler::NativeStackSampler::~NativeStackSampler() {} +// StackSamplingProfiler ------------------------------------------------------ StackSamplingProfiler::SamplingParams::SamplingParams() : initial_delay(TimeDelta::FromMilliseconds(0)), @@ -224,19 +212,20 @@ StackSamplingProfiler::StackSamplingProfiler(PlatformThreadId thread_id, StackSamplingProfiler::~StackSamplingProfiler() {} void StackSamplingProfiler::Start() { - native_sampler_ = NativeStackSampler::Create(thread_id_); - if (!native_sampler_) + scoped_ptr<NativeStackSampler> native_sampler = + NativeStackSampler::Create(thread_id_); + if (!native_sampler) return; sampling_thread_.reset( new SamplingThread( - native_sampler_.Pass(), params_, + native_sampler.Pass(), params_, (custom_completed_callback_.is_null() ? - Bind(&PendingProfiles::PutProfiles, + Bind(&PendingProfiles::AppendProfiles, Unretained(PendingProfiles::GetInstance())) : custom_completed_callback_))); if (!PlatformThread::CreateNonJoinable(0, sampling_thread_.get())) - LOG(ERROR) << "failed to create thread"; + sampling_thread_.reset(); } void StackSamplingProfiler::Stop() { @@ -245,14 +234,11 @@ void StackSamplingProfiler::Stop() { } // static -void StackSamplingProfiler::GetPendingProfiles(std::vector<Profile>* profiles) { - PendingProfiles::GetInstance()->GetProfiles(profiles); +void StackSamplingProfiler::GetPendingProfiles(CallStackProfiles* profiles) { + PendingProfiles::GetInstance()->GetAndClearPendingProfiles(profiles); } -void StackSamplingProfiler::SetCustomCompletedCallback( - Callback<void(const std::vector<Profile>&)> callback) { - custom_completed_callback_ = callback; -} +// StackSamplingProfiler::Frame global functions ------------------------------ bool operator==(const StackSamplingProfiler::Frame &a, const StackSamplingProfiler::Frame &b) { diff --git a/base/profiler/stack_sampling_profiler.h b/base/profiler/stack_sampling_profiler.h index 4438786..bea266d 100644 --- a/base/profiler/stack_sampling_profiler.h +++ b/base/profiler/stack_sampling_profiler.h @@ -13,17 +13,20 @@ #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" +#include "base/synchronization/waitable_event.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" namespace base { +class NativeStackSampler; + // StackSamplingProfiler periodically stops a thread to sample its stack, for // the purpose of collecting information about which code paths are // executing. This information is used in aggregate by UMA to identify hot // and/or janky code paths. // -// Sample StackStackSamplingProfiler usage: +// Sample StackSamplingProfiler usage: // // // Create and customize params as desired. // base::StackStackSamplingProfiler::SamplingParams params; @@ -31,9 +34,9 @@ namespace base { // base::StackSamplingProfiler profiler(base::PlatformThread::CurrentId()), // params); // -// // To process the profiles within Chrome rather than via UMA, set a custom -// // completed callback: -// base::Callback<void(const std::vector<Profile>&)> +// // To process the call stack profiles within Chrome rather than via UMA, +// // set a custom completed callback: +// base::StackStackSamplingProfiler::CompletedCallback // thread_safe_callback = ...; // profiler.SetCustomCompletedCallback(thread_safe_callback); // @@ -41,13 +44,24 @@ namespace base { // // ... work being done on the target thread here ... // profiler.Stop(); // optional, stops collection before complete per params // -// When all profiles are complete or the profiler is stopped, if the custom -// completed callback was set it will be called from the profiler thread with -// the completed profiles. If no callback was set, the profiles are stored -// internally and retrieved for UMA through -// GetPendingProfiles(). GetPendingProfiles() should never be called by other -// code; to retrieve profiles for in-process processing, set a completed -// callback. +// The default SamplingParams causes stacks to be recorded in a single burst at +// a 10Hz interval for a total of 30 seconds. All of these parameters may be +// altered as desired. +// +// When all call stack profiles are complete or the profiler is stopped, if the +// custom completed callback was set it is called from the profiler thread with +// the completed profiles. A profile is considered complete if all requested +// samples were recorded for the profile (i.e. it was not stopped +// prematurely). If no callback was set, the completed profiles are stored +// internally and retrieved for UMA through GetPendingProfiles(). +// GetPendingProfiles() should never be called by other code; to retrieve +// profiles for in-process processing, set a completed callback. +// +// The results of the profiling are passed to the completed callback and consist +// of a vector of CallStackProfiles. Each CallStackProfile corresponds to a +// burst as specified in SamplingParams and contains a set of Samples and +// Modules. One Sample corresponds to a single recorded stack, and the Modules +// record those modules associated with the recorded stack frames. class BASE_EXPORT StackSamplingProfiler { public: // Module represents the module (DLL or exe) corresponding to a stack frame. @@ -59,6 +73,7 @@ class BASE_EXPORT StackSamplingProfiler { // Points to the base address of the module. const void* base_address; + // An opaque binary string that uniquely identifies a particular program // version with high probability. This is parsed from headers of the loaded // module. @@ -67,69 +82,50 @@ class BASE_EXPORT StackSamplingProfiler { // On Windows: // GUID + AGE in the debug image headers of a module. std::string id; + // The filename of the module. FilePath filename; }; // Frame represents an individual sampled stack frame with module information. struct BASE_EXPORT Frame { - Frame(); - Frame(const void* instruction_pointer, int module_index); + // Identifies an unknown module. + static const size_t kUnknownModuleIndex = static_cast<size_t>(-1); + + Frame(const void* instruction_pointer, size_t module_index); ~Frame(); // The sampled instruction pointer within the function. const void* instruction_pointer; - // Index of the module in the array of modules. We don't represent module - // state directly here to save space. - int module_index; + + // Index of the module in CallStackProfile::modules. We don't represent + // module state directly here to save space. + size_t module_index; }; // Sample represents a set of stack frames. using Sample = std::vector<Frame>; - // Profile represents a set of samples. - struct BASE_EXPORT Profile { - Profile(); - ~Profile(); + // CallStackProfile represents a set of samples. + struct BASE_EXPORT CallStackProfile { + CallStackProfile(); + ~CallStackProfile(); std::vector<Module> modules; std::vector<Sample> samples; + // Duration of this profile. TimeDelta profile_duration; + // Time between samples. TimeDelta sampling_period; + // True if sample ordering is important and should be preserved if and when // this profile is compressed and processed. bool preserve_sample_ordering; }; - // NativeStackSampler abstracts the native implementation required to record a - // stack sample for a given thread. - class NativeStackSampler { - public: - virtual ~NativeStackSampler(); - - // Create a stack sampler that records samples for |thread_handle|. Returns - // null if this platform does not support stack sampling. - static scoped_ptr<NativeStackSampler> Create(PlatformThreadId thread_id); - - // Notify the sampler that we're starting to record a new profile. This - // function is called on the SamplingThread. - virtual void ProfileRecordingStarting(Profile* profile) = 0; - - // Record a stack sample. This function is called on the SamplingThread. - virtual void RecordStackSample(Sample* sample) = 0; - - // Notify the sampler that we've stopped recording the current profile. This - // function is called on the SamplingThread. - virtual void ProfileRecordingStopped() = 0; - - protected: - NativeStackSampler(); - - private: - DISALLOW_COPY_AND_ASSIGN(NativeStackSampler); - }; + using CallStackProfiles = std::vector<CallStackProfile>; // Represents parameters that configure the sampling. struct BASE_EXPORT SamplingParams { @@ -137,51 +133,102 @@ class BASE_EXPORT StackSamplingProfiler { // Time to delay before first samples are taken. Defaults to 0. TimeDelta initial_delay; + // Number of sampling bursts to perform. Defaults to 1. int bursts; + // Interval between sampling bursts. This is the desired duration from the // start of one burst to the start of the next burst. Defaults to 10s. TimeDelta burst_interval; + // Number of samples to record per burst. Defaults to 300. int samples_per_burst; + // Interval between samples during a sampling burst. This is the desired - // duration from the start of one burst to the start of the next - // burst. Defaults to 100ms. + // duration from the start of one sample to the start of the next + // sample. Defaults to 100ms. TimeDelta sampling_interval; + // True if sample ordering is important and should be preserved if and when // this profile is compressed and processed. Defaults to false. bool preserve_sample_ordering; }; + // The callback type used to collect completed profiles. + // + // IMPORTANT NOTE: the callback is invoked on a thread the profiler + // constructs, rather than on the thread used to construct the profiler and + // set the callback, and thus the callback must be callable on any thread. For + // threads with message loops that create StackSamplingProfilers, posting a + // task to the message loop with a copy of the profiles is the recommended + // thread-safe callback implementation. + using CompletedCallback = Callback<void(const CallStackProfiles&)>; + StackSamplingProfiler(PlatformThreadId thread_id, const SamplingParams& params); ~StackSamplingProfiler(); // Initializes the profiler and starts sampling. void Start(); + // Stops the profiler and any ongoing sampling. Calling this function is - // optional; if not invoked profiling will terminate when all the profiling - // bursts specified in the SamplingParams are completed. + // optional; if not invoked profiling terminates when all the profiling bursts + // specified in the SamplingParams are completed. void Stop(); - // Gets the pending profiles into *|profiles| and clears the internal - // storage. This function is thread safe. + // Moves all pending call stack profiles from internal storage to + // |profiles|. This function is thread safe. // // ***This is intended for use only by UMA.*** Callers who want to process the // collected profiles should use SetCustomCompletedCallback. - static void GetPendingProfiles(std::vector<Profile>* profiles); + static void GetPendingProfiles(CallStackProfiles* call_stack_profiles); - // By default, collected profiles are stored internally and can be retrieved - // by GetPendingProfiles. If a callback is provided via this function, - // however, it will be called with the collected profiles instead. Note that - // this call to the callback occurs *on the profiler thread*. - void SetCustomCompletedCallback( - Callback<void(const std::vector<Profile>&)> callback); + // By default, collected call stack profiles are stored internally and can be + // retrieved by GetPendingProfiles. If a callback is provided via this + // function, however, it is called with the collected profiles instead. + void set_custom_completed_callback(CompletedCallback callback) { + custom_completed_callback_ = callback; + } private: - class SamplingThread; - struct SamplingThreadDeleter { - void operator() (SamplingThread* thread) const; + // SamplingThread is a separate thread used to suspend and sample stacks from + // the target thread. + class SamplingThread : public PlatformThread::Delegate { + public: + // Samples stacks using |native_sampler|. When complete, invokes + // |completed_callback| with the collected call stack profiles. + // |completed_callback| must be callable on any thread. + SamplingThread(scoped_ptr<NativeStackSampler> native_sampler, + const SamplingParams& params, + CompletedCallback completed_callback); + ~SamplingThread() override; + + // PlatformThread::Delegate: + void ThreadMain() override; + + void Stop(); + + private: + // Collects a call stack profile from a single burst. Returns true if the + // profile was collected, or false if collection was stopped before it + // completed. + bool CollectProfile(CallStackProfile* profile, TimeDelta* elapsed_time); + + // Collects call stack profiles from all bursts, or until the sampling is + // stopped. If stopped before complete, |call_stack_profiles| will contain + // only full bursts. + void CollectProfiles(CallStackProfiles* profiles); + + scoped_ptr<NativeStackSampler> native_sampler_; + const SamplingParams params_; + + // If Stop() is called, it signals this event to force the sampling to + // terminate before all the samples specified in |params_| are collected. + WaitableEvent stop_event_; + + CompletedCallback completed_callback_; + + DISALLOW_COPY_AND_ASSIGN(SamplingThread); }; // The thread whose stack will be sampled. @@ -189,18 +236,17 @@ class BASE_EXPORT StackSamplingProfiler { const SamplingParams params_; - scoped_ptr<SamplingThread, SamplingThreadDeleter> sampling_thread_; - scoped_ptr<NativeStackSampler> native_sampler_; + scoped_ptr<SamplingThread> sampling_thread_; - Callback<void(const std::vector<Profile>&)> custom_completed_callback_; + CompletedCallback custom_completed_callback_; DISALLOW_COPY_AND_ASSIGN(StackSamplingProfiler); }; -// Defined to allow equality check of Samples. +// The metrics provider code wants to put Samples in a map and compare them, +// which requires us to define a few operators. BASE_EXPORT bool operator==(const StackSamplingProfiler::Frame& a, const StackSamplingProfiler::Frame& b); -// Defined to allow ordering of Samples. BASE_EXPORT bool operator<(const StackSamplingProfiler::Frame& a, const StackSamplingProfiler::Frame& b); diff --git a/base/profiler/stack_sampling_profiler_posix.cc b/base/profiler/stack_sampling_profiler_posix.cc index 6a44d7e..bce37e1 100644 --- a/base/profiler/stack_sampling_profiler_posix.cc +++ b/base/profiler/stack_sampling_profiler_posix.cc @@ -2,12 +2,12 @@ // 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 "base/profiler/native_stack_sampler.h" namespace base { -scoped_ptr<StackSamplingProfiler::NativeStackSampler> -StackSamplingProfiler::NativeStackSampler::Create(PlatformThreadId thread_id) { +scoped_ptr<NativeStackSampler> NativeStackSampler::Create( + PlatformThreadId thread_id) { return scoped_ptr<NativeStackSampler>(); } diff --git a/base/profiler/stack_sampling_profiler_unittest.cc b/base/profiler/stack_sampling_profiler_unittest.cc index ad9e926..5bfc2d4 100644 --- a/base/profiler/stack_sampling_profiler_unittest.cc +++ b/base/profiler/stack_sampling_profiler_unittest.cc @@ -2,12 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <sstream> - #include "base/bind.h" #include "base/compiler_specific.h" #include "base/path_service.h" #include "base/profiler/stack_sampling_profiler.h" +#include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" @@ -18,27 +17,32 @@ namespace base { using Frame = StackSamplingProfiler::Frame; using Module = StackSamplingProfiler::Module; using Sample = StackSamplingProfiler::Sample; -using Profile = StackSamplingProfiler::Profile; +using CallStackProfile = StackSamplingProfiler::CallStackProfile; namespace { + // A thread to target for profiling, whose stack is guaranteed to contain // SignalAndWaitUntilSignaled() when coordinated with the main thread. class TargetThread : public PlatformThread::Delegate { public: TargetThread(); - // Implementation of PlatformThread::Delegate: + // PlatformThread::Delegate: void ThreadMain() override; - // Wait for the thread to have started and be executing in + // Waits for the thread to have started and be executing in // SignalAndWaitUntilSignaled(). void WaitForThreadStart(); - // Allow the thread to return from SignalAndWaitUntilSignaled() and finish + + // Allows the thread to return from SignalAndWaitUntilSignaled() and finish // execution. void SignalThreadToFinish(); // This function is guaranteed to be executing between calls to - // WaitForThreadStart() and SignalThreadToFinish(). + // WaitForThreadStart() and SignalThreadToFinish(). This function is static so + // that we can get a straightforward address for it in one of the tests below, + // rather than dealing with the complexity of a member function pointer + // representation. static void SignalAndWaitUntilSignaled(WaitableEvent* thread_started_event, WaitableEvent* finish_event); @@ -84,18 +88,19 @@ NOINLINE void TargetThread::SignalAndWaitUntilSignaled( // Called on the profiler thread when complete. Collects profiles produced by // the profiler, and signals an event to allow the main thread to know that that // the profiler is done. -void SaveProfilesAndSignalEvent(std::vector<Profile>* profiles, - WaitableEvent* event, - const std::vector<Profile>& pending_profiles) { +void SaveProfilesAndSignalEvent( + std::vector<CallStackProfile>* profiles, + WaitableEvent* event, + const std::vector<CallStackProfile>& pending_profiles) { *profiles = pending_profiles; event->Signal(); } -// Captures profiles as specified by |params| on the TargetThread, and returns -// them in |profiles|. Waits up to |profiler_wait_time| for the profiler to -// complete. +// Captures call stack profiles as specified by |params| on the TargetThread, +// and returns them in |profiles|. Waits up to |profiler_wait_time| for the +// profiler to complete. void CaptureProfiles(const StackSamplingProfiler::SamplingParams& params, - std::vector<Profile>* profiles, + std::vector<CallStackProfile>* profiles, TimeDelta profiler_wait_time) { TargetThread target_thread; PlatformThreadHandle target_thread_handle; @@ -106,7 +111,7 @@ void CaptureProfiles(const StackSamplingProfiler::SamplingParams& params, WaitableEvent sampling_thread_completed(true, false); profiles->clear(); StackSamplingProfiler profiler(target_thread.id(), params); - profiler.SetCustomCompletedCallback( + profiler.set_custom_completed_callback( Bind(&SaveProfilesAndSignalEvent, Unretained(profiles), Unretained(&sampling_thread_completed))); profiler.Start(); @@ -122,8 +127,9 @@ void CaptureProfiles(const StackSamplingProfiler::SamplingParams& params, // If this executable was linked with /INCREMENTAL (the default for non-official // debug and release builds on Windows), function addresses do not correspond to // function code itself, but instead to instructions in the Incremental Link -// Table that jump to the functions. Check for a jump instruction and if present -// do a little decompilation to find the function's actual starting address. +// Table that jump to the functions. Checks for a jump instruction and if +// present does a little decompilation to find the function's actual starting +// address. const void* MaybeFixupFunctionAddressForILT(const void* function_address) { #if defined(_WIN64) const unsigned char* opcode = @@ -131,10 +137,10 @@ const void* MaybeFixupFunctionAddressForILT(const void* function_address) { if (*opcode == 0xe9) { // This is a relative jump instruction. Assume we're in the ILT and compute // the function start address from the instruction offset. - const unsigned char* offset = opcode + 1; - const unsigned char* next_instruction = opcode + 5; - return next_instruction + - static_cast<int64>(*reinterpret_cast<const int32*>(offset)); + const int32* offset = reinterpret_cast<const int32*>(opcode + 1); + const unsigned char* next_instruction = + reinterpret_cast<const unsigned char*>(offset + 1); + return next_instruction + *offset; } #endif return function_address; @@ -150,11 +156,9 @@ Sample::const_iterator FindFirstFrameWithinFunction( int function_size) { function_address = MaybeFixupFunctionAddressForILT(function_address); for (auto it = sample.begin(); it != sample.end(); ++it) { - if ((reinterpret_cast<const unsigned char*>(it->instruction_pointer) >= - reinterpret_cast<const unsigned char*>(function_address)) && - (reinterpret_cast<const unsigned char*>(it->instruction_pointer) < - (reinterpret_cast<const unsigned char*>(function_address) + - function_size))) + if ((it->instruction_pointer >= function_address) && + (it->instruction_pointer < + (static_cast<const unsigned char*>(function_address) + function_size))) return it; } return sample.end(); @@ -164,24 +168,27 @@ Sample::const_iterator FindFirstFrameWithinFunction( std::string FormatSampleForDiagnosticOutput( const Sample& sample, const std::vector<Module>& modules) { - std::ostringstream stream; + std::string output; for (const Frame& frame: sample) { - stream << frame.instruction_pointer << " " - << modules[frame.module_index].filename.value() << std::endl; + output += StringPrintf( + "0x%p %s\n", frame.instruction_pointer, + modules[frame.module_index].filename.AsUTF8Unsafe().c_str()); } - return stream.str(); + return output; } // Returns a duration that is longer than the test timeout. We would use // TimeDelta::Max() but https://crbug.com/465948. TimeDelta AVeryLongTimeDelta() { return TimeDelta::FromDays(1); } + } // namespace // The tests below are enabled for Win x64 only, pending implementation of the // tested functionality on other platforms/architectures. -// Checks that the basic expected information is present in a sampled profile. +// Checks that the basic expected information is present in a sampled call stack +// profile. #if defined(_WIN64) #define MAYBE_Basic Basic #else @@ -189,31 +196,28 @@ TimeDelta AVeryLongTimeDelta() { return TimeDelta::FromDays(1); } #endif TEST(StackSamplingProfilerTest, MAYBE_Basic) { StackSamplingProfiler::SamplingParams params; - params.initial_delay = params.burst_interval = params.sampling_interval = - TimeDelta::FromMilliseconds(0); - params.bursts = 1; + params.sampling_interval = TimeDelta::FromMilliseconds(0); params.samples_per_burst = 1; - std::vector<Profile> profiles; + std::vector<CallStackProfile> profiles; CaptureProfiles(params, &profiles, AVeryLongTimeDelta()); // Check that the profile and samples sizes are correct, and the module // indices are in range. - ASSERT_EQ(1u, profiles.size()); - const Profile& profile = profiles[0]; + const CallStackProfile& profile = profiles[0]; ASSERT_EQ(1u, profile.samples.size()); EXPECT_EQ(params.sampling_interval, profile.sampling_period); const Sample& sample = profile.samples[0]; for (const auto& frame : sample) { - ASSERT_GE(frame.module_index, 0); - ASSERT_LT(frame.module_index, static_cast<int>(profile.modules.size())); + ASSERT_GE(frame.module_index, 0u); + ASSERT_LT(frame.module_index, profile.modules.size()); } // Check that the stack contains a frame for // TargetThread::SignalAndWaitUntilSignaled() and that the frame has this // executable's module. - + // // Since we don't have a good way to know the function size, use 100 bytes as // a reasonable window to locate the instruction pointer. Sample::const_iterator loc = FindFirstFrameWithinFunction( @@ -225,17 +229,15 @@ TEST(StackSamplingProfilerTest, MAYBE_Basic) { << MaybeFixupFunctionAddressForILT( reinterpret_cast<const void*>( &TargetThread::SignalAndWaitUntilSignaled)) - << " was not found in stack:" << std::endl + << " was not found in stack:\n" << FormatSampleForDiagnosticOutput(sample, profile.modules); - FilePath executable_path; - bool got_executable_path = PathService::Get(FILE_EXE, &executable_path); - EXPECT_TRUE(got_executable_path); + EXPECT_TRUE(PathService::Get(FILE_EXE, &executable_path)); EXPECT_EQ(executable_path, profile.modules[loc->module_index].filename); } // Checks that the expected number of profiles and samples are present in the -// profiles produced. +// call stack profiles produced. #if defined(_WIN64) #define MAYBE_MultipleProfilesAndSamples MultipleProfilesAndSamples #else @@ -243,12 +245,12 @@ TEST(StackSamplingProfilerTest, MAYBE_Basic) { #endif TEST(StackSamplingProfilerTest, MAYBE_MultipleProfilesAndSamples) { StackSamplingProfiler::SamplingParams params; - params.initial_delay = params.burst_interval = params.sampling_interval = + params.burst_interval = params.sampling_interval = TimeDelta::FromMilliseconds(0); params.bursts = 2; params.samples_per_burst = 3; - std::vector<Profile> profiles; + std::vector<CallStackProfile> profiles; CaptureProfiles(params, &profiles, AVeryLongTimeDelta()); ASSERT_EQ(2u, profiles.size()); @@ -256,8 +258,8 @@ TEST(StackSamplingProfilerTest, MAYBE_MultipleProfilesAndSamples) { EXPECT_EQ(3u, profiles[1].samples.size()); } -// Checks that no profiles are captured if the profiling is stopped during the -// initial delay. +// Checks that no call stack profiles are captured if the profiling is stopped +// during the initial delay. #if defined(_WIN64) #define MAYBE_StopDuringInitialDelay StopDuringInitialDelay #else @@ -265,19 +267,16 @@ TEST(StackSamplingProfilerTest, MAYBE_MultipleProfilesAndSamples) { #endif TEST(StackSamplingProfilerTest, MAYBE_StopDuringInitialDelay) { StackSamplingProfiler::SamplingParams params; - params.burst_interval = params.sampling_interval = - TimeDelta::FromMilliseconds(0); params.initial_delay = TimeDelta::FromSeconds(60); - params.bursts = params.samples_per_burst = 1; - std::vector<Profile> profiles; + std::vector<CallStackProfile> profiles; CaptureProfiles(params, &profiles, TimeDelta::FromMilliseconds(0)); EXPECT_TRUE(profiles.empty()); } -// Checks that the single completed profile is captured if the profiling is -// stopped between bursts. +// Checks that the single completed call stack profile is captured if the +// profiling is stopped between bursts. #if defined(_WIN64) #define MAYBE_StopDuringInterBurstInterval StopDuringInterBurstInterval #else @@ -285,20 +284,19 @@ TEST(StackSamplingProfilerTest, MAYBE_StopDuringInitialDelay) { #endif TEST(StackSamplingProfilerTest, MAYBE_StopDuringInterBurstInterval) { StackSamplingProfiler::SamplingParams params; - params.initial_delay = params.sampling_interval = - TimeDelta::FromMilliseconds(0); + params.sampling_interval = TimeDelta::FromMilliseconds(0); params.burst_interval = TimeDelta::FromSeconds(60); params.bursts = 2; params.samples_per_burst = 1; - std::vector<Profile> profiles; + std::vector<CallStackProfile> profiles; CaptureProfiles(params, &profiles, TimeDelta::FromMilliseconds(50)); ASSERT_EQ(1u, profiles.size()); EXPECT_EQ(1u, profiles[0].samples.size()); } -// Checks that only completed profiles are captured. +// Checks that only completed call stack profiles are captured. #if defined(_WIN64) #define MAYBE_StopDuringInterSampleInterval StopDuringInterSampleInterval #else @@ -307,15 +305,13 @@ TEST(StackSamplingProfilerTest, MAYBE_StopDuringInterBurstInterval) { #endif TEST(StackSamplingProfilerTest, MAYBE_StopDuringInterSampleInterval) { StackSamplingProfiler::SamplingParams params; - params.initial_delay = params.burst_interval = TimeDelta::FromMilliseconds(0); params.sampling_interval = TimeDelta::FromSeconds(60); - params.bursts = 1; params.samples_per_burst = 2; - std::vector<Profile> profiles; + std::vector<CallStackProfile> profiles; CaptureProfiles(params, &profiles, TimeDelta::FromMilliseconds(50)); EXPECT_TRUE(profiles.empty()); } -} // namespace tracked_objects +} // namespace base diff --git a/base/profiler/stack_sampling_profiler_win.cc b/base/profiler/stack_sampling_profiler_win.cc index ba46cf0..4d0f1d6 100644 --- a/base/profiler/stack_sampling_profiler_win.cc +++ b/base/profiler/stack_sampling_profiler_win.cc @@ -2,14 +2,13 @@ // 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 <windows.h> -#include <dbghelp.h> #include <map> #include <utility> -#include <windows.h> #include "base/logging.h" +#include "base/profiler/native_stack_sampler.h" #include "base/time/time.h" #include "base/win/pe_image.h" #include "base/win/scoped_handle.h" @@ -18,38 +17,7 @@ namespace base { namespace { -class NativeStackSamplerWin : public StackSamplingProfiler::NativeStackSampler { - public: - explicit NativeStackSamplerWin(win::ScopedHandle thread_handle); - ~NativeStackSamplerWin() override; - - // StackSamplingProfiler::NativeStackSampler: - void ProfileRecordingStarting( - StackSamplingProfiler::Profile* profile) override; - void RecordStackSample(StackSamplingProfiler::Sample* sample) override; - void ProfileRecordingStopped() override; - - private: - static bool GetModuleInfo(HMODULE module, - StackSamplingProfiler::Module* module_info); - - void CopyToSample(const void* const instruction_pointers[], - const HMODULE modules[], - int stack_depth, - StackSamplingProfiler::Sample* sample, - std::vector<StackSamplingProfiler::Module>* module_infos); - - win::ScopedHandle thread_handle_; - // Weak. Points to the profile being recorded between - // ProfileRecordingStarting() and ProfileRecordingStopped(). - StackSamplingProfiler::Profile* current_profile_; - // Maps a module to the module's index within current_profile_->modules. - std::map<HMODULE, int> profile_module_index_; - - DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerWin); -}; - -// Walk the stack represented by |context| from the current frame downwards, +// Walks the stack represented by |context| from the current frame downwards, // recording the instruction pointers for each frame in |instruction_pointers|. int RecordStack(CONTEXT* context, int max_stack_size, @@ -58,66 +26,65 @@ int RecordStack(CONTEXT* context, #ifdef _WIN64 *last_frame_is_unknown_function = false; - IMAGEHLP_SYMBOL64 sym; - sym.SizeOfStruct = sizeof(sym); - sym.MaxNameLength = 0; - - for (int i = 0; i < max_stack_size; ++i) { + int i = 0; + for (; (i < max_stack_size) && context->Rip; ++i) { // Try to look up unwind metadata for the current function. ULONG64 image_base; PRUNTIME_FUNCTION runtime_function = RtlLookupFunctionEntry(context->Rip, &image_base, nullptr); - instruction_pointers[i] = reinterpret_cast<void*>(context->Rip); + instruction_pointers[i] = reinterpret_cast<const void*>(context->Rip); if (runtime_function) { KNONVOLATILE_CONTEXT_POINTERS nvcontext = {0}; void* handler_data; ULONG64 establisher_frame; RtlVirtualUnwind(0, image_base, context->Rip, runtime_function, context, - &handler_data, &establisher_frame, &nvcontext); + &handler_data, &establisher_frame, &nvcontext); } else { - // If we don't have a RUNTIME_FUNCTION, then we've encountered - // a leaf function. Adjust the stack appropriately. + // If we don't have a RUNTIME_FUNCTION, then we've encountered a leaf + // function. Adjust the stack appropriately prior to the next function + // lookup. context->Rip = *reinterpret_cast<PDWORD64>(context->Rsp); context->Rsp += 8; *last_frame_is_unknown_function = true; } - - if (!context->Rip) - return i; } - return max_stack_size; + return i; #else return 0; #endif } -// Fills in |modules| corresponding to the pointers to code in |addresses|. The -// modules are returned with reference counts incremented should be freed with -// FreeModules. -void FindModulesForAddresses(const void* const addresses[], HMODULE modules[], - int stack_depth, +// Fills in |module_handles| corresponding to the pointers to code in +// |addresses|. The module handles are returned with reference counts +// incremented and should be freed with FreeModuleHandles. See note in +// SuspendThreadAndRecordStack for why |addresses| and |module_handles| are +// arrays. +void FindModuleHandlesForAddresses(const void* const addresses[], + HMODULE module_handles[], int stack_depth, bool last_frame_is_unknown_function) { - const int module_frames = last_frame_is_unknown_function ? stack_depth - 1 : - stack_depth; + const int module_frames = + last_frame_is_unknown_function ? stack_depth - 1 : stack_depth; for (int i = 0; i < module_frames; ++i) { - HMODULE module = NULL; + HMODULE module_handle = NULL; if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<LPCTSTR>(addresses[i]), - &module)) { - // HMODULE is the base address of the module. - DCHECK_LT(reinterpret_cast<const void*>(module), addresses[i]); - modules[i] = module; + &module_handle)) { + // HMODULE actually represents the base address of the module, so we can + // use it directly as an address. + DCHECK_LE(reinterpret_cast<const void*>(module_handle), addresses[i]); + module_handles[i] = module_handle; } } } -// Free the modules returned by FindModulesForAddresses. -void FreeModules(int stack_depth, HMODULE modules[]) { +// Frees the modules handles returned by FindModuleHandlesForAddresses. See note +// in SuspendThreadAndRecordStack for why |module_handles| is an array. +void FreeModuleHandles(int stack_depth, HMODULE module_handles[]) { for (int i = 0; i < stack_depth; ++i) { - if (modules[i]) - ::FreeLibrary(modules[i]); + if (module_handles[i]) + ::FreeLibrary(module_handles[i]); } } @@ -141,185 +108,212 @@ ScopedDisablePriorityBoost::ScopedDisablePriorityBoost(HANDLE thread_handle) boost_state_was_disabled_(false) { got_previous_boost_state_ = ::GetThreadPriorityBoost(thread_handle_, &boost_state_was_disabled_); - if (got_previous_boost_state_ && !boost_state_was_disabled_) { - // Confusingly, TRUE disables priority boost ... + if (got_previous_boost_state_) { + // Confusingly, TRUE disables priority boost. ::SetThreadPriorityBoost(thread_handle_, TRUE); } } ScopedDisablePriorityBoost::~ScopedDisablePriorityBoost() { - if (got_previous_boost_state_ && !boost_state_was_disabled_) { - // ... and FALSE enables priority boost. - ::SetThreadPriorityBoost(thread_handle_, FALSE); - } + if (got_previous_boost_state_) + ::SetThreadPriorityBoost(thread_handle_, boost_state_was_disabled_); } // Suspends the thread with |thread_handle|, records the stack into // |instruction_pointers|, then resumes the thread. Returns the size of the // stack. +// +// IMPORTANT NOTE: No heap allocations may occur between SuspendThread and +// ResumeThread. Otherwise this code can deadlock on heap locks acquired by the +// target thread before it was suspended. This is why we pass instruction +// pointers and module handles as preallocated arrays rather than vectors, since +// vectors make it too easy to subtly allocate memory. int SuspendThreadAndRecordStack(HANDLE thread_handle, int max_stack_size, const void* instruction_pointers[], bool* last_frame_is_unknown_function) { -#if defined(_WIN64) - if (RtlVirtualUnwind == nullptr || RtlLookupFunctionEntry == nullptr) + if (::SuspendThread(thread_handle) == -1) return 0; -#endif - - if (::SuspendThread(thread_handle) == -1) { - LOG(ERROR) << "SuspendThread failed: " << GetLastError(); - return 0; - } + int stack_depth = 0; CONTEXT thread_context = {0}; thread_context.ContextFlags = CONTEXT_FULL; - if (!::GetThreadContext(thread_handle, &thread_context)) { - LOG(ERROR) << "GetThreadContext failed: " << GetLastError(); + if (::GetThreadContext(thread_handle, &thread_context)) { + stack_depth = RecordStack(&thread_context, max_stack_size, + instruction_pointers, + last_frame_is_unknown_function); } - int stack_depth = RecordStack(&thread_context, max_stack_size, - instruction_pointers, - last_frame_is_unknown_function); - - { - ScopedDisablePriorityBoost disable_priority_boost(thread_handle); - if (::ResumeThread(thread_handle) == -1) - LOG(ERROR) << "ResumeThread failed: " << GetLastError(); - } + // Disable the priority boost that the thread would otherwise receive on + // resume. We do this to avoid artificially altering the dynamics of the + // executing application any more than we already are by suspending and + // resuming the thread. + // + // Note that this can racily disable a priority boost that otherwise would + // have been given to the thread, if the thread is waiting on other wait + // conditions at the time of SuspendThread and those conditions are satisfied + // before priority boost is reenabled. The measured length of this window is + // ~100us, so this should occur fairly rarely. + ScopedDisablePriorityBoost disable_priority_boost(thread_handle); + bool resume_thread_succeeded = ::ResumeThread(thread_handle) != -1; + CHECK(resume_thread_succeeded) << "ResumeThread failed: " << GetLastError(); return stack_depth; } -} // namespace +class NativeStackSamplerWin : public NativeStackSampler { + public: + explicit NativeStackSamplerWin(win::ScopedHandle thread_handle); + ~NativeStackSamplerWin() override; -scoped_ptr<StackSamplingProfiler::NativeStackSampler> -StackSamplingProfiler::NativeStackSampler::Create(PlatformThreadId thread_id) { -#if _WIN64 - // Get the thread's handle. - HANDLE thread_handle = ::OpenThread( - THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, - FALSE, - thread_id); - DCHECK(thread_handle) << "OpenThread failed"; + // StackSamplingProfiler::NativeStackSampler: + void ProfileRecordingStarting( + std::vector<StackSamplingProfiler::Module>* modules) override; + void RecordStackSample(StackSamplingProfiler::Sample* sample) override; + void ProfileRecordingStopped() override; - return scoped_ptr<NativeStackSampler>(new NativeStackSamplerWin( - win::ScopedHandle(thread_handle))); -#else - return scoped_ptr<NativeStackSampler>(); -#endif -} + private: + // Attempts to query the module filename, base address, and id for + // |module_handle|, and store them in |module|. Returns true if it succeeded. + static bool GetModuleForHandle(HMODULE module_handle, + StackSamplingProfiler::Module* module); + + // Gets the index for the Module corresponding to |module_handle| in + // |modules|, adding it if it's not already present. Returns + // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be + // determined for |module|. + size_t GetModuleIndex(HMODULE module_handle, + std::vector<StackSamplingProfiler::Module>* modules); + + // Copies the stack information represented by |instruction_pointers| into + // |sample| and |modules|. + void CopyToSample(const void* const instruction_pointers[], + const HMODULE module_handles[], + int stack_depth, + StackSamplingProfiler::Sample* sample, + std::vector<StackSamplingProfiler::Module>* modules); + + win::ScopedHandle thread_handle_; + // Weak. Points to the modules associated with the profile being recorded + // between ProfileRecordingStarting() and ProfileRecordingStopped(). + std::vector<StackSamplingProfiler::Module>* current_modules_; + // Maps a module handle to the corresponding Module's index within + // current_modules_. + std::map<HMODULE, size_t> profile_module_index_; + + DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerWin); +}; NativeStackSamplerWin::NativeStackSamplerWin(win::ScopedHandle thread_handle) : thread_handle_(thread_handle.Take()) { -#ifdef _WIN64 - if (RtlVirtualUnwind == nullptr && RtlLookupFunctionEntry == nullptr) { - const HMODULE nt_dll_handle = ::GetModuleHandle(L"ntdll.dll"); - // This should always be non-null, but handle just in case. - if (nt_dll_handle) { - reinterpret_cast<void*&>(RtlVirtualUnwind) = - ::GetProcAddress(nt_dll_handle, "RtlVirtualUnwind"); - reinterpret_cast<void*&>(RtlLookupFunctionEntry) = - ::GetProcAddress(nt_dll_handle, "RtlLookupFunctionEntry"); - } - } -#endif } NativeStackSamplerWin::~NativeStackSamplerWin() { } void NativeStackSamplerWin::ProfileRecordingStarting( - StackSamplingProfiler::Profile* profile) { - current_profile_ = profile; + std::vector<StackSamplingProfiler::Module>* modules) { + current_modules_ = modules; profile_module_index_.clear(); } void NativeStackSamplerWin::RecordStackSample( StackSamplingProfiler::Sample* sample) { - DCHECK(current_profile_); + DCHECK(current_modules_); const int max_stack_size = 64; const void* instruction_pointers[max_stack_size] = {0}; - HMODULE modules[max_stack_size] = {0}; + HMODULE module_handles[max_stack_size] = {0}; bool last_frame_is_unknown_function = false; int stack_depth = SuspendThreadAndRecordStack( thread_handle_.Get(), max_stack_size, instruction_pointers, &last_frame_is_unknown_function); - FindModulesForAddresses(instruction_pointers, modules, stack_depth, - last_frame_is_unknown_function); - CopyToSample(instruction_pointers, modules, stack_depth, sample, - ¤t_profile_->modules); - FreeModules(stack_depth, modules); + FindModuleHandlesForAddresses(instruction_pointers, module_handles, + stack_depth, last_frame_is_unknown_function); + CopyToSample(instruction_pointers, module_handles, stack_depth, sample, + current_modules_); + FreeModuleHandles(stack_depth, module_handles); } void NativeStackSamplerWin::ProfileRecordingStopped() { - current_profile_ = nullptr; + current_modules_ = nullptr; } // static -bool NativeStackSamplerWin::GetModuleInfo( - HMODULE module, - StackSamplingProfiler::Module* module_info) { +bool NativeStackSamplerWin::GetModuleForHandle( + HMODULE module_handle, + StackSamplingProfiler::Module* module) { wchar_t module_name[MAX_PATH]; DWORD result_length = - GetModuleFileName(module, module_name, arraysize(module_name)); + GetModuleFileName(module_handle, module_name, arraysize(module_name)); if (result_length == 0) return false; - module_info->filename = base::FilePath(module_name); + module->filename = base::FilePath(module_name); - module_info->base_address = reinterpret_cast<const void*>(module); + module->base_address = reinterpret_cast<const void*>(module_handle); GUID guid; DWORD age; - win::PEImage(module).GetDebugId(&guid, &age); - module_info->id.insert(module_info->id.end(), - reinterpret_cast<char*>(&guid), - reinterpret_cast<char*>(&guid + 1)); - module_info->id.insert(module_info->id.end(), - reinterpret_cast<char*>(&age), - reinterpret_cast<char*>(&age + 1)); + win::PEImage(module_handle).GetDebugId(&guid, &age); + module->id.assign(reinterpret_cast<char*>(&guid), sizeof(guid)); + module->id.append(reinterpret_cast<char*>(&age), sizeof(age)); return true; } +size_t NativeStackSamplerWin::GetModuleIndex( + HMODULE module_handle, + std::vector<StackSamplingProfiler::Module>* modules) { + if (!module_handle) + return StackSamplingProfiler::Frame::kUnknownModuleIndex; + + auto loc = profile_module_index_.find(module_handle); + if (loc == profile_module_index_.end()) { + StackSamplingProfiler::Module module; + if (!GetModuleForHandle(module_handle, &module)) + return StackSamplingProfiler::Frame::kUnknownModuleIndex; + modules->push_back(module); + loc = profile_module_index_.insert(std::make_pair( + module_handle, modules->size() - 1)).first; + } + + return loc->second; +} + void NativeStackSamplerWin::CopyToSample( const void* const instruction_pointers[], - const HMODULE modules[], + const HMODULE module_handles[], int stack_depth, StackSamplingProfiler::Sample* sample, - std::vector<StackSamplingProfiler::Module>* module_infos) { + std::vector<StackSamplingProfiler::Module>* module) { sample->clear(); sample->reserve(stack_depth); for (int i = 0; i < stack_depth; ++i) { - sample->push_back(StackSamplingProfiler::Frame()); - StackSamplingProfiler::Frame& frame = sample->back(); - - frame.instruction_pointer = instruction_pointers[i]; + sample->push_back(StackSamplingProfiler::Frame( + instruction_pointers[i], + GetModuleIndex(module_handles[i], module))); + } +} - // Record an invalid module index if we don't have a valid module. - if (!modules[i]) { - frame.module_index = -1; - continue; - } +} // namespace - auto loc = profile_module_index_.find(modules[i]); - if (loc == profile_module_index_.end()) { - StackSamplingProfiler::Module module_info; - // Record an invalid module index if we have a module but can't find - // information on it. - if (!GetModuleInfo(modules[i], &module_info)) { - frame.module_index = -1; - continue; - } - module_infos->push_back(module_info); - loc = profile_module_index_.insert(std::make_pair( - modules[i], static_cast<int>(module_infos->size() - 1))).first; - } +scoped_ptr<NativeStackSampler> NativeStackSampler::Create( + PlatformThreadId thread_id) { +#if _WIN64 + // Get the thread's handle. + HANDLE thread_handle = ::OpenThread( + THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, + FALSE, + thread_id); - frame.module_index = loc->second; + if (thread_handle) { + return scoped_ptr<NativeStackSampler>(new NativeStackSamplerWin( + win::ScopedHandle(thread_handle))); } +#endif + return scoped_ptr<NativeStackSampler>(); } } // namespace base diff --git a/components/metrics/call_stack_profile_metrics_provider.cc b/components/metrics/call_stack_profile_metrics_provider.cc index 7ed60f5..f96923b 100644 --- a/components/metrics/call_stack_profile_metrics_provider.cc +++ b/components/metrics/call_stack_profile_metrics_provider.cc @@ -42,7 +42,7 @@ void CopySampleToProto( // A frame may not have a valid module. If so, we can't compute the // instruction pointer offset, and we don't want to send bare pointers, so // leave call_stack_entry empty. - if (frame.module_index < 0) + if (frame.module_index == StackSamplingProfiler::Frame::kUnknownModuleIndex) continue; int64 module_offset = reinterpret_cast<const char*>(frame.instruction_pointer) - @@ -55,7 +55,7 @@ void CopySampleToProto( // Transcode |profile| into |proto_profile|. void CopyProfileToProto( - const StackSamplingProfiler::Profile& profile, + const StackSamplingProfiler::CallStackProfile& profile, CallStackProfile* proto_profile) { if (profile.samples.empty()) return; @@ -112,13 +112,13 @@ CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {} void CallStackProfileMetricsProvider::ProvideGeneralMetrics( ChromeUserMetricsExtension* uma_proto) { - std::vector<StackSamplingProfiler::Profile> profiles; + std::vector<StackSamplingProfiler::CallStackProfile> profiles; if (!source_profiles_for_test_.empty()) profiles.swap(source_profiles_for_test_); else StackSamplingProfiler::GetPendingProfiles(&profiles); - for (const StackSamplingProfiler::Profile& profile : profiles) { + for (const StackSamplingProfiler::CallStackProfile& profile : profiles) { CallStackProfile* call_stack_profile = uma_proto->add_sampled_profile()->mutable_call_stack_profile(); CopyProfileToProto(profile, call_stack_profile); @@ -126,7 +126,7 @@ void CallStackProfileMetricsProvider::ProvideGeneralMetrics( } void CallStackProfileMetricsProvider::SetSourceProfilesForTesting( - const std::vector<StackSamplingProfiler::Profile>& profiles) { + const std::vector<StackSamplingProfiler::CallStackProfile>& profiles) { source_profiles_for_test_ = profiles; } diff --git a/components/metrics/call_stack_profile_metrics_provider.h b/components/metrics/call_stack_profile_metrics_provider.h index a2bb0f8..6d5ff8c 100644 --- a/components/metrics/call_stack_profile_metrics_provider.h +++ b/components/metrics/call_stack_profile_metrics_provider.h @@ -26,10 +26,12 @@ class CallStackProfileMetricsProvider : public MetricsProvider { // ProvideGeneralMetrics, rather than sourcing them from the // StackSamplingProfiler. void SetSourceProfilesForTesting( - const std::vector<base::StackSamplingProfiler::Profile>& profiles); + const std::vector<base::StackSamplingProfiler::CallStackProfile>& + profiles); private: - std::vector<base::StackSamplingProfiler::Profile> source_profiles_for_test_; + std::vector<base::StackSamplingProfiler::CallStackProfile> + source_profiles_for_test_; DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider); }; diff --git a/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/components/metrics/call_stack_profile_metrics_provider_unittest.cc index 20e53d6..a4e3efb 100644 --- a/components/metrics/call_stack_profile_metrics_provider_unittest.cc +++ b/components/metrics/call_stack_profile_metrics_provider_unittest.cc @@ -12,7 +12,7 @@ using base::StackSamplingProfiler; using Frame = StackSamplingProfiler::Frame; using Module = StackSamplingProfiler::Module; -using Profile = StackSamplingProfiler::Profile; +using Profile = StackSamplingProfiler::CallStackProfile; using Sample = StackSamplingProfiler::Sample; namespace metrics { @@ -201,7 +201,7 @@ TEST(CallStackProfileMetricsProviderTest, MultipleProfiles) { module_base_address), entry.address()); ASSERT_TRUE(entry.has_module_id_index()); EXPECT_EQ(profile_sample_frames[i][j][k].module_index, - entry.module_id_index()); + static_cast<size_t>(entry.module_id_index())); } } @@ -298,7 +298,8 @@ TEST(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) { EXPECT_EQ(static_cast<uint64>(instruction_pointer - module_base_address), entry.address()); ASSERT_TRUE(entry.has_module_id_index()); - EXPECT_EQ(sample_frames[i][j].module_index, entry.module_id_index()); + EXPECT_EQ(sample_frames[i][j].module_index, + static_cast<size_t>(entry.module_id_index())); } } } @@ -374,7 +375,8 @@ TEST(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) { EXPECT_EQ(static_cast<uint64>(instruction_pointer - module_base_address), entry.address()); ASSERT_TRUE(entry.has_module_id_index()); - EXPECT_EQ(sample_frames[i][j].module_index, entry.module_id_index()); + EXPECT_EQ(sample_frames[i][j].module_index, + static_cast<size_t>(entry.module_id_index())); } } } @@ -382,8 +384,8 @@ TEST(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) { // Checks that unknown modules produce an empty Entry. TEST(CallStackProfileMetricsProviderTest, UnknownModule) { - // -1 indicates an unknown module. - const Frame frame(reinterpret_cast<const void*>(0x1000), -1); + const Frame frame(reinterpret_cast<const void*>(0x1000), + Frame::kUnknownModuleIndex); Profile profile; |