blob: 8ae8a7b10549234bae8fd69fb5e448848d3f3ce5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/controller/highest_pmf_reporter.h"
#include "base/memory/ptr_util.h"
#include "base/test/test_mock_time_task_runner.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
class MockHighestPmfReporter : public HighestPmfReporter {
public:
MockHighestPmfReporter(
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_for_testing,
const base::TickClock* clock)
: HighestPmfReporter(task_runner_for_testing, clock) {}
~MockHighestPmfReporter() override = default;
void NotifyNavigationStart() { first_navigation_started_ = true; }
const std::vector<double>& GetReportedHighestPmf() const {
return reported_highest_pmf_;
}
const std::vector<double>& GetReportedPeakRss() const {
return reported_peak_rss_;
}
const std::vector<unsigned>& GetReportedWebpageCount() const {
return reported_webpage_count_;
}
int GetReportCount() const { return report_count_; }
private:
void ReportMetrics() override {
reported_highest_pmf_.push_back(current_highest_pmf_);
reported_peak_rss_.push_back(peak_resident_bytes_at_current_highest_pmf_);
reported_webpage_count_.push_back(webpage_counts_at_current_highest_pmf_);
}
bool FirstNavigationStarted() override {
if (!first_navigation_started_)
return false;
first_navigation_started_ = false;
return true;
}
std::vector<double> reported_highest_pmf_;
std::vector<double> reported_peak_rss_;
std::vector<unsigned> reported_webpage_count_;
bool first_navigation_started_ = false;
};
namespace peak_memory_reporter_test {
using testing::_;
// Mock that allows setting mock memory usage.
class MockMemoryUsageMonitor : public MemoryUsageMonitor {
public:
MockMemoryUsageMonitor(
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_for_testing,
const base::TickClock* clock)
: MemoryUsageMonitor(task_runner_for_testing, clock),
agent_group_scheduler_(
Thread::MainThread()->Scheduler()->CreateAgentGroupScheduler()) {
memset(&mock_memory_usage_, 0, sizeof(mock_memory_usage_));
}
~MockMemoryUsageMonitor() override = default;
MemoryUsage GetCurrentMemoryUsage() override { return mock_memory_usage_; }
void SetPrivateFootprintBytes(double private_footprint_bytes) {
mock_memory_usage_.private_footprint_bytes = private_footprint_bytes;
}
void SetPeakResidentBytes(double peak_resident_bytes) {
mock_memory_usage_.peak_resident_bytes = peak_resident_bytes;
}
// Insert fake NonOrdinaryPage into Page::OrdinaryPages().
void SetOrdinaryPageCount(unsigned page_count) {
DCHECK_GT(page_count, 0U);
while (dummy_pages_.size() < page_count) {
Page* page = CreateDummyPage();
DCHECK(page);
dummy_pages_.push_back(page);
}
if (Page::OrdinaryPages().size() > 1U) {
for (unsigned i = 0; i < dummy_pages_.size(); i++) {
if (Page::OrdinaryPages().Contains(dummy_pages_.at(i).Get()))
Page::OrdinaryPages().erase(dummy_pages_.at(i).Get());
}
}
DCHECK_EQ(Page::OrdinaryPages().size(), 1U);
std::vector<Persistent<Page>>::iterator it = dummy_pages_.begin();
while (Page::OrdinaryPages().size() < page_count) {
DCHECK(it != dummy_pages_.end());
Page::OrdinaryPages().insert(it->Get());
it++;
}
}
private:
MockMemoryUsageMonitor() = delete;
Page* CreateDummyPage() {
Page::PageClients page_clients;
FillWithEmptyClients(page_clients);
return Page::CreateNonOrdinary(page_clients, *agent_group_scheduler_);
}
MemoryUsage mock_memory_usage_;
std::vector<Persistent<Page>> dummy_pages_;
std::unique_ptr<scheduler::WebAgentGroupScheduler> agent_group_scheduler_;
};
class HighestPmfReporterTest : public PageTestBase {
public:
HighestPmfReporterTest() = default;
void SetUp() override {
test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
memory_usage_monitor_.reset(new MockMemoryUsageMonitor(
test_task_runner_, test_task_runner_->GetMockTickClock()));
MemoryUsageMonitor::SetInstanceForTesting(memory_usage_monitor_.get());
reporter_.reset(new MockHighestPmfReporter(
test_task_runner_, test_task_runner_->GetMockTickClock()));
PageTestBase::SetUp();
}
void TearDown() override {
PageTestBase::TearDown();
MemoryUsageMonitor::SetInstanceForTesting(nullptr);
memory_usage_monitor_.reset();
reporter_.reset();
}
void AdvanceClock(base::TimeDelta delta) {
test_task_runner_->FastForwardBy(delta);
}
void AdvanceClockTo(base::TimeTicks time) {
base::TimeDelta delta = time - NowTicks();
if (delta.is_zero())
return;
AdvanceClock(delta);
}
base::TimeTicks NowTicks() const {
return test_task_runner_->GetMockTickClock()->NowTicks();
}
protected:
scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
std::unique_ptr<MockMemoryUsageMonitor> memory_usage_monitor_;
std::unique_ptr<MockHighestPmfReporter> reporter_;
};
TEST_F(HighestPmfReporterTest, ReportNoMetricBeforeNavigationStart) {
EXPECT_TRUE(memory_usage_monitor_->TimerIsActive());
Page::OrdinaryPages().insert(&GetPage());
memory_usage_monitor_->SetPrivateFootprintBytes(1000.0);
AdvanceClock(base::TimeDelta::FromMinutes(1));
EXPECT_EQ(0, reporter_->GetReportCount());
EXPECT_EQ(0U, reporter_->GetReportedHighestPmf().size());
EXPECT_EQ(0U, reporter_->GetReportedPeakRss().size());
}
TEST_F(HighestPmfReporterTest, ReportMetric) {
EXPECT_TRUE(memory_usage_monitor_->TimerIsActive());
Page::OrdinaryPages().insert(&GetPage());
AdvanceClock(base::TimeDelta::FromSeconds(1));
// PMF, PeakRSS and PageCount at specified TimeSinceNavigation.
static const struct {
base::TimeDelta time_since_navigation;
double pmf;
double peak_rss;
unsigned page_count;
} time_pmf_rss_table[] = {
{base::TimeDelta::FromMinutes(0), 1000.0, 1200.0, 1},
{base::TimeDelta::FromMinutes(1), 750.0, 900.0, 1},
{base::TimeDelta::FromSeconds(80), 750.0, 1000.0, 4}, // t=1min 20sec
{base::TimeDelta::FromSeconds(90), 1100.0, 1500.0, 2}, // t=1min 30sec
{base::TimeDelta::FromMinutes(2), 900.0, 1000.0, 1},
{base::TimeDelta::FromMinutes(4), 900.0, 1000.0, 1},
{base::TimeDelta::FromMinutes(5), 1500.0, 2000.0, 3},
{base::TimeDelta::FromMinutes(7), 800.0, 900.0, 1},
{base::TimeDelta::FromMinutes(8), 900.0, 1000.0, 1},
{base::TimeDelta::FromMinutes(16), 900.0, 1000.0, 1},
};
base::TimeTicks navigation_start_time = NowTicks();
reporter_->NotifyNavigationStart();
for (const auto& item : time_pmf_rss_table) {
AdvanceClockTo(navigation_start_time + item.time_since_navigation);
// PMF, PeakRSS, Webpage count are captured at next OnMemoryPing.
memory_usage_monitor_->SetPrivateFootprintBytes(item.pmf);
memory_usage_monitor_->SetPeakResidentBytes(item.peak_rss);
memory_usage_monitor_->SetOrdinaryPageCount(item.page_count);
}
AdvanceClockTo(navigation_start_time + base::TimeDelta::FromMinutes(17));
EXPECT_EQ(4, reporter_->GetReportCount());
EXPECT_EQ(4U, reporter_->GetReportedHighestPmf().size());
EXPECT_NEAR(1100.0, reporter_->GetReportedHighestPmf().at(0), 0.001);
EXPECT_NEAR(900.0, reporter_->GetReportedHighestPmf().at(1), 0.001);
EXPECT_NEAR(1500.0, reporter_->GetReportedHighestPmf().at(2), 0.001);
EXPECT_NEAR(900.0, reporter_->GetReportedHighestPmf().at(3), 0.001);
EXPECT_EQ(4U, reporter_->GetReportedPeakRss().size());
EXPECT_NEAR(1500.0, reporter_->GetReportedPeakRss().at(0), 0.001);
EXPECT_NEAR(1000.0, reporter_->GetReportedPeakRss().at(1), 0.001);
EXPECT_NEAR(2000.0, reporter_->GetReportedPeakRss().at(2), 0.001);
EXPECT_NEAR(1000.0, reporter_->GetReportedPeakRss().at(3), 0.001);
EXPECT_EQ(4U, reporter_->GetReportedWebpageCount().size());
EXPECT_EQ(2U, reporter_->GetReportedWebpageCount().at(0));
EXPECT_EQ(1U, reporter_->GetReportedWebpageCount().at(1));
EXPECT_EQ(3U, reporter_->GetReportedWebpageCount().at(2));
EXPECT_EQ(1U, reporter_->GetReportedWebpageCount().at(3));
}
TEST_F(HighestPmfReporterTest, TestReportTiming) {
EXPECT_TRUE(memory_usage_monitor_->TimerIsActive());
Page::OrdinaryPages().insert(&GetPage());
memory_usage_monitor_->SetPrivateFootprintBytes(1000.0);
base::TimeTicks navigation_start_time = NowTicks();
reporter_->NotifyNavigationStart();
AdvanceClock(base::TimeDelta::FromSeconds(1));
// Now ReportMetrics task is posted with 2minutes delay.
// The task will be executed at "navigation_start_time + 2min + 1sec."
EXPECT_EQ(0, reporter_->GetReportCount());
AdvanceClockTo(navigation_start_time + base::TimeDelta::FromMinutes(2));
EXPECT_EQ(0, reporter_->GetReportCount());
// ReportMetrics task is executed and next ReportMetrics task is posted.
AdvanceClock(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1, reporter_->GetReportCount());
AdvanceClockTo(navigation_start_time + base::TimeDelta::FromMinutes(4));
EXPECT_EQ(1, reporter_->GetReportCount());
// ReportMetrics task is executed and next ReportMetrics task is posted.
AdvanceClock(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(2, reporter_->GetReportCount());
AdvanceClockTo(navigation_start_time + base::TimeDelta::FromMinutes(8));
EXPECT_EQ(2, reporter_->GetReportCount());
// ReportMetrics task is executed and next ReportMetrics task is posted.
AdvanceClock(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(3, reporter_->GetReportCount());
AdvanceClockTo(navigation_start_time + base::TimeDelta::FromMinutes(16));
EXPECT_EQ(3, reporter_->GetReportCount());
// ReportMetrics task is executed and next ReportMetrics task is posted.
AdvanceClock(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(4, reporter_->GetReportCount());
}
} // namespace peak_memory_reporter_test
} // namespace blink