| Index: base/trace_event/memory_peak_detector_unittest.cc
|
| diff --git a/base/trace_event/memory_peak_detector_unittest.cc b/base/trace_event/memory_peak_detector_unittest.cc
|
| index 9a9b92217c123958be39ee07a6a5092a63c149b9..31342de80e3146991470c2818888910edac04614 100644
|
| --- a/base/trace_event/memory_peak_detector_unittest.cc
|
| +++ b/base/trace_event/memory_peak_detector_unittest.cc
|
| @@ -10,7 +10,6 @@
|
| #include "base/logging.h"
|
| #include "base/run_loop.h"
|
| #include "base/synchronization/waitable_event.h"
|
| -#include "base/test/test_timeouts.h"
|
| #include "base/threading/platform_thread.h"
|
| #include "base/threading/thread.h"
|
| #include "base/threading/thread_task_runner_handle.h"
|
| @@ -28,6 +27,12 @@ namespace trace_event {
|
|
|
| namespace {
|
|
|
| +const TimeDelta kMs = TimeDelta::FromMilliseconds(1);
|
| +const MemoryPeakDetector::Config kConfigNoCallbacks = {
|
| + 1 /* polling_interval_ms */, 60000 /* min_time_between_peaks_ms */,
|
| + false /* enable_verbose_poll_tracing */
|
| +};
|
| +
|
| class MockMemoryDumpProvider : public MemoryDumpProvider {
|
| public:
|
| bool OnMemoryDump(const MemoryDumpArgs&, ProcessMemoryDump*) override {
|
| @@ -52,6 +57,8 @@ class MemoryPeakDetectorTest : public testing::Test {
|
| };
|
|
|
| MemoryPeakDetectorTest() : testing::Test() {}
|
| + static const uint64_t kSlidingWindowNumSamples =
|
| + MemoryPeakDetector::kSlidingWindowNumSamples;
|
|
|
| std::unique_ptr<MemoryPeakDetector, FriendDeleter> NewInstance() {
|
| return std::unique_ptr<MemoryPeakDetector, FriendDeleter>(
|
| @@ -117,6 +124,41 @@ class MemoryPeakDetectorTest : public testing::Test {
|
| return res;
|
| }
|
|
|
| + // Runs the peak detector with a mock MDP with the given
|
| + // |config|. The mock MDP will invoke the |poll_function| on any call to
|
| + // PollFastMemoryTotal(), until |num_samples| have been polled.
|
| + // It returns the number of peaks detected.
|
| + uint32_t RunWithCustomPollFunction(
|
| + MemoryPeakDetector::Config config,
|
| + uint32_t num_samples,
|
| + RepeatingCallback<uint64_t(uint32_t)> poll_function) {
|
| + WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| + WaitableEvent::InitialState::NOT_SIGNALED);
|
| + scoped_refptr<MemoryDumpProviderInfo> mdp = CreateMockDumpProvider();
|
| + dump_providers_.push_back(mdp);
|
| + uint32_t cur_sample_idx = 0;
|
| + EXPECT_CALL(GetMockMDP(mdp), PollFastMemoryTotal(_))
|
| + .WillRepeatedly(Invoke(
|
| + [&cur_sample_idx, &evt, poll_function, num_samples](uint64_t* mem) {
|
| + if (cur_sample_idx >= num_samples) {
|
| + *mem = 1;
|
| + evt.Signal();
|
| + } else {
|
| + *mem = poll_function.Run(cur_sample_idx++);
|
| + }
|
| + }));
|
| +
|
| + uint32_t num_peaks = 0;
|
| + EXPECT_CALL(on_peak_callback_, OnPeak())
|
| + .WillRepeatedly(Invoke([&num_peaks] { num_peaks++; }));
|
| + peak_detector_->Start(config);
|
| + evt.Wait(); // Wait for |num_samples| invocations of PollFastMemoryTotal().
|
| + peak_detector_->Stop();
|
| + EXPECT_EQ(num_samples, cur_sample_idx);
|
| + EXPECT_EQ(MemoryPeakDetector::DISABLED, GetPeakDetectorState());
|
| + return num_peaks;
|
| + }
|
| +
|
| // Called on the |bg_thread_|.
|
| void MockGetDumpProviders(MemoryPeakDetector::DumpProvidersList* mdps) {
|
| get_mdp_call_count_++;
|
| @@ -145,6 +187,27 @@ class MemoryPeakDetectorTest : public testing::Test {
|
| return *static_cast<MockMemoryDumpProvider*>(mdp_info->dump_provider);
|
| }
|
|
|
| + static uint64_t PollFunctionThatCausesPeakViaStdDev(uint32_t sample_idx) {
|
| + // Start with a baseline of 50 MB.
|
| + if (sample_idx < kSlidingWindowNumSamples)
|
| + return 50000 + (sample_idx % 3) * 100;
|
| +
|
| + // Then 10 samples around 80 MB
|
| + if (sample_idx < 10 + kSlidingWindowNumSamples)
|
| + return 80000 + (sample_idx % 3) * 200;
|
| +
|
| + // Than back to 60 MB.
|
| + if (sample_idx < 2 * kSlidingWindowNumSamples)
|
| + return 60000 + (sample_idx % 3) * 100;
|
| +
|
| + // Then 20 samples around 120 MB.
|
| + if (sample_idx < 20 + 2 * kSlidingWindowNumSamples)
|
| + return 120000 + (sample_idx % 3) * 200;
|
| +
|
| + // Then back to idle to around 50 MB until the end.
|
| + return 50000 + (sample_idx % 3) * 100;
|
| + }
|
| +
|
| protected:
|
| MemoryPeakDetector::DumpProvidersList dump_providers_;
|
| uint32_t get_mdp_call_count_;
|
| @@ -153,9 +216,11 @@ class MemoryPeakDetectorTest : public testing::Test {
|
| OnPeakDetectedWrapper on_peak_callback_;
|
| };
|
|
|
| +const uint64_t MemoryPeakDetectorTest::kSlidingWindowNumSamples;
|
| +
|
| TEST_F(MemoryPeakDetectorTest, GetDumpProvidersFunctionCalled) {
|
| EXPECT_EQ(MemoryPeakDetector::DISABLED, GetPeakDetectorState());
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(1u, GetNumGetDumpProvidersCalls());
|
| EXPECT_EQ(MemoryPeakDetector::ENABLED, GetPeakDetectorState());
|
|
|
| @@ -164,7 +229,7 @@ TEST_F(MemoryPeakDetectorTest, GetDumpProvidersFunctionCalled) {
|
| EXPECT_EQ(0u, GetNumPollingTasksRan());
|
| }
|
|
|
| -TEST_F(MemoryPeakDetectorTest, NotifyBeforeInitialize) {
|
| +TEST_F(MemoryPeakDetectorTest, ThrottleAndNotifyBeforeInitialize) {
|
| peak_detector_->TearDown();
|
|
|
| WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| @@ -173,11 +238,12 @@ TEST_F(MemoryPeakDetectorTest, NotifyBeforeInitialize) {
|
| EXPECT_CALL(GetMockMDP(mdp), PollFastMemoryTotal(_))
|
| .WillRepeatedly(Invoke([&evt](uint64_t*) { evt.Signal(); }));
|
| dump_providers_.push_back(mdp);
|
| + peak_detector_->Throttle();
|
| peak_detector_->NotifyMemoryDumpProvidersChanged();
|
| EXPECT_EQ(MemoryPeakDetector::NOT_INITIALIZED, GetPeakDetectorState());
|
| RestartThreadAndReinitializePeakDetector();
|
|
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(MemoryPeakDetector::RUNNING, GetPeakDetectorState());
|
| evt.Wait(); // Wait for a PollFastMemoryTotal() call.
|
|
|
| @@ -188,7 +254,7 @@ TEST_F(MemoryPeakDetectorTest, NotifyBeforeInitialize) {
|
| }
|
|
|
| TEST_F(MemoryPeakDetectorTest, DoubleStop) {
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(MemoryPeakDetector::ENABLED, GetPeakDetectorState());
|
|
|
| peak_detector_->Stop();
|
| @@ -209,7 +275,7 @@ TEST_F(MemoryPeakDetectorTest, OneDumpProviderRegisteredBeforeStart) {
|
| .WillRepeatedly(Invoke([&evt](uint64_t*) { evt.Signal(); }));
|
| dump_providers_.push_back(mdp);
|
|
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| evt.Wait(); // Signaled when PollFastMemoryTotal() is called on the MockMDP.
|
| EXPECT_EQ(MemoryPeakDetector::RUNNING, GetPeakDetectorState());
|
|
|
| @@ -229,7 +295,7 @@ TEST_F(MemoryPeakDetectorTest, ReInitializeAndRebindToNewThread) {
|
|
|
| for (int i = 0; i < 5; ++i) {
|
| evt.Reset();
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| evt.Wait(); // Wait for a PollFastMemoryTotal() call.
|
| // Check that calling TearDown implicitly does a Stop().
|
| peak_detector_->TearDown();
|
| @@ -240,16 +306,16 @@ TEST_F(MemoryPeakDetectorTest, ReInitializeAndRebindToNewThread) {
|
| }
|
|
|
| TEST_F(MemoryPeakDetectorTest, OneDumpProviderRegisteredOutOfBand) {
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(MemoryPeakDetector::ENABLED, GetPeakDetectorState());
|
| EXPECT_EQ(1u, GetNumGetDumpProvidersCalls());
|
|
|
| // Check that no poll tasks are posted before any dump provider is registered.
|
| - PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| + PlatformThread::Sleep(5 * kConfigNoCallbacks.polling_interval_ms * kMs);
|
| EXPECT_EQ(0u, GetNumPollingTasksRan());
|
|
|
| // Registed the MDP After Start() has been issued and expect that the
|
| - // PeakDetector transitions ENABLED -> RUNNING on the next
|
| + // PeakDetector transitions ENABLED -> RUNNING on the next
|
| // NotifyMemoryDumpProvidersChanged() call.
|
| WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| WaitableEvent::InitialState::NOT_SIGNALED);
|
| @@ -273,7 +339,7 @@ TEST_F(MemoryPeakDetectorTest, OneDumpProviderRegisteredOutOfBand) {
|
| EXPECT_GT(num_poll_tasks, 0u);
|
|
|
| // At this point, no more polling tasks should be posted.
|
| - PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| + PlatformThread::Sleep(5 * kConfigNoCallbacks.polling_interval_ms * kMs);
|
| peak_detector_->Stop();
|
| EXPECT_EQ(MemoryPeakDetector::DISABLED, GetPeakDetectorState());
|
| EXPECT_EQ(num_poll_tasks, GetNumPollingTasksRan());
|
| @@ -296,17 +362,15 @@ TEST_F(MemoryPeakDetectorTest, StartStopQuickly) {
|
|
|
| const TimeTicks tstart = TimeTicks::Now();
|
| for (int i = 0; i < 5; i++) {
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| peak_detector_->Stop();
|
| }
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(MemoryPeakDetector::RUNNING, GetPeakDetectorState());
|
| evt.Wait(); // Wait for kNumPolls.
|
| const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF();
|
|
|
| - // TODO(primiano): this will become config.polling_interval_ms in the next CL.
|
| - const uint32_t polling_interval_ms = 1;
|
| - EXPECT_GE(time_ms, kNumPolls * polling_interval_ms);
|
| + EXPECT_GE(time_ms, kNumPolls * kConfigNoCallbacks.polling_interval_ms);
|
| peak_detector_->Stop();
|
| }
|
|
|
| @@ -324,7 +388,7 @@ TEST_F(MemoryPeakDetectorTest, RegisterAndUnregisterTwoDumpProviders) {
|
|
|
| // Register only one MDP and start the detector.
|
| dump_providers_.push_back(mdp1);
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(MemoryPeakDetector::RUNNING, GetPeakDetectorState());
|
|
|
| // Wait for one poll task and then register also the other one.
|
| @@ -366,9 +430,9 @@ TEST_F(MemoryPeakDetectorTest, RegisterAndUnregisterTwoDumpProviders) {
|
| mdp2 = nullptr;
|
|
|
| num_poll_tasks = GetNumPollingTasksRan();
|
| - peak_detector_->Start();
|
| + peak_detector_->Start(kConfigNoCallbacks);
|
| EXPECT_EQ(MemoryPeakDetector::ENABLED, GetPeakDetectorState());
|
| - PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| + PlatformThread::Sleep(5 * kConfigNoCallbacks.polling_interval_ms * kMs);
|
|
|
| peak_detector_->Stop();
|
| EXPECT_EQ(MemoryPeakDetector::DISABLED, GetPeakDetectorState());
|
| @@ -377,5 +441,116 @@ TEST_F(MemoryPeakDetectorTest, RegisterAndUnregisterTwoDumpProviders) {
|
| EXPECT_EQ(6u, GetNumGetDumpProvidersCalls());
|
| }
|
|
|
| +// Tests the behavior of the static threshold detector, which is supposed to
|
| +// detect a peak whenever an increase >= threshold is detected.
|
| +TEST_F(MemoryPeakDetectorTest, StaticThreshold) {
|
| + const uint32_t kNumSamples = 2 * kSlidingWindowNumSamples;
|
| + constexpr uint32_t kNumSamplesPerStep = 10;
|
| + constexpr uint64_t kThreshold = 1000000;
|
| + peak_detector_->SetStaticThresholdForTesting(kThreshold);
|
| + const MemoryPeakDetector::Config kConfig = {
|
| + 1 /* polling_interval_ms */, 0 /* min_time_between_peaks_ms */,
|
| + false /* enable_verbose_poll_tracing */
|
| + };
|
| +
|
| + // The mocked PollFastMemoryTotal() will return a step function,
|
| + // e.g. (1, 1, 1, 5, 5, 5, ...) where the steps are 2x threshold, in order to
|
| + // trigger only the static threshold logic.
|
| + auto poll_fn = Bind(
|
| + [](const uint32_t kNumSamplesPerStep, const uint64_t kThreshold,
|
| + uint32_t sample_idx) -> uint64_t {
|
| + return (1 + sample_idx / kNumSamplesPerStep) * 2 * kThreshold;
|
| + },
|
| + kNumSamplesPerStep, kThreshold);
|
| + uint32_t num_peaks = RunWithCustomPollFunction(kConfig, kNumSamples, poll_fn);
|
| + EXPECT_EQ(kNumSamples / kNumSamplesPerStep - 1, num_peaks);
|
| +}
|
| +
|
| +// Checks the throttling logic of Config's |min_time_between_peaks_ms|.
|
| +TEST_F(MemoryPeakDetectorTest, PeakCallbackThrottling) {
|
| + const size_t kNumSamples = 2 * kSlidingWindowNumSamples;
|
| + constexpr uint64_t kThreshold = 1000000;
|
| + peak_detector_->SetStaticThresholdForTesting(kThreshold);
|
| + const MemoryPeakDetector::Config kConfig = {
|
| + 1 /* polling_interval_ms */, 4 /* min_time_between_peaks_ms */,
|
| + false /* enable_verbose_poll_tracing */
|
| + };
|
| +
|
| + // Each mock value returned is N * 2 * threshold, so all of them would be
|
| + // eligible to be a peak if throttling wasn't enabled.
|
| + auto poll_fn = Bind(
|
| + [](uint64_t kThreshold, uint32_t sample_idx) -> uint64_t {
|
| + return (sample_idx + 1) * 2 * kThreshold;
|
| + },
|
| + kThreshold);
|
| + uint32_t num_peaks = RunWithCustomPollFunction(kConfig, kNumSamples, poll_fn);
|
| + const uint32_t kExpectedThrottlingRate =
|
| + kConfig.min_time_between_peaks_ms / kConfig.polling_interval_ms;
|
| + EXPECT_LT(num_peaks, kNumSamples / kExpectedThrottlingRate);
|
| +}
|
| +
|
| +TEST_F(MemoryPeakDetectorTest, StdDev) {
|
| + // Set the threshold to some arbitrarily high value, so that the static
|
| + // threshold logic is not hit in this test.
|
| + constexpr uint64_t kThreshold = 1024 * 1024 * 1024;
|
| + peak_detector_->SetStaticThresholdForTesting(kThreshold);
|
| + const size_t kNumSamples = 3 * kSlidingWindowNumSamples;
|
| + const MemoryPeakDetector::Config kConfig = {
|
| + 1 /* polling_interval_ms */, 0 /* min_time_between_peaks_ms */,
|
| + false /* enable_verbose_poll_tracing */
|
| + };
|
| +
|
| + auto poll_fn = Bind(&PollFunctionThatCausesPeakViaStdDev);
|
| + uint32_t num_peaks = RunWithCustomPollFunction(kConfig, kNumSamples, poll_fn);
|
| + EXPECT_EQ(2u, num_peaks); // 80 MB, 120 MB.
|
| +}
|
| +
|
| +// Tests that Throttle() actually holds back peak notifications.
|
| +TEST_F(MemoryPeakDetectorTest, Throttle) {
|
| + constexpr uint64_t kThreshold = 1024 * 1024 * 1024;
|
| + const uint32_t kNumSamples = 3 * kSlidingWindowNumSamples;
|
| + peak_detector_->SetStaticThresholdForTesting(kThreshold);
|
| + const MemoryPeakDetector::Config kConfig = {
|
| + 1 /* polling_interval_ms */, 0 /* min_time_between_peaks_ms */,
|
| + false /* enable_verbose_poll_tracing */
|
| + };
|
| +
|
| + auto poll_fn = Bind(
|
| + [](MemoryPeakDetector* peak_detector, uint32_t sample_idx) -> uint64_t {
|
| + if (sample_idx % 20 == 20 - 1)
|
| + peak_detector->Throttle();
|
| + return PollFunctionThatCausesPeakViaStdDev(sample_idx);
|
| + },
|
| + Unretained(&*peak_detector_));
|
| + uint32_t num_peaks = RunWithCustomPollFunction(kConfig, kNumSamples, poll_fn);
|
| + EXPECT_EQ(0u, num_peaks);
|
| +}
|
| +
|
| +// Tests that the windows stddev state is not carried over through
|
| +// Stop() -> Start() sequences.
|
| +TEST_F(MemoryPeakDetectorTest, RestartClearsState) {
|
| + constexpr uint64_t kThreshold = 1024 * 1024 * 1024;
|
| + peak_detector_->SetStaticThresholdForTesting(kThreshold);
|
| + const size_t kNumSamples = 3 * kSlidingWindowNumSamples;
|
| + const MemoryPeakDetector::Config kConfig = {
|
| + 1 /* polling_interval_ms */, 0 /* min_time_between_peaks_ms */,
|
| + false /* enable_verbose_poll_tracing */
|
| + };
|
| + auto poll_fn = Bind(
|
| + [](MemoryPeakDetector* peak_detector,
|
| + const uint32_t kSlidingWindowNumSamples,
|
| + MemoryPeakDetector::Config kConfig, uint32_t sample_idx) -> uint64_t {
|
| + if (sample_idx % kSlidingWindowNumSamples ==
|
| + kSlidingWindowNumSamples - 1) {
|
| + peak_detector->Stop();
|
| + peak_detector->Start(kConfig);
|
| + }
|
| + return PollFunctionThatCausesPeakViaStdDev(sample_idx);
|
| + },
|
| + Unretained(&*peak_detector_), kSlidingWindowNumSamples, kConfig);
|
| + uint32_t num_peaks = RunWithCustomPollFunction(kConfig, kNumSamples, poll_fn);
|
| + EXPECT_EQ(0u, num_peaks);
|
| +}
|
| +
|
| } // namespace trace_event
|
| } // namespace base
|
|
|