477 lines
17 KiB
C++
477 lines
17 KiB
C++
|
|
// Copyright 2015 The Chromium Authors
|
||
|
|
// 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 <utility>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
#include "base/check.h"
|
||
|
|
#include "base/feature_list.h"
|
||
|
|
#include "base/functional/bind.h"
|
||
|
|
#include "base/no_destructor.h"
|
||
|
|
#include "base/ranges/algorithm.h"
|
||
|
|
#include "base/synchronization/lock.h"
|
||
|
|
#include "base/thread_annotations.h"
|
||
|
|
#include "base/time/time.h"
|
||
|
|
#include "sampled_profile.pb.h"
|
||
|
|
#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
|
||
|
|
|
||
|
|
namespace metrics {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
constexpr base::FeatureState kSamplingProfilerReportingDefaultState =
|
||
|
|
base::FEATURE_ENABLED_BY_DEFAULT;
|
||
|
|
|
||
|
|
bool SamplingProfilerReportingEnabled() {
|
||
|
|
// TODO(crbug.com/1384179): Do not call this function before the FeatureList
|
||
|
|
// is registered.
|
||
|
|
if (!base::FeatureList::GetInstance()) {
|
||
|
|
// The FeatureList is not registered: use the feature's default state. This
|
||
|
|
// means that any override from the command line or variations service is
|
||
|
|
// ignored.
|
||
|
|
return kSamplingProfilerReportingDefaultState ==
|
||
|
|
base::FEATURE_ENABLED_BY_DEFAULT;
|
||
|
|
}
|
||
|
|
return base::FeatureList::IsEnabled(kSamplingProfilerReporting);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cap the number of pending profiles to avoid excessive performance overhead
|
||
|
|
// due to profile deserialization when profile uploads are delayed (e.g. due to
|
||
|
|
// being offline). Capping at this threshold loses approximately 0.5% of
|
||
|
|
// profiles on canary and dev.
|
||
|
|
//
|
||
|
|
// TODO(wittman): Remove this threshold after crbug.com/903972 is fixed.
|
||
|
|
const size_t kMaxPendingProfiles = 1250;
|
||
|
|
|
||
|
|
// Provides access to the singleton interceptor callback instance for CPU
|
||
|
|
// profiles. Accessed asynchronously on the profiling thread after profiling has
|
||
|
|
// been started.
|
||
|
|
CallStackProfileMetricsProvider::InterceptorCallback&
|
||
|
|
GetCpuInterceptorCallbackInstance() {
|
||
|
|
static base::NoDestructor<
|
||
|
|
CallStackProfileMetricsProvider::InterceptorCallback>
|
||
|
|
instance;
|
||
|
|
return *instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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();
|
||
|
|
|
||
|
|
PendingProfiles(const PendingProfiles&) = delete;
|
||
|
|
PendingProfiles& operator=(const PendingProfiles&) = delete;
|
||
|
|
|
||
|
|
// 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 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| must be passed with std::move because it could be very
|
||
|
|
// large.
|
||
|
|
void MaybeCollectSerializedProfile(base::TimeTicks profile_start_time,
|
||
|
|
std::string&& serialized_profile);
|
||
|
|
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
// Returns all the serialized profiles that have been collected but not yet
|
||
|
|
// retrieved. For thread-safety reasons, returns a copy, so this is an
|
||
|
|
// expensive function. Fortunately, it's only called during ChromeOS tast
|
||
|
|
// integration tests.
|
||
|
|
std::vector<std::string> GetUnretrievedProfiles() {
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
return serialized_profiles_;
|
||
|
|
}
|
||
|
|
#endif // BUILDFLAG(IS_CHROMEOS)
|
||
|
|
|
||
|
|
// 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
|
||
|
|
EXCLUSIVE_LOCKS_REQUIRED(lock_);
|
||
|
|
|
||
|
|
mutable base::Lock lock_;
|
||
|
|
|
||
|
|
// If true, profiles provided to MaybeCollect*Profile should be collected.
|
||
|
|
// Otherwise they will be ignored.
|
||
|
|
// |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.
|
||
|
|
bool collection_enabled_ GUARDED_BY(lock_) = true;
|
||
|
|
|
||
|
|
// 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_ GUARDED_BY(lock_);
|
||
|
|
|
||
|
|
// 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_ GUARDED_BY(lock_);
|
||
|
|
|
||
|
|
// The set of completed serialized profiles that should be reported.
|
||
|
|
std::vector<std::string> serialized_profiles_ GUARDED_BY(lock_);
|
||
|
|
};
|
||
|
|
|
||
|
|
// 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<std::string> serialized_profiles;
|
||
|
|
|
||
|
|
{
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
serialized_profiles.swap(serialized_profiles_);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Deserialize all serialized profiles, skipping over any that fail to parse.
|
||
|
|
std::vector<SampledProfile> profiles;
|
||
|
|
profiles.reserve(serialized_profiles.size());
|
||
|
|
for (const auto& serialized_profile : serialized_profiles) {
|
||
|
|
SampledProfile profile;
|
||
|
|
if (profile.ParseFromString(serialized_profile)) {
|
||
|
|
profiles.push_back(std::move(profile));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return profiles;
|
||
|
|
}
|
||
|
|
|
||
|
|
void PendingProfiles::SetCollectionEnabled(bool enabled) {
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
|
||
|
|
collection_enabled_ = enabled;
|
||
|
|
|
||
|
|
if (!collection_enabled_) {
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
void PendingProfiles::MaybeCollectProfile(base::TimeTicks profile_start_time,
|
||
|
|
SampledProfile profile) {
|
||
|
|
{
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
|
||
|
|
if (!IsCollectionEnabledForProfile(profile_start_time))
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Serialize the profile without holding the lock.
|
||
|
|
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_);
|
||
|
|
|
||
|
|
// There is no room for additional profiles.
|
||
|
|
if (serialized_profiles_.size() >= kMaxPendingProfiles)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (IsCollectionEnabledForProfile(profile_start_time))
|
||
|
|
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();
|
||
|
|
serialized_profiles_.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
PendingProfiles::PendingProfiles() = default;
|
||
|
|
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
// A class that records the number of minimally-successful profiles received
|
||
|
|
// over time. In ChromeOS, this is used by the ui.StackSampledMetrics tast
|
||
|
|
// integration test to confirm that stack-sampled metrics are working on
|
||
|
|
// all the various ChromeOS boards.
|
||
|
|
class ReceivedProfileCounter {
|
||
|
|
public:
|
||
|
|
static ReceivedProfileCounter* GetInstance();
|
||
|
|
|
||
|
|
ReceivedProfileCounter(const ReceivedProfileCounter&) = delete;
|
||
|
|
ReceivedProfileCounter& operator=(const ReceivedProfileCounter&) = delete;
|
||
|
|
~ReceivedProfileCounter() = delete;
|
||
|
|
|
||
|
|
// Gets the counts of all successfully collected profiles, broken down by
|
||
|
|
// process type and thread type. "Successfully collected" is defined pretty
|
||
|
|
// minimally (we got a couple of frames).
|
||
|
|
CallStackProfileMetricsProvider::ProcessThreadCount
|
||
|
|
GetSuccessfullyCollectedCounts();
|
||
|
|
|
||
|
|
// Given a list of profiles returned from PendingProfiles::RetrieveProfiles(),
|
||
|
|
// add counts from all the successful profiles in the list to our counts for
|
||
|
|
// later.
|
||
|
|
void OnRetrieveProfiles(const std::vector<SampledProfile>& profiles);
|
||
|
|
|
||
|
|
// Allows testing against the initial state multiple times.
|
||
|
|
void ResetToDefaultStateForTesting(); // IN-TEST
|
||
|
|
|
||
|
|
private:
|
||
|
|
friend class base::NoDestructor<ReceivedProfileCounter>;
|
||
|
|
|
||
|
|
ReceivedProfileCounter() = default;
|
||
|
|
|
||
|
|
// Returns true if the given profile was success enough to be counted in
|
||
|
|
// retrieved_successful_counts_.
|
||
|
|
static bool WasMinimallySuccessful(const SampledProfile& profile);
|
||
|
|
|
||
|
|
mutable base::Lock lock_;
|
||
|
|
|
||
|
|
// Count of successfully-stack-walked SampledProfiles retrieved since startup.
|
||
|
|
// "success" is defined by WasMinimallySuccessful().
|
||
|
|
CallStackProfileMetricsProvider::ProcessThreadCount
|
||
|
|
retrieved_successful_counts_ GUARDED_BY(lock_);
|
||
|
|
};
|
||
|
|
|
||
|
|
// static
|
||
|
|
ReceivedProfileCounter* ReceivedProfileCounter::GetInstance() {
|
||
|
|
static base::NoDestructor<ReceivedProfileCounter> instance;
|
||
|
|
return instance.get();
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
bool ReceivedProfileCounter::WasMinimallySuccessful(
|
||
|
|
const SampledProfile& profile) {
|
||
|
|
// If we don't have a process or thread, we don't understand the profile.
|
||
|
|
if (!profile.has_process() || !profile.has_thread()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Since we can't symbolize the stacks, "successful" here just means that the
|
||
|
|
// stack has at least 2 frames. (The current instruction pointer should always
|
||
|
|
// count as one, so two means we had some luck walking the stack.)
|
||
|
|
const auto& stacks = profile.call_stack_profile().stack();
|
||
|
|
return base::ranges::find_if(stacks,
|
||
|
|
[](const CallStackProfile::Stack& stack) {
|
||
|
|
return stack.frame_size() >= 2;
|
||
|
|
}) != stacks.end();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ReceivedProfileCounter::OnRetrieveProfiles(
|
||
|
|
const std::vector<SampledProfile>& profiles) {
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
for (const auto& profile : profiles) {
|
||
|
|
if (WasMinimallySuccessful(profile)) {
|
||
|
|
++retrieved_successful_counts_[profile.process()][profile.thread()];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
CallStackProfileMetricsProvider::ProcessThreadCount
|
||
|
|
ReceivedProfileCounter::GetSuccessfullyCollectedCounts() {
|
||
|
|
CallStackProfileMetricsProvider::ProcessThreadCount successful_counts;
|
||
|
|
|
||
|
|
{
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
// Start with count of profiles we've already sent
|
||
|
|
successful_counts = retrieved_successful_counts_;
|
||
|
|
}
|
||
|
|
|
||
|
|
// And then add in any pending ones. Copying and then deserializing all the
|
||
|
|
// profiles is expensive, but again, this should only be called during tast
|
||
|
|
// integration tests.
|
||
|
|
std::vector<std::string> unretrieved_profiles(
|
||
|
|
PendingProfiles::GetInstance()->GetUnretrievedProfiles());
|
||
|
|
for (const std::string& serialized_profile : unretrieved_profiles) {
|
||
|
|
SampledProfile profile;
|
||
|
|
if (profile.ParseFromString(serialized_profile)) {
|
||
|
|
if (WasMinimallySuccessful(profile)) {
|
||
|
|
++successful_counts[profile.process()][profile.thread()];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return successful_counts;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ReceivedProfileCounter::ResetToDefaultStateForTesting() {
|
||
|
|
base::AutoLock scoped_lock(lock_);
|
||
|
|
retrieved_successful_counts_.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif // BUILDFLAG(IS_CHROMEOS)
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
// CallStackProfileMetricsProvider --------------------------------------------
|
||
|
|
|
||
|
|
BASE_FEATURE(kSamplingProfilerReporting,
|
||
|
|
"SamplingProfilerReporting",
|
||
|
|
kSamplingProfilerReportingDefaultState);
|
||
|
|
|
||
|
|
CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() = default;
|
||
|
|
CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() = default;
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileMetricsProvider::ReceiveProfile(
|
||
|
|
base::TimeTicks profile_start_time,
|
||
|
|
SampledProfile profile) {
|
||
|
|
if (GetCpuInterceptorCallbackInstance() &&
|
||
|
|
(profile.trigger_event() == SampledProfile::PROCESS_STARTUP ||
|
||
|
|
profile.trigger_event() == SampledProfile::PERIODIC_COLLECTION)) {
|
||
|
|
GetCpuInterceptorCallbackInstance().Run(std::move(profile));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (profile.trigger_event() != SampledProfile::PERIODIC_HEAP_COLLECTION &&
|
||
|
|
!SamplingProfilerReportingEnabled()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
PendingProfiles::GetInstance()->MaybeCollectProfile(profile_start_time,
|
||
|
|
std::move(profile));
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileMetricsProvider::ReceiveSerializedProfile(
|
||
|
|
base::TimeTicks profile_start_time,
|
||
|
|
bool is_heap_profile,
|
||
|
|
std::string&& serialized_profile) {
|
||
|
|
// Note: All parameters of this function come from a Mojo message from an
|
||
|
|
// untrusted process.
|
||
|
|
if (GetCpuInterceptorCallbackInstance()) {
|
||
|
|
// GetCpuInterceptorCallbackInstance() is set only in tests, so it's safe to
|
||
|
|
// trust `is_heap_profile` and `serialized_profile` here.
|
||
|
|
DCHECK(!is_heap_profile);
|
||
|
|
SampledProfile profile;
|
||
|
|
if (profile.ParseFromString(serialized_profile)) {
|
||
|
|
DCHECK(profile.trigger_event() == SampledProfile::PROCESS_STARTUP ||
|
||
|
|
profile.trigger_event() == SampledProfile::PERIODIC_COLLECTION);
|
||
|
|
GetCpuInterceptorCallbackInstance().Run(std::move(profile));
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If an attacker spoofs `is_heap_profile` or `profile_start_time`, the worst
|
||
|
|
// they can do is cause `serialized_profile` to be sent to UMA when profile
|
||
|
|
// reporting should be disabled.
|
||
|
|
if (!is_heap_profile && !SamplingProfilerReportingEnabled()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
PendingProfiles::GetInstance()->MaybeCollectSerializedProfile(
|
||
|
|
profile_start_time, std::move(serialized_profile));
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileMetricsProvider::SetCpuInterceptorCallbackForTesting(
|
||
|
|
InterceptorCallback callback) {
|
||
|
|
GetCpuInterceptorCallbackInstance() = std::move(callback);
|
||
|
|
}
|
||
|
|
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
// static
|
||
|
|
CallStackProfileMetricsProvider::ProcessThreadCount
|
||
|
|
CallStackProfileMetricsProvider::GetSuccessfullyCollectedCounts() {
|
||
|
|
return ReceivedProfileCounter::GetInstance()
|
||
|
|
->GetSuccessfullyCollectedCounts();
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
void CallStackProfileMetricsProvider::OnRecordingEnabled() {
|
||
|
|
PendingProfiles::GetInstance()->SetCollectionEnabled(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileMetricsProvider::OnRecordingDisabled() {
|
||
|
|
PendingProfiles::GetInstance()->SetCollectionEnabled(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileMetricsProvider::ProvideCurrentSessionData(
|
||
|
|
ChromeUserMetricsExtension* uma_proto) {
|
||
|
|
std::vector<SampledProfile> profiles =
|
||
|
|
PendingProfiles::GetInstance()->RetrieveProfiles();
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
ReceivedProfileCounter::GetInstance()->OnRetrieveProfiles(profiles);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
for (auto& profile : profiles) {
|
||
|
|
// Only heap samples should ever be received if SamplingProfilerReporting is
|
||
|
|
// disabled.
|
||
|
|
DCHECK(SamplingProfilerReportingEnabled() ||
|
||
|
|
profile.trigger_event() == SampledProfile::PERIODIC_HEAP_COLLECTION);
|
||
|
|
*uma_proto->add_sampled_profile() = std::move(profile);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileMetricsProvider::ResetStaticStateForTesting() {
|
||
|
|
PendingProfiles::GetInstance()->ResetToDefaultStateForTesting();
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
ReceivedProfileCounter::GetInstance()
|
||
|
|
->ResetToDefaultStateForTesting(); // IN-TEST
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace metrics
|