314 lines
11 KiB
C++
314 lines
11 KiB
C++
|
|
// Copyright 2018 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_builder.h"
|
||
|
|
|
||
|
|
#include <algorithm>
|
||
|
|
#include <iterator>
|
||
|
|
#include <map>
|
||
|
|
#include <memory>
|
||
|
|
#include <string>
|
||
|
|
#include <tuple>
|
||
|
|
#include <utility>
|
||
|
|
|
||
|
|
#include "base/check.h"
|
||
|
|
#include "base/files/file_path.h"
|
||
|
|
#include "base/logging.h"
|
||
|
|
#include "base/metrics/metrics_hashes.h"
|
||
|
|
#include "base/no_destructor.h"
|
||
|
|
#include "base/time/time.h"
|
||
|
|
#include "build/build_config.h"
|
||
|
|
#include "components/metrics/call_stack_profile_encoding.h"
|
||
|
|
|
||
|
|
namespace metrics {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// Only used by child processes. This returns a unique_ptr so that it can be
|
||
|
|
// reset during tests.
|
||
|
|
std::unique_ptr<ChildCallStackProfileCollector>&
|
||
|
|
GetChildCallStackProfileCollector() {
|
||
|
|
static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
|
||
|
|
instance(std::make_unique<ChildCallStackProfileCollector>());
|
||
|
|
return *instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
|
||
|
|
GetBrowserProcessReceiverCallbackInstance() {
|
||
|
|
static base::NoDestructor<
|
||
|
|
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
|
||
|
|
instance;
|
||
|
|
return *instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert |filename| to its MD5 hash.
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
CallStackProfileBuilder::CallStackProfileBuilder(
|
||
|
|
const CallStackProfileParams& profile_params,
|
||
|
|
const WorkIdRecorder* work_id_recorder,
|
||
|
|
base::OnceClosure completed_callback)
|
||
|
|
: work_id_recorder_(work_id_recorder) {
|
||
|
|
completed_callback_ = std::move(completed_callback);
|
||
|
|
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));
|
||
|
|
if (!profile_params.time_offset.is_zero()) {
|
||
|
|
DCHECK(profile_params.time_offset.is_positive());
|
||
|
|
CallStackProfile* call_stack_profile =
|
||
|
|
sampled_profile_.mutable_call_stack_profile();
|
||
|
|
call_stack_profile->set_profile_time_offset_ms(
|
||
|
|
profile_params.time_offset.InMilliseconds());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
CallStackProfileBuilder::~CallStackProfileBuilder() = default;
|
||
|
|
|
||
|
|
base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
|
||
|
|
return &module_cache_;
|
||
|
|
}
|
||
|
|
|
||
|
|
// This function is invoked on the profiler thread while the target thread is
|
||
|
|
// suspended so must not take any locks, including indirectly through use of
|
||
|
|
// heap allocation, LOG, CHECK, or DCHECK.
|
||
|
|
void CallStackProfileBuilder::RecordMetadata(
|
||
|
|
const base::MetadataRecorder::MetadataProvider& metadata_provider) {
|
||
|
|
if (work_id_recorder_) {
|
||
|
|
unsigned int work_id = work_id_recorder_->RecordWorkId();
|
||
|
|
// A work id of 0 indicates that the message loop has not yet started.
|
||
|
|
if (work_id != 0) {
|
||
|
|
is_continued_work_ = (last_work_id_ == work_id);
|
||
|
|
last_work_id_ = work_id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
metadata_.RecordMetadata(metadata_provider);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileBuilder::ApplyMetadataRetrospectively(
|
||
|
|
base::TimeTicks period_start,
|
||
|
|
base::TimeTicks period_end,
|
||
|
|
const base::MetadataRecorder::Item& item) {
|
||
|
|
DCHECK_LE(period_start, period_end);
|
||
|
|
DCHECK_LE(period_end, base::TimeTicks::Now());
|
||
|
|
|
||
|
|
// We don't set metadata if the period extends before the start of the
|
||
|
|
// sampling, to avoid biasing against the unobserved execution. This will
|
||
|
|
// introduce bias due to dropping periods longer than the sampling time, but
|
||
|
|
// that bias is easier to reason about and account for.
|
||
|
|
if (period_start < profile_start_time_)
|
||
|
|
return;
|
||
|
|
|
||
|
|
CallStackProfile* call_stack_profile =
|
||
|
|
sampled_profile_.mutable_call_stack_profile();
|
||
|
|
google::protobuf::RepeatedPtrField<CallStackProfile::StackSample>* samples =
|
||
|
|
call_stack_profile->mutable_stack_sample();
|
||
|
|
|
||
|
|
DCHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
|
||
|
|
|
||
|
|
const ptrdiff_t start_offset =
|
||
|
|
std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
|
||
|
|
period_start) -
|
||
|
|
sample_timestamps_.begin();
|
||
|
|
const ptrdiff_t end_offset =
|
||
|
|
std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
|
||
|
|
period_end) -
|
||
|
|
sample_timestamps_.begin();
|
||
|
|
|
||
|
|
metadata_.ApplyMetadata(item, samples->begin() + start_offset,
|
||
|
|
samples->begin() + end_offset, samples,
|
||
|
|
call_stack_profile->mutable_metadata_name_hash());
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileBuilder::AddProfileMetadata(
|
||
|
|
const base::MetadataRecorder::Item& item) {
|
||
|
|
CallStackProfile* call_stack_profile =
|
||
|
|
sampled_profile_.mutable_call_stack_profile();
|
||
|
|
|
||
|
|
metadata_.SetMetadata(item,
|
||
|
|
call_stack_profile->mutable_profile_metadata()->Add(),
|
||
|
|
call_stack_profile->mutable_metadata_name_hash());
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileBuilder::OnSampleCompleted(
|
||
|
|
std::vector<base::Frame> frames,
|
||
|
|
base::TimeTicks sample_timestamp) {
|
||
|
|
OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileBuilder::OnSampleCompleted(
|
||
|
|
std::vector<base::Frame> frames,
|
||
|
|
base::TimeTicks sample_timestamp,
|
||
|
|
size_t weight,
|
||
|
|
size_t count) {
|
||
|
|
// Write CallStackProfile::Stack protobuf message.
|
||
|
|
CallStackProfile::Stack stack;
|
||
|
|
|
||
|
|
for (const auto& frame : frames) {
|
||
|
|
// The function name should never be provided in UMA profiler usage.
|
||
|
|
DCHECK(frame.function_name.empty());
|
||
|
|
|
||
|
|
// keep the frame information even if its module is invalid so we have
|
||
|
|
// visibility into how often this issue is happening on the server.
|
||
|
|
CallStackProfile::Location* location = stack.add_frame();
|
||
|
|
if (!frame.module)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
// Dedup modules.
|
||
|
|
auto module_loc = module_index_.find(frame.module);
|
||
|
|
if (module_loc == module_index_.end()) {
|
||
|
|
modules_.push_back(frame.module);
|
||
|
|
size_t index = modules_.size() - 1;
|
||
|
|
module_loc = module_index_.emplace(frame.module, index).first;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write CallStackProfile::Location protobuf message.
|
||
|
|
uintptr_t instruction_pointer = frame.instruction_pointer;
|
||
|
|
#if BUILDFLAG(IS_IOS)
|
||
|
|
#if !TARGET_IPHONE_SIMULATOR
|
||
|
|
// Some iOS devices enable pointer authentication, which uses the
|
||
|
|
// higher-order bits of pointers to store a signature. Strip that signature
|
||
|
|
// off before computing the module_offset.
|
||
|
|
// TODO(crbug.com/1084272): Use the ptrauth_strip() macro once it is
|
||
|
|
// available.
|
||
|
|
instruction_pointer &= 0xFFFFFFFFF;
|
||
|
|
#endif // !TARGET_IPHONE_SIMULATOR
|
||
|
|
#endif // BUILDFLAG(IS_IOS)
|
||
|
|
|
||
|
|
ptrdiff_t module_offset =
|
||
|
|
reinterpret_cast<const char*>(instruction_pointer) -
|
||
|
|
reinterpret_cast<const char*>(frame.module->GetBaseAddress());
|
||
|
|
DCHECK_GE(module_offset, 0);
|
||
|
|
location->set_address(static_cast<uint64_t>(module_offset));
|
||
|
|
location->set_module_id_index(module_loc->second);
|
||
|
|
}
|
||
|
|
|
||
|
|
CallStackProfile* call_stack_profile =
|
||
|
|
sampled_profile_.mutable_call_stack_profile();
|
||
|
|
|
||
|
|
// Dedup Stacks.
|
||
|
|
auto stack_loc = stack_index_.find(&stack);
|
||
|
|
if (stack_loc == stack_index_.end()) {
|
||
|
|
*call_stack_profile->add_stack() = std::move(stack);
|
||
|
|
int stack_index = call_stack_profile->stack_size() - 1;
|
||
|
|
// It is safe to store the Stack pointer because the repeated message
|
||
|
|
// representation ensures pointer stability.
|
||
|
|
stack_loc = stack_index_
|
||
|
|
.emplace(call_stack_profile->mutable_stack(stack_index),
|
||
|
|
stack_index)
|
||
|
|
.first;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write CallStackProfile::StackSample protobuf message.
|
||
|
|
CallStackProfile::StackSample* stack_sample_proto =
|
||
|
|
call_stack_profile->add_stack_sample();
|
||
|
|
stack_sample_proto->set_stack_index(stack_loc->second);
|
||
|
|
if (weight != 1)
|
||
|
|
stack_sample_proto->set_weight(weight);
|
||
|
|
if (count != 1)
|
||
|
|
stack_sample_proto->set_count(count);
|
||
|
|
if (is_continued_work_)
|
||
|
|
stack_sample_proto->set_continued_work(is_continued_work_);
|
||
|
|
|
||
|
|
*stack_sample_proto->mutable_metadata() = metadata_.CreateSampleMetadata(
|
||
|
|
call_stack_profile->mutable_metadata_name_hash());
|
||
|
|
|
||
|
|
if (profile_start_time_.is_null())
|
||
|
|
profile_start_time_ = sample_timestamp;
|
||
|
|
|
||
|
|
sample_timestamps_.push_back(sample_timestamp);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileBuilder::OnProfileCompleted(
|
||
|
|
base::TimeDelta profile_duration,
|
||
|
|
base::TimeDelta sampling_period) {
|
||
|
|
// Build the SampledProfile protobuf message.
|
||
|
|
CallStackProfile* call_stack_profile =
|
||
|
|
sampled_profile_.mutable_call_stack_profile();
|
||
|
|
call_stack_profile->set_profile_duration_ms(
|
||
|
|
profile_duration.InMilliseconds());
|
||
|
|
call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
|
||
|
|
|
||
|
|
// Write CallStackProfile::ModuleIdentifier protobuf message.
|
||
|
|
for (const auto* module : modules_) {
|
||
|
|
CallStackProfile::ModuleIdentifier* module_id =
|
||
|
|
call_stack_profile->add_module_id();
|
||
|
|
module_id->set_build_id(module->GetId());
|
||
|
|
module_id->set_name_md5_prefix(
|
||
|
|
HashModuleFilename(module->GetDebugBasename()));
|
||
|
|
}
|
||
|
|
|
||
|
|
PassProfilesToMetricsProvider(profile_start_time_,
|
||
|
|
std::move(sampled_profile_));
|
||
|
|
|
||
|
|
// Run the completed callback if there is one.
|
||
|
|
if (!completed_callback_.is_null())
|
||
|
|
std::move(completed_callback_).Run();
|
||
|
|
|
||
|
|
// Clear the caches.
|
||
|
|
stack_index_.clear();
|
||
|
|
module_index_.clear();
|
||
|
|
modules_.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
|
||
|
|
const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
|
||
|
|
callback) {
|
||
|
|
GetBrowserProcessReceiverCallbackInstance() = callback;
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
|
||
|
|
mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
|
||
|
|
browser_interface) {
|
||
|
|
GetChildCallStackProfileCollector()->SetParentProfileCollector(
|
||
|
|
std::move(browser_interface));
|
||
|
|
}
|
||
|
|
|
||
|
|
// static
|
||
|
|
void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
|
||
|
|
GetChildCallStackProfileCollector() =
|
||
|
|
std::make_unique<ChildCallStackProfileCollector>();
|
||
|
|
}
|
||
|
|
|
||
|
|
void CallStackProfileBuilder::PassProfilesToMetricsProvider(
|
||
|
|
base::TimeTicks profile_start_time,
|
||
|
|
SampledProfile sampled_profile) {
|
||
|
|
if (sampled_profile.process() == BROWSER_PROCESS) {
|
||
|
|
GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time,
|
||
|
|
std::move(sampled_profile));
|
||
|
|
} else {
|
||
|
|
GetChildCallStackProfileCollector()->Collect(profile_start_time,
|
||
|
|
std::move(sampled_profile));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CallStackProfileBuilder::StackComparer::operator()(
|
||
|
|
const CallStackProfile::Stack* stack1,
|
||
|
|
const CallStackProfile::Stack* stack2) const {
|
||
|
|
return std::lexicographical_compare(
|
||
|
|
stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
|
||
|
|
stack2->frame().end(),
|
||
|
|
[](const CallStackProfile::Location& loc1,
|
||
|
|
const CallStackProfile::Location& loc2) {
|
||
|
|
return std::make_pair(loc1.address(), loc1.module_id_index()) <
|
||
|
|
std::make_pair(loc2.address(), loc2.module_id_index());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace metrics
|