blob: cb8339093c8303f9568f0703144c91a9c77f7d6e [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 <algorithm>
#include <iterator>
#include <map>
#include <string>
#include <tuple>
#include <utility>
#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;
}
// 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);
}
std::map<uint64_t, int64_t> CreateMetadataMap(
base::MetadataRecorder::ItemArray items,
size_t item_count) {
std::map<uint64_t, int64_t> item_map;
for (size_t i = 0; i < item_count; ++i) {
item_map[items[i].name_hash] = items[i].value;
}
return item_map;
}
// Returns all metadata items with new values in the current sample.
std::map<uint64_t, int64_t> GetNewOrModifiedMetadataItems(
const std::map<uint64_t, int64_t>& current_items,
const std::map<uint64_t, int64_t>& previous_items) {
std::map<uint64_t, int64_t> new_or_modified_items;
// By default, std::pairs are sorted by the first then second pair elements
// and therefore pairs with either element differing are treated as different.
std::set_difference(
current_items.begin(), current_items.end(), previous_items.begin(),
previous_items.end(),
std::inserter(new_or_modified_items, new_or_modified_items.begin()));
return new_or_modified_items;
}
// Returns all metadata items deleted since the previous sample.
std::map<uint64_t, int64_t> GetDeletedMetadataItems(
const std::map<uint64_t, int64_t>& current_items,
const std::map<uint64_t, int64_t>& previous_items) {
std::map<uint64_t, int64_t> deleted_items;
// By default, std::pairs are sorted by the first then second pair elements
// and therefore pairs with either element differing are treated as different.
//
// To find removed items, we need to override this comparator to do a set
// subtraction based only on the item name hashes, ignoring the item values.
//
// The set_difference algorithm requires that the items in the set already be
// sorted according to whatever comparator is passed to set_difference.
// Because our new sort order is just a looser version of the existing set
// sort order, we can find the set_difference here without creating a new set.
auto name_hash_comparator = [](const std::pair<uint64_t, int64_t>& lhs,
const std::pair<uint64_t, int64_t>& rhs) {
return lhs.first < rhs.first;
};
std::set_difference(previous_items.begin(), previous_items.end(),
current_items.begin(), current_items.end(),
std::inserter(deleted_items, deleted_items.begin()),
name_hash_comparator);
return deleted_items;
}
} // namespace
CallStackProfileBuilder::CallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder,
const base::MetadataRecorder* metadata_recorder,
base::OnceClosure completed_callback)
: work_id_recorder_(work_id_recorder),
metadata_recorder_(metadata_recorder),
profile_start_time_(base::TimeTicks::Now()) {
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));
}
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() {
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;
}
}
if (metadata_recorder_)
metadata_item_count_ = metadata_recorder_->GetItems(&metadata_items_);
}
void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::Frame> frames) {
OnSampleCompleted(std::move(frames), 1, 1);
}
void CallStackProfileBuilder::OnSampleCompleted(std::vector<base::Frame> frames,
size_t weight,
size_t count) {
// Write CallStackProfile::Stack protobuf message.
CallStackProfile::Stack stack;
for (const auto& frame : frames) {
// 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.
ptrdiff_t module_offset =
reinterpret_cast<const char*>(frame.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_);
AddSampleMetadata(call_stack_profile, stack_sample_proto);
}
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(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(
metrics::mojom::CallStackProfileCollectorPtr browser_interface) {
g_child_call_stack_profile_collector.Get().SetParentProfileCollector(
std::move(browser_interface));
}
void CallStackProfileBuilder::PassProfilesToMetricsProvider(
SampledProfile sampled_profile) {
if (sampled_profile.process() == 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));
}
}
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());
});
}
void CallStackProfileBuilder::AddSampleMetadata(
CallStackProfile* profile,
CallStackProfile::StackSample* sample) {
std::map<uint64_t, int64_t> current_items =
CreateMetadataMap(metadata_items_, metadata_item_count_);
for (auto item :
GetNewOrModifiedMetadataItems(current_items, previous_items_)) {
size_t name_hash_index = MaybeAddNameHashToProfile(profile, item.first);
CallStackProfile::MetadataItem* profile_item = sample->add_metadata();
profile_item->set_name_hash_index(name_hash_index);
profile_item->set_value(item.second);
}
for (auto item : GetDeletedMetadataItems(current_items, previous_items_)) {
size_t name_hash_index = MaybeAddNameHashToProfile(profile, item.first);
CallStackProfile::MetadataItem* profile_item = sample->add_metadata();
profile_item->set_name_hash_index(name_hash_index);
// Leave the value empty to indicate that the item was deleted.
}
previous_items_ = std::move(current_items);
metadata_item_count_ = 0;
}
size_t CallStackProfileBuilder::MaybeAddNameHashToProfile(
CallStackProfile* profile,
uint64_t name_hash) {
std::unordered_map<uint64_t, int>::iterator it;
bool inserted;
int next_item_index = profile->metadata_name_hash_size();
std::tie(it, inserted) =
metadata_hashes_cache_.emplace(name_hash, next_item_index);
if (inserted)
profile->add_metadata_name_hash(name_hash);
return it->second;
}
} // namespace metrics