| // 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 "components/metrics/call_stack_profile_metrics_provider.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <cstring> | 
 | #include <map> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/location.h" | 
 | #include "base/logging.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/singleton.h" | 
 | #include "base/metrics/field_trial.h" | 
 | #include "base/profiler/stack_sampling_profiler.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/synchronization/lock.h" | 
 | #include "base/thread_task_runner_handle.h" | 
 | #include "base/time/time.h" | 
 | #include "components/metrics/metrics_hashes.h" | 
 | #include "components/metrics/proto/chrome_user_metrics_extension.pb.h" | 
 |  | 
 | using base::StackSamplingProfiler; | 
 |  | 
 | namespace metrics { | 
 |  | 
 | namespace { | 
 |  | 
 | // ProfilesState -------------------------------------------------------------- | 
 |  | 
 | // A set of profiles and the CallStackProfileMetricsProvider state associated | 
 | // with them. | 
 | struct ProfilesState { | 
 |   ProfilesState(const CallStackProfileMetricsProvider::Params& params, | 
 |                 const base::StackSamplingProfiler::CallStackProfiles& profiles, | 
 |                 base::TimeTicks start_timestamp); | 
 |  | 
 |   // The metrics-related parameters provided to | 
 |   // CallStackProfileMetricsProvider::GetProfilerCallback(). | 
 |   CallStackProfileMetricsProvider::Params params; | 
 |  | 
 |   // The call stack profiles collected by the profiler. | 
 |   base::StackSamplingProfiler::CallStackProfiles profiles; | 
 |  | 
 |   // The time at which the CallStackProfileMetricsProvider became aware of the | 
 |   // request for profiling. In particular, this is when callback was requested | 
 |   // via CallStackProfileMetricsProvider::GetProfilerCallback(). Used to | 
 |   // determine if collection was disabled during the collection of the profile. | 
 |   base::TimeTicks start_timestamp; | 
 | }; | 
 |  | 
 | ProfilesState::ProfilesState( | 
 |     const CallStackProfileMetricsProvider::Params& params, | 
 |     const base::StackSamplingProfiler::CallStackProfiles& profiles, | 
 |     base::TimeTicks start_timestamp) | 
 |     : params(params), | 
 |       profiles(profiles), | 
 |       start_timestamp(start_timestamp) { | 
 | } | 
 |  | 
 | // PendingProfiles ------------------------------------------------------------ | 
 |  | 
 | // Singleton class responsible for retaining profiles received via the callback | 
 | // created by CallStackProfileMetricsProvider::GetProfilerCallback(). These are | 
 | // then sent to UMA on the invocation of | 
 | // CallStackProfileMetricsProvider::ProvideGeneralMetrics(). We need to store | 
 | // the profiles outside of a CallStackProfileMetricsProvider instance since | 
 | // callers may start profiling before the CallStackProfileMetricsProvider is | 
 | // created. | 
 | // | 
 | // Member functions on this class may be called on any thread. | 
 | class PendingProfiles { | 
 |  public: | 
 |   static PendingProfiles* GetInstance(); | 
 |  | 
 |   void Clear(); | 
 |   void Swap(std::vector<ProfilesState>* profiles); | 
 |  | 
 |   // Enables the collection of profiles by CollectProfilesIfCollectionEnabled if | 
 |   // |enabled| is true. Otherwise, clears current profiles and ignores profiles | 
 |   // provided to future invocations of CollectProfilesIfCollectionEnabled. | 
 |   void SetCollectionEnabled(bool enabled); | 
 |  | 
 |   // True if profiles are being collected. | 
 |   bool IsCollectionEnabled() const; | 
 |  | 
 |   // Adds |profile| to the list of profiles if collection is enabled. | 
 |   void CollectProfilesIfCollectionEnabled(const ProfilesState& profiles); | 
 |  | 
 |   // Allows testing against the initial state multiple times. | 
 |   void ResetToDefaultStateForTesting(); | 
 |  | 
 |  private: | 
 |   friend struct DefaultSingletonTraits<PendingProfiles>; | 
 |  | 
 |   PendingProfiles(); | 
 |   ~PendingProfiles(); | 
 |  | 
 |   mutable base::Lock lock_; | 
 |  | 
 |   // If true, profiles provided to CollectProfilesIfCollectionEnabled should be | 
 |   // collected. Otherwise they will be ignored. | 
 |   bool collection_enabled_; | 
 |  | 
 |   // The last time collection was disabled. Used to determine if collection was | 
 |   // disabled at any point since a profile was started. | 
 |   base::TimeTicks last_collection_disable_time_; | 
 |  | 
 |   // The set of completed profiles that should be reported. | 
 |   std::vector<ProfilesState> profiles_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(PendingProfiles); | 
 | }; | 
 |  | 
 | // static | 
 | PendingProfiles* PendingProfiles::GetInstance() { | 
 |   // Leaky for performance rather than correctness reasons. | 
 |   return Singleton<PendingProfiles, | 
 |                    LeakySingletonTraits<PendingProfiles>>::get(); | 
 | } | 
 |  | 
 | void PendingProfiles::Clear() { | 
 |   base::AutoLock scoped_lock(lock_); | 
 |   profiles_.clear(); | 
 | } | 
 |  | 
 | void PendingProfiles::Swap(std::vector<ProfilesState>* profiles) { | 
 |   base::AutoLock scoped_lock(lock_); | 
 |   profiles_.swap(*profiles); | 
 | } | 
 |  | 
 | void PendingProfiles::SetCollectionEnabled(bool enabled) { | 
 |   base::AutoLock scoped_lock(lock_); | 
 |  | 
 |   collection_enabled_ = enabled; | 
 |  | 
 |   if (!collection_enabled_) { | 
 |     profiles_.clear(); | 
 |     last_collection_disable_time_ = base::TimeTicks::Now(); | 
 |   } | 
 | } | 
 |  | 
 | bool PendingProfiles::IsCollectionEnabled() const { | 
 |   base::AutoLock scoped_lock(lock_); | 
 |   return collection_enabled_; | 
 | } | 
 |  | 
 | void PendingProfiles::CollectProfilesIfCollectionEnabled( | 
 |     const ProfilesState& profiles) { | 
 |   base::AutoLock scoped_lock(lock_); | 
 |  | 
 |   // Only collect if collection is not disabled and hasn't been disabled | 
 |   // since the start of collection for this profile. | 
 |   if (!collection_enabled_ || | 
 |       (!last_collection_disable_time_.is_null() && | 
 |        last_collection_disable_time_ >= profiles.start_timestamp)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   profiles_.push_back(profiles); | 
 | } | 
 |  | 
 | void PendingProfiles::ResetToDefaultStateForTesting() { | 
 |   base::AutoLock scoped_lock(lock_); | 
 |  | 
 |   collection_enabled_ = true; | 
 |   last_collection_disable_time_ = base::TimeTicks(); | 
 |   profiles_.clear(); | 
 | } | 
 |  | 
 | // |collection_enabled_| is initialized to true to collect any profiles that are | 
 | // generated prior to creation of the CallStackProfileMetricsProvider. The | 
 | // ultimate disposition of these pre-creation collected profiles will be | 
 | // determined by the initial recording state provided to | 
 | // CallStackProfileMetricsProvider. | 
 | PendingProfiles::PendingProfiles() : collection_enabled_(true) {} | 
 |  | 
 | PendingProfiles::~PendingProfiles() {} | 
 |  | 
 | // Functions to process completed profiles ------------------------------------ | 
 |  | 
 | // Invoked on the profiler's thread. Provides the profiles to PendingProfiles to | 
 | // append, if the collecting state allows. | 
 | void ReceiveCompletedProfiles( | 
 |     const CallStackProfileMetricsProvider::Params& params, | 
 |     base::TimeTicks start_timestamp, | 
 |     const StackSamplingProfiler::CallStackProfiles& profiles) { | 
 |   PendingProfiles::GetInstance()->CollectProfilesIfCollectionEnabled( | 
 |       ProfilesState(params, profiles, start_timestamp)); | 
 | } | 
 |  | 
 | // Invoked on an arbitrary thread. Ignores the provided profiles. | 
 | void IgnoreCompletedProfiles( | 
 |     const StackSamplingProfiler::CallStackProfiles& profiles) { | 
 | } | 
 |  | 
 | // Functions to encode protobufs ---------------------------------------------- | 
 |  | 
 | // The protobuf expects the MD5 checksum prefix of the module name. | 
 | uint64 HashModuleFilename(const base::FilePath& filename) { | 
 |   const base::FilePath::StringType basename = filename.BaseName().value(); | 
 |   // Copy the bytes in basename into a string buffer. | 
 |   size_t basename_length_in_bytes = | 
 |       basename.size() * sizeof(base::FilePath::CharType); | 
 |   std::string name_bytes(basename_length_in_bytes, '\0'); | 
 |   memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes); | 
 |   return HashMetricName(name_bytes); | 
 | } | 
 |  | 
 | // Transcode |sample| into |proto_sample|, using base addresses in |modules| to | 
 | // compute module instruction pointer offsets. | 
 | void CopySampleToProto( | 
 |     const StackSamplingProfiler::Sample& sample, | 
 |     const std::vector<StackSamplingProfiler::Module>& modules, | 
 |     CallStackProfile::Sample* proto_sample) { | 
 |   for (const StackSamplingProfiler::Frame& frame : sample) { | 
 |     CallStackProfile::Entry* entry = proto_sample->add_entry(); | 
 |     // 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 == StackSamplingProfiler::Frame::kUnknownModuleIndex) | 
 |       continue; | 
 |     int64 module_offset = | 
 |         reinterpret_cast<const char*>(frame.instruction_pointer) - | 
 |         reinterpret_cast<const char*>(modules[frame.module_index].base_address); | 
 |     DCHECK_GE(module_offset, 0); | 
 |     entry->set_address(static_cast<uint64>(module_offset)); | 
 |     entry->set_module_id_index(frame.module_index); | 
 |   } | 
 | } | 
 |  | 
 | // Transcode |profile| into |proto_profile|. | 
 | void CopyProfileToProto( | 
 |     const StackSamplingProfiler::CallStackProfile& profile, | 
 |     bool preserve_sample_ordering, | 
 |     CallStackProfile* proto_profile) { | 
 |   if (profile.samples.empty()) | 
 |     return; | 
 |  | 
 |   if (preserve_sample_ordering) { | 
 |     // Collapse only consecutive repeated samples together. | 
 |     CallStackProfile::Sample* current_sample_proto = nullptr; | 
 |     for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) { | 
 |       if (!current_sample_proto || *it != *(it - 1)) { | 
 |         current_sample_proto = proto_profile->add_sample(); | 
 |         CopySampleToProto(*it, profile.modules, current_sample_proto); | 
 |         current_sample_proto->set_count(1); | 
 |       } else { | 
 |         current_sample_proto->set_count(current_sample_proto->count() + 1); | 
 |       } | 
 |     } | 
 |   } else { | 
 |     // Collapse all repeated samples together. | 
 |     std::map<StackSamplingProfiler::Sample, int> sample_index; | 
 |     for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) { | 
 |       auto location = sample_index.find(*it); | 
 |       if (location == sample_index.end()) { | 
 |         CallStackProfile::Sample* sample_proto = proto_profile->add_sample(); | 
 |         CopySampleToProto(*it, profile.modules, sample_proto); | 
 |         sample_proto->set_count(1); | 
 |         sample_index.insert( | 
 |             std::make_pair( | 
 |                 *it, static_cast<int>(proto_profile->sample().size()) - 1)); | 
 |       } else { | 
 |         CallStackProfile::Sample* sample_proto = | 
 |             proto_profile->mutable_sample()->Mutable(location->second); | 
 |         sample_proto->set_count(sample_proto->count() + 1); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   for (const StackSamplingProfiler::Module& module : profile.modules) { | 
 |     CallStackProfile::ModuleIdentifier* module_id = | 
 |         proto_profile->add_module_id(); | 
 |     module_id->set_build_id(module.id); | 
 |     module_id->set_name_md5_prefix(HashModuleFilename(module.filename)); | 
 |   } | 
 |  | 
 |   proto_profile->set_profile_duration_ms( | 
 |       profile.profile_duration.InMilliseconds()); | 
 |   proto_profile->set_sampling_period_ms( | 
 |       profile.sampling_period.InMilliseconds()); | 
 | } | 
 |  | 
 | // Translates CallStackProfileMetricsProvider's trigger to the corresponding | 
 | // SampledProfile TriggerEvent. | 
 | SampledProfile::TriggerEvent ToSampledProfileTriggerEvent( | 
 |     CallStackProfileMetricsProvider::Trigger trigger) { | 
 |   switch (trigger) { | 
 |     case CallStackProfileMetricsProvider::UNKNOWN: | 
 |       return SampledProfile::UNKNOWN_TRIGGER_EVENT; | 
 |       break; | 
 |     case CallStackProfileMetricsProvider::PROCESS_STARTUP: | 
 |       return SampledProfile::PROCESS_STARTUP; | 
 |       break; | 
 |     case CallStackProfileMetricsProvider::JANKY_TASK: | 
 |       return SampledProfile::JANKY_TASK; | 
 |       break; | 
 |     case CallStackProfileMetricsProvider::THREAD_HUNG: | 
 |       return SampledProfile::THREAD_HUNG; | 
 |       break; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return SampledProfile::UNKNOWN_TRIGGER_EVENT; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // CallStackProfileMetricsProvider::Params ------------------------------------ | 
 |  | 
 | CallStackProfileMetricsProvider::Params::Params( | 
 |     CallStackProfileMetricsProvider::Trigger trigger) | 
 |     : Params(trigger, false) { | 
 | } | 
 |  | 
 | CallStackProfileMetricsProvider::Params::Params( | 
 |     CallStackProfileMetricsProvider::Trigger trigger, | 
 |     bool preserve_sample_ordering) | 
 |     : trigger(trigger), | 
 |       preserve_sample_ordering(preserve_sample_ordering) { | 
 | } | 
 |  | 
 | // CallStackProfileMetricsProvider -------------------------------------------- | 
 |  | 
 | const char CallStackProfileMetricsProvider::kFieldTrialName[] = | 
 |     "StackProfiling"; | 
 | const char CallStackProfileMetricsProvider::kReportProfilesGroupName[] = | 
 |     "Report profiles"; | 
 |  | 
 | CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() { | 
 | } | 
 |  | 
 | CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() { | 
 | } | 
 |  | 
 | // This function can be invoked on an abitrary thread. | 
 | base::StackSamplingProfiler::CompletedCallback | 
 | CallStackProfileMetricsProvider::GetProfilerCallback(const Params& params) { | 
 |   // Ignore the profiles if the collection is disabled. If the collection state | 
 |   // changes while collecting, this will be detected by the callback and | 
 |   // profiles will be ignored at that point. | 
 |   if (!PendingProfiles::GetInstance()->IsCollectionEnabled()) | 
 |     return base::Bind(&IgnoreCompletedProfiles); | 
 |  | 
 |   return base::Bind(&ReceiveCompletedProfiles, params, base::TimeTicks::Now()); | 
 | } | 
 |  | 
 | void CallStackProfileMetricsProvider::OnRecordingEnabled() { | 
 |   PendingProfiles::GetInstance()->SetCollectionEnabled(true); | 
 | } | 
 |  | 
 | void CallStackProfileMetricsProvider::OnRecordingDisabled() { | 
 |   PendingProfiles::GetInstance()->SetCollectionEnabled(false); | 
 | } | 
 |  | 
 | void CallStackProfileMetricsProvider::ProvideGeneralMetrics( | 
 |     ChromeUserMetricsExtension* uma_proto) { | 
 |   std::vector<ProfilesState> pending_profiles; | 
 |   PendingProfiles::GetInstance()->Swap(&pending_profiles); | 
 |  | 
 |   DCHECK(IsReportingEnabledByFieldTrial() || pending_profiles.empty()); | 
 |  | 
 |   for (const ProfilesState& profiles_state : pending_profiles) { | 
 |     for (const StackSamplingProfiler::CallStackProfile& profile : | 
 |              profiles_state.profiles) { | 
 |       SampledProfile* sampled_profile = uma_proto->add_sampled_profile(); | 
 |       sampled_profile->set_trigger_event(ToSampledProfileTriggerEvent( | 
 |           profiles_state.params.trigger)); | 
 |       CopyProfileToProto(profile, | 
 |                          profiles_state.params.preserve_sample_ordering, | 
 |                          sampled_profile->mutable_call_stack_profile()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // static | 
 | void CallStackProfileMetricsProvider::ResetStaticStateForTesting() { | 
 |   PendingProfiles::GetInstance()->ResetToDefaultStateForTesting(); | 
 | } | 
 |  | 
 | // static | 
 | bool CallStackProfileMetricsProvider::IsReportingEnabledByFieldTrial() { | 
 |   const std::string group_name = base::FieldTrialList::FindFullName( | 
 |       CallStackProfileMetricsProvider::kFieldTrialName); | 
 |   return group_name == | 
 |       CallStackProfileMetricsProvider::kReportProfilesGroupName; | 
 | } | 
 |  | 
 | }  // namespace metrics |