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.
4
5 #include "components/metrics/call_stacks/call_stack_profile_builder.h"
6
7 #include <stdint.h>
8 #include <algorithm>
9 #include <iterator>
10 #include <map>
11 #include <memory>
12 #include <string>
13 #include <tuple>
14 #include <utility>
15
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_stacks/call_stack_profile_encoding.h"
25
26 namespace metrics {
27
28 namespace {
29
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>&
GetChildCallStackProfileCollector()33 GetChildCallStackProfileCollector() {
34 static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
35 instance(std::make_unique<ChildCallStackProfileCollector>());
36 return *instance;
37 }
38
39 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
GetBrowserProcessReceiverCallbackInstance()40 GetBrowserProcessReceiverCallbackInstance() {
41 static base::NoDestructor<
42 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
43 instance;
44 return *instance;
45 }
46
47 // Convert |filename| to its MD5 hash.
HashModuleFilename(const base::FilePath & filename)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);
56 }
57
58 } // namespace
59
CallStackProfileBuilder(const CallStackProfileParams & profile_params,const WorkIdRecorder * work_id_recorder,base::OnceClosure completed_callback)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());
77 }
78 }
79
80 CallStackProfileBuilder::~CallStackProfileBuilder() = default;
81
GetModuleCache()82 base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
83 return &module_cache_;
84 }
85
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.
RecordMetadata(const base::MetadataRecorder::MetadataProvider & metadata_provider)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.
94 if (work_id != 0) {
95 is_continued_work_ = (last_work_id_ == work_id);
96 last_work_id_ = work_id;
97 }
98 }
99
100 metadata_.RecordMetadata(metadata_provider);
101 }
102
ApplyMetadataRetrospectively(base::TimeTicks period_start,base::TimeTicks period_end,const base::MetadataRecorder::Item & item)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());
109
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_)
115 return;
116
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();
121
122 CHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
123
124 const ptrdiff_t start_offset =
125 std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
126 period_start) -
127 sample_timestamps_.begin();
128 const ptrdiff_t end_offset =
129 std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
130 period_end) -
131 sample_timestamps_.begin();
132
133 metadata_.ApplyMetadata(item, samples->begin() + start_offset,
134 samples->begin() + end_offset, samples,
135 call_stack_profile->mutable_metadata_name_hash());
136 }
137
AddProfileMetadata(const base::MetadataRecorder::Item & item)138 void CallStackProfileBuilder::AddProfileMetadata(
139 const base::MetadataRecorder::Item& item) {
140 CallStackProfile* call_stack_profile =
141 sampled_profile_.mutable_call_stack_profile();
142
143 metadata_.SetMetadata(item,
144 call_stack_profile->mutable_profile_metadata()->Add(),
145 call_stack_profile->mutable_metadata_name_hash());
146 }
147
OnSampleCompleted(std::vector<base::Frame> frames,base::TimeTicks sample_timestamp)148 void CallStackProfileBuilder::OnSampleCompleted(
149 std::vector<base::Frame> frames,
150 base::TimeTicks sample_timestamp) {
151 OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
152 }
153
OnSampleCompleted(std::vector<base::Frame> frames,base::TimeTicks sample_timestamp,size_t weight,size_t count)154 void CallStackProfileBuilder::OnSampleCompleted(
155 std::vector<base::Frame> frames,
156 base::TimeTicks sample_timestamp,
157 size_t weight,
158 size_t count) {
159 // Write CallStackProfile::Stack protobuf message.
160 CallStackProfile::Stack stack;
161
162 for (const auto& frame : frames) {
163 // The function name should never be provided in UMA profiler usage.
164 DCHECK(frame.function_name.empty());
165
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();
169 if (!frame.module)
170 continue;
171
172 // Dedup modules.
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;
178 }
179
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
188 // available.
189 instruction_pointer &= 0xFFFFFFFFF;
190 #endif // !TARGET_IPHONE_SIMULATOR
191 #endif // BUILDFLAG(IS_IOS)
192
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);
199 }
200
201 CallStackProfile* call_stack_profile =
202 sampled_profile_.mutable_call_stack_profile();
203
204 // Dedup Stacks.
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),
213 stack_index)
214 .first;
215 }
216
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);
221 if (weight != 1)
222 stack_sample_proto->set_weight(weight);
223 if (count != 1)
224 stack_sample_proto->set_count(count);
225 if (is_continued_work_)
226 stack_sample_proto->set_continued_work(is_continued_work_);
227
228 *stack_sample_proto->mutable_metadata() = metadata_.CreateSampleMetadata(
229 call_stack_profile->mutable_metadata_name_hash());
230
231 if (profile_start_time_.is_null())
232 profile_start_time_ = sample_timestamp;
233
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));
242
243 sample_timestamps_.push_back(sample_timestamp);
244 }
245
OnProfileCompleted(base::TimeDelta profile_duration,base::TimeDelta sampling_period)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());
255
256 // Heap profiler sets `profile_time_offset_ms` through constructor.
257 // For CPU profiles, `profile_time_offset_ms` is the time of the first
258 // sample.
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());
262 }
263
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()));
271 }
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());
277
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();
283
284 // Run the completed callback if there is one.
285 if (!completed_callback_.is_null())
286 std::move(completed_callback_).Run();
287
288 // Clear the caches.
289 stack_index_.clear();
290 module_index_.clear();
291 modules_.clear();
292 sample_timestamps_.clear();
293 }
294
295 // static
SetBrowserProcessReceiverCallback(const base::RepeatingCallback<void (base::TimeTicks,SampledProfile)> & callback)296 void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
297 const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
298 callback) {
299 GetBrowserProcessReceiverCallbackInstance() = callback;
300 }
301
302 // static
SetParentProfileCollectorForChildProcess(mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> browser_interface)303 void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
304 mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
305 browser_interface) {
306 GetChildCallStackProfileCollector()->SetParentProfileCollector(
307 std::move(browser_interface));
308 }
309
310 // static
ResetChildCallStackProfileCollectorForTesting()311 void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
312 GetChildCallStackProfileCollector() =
313 std::make_unique<ChildCallStackProfileCollector>();
314 }
315
PassProfilesToMetricsProvider(base::TimeTicks profile_start_time,SampledProfile sampled_profile)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));
322 } else {
323 GetChildCallStackProfileCollector()->Collect(profile_start_time,
324 std::move(sampled_profile));
325 }
326 }
327
operator ()(const CallStackProfile::Stack * stack1,const CallStackProfile::Stack * stack2) const328 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());
338 });
339 }
340
341 } // namespace metrics
342