| // 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 <iterator> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/no_destructor.h" |
| #include "base/synchronization/lock.h" |
| #include "base/time/time.h" |
| #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" |
| |
| namespace metrics { |
| |
| namespace { |
| |
| // Cap the number of pending profiles to avoid excessive memory usage when |
| // profile uploads are delayed (e.g. due to being offline). 1250 profiles |
| // corresponds to 80MB of storage. Capping at this threshold loses approximately |
| // 0.5% of profiles on canary and dev. |
| // TODO(chengx): Remove this threshold after moving to a more memory-efficient |
| // profile representation. |
| const size_t kMaxPendingProfiles = 1250; |
| |
| // Cap the number of pending unserialized profiles to avoid excessive memory |
| // usage when profile uploads are delayed (e.g., due to being offline). When the |
| // number of pending unserialized profiles exceeds this cap, serialize all |
| // additional unserialized profiles to save memory. Since profile serialization |
| // and deserialization (required later for uploads) are expensive, we choose 250 |
| // as the capacity to balance speed and memory. 250 unserialized profiles |
| // corresponds to 16MB of storage. |
| // TODO(chengx): Remove this threshold after moving to a more memory-efficient |
| // profile representation. |
| constexpr size_t kMaxPendingUnserializedProfiles = 250; |
| |
| // Merges the serialized profiles into the unserialized ones, by appending them. |
| // The params are not const& because they should be passed using std::move. |
| // Implementation note: In order to maintain the invariant that profiles are |
| // reported in correct temporal sequence, it's important to order the serialized |
| // profiles to follow the unserialized profiles. This way, profiles that were |
| // serialized simply for space efficiency will end up ordered correctly. |
| std::vector<SampledProfile> MergeProfiles( |
| std::vector<SampledProfile> profiles, |
| std::vector<std::string> serialized_profiles) { |
| // Deserialize all serialized profiles, skipping over any that fail to parse. |
| std::vector<SampledProfile> deserialized_profiles; |
| deserialized_profiles.reserve(serialized_profiles.size()); |
| for (const auto& serialized_profile : serialized_profiles) { |
| SampledProfile profile; |
| if (profile.ParseFromArray(serialized_profile.data(), |
| serialized_profile.size())) { |
| deserialized_profiles.push_back(std::move(profile)); |
| } |
| } |
| |
| // Merge the profiles. |
| profiles.reserve(profiles.size() + deserialized_profiles.size()); |
| std::move(deserialized_profiles.begin(), deserialized_profiles.end(), |
| std::back_inserter(profiles)); |
| |
| return profiles; |
| } |
| |
| // PendingProfiles ------------------------------------------------------------ |
| |
| // Singleton class responsible for retaining profiles received from |
| // CallStackProfileBuilder. These are then sent to UMA on the invocation of |
| // CallStackProfileMetricsProvider::ProvideCurrentSessionData(). 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(); |
| |
| // Retrieves all the pending profiles. |
| std::vector<SampledProfile> RetrieveProfiles(); |
| |
| // Enables the collection of profiles by MaybeCollect*Profile if |enabled| is |
| // true. Otherwise, clears the currently collected profiles and ignores |
| // profiles provided to future invocations of MaybeCollect*Profile. |
| void SetCollectionEnabled(bool enabled); |
| |
| // Collects |profile|. It may be stored as it is, or in a serialized form, or |
| // ignored, depending on the pre-defined storage capacity and whether |
| // collection is enabled. |profile| is not const& because it must be passed |
| // with std::move. |
| void MaybeCollectProfile(base::TimeTicks profile_start_time, |
| SampledProfile profile); |
| |
| // Collects |serialized_profile|. It may be ignored depending on the |
| // pre-defined storage capacity and whether collection is enabled. |
| // |serialized_profile| is not const& because it must be passed with |
| // std::move. |
| void MaybeCollectSerializedProfile(base::TimeTicks profile_start_time, |
| std::string serialized_profile); |
| |
| // Allows testing against the initial state multiple times. |
| void ResetToDefaultStateForTesting(); |
| |
| private: |
| friend class base::NoDestructor<PendingProfiles>; |
| |
| PendingProfiles(); |
| ~PendingProfiles() = delete; |
| |
| // Returns true if collection is enabled for a given profile based on its |
| // |profile_start_time|. The |lock_| must be held prior to calling this |
| // method. |
| bool IsCollectionEnabledForProfile(base::TimeTicks profile_start_time) const; |
| |
| // Whether there is spare capacity to store an additional profile. |
| // The |lock_| must be held prior to calling this method. |
| bool HasSpareCapacity() const; |
| |
| mutable base::Lock lock_; |
| |
| // If true, profiles provided to MaybeCollect*Profile 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 last time collection was enabled. Used to determine if collection was |
| // enabled at any point since a profile was started. |
| base::TimeTicks last_collection_enable_time_; |
| |
| // The set of completed unserialized profiles that should be reported. |
| std::vector<SampledProfile> unserialized_profiles_; |
| |
| // The set of completed serialized profiles that should be reported. |
| std::vector<std::string> serialized_profiles_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PendingProfiles); |
| }; |
| |
| // static |
| PendingProfiles* PendingProfiles::GetInstance() { |
| // Singleton for performance rather than correctness reasons. |
| static base::NoDestructor<PendingProfiles> instance; |
| return instance.get(); |
| } |
| |
| std::vector<SampledProfile> PendingProfiles::RetrieveProfiles() { |
| std::vector<SampledProfile> profiles; |
| std::vector<std::string> serialized_profiles; |
| |
| { |
| base::AutoLock scoped_lock(lock_); |
| profiles.swap(unserialized_profiles_); |
| serialized_profiles.swap(serialized_profiles_); |
| } |
| |
| // Merge the serialized profiles by deserializing them. Note that this work is |
| // performed without holding the lock, to avoid blocking the lock for an |
| // extended period of time. |
| return MergeProfiles(std::move(profiles), std::move(serialized_profiles)); |
| } |
| |
| void PendingProfiles::SetCollectionEnabled(bool enabled) { |
| base::AutoLock scoped_lock(lock_); |
| |
| collection_enabled_ = enabled; |
| |
| if (!collection_enabled_) { |
| unserialized_profiles_.clear(); |
| serialized_profiles_.clear(); |
| last_collection_disable_time_ = base::TimeTicks::Now(); |
| } else { |
| last_collection_enable_time_ = base::TimeTicks::Now(); |
| } |
| } |
| |
| bool PendingProfiles::IsCollectionEnabledForProfile( |
| base::TimeTicks profile_start_time) const { |
| lock_.AssertAcquired(); |
| |
| // Scenario 1: return false if collection is disabled. |
| if (!collection_enabled_) |
| return false; |
| |
| // Scenario 2: return false if collection is disabled after the start of |
| // collection for this profile. |
| if (!last_collection_disable_time_.is_null() && |
| last_collection_disable_time_ >= profile_start_time) { |
| return false; |
| } |
| |
| // Scenario 3: return false if collection is disabled before the start of |
| // collection and re-enabled after the start. Note that this is different from |
| // scenario 1 where re-enabling never happens. |
| if (!last_collection_disable_time_.is_null() && |
| !last_collection_enable_time_.is_null() && |
| last_collection_enable_time_ >= profile_start_time) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool PendingProfiles::HasSpareCapacity() const { |
| lock_.AssertAcquired(); |
| return (unserialized_profiles_.size() + serialized_profiles_.size()) < |
| kMaxPendingProfiles; |
| } |
| |
| void PendingProfiles::MaybeCollectProfile(base::TimeTicks profile_start_time, |
| SampledProfile profile) { |
| { |
| base::AutoLock scoped_lock(lock_); |
| |
| if (!IsCollectionEnabledForProfile(profile_start_time)) |
| return; |
| |
| // Store the unserialized profile directly if there's room. |
| if (unserialized_profiles_.size() < kMaxPendingUnserializedProfiles) { |
| unserialized_profiles_.push_back(std::move(profile)); |
| return; |
| } |
| |
| // This early return is strictly a performance optimization to avoid doing |
| // unnecessary serialization below. For correctness, since the |
| // serialization happens without holding the lock, it's necessary to check |
| // this condition again prior to actually collecting the serialized profile. |
| if (!HasSpareCapacity()) |
| return; |
| } |
| |
| // There was no room to store the unserialized profile directly, but there was |
| // room to store it in serialized form. Serialize the profile without holding |
| // the lock, then try again to store it. |
| std::string serialized_profile; |
| profile.SerializeToString(&serialized_profile); |
| MaybeCollectSerializedProfile(profile_start_time, |
| std::move(serialized_profile)); |
| } |
| |
| void PendingProfiles::MaybeCollectSerializedProfile( |
| base::TimeTicks profile_start_time, |
| std::string serialized_profile) { |
| base::AutoLock scoped_lock(lock_); |
| |
| if (IsCollectionEnabledForProfile(profile_start_time) && HasSpareCapacity()) |
| serialized_profiles_.push_back(std::move(serialized_profile)); |
| } |
| |
| void PendingProfiles::ResetToDefaultStateForTesting() { |
| base::AutoLock scoped_lock(lock_); |
| |
| collection_enabled_ = true; |
| last_collection_disable_time_ = base::TimeTicks(); |
| last_collection_enable_time_ = base::TimeTicks(); |
| unserialized_profiles_.clear(); |
| serialized_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) {} |
| |
| } // namespace |
| |
| // CallStackProfileMetricsProvider -------------------------------------------- |
| |
| const base::Feature CallStackProfileMetricsProvider::kEnableReporting = { |
| "SamplingProfilerReporting", base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() {} |
| |
| CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {} |
| |
| // static |
| void CallStackProfileMetricsProvider::ReceiveProfile( |
| base::TimeTicks profile_start_time, |
| SampledProfile profile) { |
| PendingProfiles::GetInstance()->MaybeCollectProfile(profile_start_time, |
| std::move(profile)); |
| } |
| |
| // static |
| void CallStackProfileMetricsProvider::ReceiveSerializedProfile( |
| base::TimeTicks profile_start_time, |
| std::string serialized_profile) { |
| PendingProfiles::GetInstance()->MaybeCollectSerializedProfile( |
| profile_start_time, std::move(serialized_profile)); |
| } |
| |
| void CallStackProfileMetricsProvider::OnRecordingEnabled() { |
| PendingProfiles::GetInstance()->SetCollectionEnabled( |
| base::FeatureList::IsEnabled(kEnableReporting)); |
| } |
| |
| void CallStackProfileMetricsProvider::OnRecordingDisabled() { |
| PendingProfiles::GetInstance()->SetCollectionEnabled(false); |
| } |
| |
| void CallStackProfileMetricsProvider::ProvideCurrentSessionData( |
| ChromeUserMetricsExtension* uma_proto) { |
| std::vector<SampledProfile> profiles = |
| PendingProfiles::GetInstance()->RetrieveProfiles(); |
| |
| DCHECK(base::FeatureList::IsEnabled(kEnableReporting) || profiles.empty()); |
| |
| for (auto& profile : profiles) |
| *uma_proto->add_sampled_profile() = std::move(profile); |
| } |
| |
| // static |
| void CallStackProfileMetricsProvider::ResetStaticStateForTesting() { |
| PendingProfiles::GetInstance()->ResetToDefaultStateForTesting(); |
| } |
| |
| } // namespace metrics |