| Index: base/trace_event/memory_dump_scheduler_unittest.cc
|
| diff --git a/base/trace_event/memory_dump_scheduler_unittest.cc b/base/trace_event/memory_dump_scheduler_unittest.cc
|
| index 9af2a3b4306bf1b6ecf08b3cf66a417b61659ee5..17fb74361f5f47a4889a5c1cf5ead8598067b53a 100644
|
| --- a/base/trace_event/memory_dump_scheduler_unittest.cc
|
| +++ b/base/trace_event/memory_dump_scheduler_unittest.cc
|
| @@ -6,95 +6,195 @@
|
|
|
| #include <memory>
|
|
|
| +#include "base/bind.h"
|
| #include "base/single_thread_task_runner.h"
|
| +#include "base/synchronization/waitable_event.h"
|
| +#include "base/threading/thread.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| #include "testing/gtest/include/gtest/gtest.h"
|
|
|
| +using ::testing::Invoke;
|
| +using ::testing::_;
|
| +
|
| namespace base {
|
| namespace trace_event {
|
|
|
| -class MemoryDumpSchedulerPollingTest : public testing::Test {
|
| +namespace {
|
| +
|
| +// Wrapper to use gmock on a callback.
|
| +struct CallbackWrapper {
|
| + MOCK_METHOD1(OnTick, void(MemoryDumpLevelOfDetail));
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class MemoryDumpSchedulerTest : public testing::Test {
|
| public:
|
| - static const uint32_t kMinPollsToDump = 5;
|
| + struct FriendDeleter {
|
| + void operator()(MemoryDumpScheduler* inst) { delete inst; }
|
| + };
|
|
|
| - MemoryDumpSchedulerPollingTest()
|
| - : testing::Test(),
|
| - num_samples_tracked_(
|
| - MemoryDumpScheduler::PollingTriggerState::kMaxNumMemorySamples) {}
|
| + MemoryDumpSchedulerTest() : testing::Test() {}
|
|
|
| void SetUp() override {
|
| - MemoryDumpScheduler::SetPollingIntervalForTesting(1);
|
| - uint32_t kMinPollsToDump = 5;
|
| - mds_ = MemoryDumpScheduler::GetInstance();
|
| - mds_->Setup(nullptr, nullptr);
|
| - mds_->AddTrigger(MemoryDumpType::PEAK_MEMORY_USAGE,
|
| - MemoryDumpLevelOfDetail::LIGHT, kMinPollsToDump);
|
| - mds_->polling_state_->ResetTotals();
|
| - mds_->polling_state_->current_state =
|
| - MemoryDumpScheduler::PollingTriggerState::ENABLED;
|
| + bg_thread_.reset(new Thread("MemoryDumpSchedulerTest Thread"));
|
| + bg_thread_->Start();
|
| + scheduler_.reset(new MemoryDumpScheduler());
|
| }
|
|
|
| void TearDown() override {
|
| - mds_->polling_state_->current_state =
|
| - MemoryDumpScheduler::PollingTriggerState::DISABLED;
|
| + bg_thread_.reset();
|
| + scheduler_.reset();
|
| }
|
|
|
| protected:
|
| - bool ShouldTriggerDump(uint64_t total) {
|
| - return mds_->ShouldTriggerDump(total);
|
| - }
|
| -
|
| - uint32_t num_samples_tracked_;
|
| - MemoryDumpScheduler* mds_;
|
| + std::unique_ptr<MemoryDumpScheduler, FriendDeleter> scheduler_;
|
| + std::unique_ptr<Thread> bg_thread_;
|
| + CallbackWrapper on_tick_;
|
| };
|
|
|
| -TEST_F(MemoryDumpSchedulerPollingTest, PeakDetection) {
|
| - for (uint32_t i = 0; i < num_samples_tracked_ * 6; ++i) {
|
| - // Memory is increased in steps and dumps must be triggered at every step.
|
| - uint64_t total = (2 + (i / (2 * num_samples_tracked_))) * 1024 * 1204;
|
| - bool did_trigger = ShouldTriggerDump(total);
|
| - // Dumps must be triggered only at specific iterations.
|
| - bool should_have_triggered = i == 0;
|
| - should_have_triggered |=
|
| - (i > num_samples_tracked_) && (i % (2 * num_samples_tracked_) == 1);
|
| - if (should_have_triggered) {
|
| - ASSERT_TRUE(did_trigger) << "Dump wasn't triggered at " << i;
|
| - } else {
|
| - ASSERT_FALSE(did_trigger) << "Unexpected dump at " << i;
|
| - }
|
| - }
|
| +TEST_F(MemoryDumpSchedulerTest, SingleTrigger) {
|
| + const uint32_t kPeriodMs = 1;
|
| + const auto kLevelOfDetail = MemoryDumpLevelOfDetail::DETAILED;
|
| + const uint32_t kTicks = 5;
|
| + WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| + WaitableEvent::InitialState::NOT_SIGNALED);
|
| + MemoryDumpScheduler::Config config;
|
| + config.triggers.push_back({kLevelOfDetail, kPeriodMs});
|
| + config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
|
| +
|
| + testing::InSequence sequence;
|
| + EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
|
| + EXPECT_CALL(on_tick_, OnTick(_))
|
| + .WillRepeatedly(Invoke(
|
| + [&evt, kLevelOfDetail](MemoryDumpLevelOfDetail level_of_detail) {
|
| + EXPECT_EQ(kLevelOfDetail, level_of_detail);
|
| + evt.Signal();
|
| + }));
|
| +
|
| + // Check that Stop() before Start() doesn't cause any error.
|
| + scheduler_->Stop();
|
| +
|
| + const TimeTicks tstart = TimeTicks::Now();
|
| + scheduler_->Start(config, bg_thread_->task_runner());
|
| + evt.Wait();
|
| + const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF();
|
| +
|
| + // It takes N-1 ms to perform N ticks of 1ms each.
|
| + EXPECT_GE(time_ms, kPeriodMs * (kTicks - 1));
|
| +
|
| + // Check that stopping twice doesn't cause any problems.
|
| + scheduler_->Stop();
|
| + scheduler_->Stop();
|
| }
|
|
|
| -TEST_F(MemoryDumpSchedulerPollingTest, SlowGrowthDetection) {
|
| - for (uint32_t i = 0; i < 15; ++i) {
|
| - // Record 1GiB of increase in each call. Dumps are triggered with 1% w.r.t
|
| - // system's total memory.
|
| - uint64_t total = static_cast<uint64_t>(i + 1) * 1024 * 1024 * 1024;
|
| - bool did_trigger = ShouldTriggerDump(total);
|
| - bool should_have_triggered = i % kMinPollsToDump == 0;
|
| - if (should_have_triggered) {
|
| - ASSERT_TRUE(did_trigger) << "Dump wasn't triggered at " << i;
|
| - } else {
|
| - ASSERT_FALSE(did_trigger) << "Unexpected dump at " << i;
|
| - }
|
| - }
|
| +TEST_F(MemoryDumpSchedulerTest, MultipleTriggers) {
|
| + const uint32_t kPeriodLightMs = 3;
|
| + const uint32_t kPeriodDetailedMs = 9;
|
| + WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| + WaitableEvent::InitialState::NOT_SIGNALED);
|
| + MemoryDumpScheduler::Config config;
|
| + const MemoryDumpLevelOfDetail kLight = MemoryDumpLevelOfDetail::LIGHT;
|
| + const MemoryDumpLevelOfDetail kDetailed = MemoryDumpLevelOfDetail::DETAILED;
|
| + config.triggers.push_back({kLight, kPeriodLightMs});
|
| + config.triggers.push_back({kDetailed, kPeriodDetailedMs});
|
| + config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
|
| +
|
| + TimeTicks t1, t2, t3;
|
| +
|
| + testing::InSequence sequence;
|
| + EXPECT_CALL(on_tick_, OnTick(kDetailed))
|
| + .WillOnce(
|
| + Invoke([&t1](MemoryDumpLevelOfDetail) { t1 = TimeTicks::Now(); }));
|
| + EXPECT_CALL(on_tick_, OnTick(kLight)).Times(1);
|
| + EXPECT_CALL(on_tick_, OnTick(kLight)).Times(1);
|
| + EXPECT_CALL(on_tick_, OnTick(kDetailed))
|
| + .WillOnce(
|
| + Invoke([&t2](MemoryDumpLevelOfDetail) { t2 = TimeTicks::Now(); }));
|
| + EXPECT_CALL(on_tick_, OnTick(kLight))
|
| + .WillOnce(
|
| + Invoke([&t3](MemoryDumpLevelOfDetail) { t3 = TimeTicks::Now(); }));
|
| +
|
| + // Rationale for WillRepeatedly and not just WillOnce: Extra ticks might
|
| + // happen if the Stop() takes time. Not an interesting case, but we need to
|
| + // avoid gmock to shout in that case.
|
| + EXPECT_CALL(on_tick_, OnTick(_))
|
| + .WillRepeatedly(
|
| + Invoke([&evt](MemoryDumpLevelOfDetail) { evt.Signal(); }));
|
| +
|
| + scheduler_->Start(config, bg_thread_->task_runner());
|
| + evt.Wait();
|
| + scheduler_->Stop();
|
| + EXPECT_GE((t2 - t1).InMillisecondsF(), kPeriodDetailedMs);
|
| + EXPECT_GE((t3 - t2).InMillisecondsF(), kPeriodLightMs);
|
| }
|
|
|
| -TEST_F(MemoryDumpSchedulerPollingTest, NotifyDumpTriggered) {
|
| - for (uint32_t i = 0; i < num_samples_tracked_ * 6; ++i) {
|
| - uint64_t total = (2 + (i / (2 * num_samples_tracked_))) * 1024 * 1204;
|
| - if (i % num_samples_tracked_ == 0)
|
| - mds_->NotifyDumpTriggered();
|
| - bool did_trigger = ShouldTriggerDump(total);
|
| - // Dumps should never be triggered since NotifyDumpTriggered() is called
|
| - // frequently.
|
| - EXPECT_NE(0u, mds_->polling_state_->last_dump_memory_total);
|
| - EXPECT_GT(num_samples_tracked_ - 1,
|
| - mds_->polling_state_->last_memory_totals_kb_index);
|
| - EXPECT_LT(static_cast<int64_t>(
|
| - total - mds_->polling_state_->last_dump_memory_total),
|
| - mds_->polling_state_->memory_increase_threshold);
|
| - ASSERT_FALSE(did_trigger && i) << "Unexpected dump at " << i;
|
| +TEST_F(MemoryDumpSchedulerTest, StartStopQuickly) {
|
| + const uint32_t kPeriodMs = 1;
|
| + const uint32_t kTicks = 10;
|
| + WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| + WaitableEvent::InitialState::NOT_SIGNALED);
|
| + MemoryDumpScheduler::Config config;
|
| + config.triggers.push_back({MemoryDumpLevelOfDetail::DETAILED, kPeriodMs});
|
| + config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
|
| +
|
| + testing::InSequence sequence;
|
| + EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
|
| + EXPECT_CALL(on_tick_, OnTick(_))
|
| + .WillRepeatedly(
|
| + Invoke([&evt](MemoryDumpLevelOfDetail) { evt.Signal(); }));
|
| +
|
| + const TimeTicks tstart = TimeTicks::Now();
|
| + for (int i = 0; i < 5; i++) {
|
| + scheduler_->Stop();
|
| + scheduler_->Start(config, bg_thread_->task_runner());
|
| }
|
| + evt.Wait();
|
| + const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF();
|
| + scheduler_->Stop();
|
| +
|
| + // It takes N-1 ms to perform N ticks of 1ms each.
|
| + EXPECT_GE(time_ms, kPeriodMs * (kTicks - 1));
|
| +}
|
| +
|
| +TEST_F(MemoryDumpSchedulerTest, StopAndStartOnAnotherThread) {
|
| + const uint32_t kPeriodMs = 1;
|
| + const uint32_t kTicks = 3;
|
| + WaitableEvent evt(WaitableEvent::ResetPolicy::MANUAL,
|
| + WaitableEvent::InitialState::NOT_SIGNALED);
|
| + MemoryDumpScheduler::Config config;
|
| + config.triggers.push_back({MemoryDumpLevelOfDetail::DETAILED, kPeriodMs});
|
| + config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
|
| +
|
| + scoped_refptr<TaskRunner> expected_task_runner = bg_thread_->task_runner();
|
| + testing::InSequence sequence;
|
| + EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
|
| + EXPECT_CALL(on_tick_, OnTick(_))
|
| + .WillRepeatedly(
|
| + Invoke([&evt, expected_task_runner](MemoryDumpLevelOfDetail) {
|
| + EXPECT_TRUE(expected_task_runner->RunsTasksOnCurrentThread());
|
| + evt.Signal();
|
| + }));
|
| +
|
| + scheduler_->Start(config, bg_thread_->task_runner());
|
| + evt.Wait();
|
| + scheduler_->Stop();
|
| + bg_thread_->Stop();
|
| +
|
| + bg_thread_.reset(new Thread("MemoryDumpSchedulerTest Thread 2"));
|
| + bg_thread_->Start();
|
| + evt.Reset();
|
| + expected_task_runner = bg_thread_->task_runner();
|
| + scheduler_->Start(config, bg_thread_->task_runner());
|
| + EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
|
| + EXPECT_CALL(on_tick_, OnTick(_))
|
| + .WillRepeatedly(
|
| + Invoke([&evt, expected_task_runner](MemoryDumpLevelOfDetail) {
|
| + EXPECT_TRUE(expected_task_runner->RunsTasksOnCurrentThread());
|
| + evt.Signal();
|
| + }));
|
| + evt.Wait();
|
| + scheduler_->Stop();
|
| }
|
|
|
| } // namespace trace_event
|
|
|