blob: 7b2890f06a1ff1d359311936bf2a59656d490761 [file] [log] [blame]
// Copyright 2018 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_builder.h"
#include <string>
#include <utility>
#include "base/atomicops.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/metrics_hashes.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "components/metrics/call_stack_profile_encoding.h"
namespace metrics {
namespace {
// Only used by child processes.
base::LazyInstance<ChildCallStackProfileCollector>::Leaky
g_child_call_stack_profile_collector = LAZY_INSTANCE_INITIALIZER;
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
GetBrowserProcessReceiverCallbackInstance() {
static base::NoDestructor<
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
instance;
return *instance;
}
// Identifies an unknown module.
const size_t kUnknownModuleIndex = static_cast<size_t>(-1);
// This global variables holds the current system state and is recorded with
// every captured sample, done on a separate thread which is why updates to
// this must be atomic. A PostTask to move the the updates to that thread
// would skew the timing and a lock could result in deadlock if the thread
// making a change was also being profiled and got stopped.
static base::subtle::Atomic32 g_process_milestones = 0;
void ChangeAtomicFlags(base::subtle::Atomic32* flags,
base::subtle::Atomic32 set,
base::subtle::Atomic32 clear) {
DCHECK(set != 0 || clear != 0);
DCHECK_EQ(0, set & clear);
base::subtle::Atomic32 bits = base::subtle::NoBarrier_Load(flags);
while (true) {
base::subtle::Atomic32 existing = base::subtle::NoBarrier_CompareAndSwap(
flags, bits, (bits | set) & ~clear);
if (existing == bits)
break;
bits = existing;
}
}
// Provide a mapping from the C++ "enum" definition of various process mile-
// stones to the equivalent protobuf "enum" definition. This table-lookup
// conversion allows for the implementation to evolve and still be compatible
// with the protobuf -- even if there are ever more than 32 defined proto
// values, though never more than 32 could be in-use in a given C++ version
// of the code.
const ProcessPhase kProtoPhases[CallStackProfileBuilder::MILESTONES_MAX_VALUE] =
{
ProcessPhase::MAIN_LOOP_START,
ProcessPhase::MAIN_NAVIGATION_START,
ProcessPhase::MAIN_NAVIGATION_FINISHED,
ProcessPhase::FIRST_NONEMPTY_PAINT,
ProcessPhase::SHUTDOWN_START,
};
// These functions are used 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 CallStackProfileBuilder::Sample& sample,
const std::vector<base::ModuleCache::Module>& modules,
CallStackProfile::Sample* proto_sample) {
for (const auto& frame : sample.frames) {
CallStackProfile::Location* location = proto_sample->add_frame();
// 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 == 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);
location->set_address(static_cast<uint64_t>(module_offset));
location->set_module_id_index(frame.module_index);
}
}
// Transcode Sample annotations into protobuf fields. The C++ code uses a
// bit- field with each bit corresponding to an entry in an enumeration
// while the protobuf uses a repeated field of individual values. Conversion
// tables allow for arbitrary mapping, though no more than 32 in any given
// version of the code.
void CopyAnnotationsToProto(uint32_t new_milestones,
CallStackProfile::Sample* sample_proto) {
for (size_t bit = 0; new_milestones != 0 && bit < sizeof(new_milestones) * 8;
++bit) {
const uint32_t flag = 1U << bit;
if (new_milestones & flag) {
if (bit >= base::size(kProtoPhases)) {
NOTREACHED();
continue;
}
sample_proto->add_process_phase(kProtoPhases[bit]);
new_milestones ^= flag; // Bit is set so XOR will clear it.
}
}
}
} // namespace
// CallStackProfileBuilder::Frame ---------------------------------------------
CallStackProfileBuilder::Frame::Frame(uintptr_t instruction_pointer,
size_t module_index)
: instruction_pointer(instruction_pointer), module_index(module_index) {}
CallStackProfileBuilder::Frame::~Frame() = default;
CallStackProfileBuilder::Frame::Frame()
: instruction_pointer(0), module_index(kUnknownModuleIndex) {}
// CallStackProfileBuilder::Sample --------------------------------------------
CallStackProfileBuilder::Sample::Sample() = default;
CallStackProfileBuilder::Sample::Sample(const Sample& sample) = default;
CallStackProfileBuilder::Sample::~Sample() = default;
CallStackProfileBuilder::Sample::Sample(const Frame& frame) {
frames.push_back(std::move(frame));
}
CallStackProfileBuilder::Sample::Sample(const std::vector<Frame>& frames)
: frames(frames) {}
CallStackProfileBuilder::CallStackProfileBuilder(
const CallStackProfileParams& profile_params,
base::OnceClosure completed_callback)
: profile_params_(profile_params),
profile_start_time_(base::TimeTicks::Now()) {
completed_callback_ = std::move(completed_callback);
}
CallStackProfileBuilder::~CallStackProfileBuilder() = default;
void CallStackProfileBuilder::RecordAnnotations() {
// The code inside this method must not do anything that could acquire a
// mutex, including allocating memory (which includes LOG messages) because
// that mutex could be held by a stopped thread, thus resulting in deadlock.
sample_.process_milestones =
base::subtle::NoBarrier_Load(&g_process_milestones);
}
void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::StackSamplingProfiler::Frame> frames) {
// Assemble sample_ from |frames| first.
for (const auto& frame : frames) {
const base::ModuleCache::Module& module(frame.module);
if (!module.is_valid) {
sample_.frames.emplace_back(frame.instruction_pointer,
kUnknownModuleIndex);
continue;
}
// Dedup modules and cache them in modules_.
auto loc = module_index_.find(module.base_address);
if (loc == module_index_.end()) {
modules_.push_back(module);
size_t index = modules_.size() - 1;
loc = module_index_.insert(std::make_pair(module.base_address, index))
.first;
}
sample_.frames.emplace_back(frame.instruction_pointer, loc->second);
}
// Write CallStackProfile::Sample protocol buffer message based on sample_.
int existing_sample_index = -1;
auto location = sample_index_.find(sample_);
if (location != sample_index_.end())
existing_sample_index = location->second;
if (existing_sample_index != -1) {
CallStackProfile::Sample* sample_proto =
proto_profile_.mutable_deprecated_sample(existing_sample_index);
sample_proto->set_count(sample_proto->count() + 1);
return;
}
CallStackProfile::Sample* sample_proto =
proto_profile_.add_deprecated_sample();
CopySampleToProto(sample_, modules_, sample_proto);
sample_proto->set_count(1);
CopyAnnotationsToProto(sample_.process_milestones & ~milestones_,
sample_proto);
milestones_ = sample_.process_milestones;
sample_index_.insert(std::make_pair(
sample_, static_cast<int>(proto_profile_.deprecated_sample_size()) - 1));
sample_ = Sample();
}
// Build a SampledProfile in the protocol buffer message format from the
// collected sampling data. The message is then passed to
// CallStackProfileMetricsProvider or ChildCallStackProfileCollector.
// A SampledProfile message (third_party/metrics_proto/sampled_profile.proto)
// contains a CallStackProfile message
// (third_party/metrics_proto/call_stack_profile.proto) and associated profile
// parameters (process/thread/trigger event). A CallStackProfile message
// contains a set of Sample messages and ModuleIdentifier messages, and other
// sampling information. One Sample corresponds to a single recorded stack, and
// the ModuleIdentifiers record those modules associated with the recorded stack
// frames.
void CallStackProfileBuilder::OnProfileCompleted(
base::TimeDelta profile_duration,
base::TimeDelta sampling_period) {
proto_profile_.set_profile_duration_ms(profile_duration.InMilliseconds());
proto_profile_.set_sampling_period_ms(sampling_period.InMilliseconds());
for (const auto& module : 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));
}
// Clear the caches etc.
modules_.clear();
module_index_.clear();
sample_index_.clear();
// Assemble the SampledProfile protocol buffer message and run the associated
// callback to pass it.
SampledProfile sampled_profile;
CallStackProfile* proto_profile =
sampled_profile.mutable_call_stack_profile();
*proto_profile = std::move(proto_profile_);
sampled_profile.set_process(
ToExecutionContextProcess(profile_params_.process));
sampled_profile.set_thread(ToExecutionContextThread(profile_params_.thread));
sampled_profile.set_trigger_event(
ToSampledProfileTriggerEvent(profile_params_.trigger));
PassProfilesToMetricsProvider(std::move(sampled_profile));
// Run the completed callback if there is one.
if (!completed_callback_.is_null())
std::move(completed_callback_).Run();
}
// static
void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
callback) {
GetBrowserProcessReceiverCallbackInstance() = callback;
}
void CallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) {
if (profile_params_.process == CallStackProfileParams::BROWSER_PROCESS) {
GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time_,
std::move(sampled_profile));
} else {
g_child_call_stack_profile_collector.Get()
.ChildCallStackProfileCollector::Collect(profile_start_time_,
std::move(sampled_profile));
}
}
// static
void CallStackProfileBuilder::SetProcessMilestone(int milestone) {
DCHECK_LE(0, milestone);
DCHECK_GT(static_cast<int>(sizeof(g_process_milestones) * 8), milestone);
DCHECK_EQ(0, base::subtle::NoBarrier_Load(&g_process_milestones) &
(1 << milestone));
ChangeAtomicFlags(&g_process_milestones, 1 << milestone, 0);
}
// static
void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
metrics::mojom::CallStackProfileCollectorPtr browser_interface) {
g_child_call_stack_profile_collector.Get().SetParentProfileCollector(
std::move(browser_interface));
}
// These operators permit types to be compared and used in a map of Samples.
bool operator==(const CallStackProfileBuilder::Sample& a,
const CallStackProfileBuilder::Sample& b) {
return a.process_milestones == b.process_milestones && a.frames == b.frames;
}
bool operator!=(const CallStackProfileBuilder::Sample& a,
const CallStackProfileBuilder::Sample& b) {
return !(a == b);
}
bool operator<(const CallStackProfileBuilder::Sample& a,
const CallStackProfileBuilder::Sample& b) {
if (a.process_milestones != b.process_milestones)
return a.process_milestones < b.process_milestones;
return a.frames < b.frames;
}
bool operator==(const CallStackProfileBuilder::Frame& a,
const CallStackProfileBuilder::Frame& b) {
return a.instruction_pointer == b.instruction_pointer &&
a.module_index == b.module_index;
}
bool operator<(const CallStackProfileBuilder::Frame& a,
const CallStackProfileBuilder::Frame& b) {
if (a.module_index != b.module_index)
return a.module_index < b.module_index;
return a.instruction_pointer < b.instruction_pointer;
}
} // namespace metrics