• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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