blob: 15840e9622919e8a454740d5942c0eb3535e8912 [file] [log] [blame]
// 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 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 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