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 <algorithm>
8 #include <iterator>
9 #include <map>
10 #include <memory>
11 #include <string>
12 #include <tuple>
13 #include <utility>
14
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_stacks/call_stack_profile_encoding.h"
23
24 namespace metrics {
25
26 namespace {
27
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>&
GetChildCallStackProfileCollector()31 GetChildCallStackProfileCollector() {
32 static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
33 instance(std::make_unique<ChildCallStackProfileCollector>());
34 return *instance;
35 }
36
37 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
GetBrowserProcessReceiverCallbackInstance()38 GetBrowserProcessReceiverCallbackInstance() {
39 static base::NoDestructor<
40 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
41 instance;
42 return *instance;
43 }
44
45 // Convert |filename| to its MD5 hash.
HashModuleFilename(const base::FilePath & filename)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);
54 }
55
56 } // namespace
57
CallStackProfileBuilder(const CallStackProfileParams & profile_params,const WorkIdRecorder * work_id_recorder,base::OnceClosure completed_callback)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());
75 }
76 }
77
78 CallStackProfileBuilder::~CallStackProfileBuilder() = default;
79
GetModuleCache()80 base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
81 return &module_cache_;
82 }
83
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.
RecordMetadata(const base::MetadataRecorder::MetadataProvider & metadata_provider)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.
92 if (work_id != 0) {
93 is_continued_work_ = (last_work_id_ == work_id);
94 last_work_id_ = work_id;
95 }
96 }
97
98 metadata_.RecordMetadata(metadata_provider);
99 }
100
ApplyMetadataRetrospectively(base::TimeTicks period_start,base::TimeTicks period_end,const base::MetadataRecorder::Item & item)101 void CallStackProfileBuilder::ApplyMetadataRetrospectively(
102 base::TimeTicks period_start,
103 base::TimeTicks period_end,
104 const base::MetadataRecorder::Item& item) {
105 CHECK_LE(period_start, period_end);
106 CHECK_LE(period_end, base::TimeTicks::Now());
107
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_)
113 return;
114
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();
119
120 CHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
121
122 const ptrdiff_t start_offset =
123 std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
124 period_start) -
125 sample_timestamps_.begin();
126 const ptrdiff_t end_offset =
127 std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
128 period_end) -
129 sample_timestamps_.begin();
130
131 metadata_.ApplyMetadata(item, samples->begin() + start_offset,
132 samples->begin() + end_offset, samples,
133 call_stack_profile->mutable_metadata_name_hash());
134 }
135
AddProfileMetadata(const base::MetadataRecorder::Item & item)136 void CallStackProfileBuilder::AddProfileMetadata(
137 const base::MetadataRecorder::Item& item) {
138 CallStackProfile* call_stack_profile =
139 sampled_profile_.mutable_call_stack_profile();
140
141 metadata_.SetMetadata(item,
142 call_stack_profile->mutable_profile_metadata()->Add(),
143 call_stack_profile->mutable_metadata_name_hash());
144 }
145
OnSampleCompleted(std::vector<base::Frame> frames,base::TimeTicks sample_timestamp)146 void CallStackProfileBuilder::OnSampleCompleted(
147 std::vector<base::Frame> frames,
148 base::TimeTicks sample_timestamp) {
149 OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
150 }
151
OnSampleCompleted(std::vector<base::Frame> frames,base::TimeTicks sample_timestamp,size_t weight,size_t count)152 void CallStackProfileBuilder::OnSampleCompleted(
153 std::vector<base::Frame> frames,
154 base::TimeTicks sample_timestamp,
155 size_t weight,
156 size_t count) {
157 // Write CallStackProfile::Stack protobuf message.
158 CallStackProfile::Stack stack;
159
160 for (const auto& frame : frames) {
161 // The function name should never be provided in UMA profiler usage.
162 DCHECK(frame.function_name.empty());
163
164 // keep the frame information even if its module is invalid so we have
165 // visibility into how often this issue is happening on the server.
166 CallStackProfile::Location* location = stack.add_frame();
167 if (!frame.module)
168 continue;
169
170 // Dedup modules.
171 auto module_loc = module_index_.find(frame.module);
172 if (module_loc == module_index_.end()) {
173 modules_.push_back(frame.module);
174 size_t index = modules_.size() - 1;
175 module_loc = module_index_.emplace(frame.module, index).first;
176 }
177
178 // Write CallStackProfile::Location protobuf message.
179 uintptr_t instruction_pointer = frame.instruction_pointer;
180 #if BUILDFLAG(IS_IOS)
181 #if !TARGET_IPHONE_SIMULATOR
182 // Some iOS devices enable pointer authentication, which uses the
183 // higher-order bits of pointers to store a signature. Strip that signature
184 // off before computing the module_offset.
185 // TODO(crbug.com/40131654): Use the ptrauth_strip() macro once it is
186 // available.
187 instruction_pointer &= 0xFFFFFFFFF;
188 #endif // !TARGET_IPHONE_SIMULATOR
189 #endif // BUILDFLAG(IS_IOS)
190
191 ptrdiff_t module_offset =
192 reinterpret_cast<const char*>(instruction_pointer) -
193 reinterpret_cast<const char*>(frame.module->GetBaseAddress());
194 DCHECK_GE(module_offset, 0);
195 location->set_address(static_cast<uint64_t>(module_offset));
196 location->set_module_id_index(module_loc->second);
197 }
198
199 CallStackProfile* call_stack_profile =
200 sampled_profile_.mutable_call_stack_profile();
201
202 // Dedup Stacks.
203 auto stack_loc = stack_index_.find(&stack);
204 if (stack_loc == stack_index_.end()) {
205 *call_stack_profile->add_stack() = std::move(stack);
206 int stack_index = call_stack_profile->stack_size() - 1;
207 // It is safe to store the Stack pointer because the repeated message
208 // representation ensures pointer stability.
209 stack_loc = stack_index_
210 .emplace(call_stack_profile->mutable_stack(stack_index),
211 stack_index)
212 .first;
213 }
214
215 // Write CallStackProfile::StackSample protobuf message.
216 CallStackProfile::StackSample* stack_sample_proto =
217 call_stack_profile->add_stack_sample();
218 stack_sample_proto->set_stack_index(stack_loc->second);
219 if (weight != 1)
220 stack_sample_proto->set_weight(weight);
221 if (count != 1)
222 stack_sample_proto->set_count(count);
223 if (is_continued_work_)
224 stack_sample_proto->set_continued_work(is_continued_work_);
225
226 *stack_sample_proto->mutable_metadata() = metadata_.CreateSampleMetadata(
227 call_stack_profile->mutable_metadata_name_hash());
228
229 if (profile_start_time_.is_null())
230 profile_start_time_ = sample_timestamp;
231
232 sample_timestamps_.push_back(sample_timestamp);
233 }
234
OnProfileCompleted(base::TimeDelta profile_duration,base::TimeDelta sampling_period)235 void CallStackProfileBuilder::OnProfileCompleted(
236 base::TimeDelta profile_duration,
237 base::TimeDelta sampling_period) {
238 // Build the SampledProfile protobuf message.
239 CallStackProfile* call_stack_profile =
240 sampled_profile_.mutable_call_stack_profile();
241 call_stack_profile->set_profile_duration_ms(
242 profile_duration.InMilliseconds());
243 call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
244
245 // Write CallStackProfile::ModuleIdentifier protobuf message.
246 for (const base::ModuleCache::Module* module : modules_) {
247 CallStackProfile::ModuleIdentifier* module_id =
248 call_stack_profile->add_module_id();
249 module_id->set_build_id(module->GetId());
250 module_id->set_name_md5_prefix(
251 HashModuleFilename(module->GetDebugBasename()));
252 }
253 // sampled_profile_ cannot be reused after it is cleared by this function.
254 // Check we still have the information from the constructor.
255 CHECK(sampled_profile_.has_process());
256 CHECK(sampled_profile_.has_thread());
257 CHECK(sampled_profile_.has_trigger_event());
258
259 PassProfilesToMetricsProvider(profile_start_time_,
260 std::move(sampled_profile_));
261 // Protobuffers are in an uncertain state after moving from; clear to get
262 // back to known state.
263 sampled_profile_.Clear();
264
265 // Run the completed callback if there is one.
266 if (!completed_callback_.is_null())
267 std::move(completed_callback_).Run();
268
269 // Clear the caches.
270 stack_index_.clear();
271 module_index_.clear();
272 modules_.clear();
273 sample_timestamps_.clear();
274 }
275
276 // static
SetBrowserProcessReceiverCallback(const base::RepeatingCallback<void (base::TimeTicks,SampledProfile)> & callback)277 void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
278 const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
279 callback) {
280 GetBrowserProcessReceiverCallbackInstance() = callback;
281 }
282
283 // static
SetParentProfileCollectorForChildProcess(mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> browser_interface)284 void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
285 mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
286 browser_interface) {
287 GetChildCallStackProfileCollector()->SetParentProfileCollector(
288 std::move(browser_interface));
289 }
290
291 // static
ResetChildCallStackProfileCollectorForTesting()292 void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
293 GetChildCallStackProfileCollector() =
294 std::make_unique<ChildCallStackProfileCollector>();
295 }
296
PassProfilesToMetricsProvider(base::TimeTicks profile_start_time,SampledProfile sampled_profile)297 void CallStackProfileBuilder::PassProfilesToMetricsProvider(
298 base::TimeTicks profile_start_time,
299 SampledProfile sampled_profile) {
300 if (sampled_profile.process() == BROWSER_PROCESS) {
301 GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time,
302 std::move(sampled_profile));
303 } else {
304 GetChildCallStackProfileCollector()->Collect(profile_start_time,
305 std::move(sampled_profile));
306 }
307 }
308
operator ()(const CallStackProfile::Stack * stack1,const CallStackProfile::Stack * stack2) const309 bool CallStackProfileBuilder::StackComparer::operator()(
310 const CallStackProfile::Stack* stack1,
311 const CallStackProfile::Stack* stack2) const {
312 return std::lexicographical_compare(
313 stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
314 stack2->frame().end(),
315 [](const CallStackProfile::Location& loc1,
316 const CallStackProfile::Location& loc2) {
317 return std::make_pair(loc1.address(), loc1.module_id_index()) <
318 std::make_pair(loc2.address(), loc2.module_id_index());
319 });
320 }
321
322 } // namespace metrics
323