1 // Copyright 2022 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/sampling_heap_profiler/poisson_allocation_sampler.h"
6
7 #include <stdlib.h>
8
9 #include <array>
10 #include <atomic>
11 #include <bitset>
12 #include <memory>
13 #include <utility>
14 #include <vector>
15
16 #include "base/barrier_closure.h"
17 #include "base/containers/extend.h"
18 #include "base/functional/bind.h"
19 #include "base/functional/callback.h"
20 #include "base/memory/scoped_refptr.h"
21 #include "base/run_loop.h"
22 #include "base/task/bind_post_task.h"
23 #include "base/task/single_thread_task_runner.h"
24 #include "base/task/single_thread_task_runner_thread_mode.h"
25 #include "base/task/thread_pool.h"
26 #include "base/test/task_environment.h"
27 #include "testing/gmock/include/gmock/gmock.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29
30 namespace base {
31
32 namespace {
33
34 using ::testing::AssertionFailure;
35 using ::testing::AssertionResult;
36 using ::testing::AssertionSuccess;
37
38 class DummySamplesObserver : public PoissonAllocationSampler::SamplesObserver {
39 public:
SampleAdded(void * address,size_t size,size_t total,base::allocator::dispatcher::AllocationSubsystem type,const char * context)40 void SampleAdded(void* address,
41 size_t size,
42 size_t total,
43 base::allocator::dispatcher::AllocationSubsystem type,
44 const char* context) final {}
SampleRemoved(void * address)45 void SampleRemoved(void* address) final {}
46 };
47
48 class AddRemoveObserversTester {
49 public:
50 AddRemoveObserversTester() = default;
51 ~AddRemoveObserversTester() = default;
52
53 // Posts tasks to add and remove observers to `task_runner_`. Invokes
54 // `barrier_closure` if a task fails or after `num_repetitions`.
55 void Start(base::RepeatingClosure* barrier_closure, int num_repetitions);
56
57 // Gives the caller ownership of the observers so that they can be safely
58 // deleted in single-threaded context once the PoissonAllocationSampler is not
59 // running.
DetachObservers()60 std::vector<std::unique_ptr<DummySamplesObserver>> DetachObservers() {
61 std::vector<std::unique_ptr<DummySamplesObserver>> observers;
62 observers.push_back(std::move(observer1_));
63 observers.push_back(std::move(observer2_));
64 return observers;
65 }
66
67 private:
68 // Posts tasks to add and remove observers to `task_runner_`. If they fail or
69 // if this is the last repetition, invokes `barrier_closure`, otherwise
70 // schedules another repetition.
71 void TestAddRemoveObservers(base::RepeatingClosure* barrier_closure,
72 int remaining_repetitions);
73
74 // These observers must live until all threads are destroyed, because the
75 // profiler might call into them racily after they're removed. (See the
76 // comment on PoissonAllocationSampler::RemoveSamplesObserver().) They can
77 // safely be destroyed on the main thread after the test is back in a
78 // single-threaded context.
79 std::unique_ptr<DummySamplesObserver> observer1_ =
80 std::make_unique<DummySamplesObserver>();
81 std::unique_ptr<DummySamplesObserver> observer2_ =
82 std::make_unique<DummySamplesObserver>();
83
84 scoped_refptr<base::SequencedTaskRunner> task_runner_ =
85 base::ThreadPool::CreateSingleThreadTaskRunner(
86 {},
87 base::SingleThreadTaskRunnerThreadMode::DEDICATED);
88 };
89
90 class MuteHookedSamplesTester {
91 public:
92 MuteHookedSamplesTester() = default;
93 ~MuteHookedSamplesTester() = default;
94
95 // Posts tasks to mute and unmute samples to `task_runner_`. Invokes
96 // `barrier_closure` if a task fails or after `num_repetitions`.
97 void Start(base::RepeatingClosure* barrier_closure, int num_repetitions);
98
99 private:
100 // Posts tasks to mute and unmute samples to `task_runner_`. If they fail or
101 // if this is the last repetition, invokes `barrier_closure`, otherwise
102 // schedules another repetition.
103 void TestMuteUnmuteSamples(base::RepeatingClosure* barrier_closure,
104 int remaining_repetitions);
105
106 base::OnceClosure unmute_samples_closure_;
107
108 scoped_refptr<base::SequencedTaskRunner> task_runner_ =
109 base::ThreadPool::CreateSingleThreadTaskRunner(
110 {},
111 base::SingleThreadTaskRunnerThreadMode::DEDICATED);
112 };
113
114 } // namespace
115
116 // PoissonAllocationSamplerStateTest has access to read the state of
117 // PoissonAllocationSampler.
118 class PoissonAllocationSamplerStateTest : public ::testing::Test {
119 public:
120 static void AddSamplesObservers(DummySamplesObserver* observer1,
121 DummySamplesObserver* observer2);
122
123 static void RemoveSamplesObservers(DummySamplesObserver* observer1,
124 DummySamplesObserver* observer2);
125
126 // Creates a ScopedMuteHookedSamplesForTesting object and returns a closure
127 // that will destroy it.
128 static base::OnceClosure MuteHookedSamples();
129
130 static void UnmuteHookedSamples(base::OnceClosure unmute_closure);
131
132 protected:
133 using ProfilingStateFlag = PoissonAllocationSampler::ProfilingStateFlag;
134 using ProfilingStateFlagMask =
135 PoissonAllocationSampler::ProfilingStateFlagMask;
136
HasAllStateFlags(ProfilingStateFlagMask flags)137 static AssertionResult HasAllStateFlags(ProfilingStateFlagMask flags) {
138 const ProfilingStateFlagMask state =
139 PoissonAllocationSampler::profiling_state_.load(
140 std::memory_order_relaxed);
141 AssertionResult result =
142 (state & flags) == flags ? AssertionSuccess() : AssertionFailure();
143 return result << "Expected all of " << std::bitset<4>(flags) << ", got "
144 << std::bitset<4>(state);
145 }
146
HasAnyStateFlags(ProfilingStateFlagMask flags)147 static AssertionResult HasAnyStateFlags(ProfilingStateFlagMask flags) {
148 const ProfilingStateFlagMask state =
149 PoissonAllocationSampler::profiling_state_.load(
150 std::memory_order_relaxed);
151 AssertionResult result =
152 (state & flags) != 0 ? AssertionSuccess() : AssertionFailure();
153 return result << "Expected any of " << std::bitset<4>(flags) << ", got "
154 << std::bitset<4>(state);
155 }
156
157 // This must come before `task_environment_` do that it's deleted afterward,
158 // since DummySamplesObserver objects must be deleted in single-threaded
159 // context.
160 std::vector<std::unique_ptr<DummySamplesObserver>> observers_to_delete_;
161
162 base::test::TaskEnvironment task_environment_;
163 };
164
Start(base::RepeatingClosure * barrier_closure,int num_repetitions)165 void AddRemoveObserversTester::Start(base::RepeatingClosure* barrier_closure,
166 int num_repetitions) {
167 // Unretained is safe because `this` isn't deleted until the
168 // `barrier_closure` is invoked often enough to quit the main RunLoop.
169 task_runner_->PostTask(
170 FROM_HERE,
171 base::BindOnce(&AddRemoveObserversTester::TestAddRemoveObservers,
172 base::Unretained(this), barrier_closure, num_repetitions));
173 }
174
TestAddRemoveObservers(base::RepeatingClosure * barrier_closure,int remaining_repetitions)175 void AddRemoveObserversTester::TestAddRemoveObservers(
176 base::RepeatingClosure* barrier_closure,
177 int remaining_repetitions) {
178 if (!remaining_repetitions || ::testing::Test::HasFailure()) {
179 barrier_closure->Run();
180 return;
181 }
182 auto add_observers =
183 base::BindOnce(&PoissonAllocationSamplerStateTest::AddSamplesObservers,
184 observer1_.get(), observer2_.get());
185 auto remove_observers = base::BindPostTask(
186 task_runner_,
187 base::BindOnce(&PoissonAllocationSamplerStateTest::RemoveSamplesObservers,
188 observer1_.get(), observer2_.get()));
189 // Unretained is safe because `this` isn't deleted until the
190 // `barrier_closure` is invoked often enough to quit the main RunLoop.
191 auto next_repetition = base::BindPostTask(
192 task_runner_,
193 base::BindOnce(&AddRemoveObserversTester::TestAddRemoveObservers,
194 base::Unretained(this), barrier_closure,
195 remaining_repetitions - 1));
196 task_runner_->PostTask(FROM_HERE, std::move(add_observers)
197 .Then(std::move(remove_observers))
198 .Then(std::move(next_repetition)));
199 }
200
Start(base::RepeatingClosure * barrier_closure,int num_repetitions)201 void MuteHookedSamplesTester::Start(base::RepeatingClosure* barrier_closure,
202 int num_repetitions) {
203 // Unretained is safe because `this` isn't deleted until the
204 // `barrier_closure` is invoked often enough to quit the main RunLoop.
205 task_runner_->PostTask(
206 FROM_HERE,
207 base::BindOnce(&MuteHookedSamplesTester::TestMuteUnmuteSamples,
208 base::Unretained(this), barrier_closure, num_repetitions));
209 }
210
TestMuteUnmuteSamples(base::RepeatingClosure * barrier_closure,int remaining_repetitions)211 void MuteHookedSamplesTester::TestMuteUnmuteSamples(
212 base::RepeatingClosure* barrier_closure,
213 int remaining_repetitions) {
214 if (!remaining_repetitions || ::testing::Test::HasFailure()) {
215 barrier_closure->Run();
216 return;
217 }
218 auto mute_samples =
219 base::BindOnce(&PoissonAllocationSamplerStateTest::MuteHookedSamples);
220 auto unmute_samples = base::BindPostTask(
221 task_runner_,
222 base::BindOnce(&PoissonAllocationSamplerStateTest::UnmuteHookedSamples));
223 // Unretained is safe because `this` isn't deleted until the
224 // `barrier_closure` is invoked often enough to quit the main RunLoop.
225 auto next_repetition = base::BindPostTask(
226 task_runner_,
227 base::BindOnce(&MuteHookedSamplesTester::TestMuteUnmuteSamples,
228 base::Unretained(this), barrier_closure,
229 remaining_repetitions - 1));
230 task_runner_->PostTask(FROM_HERE, std::move(mute_samples)
231 .Then(std::move(unmute_samples))
232 .Then(std::move(next_repetition)));
233 }
234
235 // static
AddSamplesObservers(DummySamplesObserver * observer1,DummySamplesObserver * observer2)236 void PoissonAllocationSamplerStateTest::AddSamplesObservers(
237 DummySamplesObserver* observer1,
238 DummySamplesObserver* observer2) {
239 // The first observer should start the profiler running if it isn't already.
240 // The second should not change the state.
241 PoissonAllocationSampler::Get()->AddSamplesObserver(observer1);
242 EXPECT_TRUE(HasAllStateFlags(ProfilingStateFlag::kIsRunning |
243 ProfilingStateFlag::kWasStarted));
244 PoissonAllocationSampler::Get()->AddSamplesObserver(observer2);
245 EXPECT_TRUE(HasAllStateFlags(ProfilingStateFlag::kIsRunning |
246 ProfilingStateFlag::kWasStarted));
247 }
248
249 // static
RemoveSamplesObservers(DummySamplesObserver * observer1,DummySamplesObserver * observer2)250 void PoissonAllocationSamplerStateTest::RemoveSamplesObservers(
251 DummySamplesObserver* observer1,
252 DummySamplesObserver* observer2) {
253 // Removing the first observer should leave the profiler running. Removing the
254 // second might leave it running, or might stop it. It should never remove the
255 // kWasStarted flag.
256 EXPECT_TRUE(HasAllStateFlags(ProfilingStateFlag::kIsRunning |
257 ProfilingStateFlag::kWasStarted));
258 PoissonAllocationSampler::Get()->RemoveSamplesObserver(observer1);
259 EXPECT_TRUE(HasAllStateFlags(ProfilingStateFlag::kIsRunning |
260 ProfilingStateFlag::kWasStarted));
261 PoissonAllocationSampler::Get()->RemoveSamplesObserver(observer2);
262 EXPECT_TRUE(HasAllStateFlags(ProfilingStateFlag::kWasStarted));
263 }
264
265 // static
MuteHookedSamples()266 base::OnceClosure PoissonAllocationSamplerStateTest::MuteHookedSamples() {
267 using ScopedMuteHookedSamplesForTesting =
268 PoissonAllocationSampler::ScopedMuteHookedSamplesForTesting;
269 EXPECT_FALSE(
270 HasAllStateFlags(ProfilingStateFlag::kHookedSamplesMutedForTesting));
271 auto scoped_mute_hooked_samples =
272 std::make_unique<ScopedMuteHookedSamplesForTesting>();
273 EXPECT_TRUE(
274 HasAllStateFlags(ProfilingStateFlag::kHookedSamplesMutedForTesting));
275 auto unmute_closure = base::BindOnce(
276 [](std::unique_ptr<ScopedMuteHookedSamplesForTesting> p) { p.reset(); },
277 std::move(scoped_mute_hooked_samples));
278 return unmute_closure;
279 }
280
281 // static
UnmuteHookedSamples(base::OnceClosure unmute_closure)282 void PoissonAllocationSamplerStateTest::UnmuteHookedSamples(
283 base::OnceClosure unmute_closure) {
284 EXPECT_TRUE(
285 HasAllStateFlags(ProfilingStateFlag::kHookedSamplesMutedForTesting));
286 std::move(unmute_closure).Run();
287 EXPECT_FALSE(
288 HasAllStateFlags(ProfilingStateFlag::kHookedSamplesMutedForTesting));
289 }
290
TEST(PoissonAllocationSamplerTest,MuteHooksWithoutInit)291 TEST(PoissonAllocationSamplerTest, MuteHooksWithoutInit) {
292 // Make sure it's safe to create a ScopedMuteHookedSamplesForTesting from
293 // tests that might not call PoissonAllocationSampler::Get() to initialize the
294 // rest of the PoissonAllocationSampler.
295 EXPECT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
296 void* volatile p = nullptr;
297 {
298 PoissonAllocationSampler::ScopedMuteHookedSamplesForTesting mute_hooks;
299 EXPECT_TRUE(PoissonAllocationSampler::AreHookedSamplesMuted());
300 p = malloc(10000);
301 }
302 EXPECT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
303 free(p);
304 }
305
TEST_F(PoissonAllocationSamplerStateTest,UpdateProfilingState)306 TEST_F(PoissonAllocationSamplerStateTest, UpdateProfilingState) {
307 constexpr int kNumObserverThreads = 100;
308 constexpr int kNumRepetitions = 100;
309
310 base::RunLoop run_loop;
311
312 // Quit the run loop once all AddRemoveObserversTesters and the
313 // MuteUnmuteSamplesTester signal that they're done.
314 auto barrier_closure =
315 base::BarrierClosure(kNumObserverThreads + 1, run_loop.QuitClosure());
316
317 // No observers or ScopedMuteHookedSamplesForTesting objects exist.
318 // The kWasStarted flag may or may not be set depending on whether other tests
319 // have changed the singleton state.
320 ASSERT_FALSE(
321 HasAnyStateFlags(ProfilingStateFlag::kIsRunning |
322 ProfilingStateFlag::kHookedSamplesMutedForTesting));
323
324 std::array<std::unique_ptr<AddRemoveObserversTester>, kNumObserverThreads>
325 observer_testers;
326 for (int i = 0; i < kNumObserverThreads; ++i) {
327 // Start a thread to add and remove observers, toggling the kIsRunning and
328 // kHookedSamplesMutedForTesting state flags.
329 observer_testers[i] = std::make_unique<AddRemoveObserversTester>();
330 observer_testers[i]->Start(&barrier_closure, kNumRepetitions);
331 }
332
333 // There can only be one ScopedMuteHookedSamplesForTesting object at a time so
334 // test them on only one thread.
335 MuteHookedSamplesTester mute_samples_tester;
336 mute_samples_tester.Start(&barrier_closure, kNumRepetitions);
337
338 run_loop.Run();
339
340 // No observers or ScopedMuteHookedSamplesForTesting objects exist again.
341 EXPECT_FALSE(
342 HasAnyStateFlags(ProfilingStateFlag::kIsRunning |
343 ProfilingStateFlag::kHookedSamplesMutedForTesting));
344
345 // Move the observers into `observers_to_delete_` to be destroyed during
346 // teardown, in single-threaded context.
347 for (int i = 0; i < kNumObserverThreads; ++i) {
348 base::Extend(observers_to_delete_, observer_testers[i]->DetachObservers());
349 }
350 }
351
352 } // namespace base
353