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 #include "components/sampling_profiler/call_stack_profile_params.h"
24
25 namespace metrics {
26
27 namespace {
28
29 // Only used by child processes. This returns a unique_ptr so that it can be
30 // reset during tests.
31 std::unique_ptr<ChildCallStackProfileCollector>&
GetChildCallStackProfileCollector()32 GetChildCallStackProfileCollector() {
33 static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
34 instance(std::make_unique<ChildCallStackProfileCollector>());
35 return *instance;
36 }
37
38 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
GetBrowserProcessReceiverCallbackInstance()39 GetBrowserProcessReceiverCallbackInstance() {
40 static base::NoDestructor<
41 base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
42 instance;
43 return *instance;
44 }
45
46 // Convert |filename| to its MD5 hash.
HashModuleFilename(const base::FilePath & filename)47 uint64_t HashModuleFilename(const base::FilePath& filename) {
48 const base::FilePath::StringType basename = filename.BaseName().value();
49 // Copy the bytes in basename into a string buffer.
50 size_t basename_length_in_bytes =
51 basename.size() * sizeof(base::FilePath::CharType);
52 std::string name_bytes(basename_length_in_bytes, '\0');
53 memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
54 return base::HashMetricName(name_bytes);
55 }
56
57 } // namespace
58
CallStackProfileBuilder(const sampling_profiler::CallStackProfileParams & profile_params,const WorkIdRecorder * work_id_recorder,base::OnceClosure completed_callback)59 CallStackProfileBuilder::CallStackProfileBuilder(
60 const sampling_profiler::CallStackProfileParams& profile_params,
61 const WorkIdRecorder* work_id_recorder,
62 base::OnceClosure completed_callback)
63 : work_id_recorder_(work_id_recorder) {
64 completed_callback_ = std::move(completed_callback);
65 sampled_profile_.set_process(
66 ToExecutionContextProcess(profile_params.process));
67 sampled_profile_.set_thread(ToExecutionContextThread(profile_params.thread));
68 sampled_profile_.set_trigger_event(
69 ToSampledProfileTriggerEvent(profile_params.trigger));
70 if (!profile_params.time_offset.is_zero()) {
71 DCHECK(profile_params.time_offset.is_positive());
72 CallStackProfile* call_stack_profile =
73 sampled_profile_.mutable_call_stack_profile();
74 call_stack_profile->set_profile_time_offset_ms(
75 profile_params.time_offset.InMilliseconds());
76 }
77 }
78
79 CallStackProfileBuilder::~CallStackProfileBuilder() = default;
80
GetModuleCache()81 base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
82 return &module_cache_;
83 }
84
85 // This function is invoked on the profiler thread while the target thread is
86 // suspended so must not take any locks, including indirectly through use of
87 // heap allocation, LOG, CHECK, or DCHECK.
RecordMetadata(const base::MetadataRecorder::MetadataProvider & metadata_provider)88 void CallStackProfileBuilder::RecordMetadata(
89 const base::MetadataRecorder::MetadataProvider& metadata_provider) {
90 if (work_id_recorder_) {
91 unsigned int work_id = work_id_recorder_->RecordWorkId();
92 // A work id of 0 indicates that the message loop has not yet started.
93 if (work_id != 0) {
94 is_continued_work_ = (last_work_id_ == work_id);
95 last_work_id_ = work_id;
96 }
97 }
98
99 metadata_.RecordMetadata(metadata_provider);
100 }
101
ApplyMetadataRetrospectively(base::TimeTicks period_start,base::TimeTicks period_end,const base::MetadataRecorder::Item & item)102 void CallStackProfileBuilder::ApplyMetadataRetrospectively(
103 base::TimeTicks period_start,
104 base::TimeTicks period_end,
105 const base::MetadataRecorder::Item& item) {
106 CHECK_LE(period_start, period_end);
107 CHECK_LE(period_end, base::TimeTicks::Now());
108
109 // We don't set metadata if the period extends before the start of the
110 // sampling, to avoid biasing against the unobserved execution. This will
111 // introduce bias due to dropping periods longer than the sampling time, but
112 // that bias is easier to reason about and account for.
113 if (period_start < profile_start_time_)
114 return;
115
116 CallStackProfile* call_stack_profile =
117 sampled_profile_.mutable_call_stack_profile();
118 google::protobuf::RepeatedPtrField<CallStackProfile::StackSample>* samples =
119 call_stack_profile->mutable_stack_sample();
120
121 CHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
122
123 const ptrdiff_t start_offset =
124 std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
125 period_start) -
126 sample_timestamps_.begin();
127 const ptrdiff_t end_offset =
128 std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
129 period_end) -
130 sample_timestamps_.begin();
131
132 metadata_.ApplyMetadata(item, samples->begin() + start_offset,
133 samples->begin() + end_offset, samples,
134 call_stack_profile->mutable_metadata_name_hash());
135 }
136
AddProfileMetadata(const base::MetadataRecorder::Item & item)137 void CallStackProfileBuilder::AddProfileMetadata(
138 const base::MetadataRecorder::Item& item) {
139 CallStackProfile* call_stack_profile =
140 sampled_profile_.mutable_call_stack_profile();
141
142 metadata_.SetMetadata(item,
143 call_stack_profile->mutable_profile_metadata()->Add(),
144 call_stack_profile->mutable_metadata_name_hash());
145 }
146
OnSampleCompleted(std::vector<base::Frame> frames,base::TimeTicks sample_timestamp)147 void CallStackProfileBuilder::OnSampleCompleted(
148 std::vector<base::Frame> frames,
149 base::TimeTicks sample_timestamp) {
150 OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
151 }
152
OnSampleCompleted(std::vector<base::Frame> frames,base::TimeTicks sample_timestamp,size_t weight,size_t count)153 void CallStackProfileBuilder::OnSampleCompleted(
154 std::vector<base::Frame> frames,
155 base::TimeTicks sample_timestamp,
156 size_t weight,
157 size_t count) {
158 // Write CallStackProfile::Stack protobuf message.
159 CallStackProfile::Stack stack;
160
161 for (const auto& frame : frames) {
162 // keep the frame information even if its module is invalid so we have
163 // visibility into how often this issue is happening on the server.
164 CallStackProfile::Location* location = stack.add_frame();
165 if (!frame.module)
166 continue;
167
168 // Dedup modules.
169 auto module_loc = module_index_.find(frame.module);
170 if (module_loc == module_index_.end()) {
171 modules_.push_back(frame.module.get());
172 size_t index = modules_.size() - 1;
173 module_loc = module_index_.emplace(frame.module, index).first;
174 }
175
176 // Write CallStackProfile::Location protobuf message.
177 uintptr_t instruction_pointer = frame.instruction_pointer;
178 #if BUILDFLAG(IS_IOS)
179 #if !TARGET_IPHONE_SIMULATOR
180 // Some iOS devices enable pointer authentication, which uses the
181 // higher-order bits of pointers to store a signature. Strip that signature
182 // off before computing the module_offset.
183 // TODO(crbug.com/40131654): Use the ptrauth_strip() macro once it is
184 // available.
185 instruction_pointer &= 0xFFFFFFFFF;
186 #endif // !TARGET_IPHONE_SIMULATOR
187 #endif // BUILDFLAG(IS_IOS)
188
189 ptrdiff_t module_offset =
190 reinterpret_cast<const char*>(instruction_pointer) -
191 reinterpret_cast<const char*>(frame.module->GetBaseAddress());
192 DCHECK_GE(module_offset, 0);
193 location->set_address(static_cast<uint64_t>(module_offset));
194 location->set_module_id_index(module_loc->second);
195
196 if (!frame.function_name.empty()) {
197 location->set_function_name(frame.function_name);
198 }
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 sample_timestamps_.push_back(sample_timestamp);
235 }
236
OnProfileCompleted(base::TimeDelta profile_duration,base::TimeDelta sampling_period)237 void CallStackProfileBuilder::OnProfileCompleted(
238 base::TimeDelta profile_duration,
239 base::TimeDelta sampling_period) {
240 // Build the SampledProfile protobuf message.
241 CallStackProfile* call_stack_profile =
242 sampled_profile_.mutable_call_stack_profile();
243 call_stack_profile->set_profile_duration_ms(
244 profile_duration.InMilliseconds());
245 call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
246
247 // Write CallStackProfile::ModuleIdentifier protobuf message.
248 for (const base::ModuleCache::Module* module : modules_) {
249 CallStackProfile::ModuleIdentifier* module_id =
250 call_stack_profile->add_module_id();
251 module_id->set_build_id(module->GetId());
252 module_id->set_name_md5_prefix(
253 HashModuleFilename(module->GetDebugBasename()));
254 }
255 // sampled_profile_ cannot be reused after it is cleared by this function.
256 // Check we still have the information from the constructor.
257 CHECK(sampled_profile_.has_process());
258 CHECK(sampled_profile_.has_thread());
259 CHECK(sampled_profile_.has_trigger_event());
260
261 PassProfilesToMetricsProvider(profile_start_time_,
262 std::move(sampled_profile_));
263 // Protobuffers are in an uncertain state after moving from; clear to get
264 // back to known state.
265 sampled_profile_.Clear();
266
267 // Run the completed callback if there is one.
268 if (!completed_callback_.is_null())
269 std::move(completed_callback_).Run();
270
271 // Clear the caches.
272 stack_index_.clear();
273 module_index_.clear();
274 modules_.clear();
275 sample_timestamps_.clear();
276 work_id_recorder_ = nullptr;
277 }
278
279 // static
SetBrowserProcessReceiverCallback(const base::RepeatingCallback<void (base::TimeTicks,SampledProfile)> & callback)280 void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
281 const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
282 callback) {
283 GetBrowserProcessReceiverCallbackInstance() = callback;
284 }
285
286 // static
SetParentProfileCollectorForChildProcess(mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> browser_interface)287 void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
288 mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
289 browser_interface) {
290 GetChildCallStackProfileCollector()->SetParentProfileCollector(
291 std::move(browser_interface));
292 }
293
294 // static
ResetChildCallStackProfileCollectorForTesting()295 void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
296 GetChildCallStackProfileCollector() =
297 std::make_unique<ChildCallStackProfileCollector>();
298 }
299
PassProfilesToMetricsProvider(base::TimeTicks profile_start_time,SampledProfile sampled_profile)300 void CallStackProfileBuilder::PassProfilesToMetricsProvider(
301 base::TimeTicks profile_start_time,
302 SampledProfile sampled_profile) {
303 if (sampled_profile.process() == BROWSER_PROCESS) {
304 GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time,
305 std::move(sampled_profile));
306 } else {
307 GetChildCallStackProfileCollector()->Collect(profile_start_time,
308 std::move(sampled_profile));
309 }
310 }
311
operator ()(const CallStackProfile::Stack * stack1,const CallStackProfile::Stack * stack2) const312 bool CallStackProfileBuilder::StackComparer::operator()(
313 const CallStackProfile::Stack* stack1,
314 const CallStackProfile::Stack* stack2) const {
315 return std::lexicographical_compare(
316 stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
317 stack2->frame().end(),
318 [](const CallStackProfile::Location& loc1,
319 const CallStackProfile::Location& loc2) {
320 return std::make_pair(loc1.address(), loc1.module_id_index()) <
321 std::make_pair(loc2.address(), loc2.module_id_index());
322 });
323 }
324
325 } // namespace metrics
326