1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/metrics/call_stack_profile_builder.h"
15 #include "base/check.h"
16 #include "base/files/file_path.h"
17 #include "base/logging.h"
18 #include "base/metrics/metrics_hashes.h"
19 #include "base/no_destructor.h"
20 #include "base/time/time.h"
21 #include "build/build_config.h"
22 #include "components/metrics/call_stack_profile_encoding.h"
28 // Only used by child processes. This returns a unique_ptr so that it can be
29 // reset during tests.
30 std::unique_ptr<ChildCallStackProfileCollector>&
31 GetChildCallStackProfileCollector() {
32 static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
33 instance(std::make_unique<ChildCallStackProfileCollector>());
37 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
38 GetBrowserProcessReceiverCallbackInstance() {
39 static base::NoDestructor<
40 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
45 // Convert |filename| to its MD5 hash.
46 uint64_t HashModuleFilename(const base::FilePath& filename) {
47 const base::FilePath::StringType basename = filename.BaseName().value();
48 // Copy the bytes in basename into a string buffer.
49 size_t basename_length_in_bytes =
50 basename.size() * sizeof(base::FilePath::CharType);
51 std::string name_bytes(basename_length_in_bytes, '\0');
52 memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
53 return base::HashMetricName(name_bytes);
58 CallStackProfileBuilder::CallStackProfileBuilder(
59 const CallStackProfileParams& profile_params,
60 const WorkIdRecorder* work_id_recorder,
61 base::OnceClosure completed_callback)
62 : work_id_recorder_(work_id_recorder) {
63 completed_callback_ = std::move(completed_callback);
64 sampled_profile_.set_process(
65 ToExecutionContextProcess(profile_params.process));
66 sampled_profile_.set_thread(ToExecutionContextThread(profile_params.thread));
67 sampled_profile_.set_trigger_event(
68 ToSampledProfileTriggerEvent(profile_params.trigger));
69 if (!profile_params.time_offset.is_zero()) {
70 DCHECK(profile_params.time_offset.is_positive());
71 CallStackProfile* call_stack_profile =
72 sampled_profile_.mutable_call_stack_profile();
73 call_stack_profile->set_profile_time_offset_ms(
74 profile_params.time_offset.InMilliseconds());
78 CallStackProfileBuilder::~CallStackProfileBuilder() = default;
80 base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
81 return &module_cache_;
84 // This function is invoked on the profiler thread while the target thread is
85 // suspended so must not take any locks, including indirectly through use of
86 // heap allocation, LOG, CHECK, or DCHECK.
87 void CallStackProfileBuilder::RecordMetadata(
88 const base::MetadataRecorder::MetadataProvider& metadata_provider) {
89 if (work_id_recorder_) {
90 unsigned int work_id = work_id_recorder_->RecordWorkId();
91 // A work id of 0 indicates that the message loop has not yet started.
93 is_continued_work_ = (last_work_id_ == work_id);
94 last_work_id_ = work_id;
98 metadata_.RecordMetadata(metadata_provider);
101 void CallStackProfileBuilder::ApplyMetadataRetrospectively(
102 base::TimeTicks period_start,
103 base::TimeTicks period_end,
104 const base::MetadataRecorder::Item& item) {
105 DCHECK_LE(period_start, period_end);
106 DCHECK_LE(period_end, base::TimeTicks::Now());
108 // We don't set metadata if the period extends before the start of the
109 // sampling, to avoid biasing against the unobserved execution. This will
110 // introduce bias due to dropping periods longer than the sampling time, but
111 // that bias is easier to reason about and account for.
112 if (period_start < profile_start_time_)
115 CallStackProfile* call_stack_profile =
116 sampled_profile_.mutable_call_stack_profile();
117 google::protobuf::RepeatedPtrField<CallStackProfile::StackSample>* samples =
118 call_stack_profile->mutable_stack_sample();
120 DCHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
122 const ptrdiff_t start_offset =
123 std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
125 sample_timestamps_.begin();
126 const ptrdiff_t end_offset =
127 std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
129 sample_timestamps_.begin();
131 metadata_.ApplyMetadata(item, samples->begin() + start_offset,
132 samples->begin() + end_offset, samples,
133 call_stack_profile->mutable_metadata_name_hash());
136 void CallStackProfileBuilder::OnSampleCompleted(
137 std::vector<base::Frame> frames,
138 base::TimeTicks sample_timestamp) {
139 OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
142 void CallStackProfileBuilder::OnSampleCompleted(
143 std::vector<base::Frame> frames,
144 base::TimeTicks sample_timestamp,
147 // Write CallStackProfile::Stack protobuf message.
148 CallStackProfile::Stack stack;
150 for (const auto& frame : frames) {
151 // The function name should never be provided in UMA profiler usage.
152 DCHECK(frame.function_name.empty());
154 // keep the frame information even if its module is invalid so we have
155 // visibility into how often this issue is happening on the server.
156 CallStackProfile::Location* location = stack.add_frame();
161 auto module_loc = module_index_.find(frame.module);
162 if (module_loc == module_index_.end()) {
163 modules_.push_back(frame.module);
164 size_t index = modules_.size() - 1;
165 module_loc = module_index_.emplace(frame.module, index).first;
168 // Write CallStackProfile::Location protobuf message.
169 uintptr_t instruction_pointer = frame.instruction_pointer;
170 #if BUILDFLAG(IS_IOS)
171 #if !TARGET_IPHONE_SIMULATOR
172 // Some iOS devices enable pointer authentication, which uses the
173 // higher-order bits of pointers to store a signature. Strip that signature
174 // off before computing the module_offset.
175 // TODO(crbug.com/1084272): Use the ptrauth_strip() macro once it is
177 instruction_pointer &= 0xFFFFFFFFF;
178 #endif // !TARGET_IPHONE_SIMULATOR
179 #endif // BUILDFLAG(IS_IOS)
181 ptrdiff_t module_offset =
182 reinterpret_cast<const char*>(instruction_pointer) -
183 reinterpret_cast<const char*>(frame.module->GetBaseAddress());
184 // Temporarily disable this DCHECK as there's likely bug in ModuleCache
185 // that causes this to fail. This results in bad telemetry data but no
186 // functional effect. https://crbug.com/1240645.
187 // DCHECK_GE(module_offset, 0);
188 location->set_address(static_cast<uint64_t>(module_offset));
189 location->set_module_id_index(module_loc->second);
192 CallStackProfile* call_stack_profile =
193 sampled_profile_.mutable_call_stack_profile();
196 auto stack_loc = stack_index_.find(&stack);
197 if (stack_loc == stack_index_.end()) {
198 *call_stack_profile->add_stack() = std::move(stack);
199 int stack_index = call_stack_profile->stack_size() - 1;
200 // It is safe to store the Stack pointer because the repeated message
201 // representation ensures pointer stability.
202 stack_loc = stack_index_
203 .emplace(call_stack_profile->mutable_stack(stack_index),
208 // Write CallStackProfile::StackSample protobuf message.
209 CallStackProfile::StackSample* stack_sample_proto =
210 call_stack_profile->add_stack_sample();
211 stack_sample_proto->set_stack_index(stack_loc->second);
213 stack_sample_proto->set_weight(weight);
215 stack_sample_proto->set_count(count);
216 if (is_continued_work_)
217 stack_sample_proto->set_continued_work(is_continued_work_);
219 *stack_sample_proto->mutable_metadata() = metadata_.CreateSampleMetadata(
220 call_stack_profile->mutable_metadata_name_hash());
222 if (profile_start_time_.is_null())
223 profile_start_time_ = sample_timestamp;
225 sample_timestamps_.push_back(sample_timestamp);
228 void CallStackProfileBuilder::OnProfileCompleted(
229 base::TimeDelta profile_duration,
230 base::TimeDelta sampling_period) {
231 // Build the SampledProfile protobuf message.
232 CallStackProfile* call_stack_profile =
233 sampled_profile_.mutable_call_stack_profile();
234 call_stack_profile->set_profile_duration_ms(
235 profile_duration.InMilliseconds());
236 call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
238 // Write CallStackProfile::ModuleIdentifier protobuf message.
239 for (const auto* module : modules_) {
240 CallStackProfile::ModuleIdentifier* module_id =
241 call_stack_profile->add_module_id();
242 module_id->set_build_id(module->GetId());
243 module_id->set_name_md5_prefix(
244 HashModuleFilename(module->GetDebugBasename()));
247 PassProfilesToMetricsProvider(profile_start_time_,
248 std::move(sampled_profile_));
250 // Run the completed callback if there is one.
251 if (!completed_callback_.is_null())
252 std::move(completed_callback_).Run();
255 stack_index_.clear();
256 module_index_.clear();
261 void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
262 const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
264 GetBrowserProcessReceiverCallbackInstance() = callback;
268 void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
269 mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
271 GetChildCallStackProfileCollector()->SetParentProfileCollector(
272 std::move(browser_interface));
276 void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
277 GetChildCallStackProfileCollector() =
278 std::make_unique<ChildCallStackProfileCollector>();
281 void CallStackProfileBuilder::PassProfilesToMetricsProvider(
282 base::TimeTicks profile_start_time,
283 SampledProfile sampled_profile) {
284 if (sampled_profile.process() == BROWSER_PROCESS) {
285 GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time,
286 std::move(sampled_profile));
288 GetChildCallStackProfileCollector()->Collect(profile_start_time,
289 std::move(sampled_profile));
293 bool CallStackProfileBuilder::StackComparer::operator()(
294 const CallStackProfile::Stack* stack1,
295 const CallStackProfile::Stack* stack2) const {
296 return std::lexicographical_compare(
297 stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
298 stack2->frame().end(),
299 [](const CallStackProfile::Location& loc1,
300 const CallStackProfile::Location& loc2) {
301 return std::make_pair(loc1.address(), loc1.module_id_index()) <
302 std::make_pair(loc2.address(), loc2.module_id_index());
306 } // namespace metrics