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"
16 #include "base/check.h"
17 #include "base/files/file_path.h"
18 #include "base/logging.h"
19 #include "base/metrics/metrics_hashes.h"
20 #include "base/no_destructor.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/time/time.h"
23 #include "build/build_config.h"
24 #include "components/metrics/call_stack_profile_encoding.h"
30 // Only used by child processes. This returns a unique_ptr so that it can be
31 // reset during tests.
32 std::unique_ptr<ChildCallStackProfileCollector>&
33 GetChildCallStackProfileCollector() {
34 static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
35 instance(std::make_unique<ChildCallStackProfileCollector>());
39 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
40 GetBrowserProcessReceiverCallbackInstance() {
41 static base::NoDestructor<
42 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
47 // Convert |filename| to its MD5 hash.
48 uint64_t HashModuleFilename(const base::FilePath& filename) {
49 const base::FilePath::StringType basename = filename.BaseName().value();
50 // Copy the bytes in basename into a string buffer.
51 size_t basename_length_in_bytes =
52 basename.size() * sizeof(base::FilePath::CharType);
53 std::string name_bytes(basename_length_in_bytes, '\0');
54 memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
55 return base::HashMetricName(name_bytes);
60 CallStackProfileBuilder::CallStackProfileBuilder(
61 const CallStackProfileParams& profile_params,
62 const WorkIdRecorder* work_id_recorder,
63 base::OnceClosure completed_callback)
64 : work_id_recorder_(work_id_recorder) {
65 completed_callback_ = std::move(completed_callback);
66 sampled_profile_.set_process(
67 ToExecutionContextProcess(profile_params.process));
68 sampled_profile_.set_thread(ToExecutionContextThread(profile_params.thread));
69 sampled_profile_.set_trigger_event(
70 ToSampledProfileTriggerEvent(profile_params.trigger));
71 if (!profile_params.time_offset.is_zero()) {
72 DCHECK(profile_params.time_offset.is_positive());
73 CallStackProfile* call_stack_profile =
74 sampled_profile_.mutable_call_stack_profile();
75 call_stack_profile->set_profile_time_offset_ms(
76 profile_params.time_offset.InMilliseconds());
80 CallStackProfileBuilder::~CallStackProfileBuilder() = default;
82 base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
83 return &module_cache_;
86 // This function is invoked on the profiler thread while the target thread is
87 // suspended so must not take any locks, including indirectly through use of
88 // heap allocation, LOG, CHECK, or DCHECK.
89 void CallStackProfileBuilder::RecordMetadata(
90 const base::MetadataRecorder::MetadataProvider& metadata_provider) {
91 if (work_id_recorder_) {
92 unsigned int work_id = work_id_recorder_->RecordWorkId();
93 // A work id of 0 indicates that the message loop has not yet started.
95 is_continued_work_ = (last_work_id_ == work_id);
96 last_work_id_ = work_id;
100 metadata_.RecordMetadata(metadata_provider);
103 void CallStackProfileBuilder::ApplyMetadataRetrospectively(
104 base::TimeTicks period_start,
105 base::TimeTicks period_end,
106 const base::MetadataRecorder::Item& item) {
107 CHECK_LE(period_start, period_end);
108 CHECK_LE(period_end, base::TimeTicks::Now());
110 // We don't set metadata if the period extends before the start of the
111 // sampling, to avoid biasing against the unobserved execution. This will
112 // introduce bias due to dropping periods longer than the sampling time, but
113 // that bias is easier to reason about and account for.
114 if (period_start < profile_start_time_)
117 CallStackProfile* call_stack_profile =
118 sampled_profile_.mutable_call_stack_profile();
119 google::protobuf::RepeatedPtrField<CallStackProfile::StackSample>* samples =
120 call_stack_profile->mutable_stack_sample();
122 CHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
124 const ptrdiff_t start_offset =
125 std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
127 sample_timestamps_.begin();
128 const ptrdiff_t end_offset =
129 std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
131 sample_timestamps_.begin();
133 metadata_.ApplyMetadata(item, samples->begin() + start_offset,
134 samples->begin() + end_offset, samples,
135 call_stack_profile->mutable_metadata_name_hash());
138 void CallStackProfileBuilder::AddProfileMetadata(
139 const base::MetadataRecorder::Item& item) {
140 CallStackProfile* call_stack_profile =
141 sampled_profile_.mutable_call_stack_profile();
143 metadata_.SetMetadata(item,
144 call_stack_profile->mutable_profile_metadata()->Add(),
145 call_stack_profile->mutable_metadata_name_hash());
148 void CallStackProfileBuilder::OnSampleCompleted(
149 std::vector<base::Frame> frames,
150 base::TimeTicks sample_timestamp) {
151 OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
154 void CallStackProfileBuilder::OnSampleCompleted(
155 std::vector<base::Frame> frames,
156 base::TimeTicks sample_timestamp,
159 // Write CallStackProfile::Stack protobuf message.
160 CallStackProfile::Stack stack;
162 for (const auto& frame : frames) {
163 // The function name should never be provided in UMA profiler usage.
164 DCHECK(frame.function_name.empty());
166 // keep the frame information even if its module is invalid so we have
167 // visibility into how often this issue is happening on the server.
168 CallStackProfile::Location* location = stack.add_frame();
173 auto module_loc = module_index_.find(frame.module);
174 if (module_loc == module_index_.end()) {
175 modules_.push_back(frame.module);
176 size_t index = modules_.size() - 1;
177 module_loc = module_index_.emplace(frame.module, index).first;
180 // Write CallStackProfile::Location protobuf message.
181 uintptr_t instruction_pointer = frame.instruction_pointer;
182 #if BUILDFLAG(IS_IOS)
183 #if !TARGET_IPHONE_SIMULATOR
184 // Some iOS devices enable pointer authentication, which uses the
185 // higher-order bits of pointers to store a signature. Strip that signature
186 // off before computing the module_offset.
187 // TODO(crbug.com/1084272): Use the ptrauth_strip() macro once it is
189 instruction_pointer &= 0xFFFFFFFFF;
190 #endif // !TARGET_IPHONE_SIMULATOR
191 #endif // BUILDFLAG(IS_IOS)
193 ptrdiff_t module_offset =
194 reinterpret_cast<const char*>(instruction_pointer) -
195 reinterpret_cast<const char*>(frame.module->GetBaseAddress());
196 DCHECK_GE(module_offset, 0);
197 location->set_address(static_cast<uint64_t>(module_offset));
198 location->set_module_id_index(module_loc->second);
201 CallStackProfile* call_stack_profile =
202 sampled_profile_.mutable_call_stack_profile();
205 auto stack_loc = stack_index_.find(&stack);
206 if (stack_loc == stack_index_.end()) {
207 *call_stack_profile->add_stack() = std::move(stack);
208 int stack_index = call_stack_profile->stack_size() - 1;
209 // It is safe to store the Stack pointer because the repeated message
210 // representation ensures pointer stability.
211 stack_loc = stack_index_
212 .emplace(call_stack_profile->mutable_stack(stack_index),
217 // Write CallStackProfile::StackSample protobuf message.
218 CallStackProfile::StackSample* stack_sample_proto =
219 call_stack_profile->add_stack_sample();
220 stack_sample_proto->set_stack_index(stack_loc->second);
222 stack_sample_proto->set_weight(weight);
224 stack_sample_proto->set_count(count);
225 if (is_continued_work_)
226 stack_sample_proto->set_continued_work(is_continued_work_);
228 *stack_sample_proto->mutable_metadata() = metadata_.CreateSampleMetadata(
229 call_stack_profile->mutable_metadata_name_hash());
231 if (profile_start_time_.is_null())
232 profile_start_time_ = sample_timestamp;
234 // Write timestamps to protobuf message. Currently the timestamps are only
235 // used for browser process to apply LCP tags. The browser process will clear
236 // the timestamps information once it is done with LCP tagging. Timestamps
237 // will not be included in the final profile sent to UMA.
238 const int64_t offset =
239 (sample_timestamp - profile_start_time_).InMilliseconds();
240 stack_sample_proto->set_sample_time_offset_ms(
241 base::saturated_cast<int32_t>(offset));
243 sample_timestamps_.push_back(sample_timestamp);
246 void CallStackProfileBuilder::OnProfileCompleted(
247 base::TimeDelta profile_duration,
248 base::TimeDelta sampling_period) {
249 // Build the SampledProfile protobuf message.
250 CallStackProfile* call_stack_profile =
251 sampled_profile_.mutable_call_stack_profile();
252 call_stack_profile->set_profile_duration_ms(
253 profile_duration.InMilliseconds());
254 call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
256 // Heap profiler sets `profile_time_offset_ms` through constructor.
257 // For CPU profiles, `profile_time_offset_ms` is the time of the first
259 if (!call_stack_profile->has_profile_time_offset_ms()) {
260 call_stack_profile->set_profile_time_offset_ms(
261 profile_start_time_.since_origin().InMilliseconds());
264 // Write CallStackProfile::ModuleIdentifier protobuf message.
265 for (const auto* module : modules_) {
266 CallStackProfile::ModuleIdentifier* module_id =
267 call_stack_profile->add_module_id();
268 module_id->set_build_id(module->GetId());
269 module_id->set_name_md5_prefix(
270 HashModuleFilename(module->GetDebugBasename()));
272 // sampled_profile_ cannot be reused after it is cleared by this function.
273 // Check we still have the information from the constructor.
274 CHECK(sampled_profile_.has_process());
275 CHECK(sampled_profile_.has_thread());
276 CHECK(sampled_profile_.has_trigger_event());
278 PassProfilesToMetricsProvider(profile_start_time_,
279 std::move(sampled_profile_));
280 // Protobuffers are in an uncertain state after moving from; clear to get
281 // back to known state.
282 sampled_profile_.Clear();
284 // Run the completed callback if there is one.
285 if (!completed_callback_.is_null())
286 std::move(completed_callback_).Run();
289 stack_index_.clear();
290 module_index_.clear();
292 sample_timestamps_.clear();
296 void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
297 const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
299 GetBrowserProcessReceiverCallbackInstance() = callback;
303 void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
304 mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
306 GetChildCallStackProfileCollector()->SetParentProfileCollector(
307 std::move(browser_interface));
311 void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
312 GetChildCallStackProfileCollector() =
313 std::make_unique<ChildCallStackProfileCollector>();
316 void CallStackProfileBuilder::PassProfilesToMetricsProvider(
317 base::TimeTicks profile_start_time,
318 SampledProfile sampled_profile) {
319 if (sampled_profile.process() == BROWSER_PROCESS) {
320 GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time,
321 std::move(sampled_profile));
323 GetChildCallStackProfileCollector()->Collect(profile_start_time,
324 std::move(sampled_profile));
328 bool CallStackProfileBuilder::StackComparer::operator()(
329 const CallStackProfile::Stack* stack1,
330 const CallStackProfile::Stack* stack2) const {
331 return std::lexicographical_compare(
332 stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
333 stack2->frame().end(),
334 [](const CallStackProfile::Location& loc1,
335 const CallStackProfile::Location& loc2) {
336 return std::make_pair(loc1.address(), loc1.module_id_index()) <
337 std::make_pair(loc2.address(), loc2.module_id_index());
341 } // namespace metrics