xref: /aosp_15_r20/external/cronet/base/profiler/stack_sampler.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2015 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 "base/profiler/stack_sampler.h"
6 
7 #include <iterator>
8 #include <utility>
9 
10 #include "base/check.h"
11 #include "base/compiler_specific.h"
12 #include "base/memory/ptr_util.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/metrics/histogram_functions.h"
15 #include "base/numerics/safe_conversions.h"
16 #include "base/profiler/metadata_recorder.h"
17 #include "base/profiler/profile_builder.h"
18 #include "base/profiler/sample_metadata.h"
19 #include "base/profiler/stack_buffer.h"
20 #include "base/profiler/stack_copier.h"
21 #include "base/profiler/suspendable_thread_delegate.h"
22 #include "base/profiler/unwinder.h"
23 #include "base/ranges/algorithm.h"
24 
25 // IMPORTANT NOTE: Some functions within this implementation are invoked while
26 // the target thread is suspended so it must not do any allocation from the
27 // heap, including indirectly via use of DCHECK/CHECK or other logging
28 // statements. Otherwise this code can deadlock on heap locks acquired by the
29 // target thread before it was suspended. These functions are commented with "NO
30 // HEAP ALLOCATIONS".
31 
32 namespace base {
33 
34 namespace {
35 
36 // Notifies the unwinders about the stack capture, and records metadata, while
37 // the thread is suspended.
38 class StackCopierDelegate : public StackCopier::Delegate {
39  public:
StackCopierDelegate(const base::circular_deque<std::unique_ptr<Unwinder>> * unwinders,ProfileBuilder * profile_builder,MetadataRecorder::MetadataProvider * metadata_provider)40   StackCopierDelegate(
41       const base::circular_deque<std::unique_ptr<Unwinder>>* unwinders,
42       ProfileBuilder* profile_builder,
43       MetadataRecorder::MetadataProvider* metadata_provider)
44       : unwinders_(unwinders),
45         profile_builder_(profile_builder),
46         metadata_provider_(metadata_provider) {}
47 
48   StackCopierDelegate(const StackCopierDelegate&) = delete;
49   StackCopierDelegate& operator=(const StackCopierDelegate&) = delete;
50 
51   // StackCopier::Delegate:
52   // IMPORTANT NOTE: to avoid deadlock this function must not invoke any
53   // non-reentrant code that is also invoked by the target thread. In
54   // particular, it may not perform any heap allocation or deallocation,
55   // including indirectly via use of DCHECK/CHECK or other logging statements.
OnStackCopy()56   void OnStackCopy() override {
57     for (const auto& unwinder : *unwinders_)
58       unwinder->OnStackCapture();
59 
60     profile_builder_->RecordMetadata(*metadata_provider_);
61   }
62 
63  private:
64   raw_ptr<const base::circular_deque<std::unique_ptr<Unwinder>>> unwinders_;
65   const raw_ptr<ProfileBuilder> profile_builder_;
66   const raw_ptr<const MetadataRecorder::MetadataProvider> metadata_provider_;
67 };
68 
69 }  // namespace
70 
71 StackSampler::~StackSampler() = default;
72 
CreateStackBuffer()73 std::unique_ptr<StackBuffer> StackSampler::CreateStackBuffer() {
74   size_t size = GetStackBufferSize();
75   if (size == 0)
76     return nullptr;
77   return std::make_unique<StackBuffer>(size);
78 }
79 
Initialize()80 void StackSampler::Initialize() {
81   std::vector<std::unique_ptr<Unwinder>> unwinders =
82       std::move(unwinders_factory_).Run();
83 
84   // |unwinders| is iterated backward since |unwinders_factory_| generates
85   // unwinders in increasing priority order. |unwinders_| is stored in
86   // decreasing priority order for ease of use within the class.
87   unwinders_.insert(unwinders_.end(),
88                     std::make_move_iterator(unwinders.rbegin()),
89                     std::make_move_iterator(unwinders.rend()));
90 
91   for (const auto& unwinder : unwinders_)
92     unwinder->Initialize(module_cache_);
93 
94   was_initialized_ = true;
95 }
96 
AddAuxUnwinder(std::unique_ptr<Unwinder> unwinder)97 void StackSampler::AddAuxUnwinder(std::unique_ptr<Unwinder> unwinder) {
98   // Initialize() invokes Initialize() on the unwinders that are present
99   // at the time. If it hasn't occurred yet, we allow it to add the initial
100   // modules, otherwise we do it here.
101   if (was_initialized_)
102     unwinder->Initialize(module_cache_);
103   unwinders_.push_front(std::move(unwinder));
104 }
105 
RecordStackFrames(StackBuffer * stack_buffer,ProfileBuilder * profile_builder,PlatformThreadId thread_id)106 void StackSampler::RecordStackFrames(StackBuffer* stack_buffer,
107                                      ProfileBuilder* profile_builder,
108                                      PlatformThreadId thread_id) {
109   DCHECK(stack_buffer);
110 
111   if (record_sample_callback_)
112     record_sample_callback_.Run();
113 
114   RegisterContext thread_context;
115   uintptr_t stack_top;
116   TimeTicks timestamp;
117 
118   bool copy_stack_succeeded;
119   {
120     // Make this scope as small as possible because |metadata_provider| is
121     // holding a lock.
122     MetadataRecorder::MetadataProvider metadata_provider(
123         GetSampleMetadataRecorder(), thread_id);
124     StackCopierDelegate delegate(&unwinders_, profile_builder,
125                                  &metadata_provider);
126     copy_stack_succeeded = stack_copier_->CopyStack(
127         stack_buffer, &stack_top, &timestamp, &thread_context, &delegate);
128   }
129   if (!copy_stack_succeeded) {
130     profile_builder->OnSampleCompleted(
131         {}, timestamp.is_null() ? TimeTicks::Now() : timestamp);
132     return;
133   }
134 
135   for (const auto& unwinder : unwinders_)
136     unwinder->UpdateModules();
137 
138   if (test_delegate_)
139     test_delegate_->OnPreStackWalk();
140 
141   profile_builder->OnSampleCompleted(
142       WalkStack(module_cache_, &thread_context, stack_top, unwinders_),
143       timestamp);
144 
145 #if BUILDFLAG(IS_CHROMEOS)
146   ptrdiff_t stack_size = reinterpret_cast<uint8_t*>(stack_top) -
147                          reinterpret_cast<uint8_t*>(stack_buffer->buffer());
148   constexpr int kBytesPerKilobyte = 1024;
149 
150   if ((++stack_size_histogram_sampling_counter_ %
151        kUMAHistogramDownsampleAmount) == 0) {
152     // Record the size of the stack to tune kLargeStackSize.
153     // UmaHistogramMemoryKB has a min of 1000, which isn't useful for our
154     // purposes, so call UmaHistogramCustomCounts directly.
155     // Min is 4KB, since that's the normal pagesize and setting kLargeStackSize
156     // smaller than that would be pointless. Max is 8MB since that's the
157     // current ChromeOS stack size; we shouldn't be able to get a number
158     // larger than that.
159     UmaHistogramCustomCounts(
160         "Memory.StackSamplingProfiler.StackSampleSize2",
161         saturated_cast<int>(stack_size / kBytesPerKilobyte), 4, 8 * 1024, 50);
162   }
163 
164   // We expect to very rarely see stacks larger than kLargeStackSize. If we see
165   // a stack larger than kLargeStackSize, we tell the kernel to discard the
166   // contents of the buffer (using madvise(MADV_DONTNEED)) after the first
167   // kLargeStackSize bytes to avoid permanently allocating memory that we won't
168   // use again. We don't want kLargeStackSize to be too small, however; for if
169   // we are constantly calling madvise(MADV_DONTNEED) and then writing to the
170   // same parts of the buffer, we're not saving memory and we'll cause extra
171   // page faults.
172   constexpr ptrdiff_t kLargeStackSize = 32 * kBytesPerKilobyte;
173   if (stack_size > kLargeStackSize) {
174     stack_buffer->MarkUpperBufferContentsAsUnneeded(kLargeStackSize);
175   }
176 #endif  // #if BUILDFLAG(IS_CHROMEOS)
177 }
178 
179 // static
WalkStackForTesting(ModuleCache * module_cache,RegisterContext * thread_context,uintptr_t stack_top,const base::circular_deque<std::unique_ptr<Unwinder>> & unwinders)180 std::vector<Frame> StackSampler::WalkStackForTesting(
181     ModuleCache* module_cache,
182     RegisterContext* thread_context,
183     uintptr_t stack_top,
184     const base::circular_deque<std::unique_ptr<Unwinder>>& unwinders) {
185   return WalkStack(module_cache, thread_context, stack_top, unwinders);
186 }
187 
188 // static
CreateForTesting(std::unique_ptr<StackCopier> stack_copier,UnwindersFactory core_unwinders_factory,ModuleCache * module_cache,RepeatingClosure record_sample_callback,StackSamplerTestDelegate * test_delegate)189 std::unique_ptr<StackSampler> StackSampler::CreateForTesting(
190     std::unique_ptr<StackCopier> stack_copier,
191     UnwindersFactory core_unwinders_factory,
192     ModuleCache* module_cache,
193     RepeatingClosure record_sample_callback,
194     StackSamplerTestDelegate* test_delegate) {
195   return base::WrapUnique(new StackSampler(
196       std::move(stack_copier), std::move(core_unwinders_factory), module_cache,
197       record_sample_callback, test_delegate));
198 }
199 
StackSampler(std::unique_ptr<StackCopier> stack_copier,UnwindersFactory core_unwinders_factory,ModuleCache * module_cache,RepeatingClosure record_sample_callback,StackSamplerTestDelegate * test_delegate)200 StackSampler::StackSampler(std::unique_ptr<StackCopier> stack_copier,
201                            UnwindersFactory core_unwinders_factory,
202                            ModuleCache* module_cache,
203                            RepeatingClosure record_sample_callback,
204                            StackSamplerTestDelegate* test_delegate)
205     : stack_copier_(std::move(stack_copier)),
206       unwinders_factory_(std::move(core_unwinders_factory)),
207       module_cache_(module_cache),
208       record_sample_callback_(std::move(record_sample_callback)),
209       test_delegate_(test_delegate) {
210   CHECK(unwinders_factory_);
211 }
212 
213 // static
WalkStack(ModuleCache * module_cache,RegisterContext * thread_context,uintptr_t stack_top,const base::circular_deque<std::unique_ptr<Unwinder>> & unwinders)214 std::vector<Frame> StackSampler::WalkStack(
215     ModuleCache* module_cache,
216     RegisterContext* thread_context,
217     uintptr_t stack_top,
218     const base::circular_deque<std::unique_ptr<Unwinder>>& unwinders) {
219   std::vector<Frame> stack;
220   // Reserve enough memory for most stacks, to avoid repeated
221   // allocations. Approximately 99.9% of recorded stacks are 128 frames or
222   // fewer.
223   stack.reserve(128);
224 
225   // Record the first frame from the context values.
226   stack.emplace_back(RegisterContextInstructionPointer(thread_context),
227                      module_cache->GetModuleForAddress(
228                          RegisterContextInstructionPointer(thread_context)));
229 
230   size_t prior_stack_size;
231   UnwindResult result;
232   do {
233     // Choose an authoritative unwinder for the current module. Use the first
234     // unwinder that thinks it can unwind from the current frame.
235     auto unwinder = ranges::find_if(
236         unwinders, [&stack](const std::unique_ptr<Unwinder>& unwinder) {
237           return unwinder->CanUnwindFrom(stack.back());
238         });
239     if (unwinder == unwinders.end())
240       return stack;
241 
242     prior_stack_size = stack.size();
243     result = unwinder->get()->TryUnwind(thread_context, stack_top, &stack);
244 
245     // The unwinder with the lowest priority should be the only one that returns
246     // COMPLETED since the stack starts in native code.
247     DCHECK(result != UnwindResult::kCompleted ||
248            unwinder->get() == unwinders.back().get());
249   } while (result != UnwindResult::kAborted &&
250            result != UnwindResult::kCompleted &&
251            // Give up if the authoritative unwinder for the module was unable to
252            // unwind.
253            stack.size() > prior_stack_size);
254 
255   return stack;
256 }
257 
258 StackSamplerTestDelegate::~StackSamplerTestDelegate() = default;
259 
260 StackSamplerTestDelegate::StackSamplerTestDelegate() = default;
261 
262 }  // namespace base
263