diff options
Diffstat (limited to 'chromium/components/metrics/call_stack_profile_metrics_provider.cc')
-rw-r--r-- | chromium/components/metrics/call_stack_profile_metrics_provider.cc | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.cc b/chromium/components/metrics/call_stack_profile_metrics_provider.cc new file mode 100644 index 00000000000..7ad942d3fca --- /dev/null +++ b/chromium/components/metrics/call_stack_profile_metrics_provider.cc @@ -0,0 +1,396 @@ +// 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 <stddef.h> +#include <stdint.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/metrics/metrics_hashes.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/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 base::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 base::Singleton<PendingProfiles, + base::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_t 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 base::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_t 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_t>(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 |