| // 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 <memory> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/no_destructor.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)); |
| } |
| |
| 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::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) { |
| // 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()); |
| // Temporarily disable this DCHECK as there's likely bug in ModuleCache |
| // that causes this to fail. This results in bad telemetry data but no |
| // functional effect. https://crbug.com/1240645. |
| // 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 |