xref: /aosp_15_r20/external/cronet/components/metrics/call_stacks/call_stack_profile_builder.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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