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