blob: 28fb8dc995d7d8adb98a88aeca86a3355e521155 [file] [log] [blame]
// Copyright 2017 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/oom_intervention_impl.h"
#include <unistd.h>
#include "base/files/file_util.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/oom_intervention/oom_intervention_types.h"
#include "third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
namespace blink {
namespace {
const uint64_t kTestBlinkThreshold = 80 * 1024;
const uint64_t kTestPMFThreshold = 160 * 1024;
const uint64_t kTestSwapThreshold = 500 * 1024;
const uint64_t kTestVmSizeThreshold = 1024 * 1024;
class MockOomInterventionHost : public mojom::blink::OomInterventionHost {
public:
MockOomInterventionHost(mojom::blink::OomInterventionHostRequest request)
: binding_(this, std::move(request)) {}
~MockOomInterventionHost() override = default;
void OnHighMemoryUsage() override {}
private:
mojo::Binding<mojom::blink::OomInterventionHost> binding_;
};
// Mock intervention class that has custom method for fetching metrics.
class MockOomInterventionImpl : public OomInterventionImpl {
public:
MockOomInterventionImpl() {}
~MockOomInterventionImpl() override {}
// If metrics are set by calling this method, then GetCurrentMemoryMetrics()
// will return the given metrics, else it will calculate metrics from
// providers.
void SetMetrics(OomInterventionMetrics metrics) {
metrics_ = std::make_unique<OomInterventionMetrics>();
*metrics_ = metrics;
}
private:
OomInterventionMetrics GetCurrentMemoryMetrics() override {
if (metrics_)
return *metrics_;
return CrashMemoryMetricsReporterImpl::Instance().GetCurrentMemoryMetrics();
}
std::unique_ptr<OomInterventionMetrics> metrics_;
};
} // namespace
class OomInterventionImplTest : public testing::Test {
public:
void SetUp() override {
intervention_ = std::make_unique<MockOomInterventionImpl>();
}
Page* DetectOnceOnBlankPage() {
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad("about:blank");
Page* page = web_view->MainFrameImpl()->GetFrame()->GetPage();
EXPECT_FALSE(page->Paused());
RunDetection(true, false, false);
return page;
}
void RunDetection(bool renderer_pause_enabled,
bool navigate_ads_enabled,
bool purge_v8_memory_enabled) {
mojom::blink::OomInterventionHostPtr host_ptr;
MockOomInterventionHost mock_host(mojo::MakeRequest(&host_ptr));
mojom::blink::DetectionArgsPtr args(mojom::blink::DetectionArgs::New());
args->blink_workload_threshold = kTestBlinkThreshold;
args->private_footprint_threshold = kTestPMFThreshold;
args->swap_threshold = kTestSwapThreshold;
args->virtual_memory_thresold = kTestVmSizeThreshold;
intervention_->StartDetection(std::move(host_ptr), std::move(args),
renderer_pause_enabled, navigate_ads_enabled,
purge_v8_memory_enabled);
test::RunDelayedTasks(TimeDelta::FromSeconds(1));
}
protected:
std::unique_ptr<MockOomInterventionImpl> intervention_;
frame_test_helpers::WebViewHelper web_view_helper_;
std::unique_ptr<SimRequest> main_resource_;
};
TEST_F(OomInterventionImplTest, NoDetectionOnBelowThreshold) {
OomInterventionMetrics mock_metrics = {};
// Set value less than the threshold to not trigger intervention.
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
intervention_->SetMetrics(mock_metrics);
Page* page = DetectOnceOnBlankPage();
EXPECT_FALSE(page->Paused());
}
TEST_F(OomInterventionImplTest, BlinkThresholdDetection) {
OomInterventionMetrics mock_metrics = {};
// Set value more than the threshold to not trigger intervention.
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) + 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
intervention_->SetMetrics(mock_metrics);
Page* page = DetectOnceOnBlankPage();
EXPECT_TRUE(page->Paused());
intervention_.reset();
EXPECT_FALSE(page->Paused());
}
TEST_F(OomInterventionImplTest, PmfThresholdDetection) {
OomInterventionMetrics mock_metrics = {};
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
// Set value more than the threshold to trigger intervention.
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) + 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
intervention_->SetMetrics(mock_metrics);
Page* page = DetectOnceOnBlankPage();
EXPECT_TRUE(page->Paused());
intervention_.reset();
EXPECT_FALSE(page->Paused());
}
TEST_F(OomInterventionImplTest, SwapThresholdDetection) {
OomInterventionMetrics mock_metrics = {};
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
// Set value more than the threshold to trigger intervention.
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) + 1;
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
intervention_->SetMetrics(mock_metrics);
Page* page = DetectOnceOnBlankPage();
EXPECT_TRUE(page->Paused());
intervention_.reset();
EXPECT_FALSE(page->Paused());
}
TEST_F(OomInterventionImplTest, VmSizeThresholdDetection) {
OomInterventionMetrics mock_metrics = {};
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
// Set value more than the threshold to trigger intervention.
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) + 1;
intervention_->SetMetrics(mock_metrics);
Page* page = DetectOnceOnBlankPage();
EXPECT_TRUE(page->Paused());
intervention_.reset();
EXPECT_FALSE(page->Paused());
}
TEST_F(OomInterventionImplTest, StopWatchingAfterDetection) {
OomInterventionMetrics mock_metrics = {};
// Set value more than the threshold to trigger intervention.
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) + 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
intervention_->SetMetrics(mock_metrics);
DetectOnceOnBlankPage();
EXPECT_FALSE(intervention_->timer_.IsActive());
}
TEST_F(OomInterventionImplTest, ContinueWatchingWithoutDetection) {
OomInterventionMetrics mock_metrics = {};
// Set value less than the threshold to not trigger intervention.
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
intervention_->SetMetrics(mock_metrics);
DetectOnceOnBlankPage();
EXPECT_TRUE(intervention_->timer_.IsActive());
}
TEST_F(OomInterventionImplTest, CalculateProcessFootprint) {
const char kStatusFile[] =
"First: 1\n Second: 2 kB\nVmSwap: 10 kB \n Third: 10 kB\n Last: 8";
const char kStatmFile[] = "100 40 25 0 0";
uint64_t expected_swap_kb = 10;
uint64_t expected_private_footprint_kb =
(40 - 25) * getpagesize() / 1024 + expected_swap_kb;
uint64_t expected_vm_size_kb = 100 * getpagesize() / 1024;
base::FilePath statm_path;
EXPECT_TRUE(base::CreateTemporaryFile(&statm_path));
EXPECT_EQ(static_cast<int>(sizeof(kStatmFile)),
base::WriteFile(statm_path, kStatmFile, sizeof(kStatmFile)));
base::File statm_file(statm_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
base::FilePath status_path;
EXPECT_TRUE(base::CreateTemporaryFile(&status_path));
EXPECT_EQ(static_cast<int>(sizeof(kStatusFile)),
base::WriteFile(status_path, kStatusFile, sizeof(kStatusFile)));
base::File status_file(status_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
CrashMemoryMetricsReporterImpl::Instance().statm_fd_.reset(
statm_file.TakePlatformFile());
CrashMemoryMetricsReporterImpl::Instance().status_fd_.reset(
status_file.TakePlatformFile());
mojom::blink::OomInterventionHostPtr host_ptr;
MockOomInterventionHost mock_host(mojo::MakeRequest(&host_ptr));
mojom::blink::DetectionArgsPtr args(mojom::blink::DetectionArgs::New());
intervention_->StartDetection(
std::move(host_ptr), std::move(args), true /*renderer_pause_enabled*/,
true /*navigate_ads_enabled*/, true /*purge_v8_memory_enabled*/);
// Create unsafe shared memory region to write metrics in reporter.
base::UnsafeSharedMemoryRegion shm =
base::UnsafeSharedMemoryRegion::Create(sizeof(OomInterventionMetrics));
CrashMemoryMetricsReporterImpl::Instance().shared_metrics_mapping_ =
shm.Map();
EXPECT_TRUE(CrashMemoryMetricsReporterImpl::Instance()
.shared_metrics_mapping_.IsValid());
intervention_->Check(nullptr);
OomInterventionMetrics* metrics = static_cast<OomInterventionMetrics*>(
CrashMemoryMetricsReporterImpl::Instance()
.shared_metrics_mapping_.memory());
EXPECT_EQ(expected_private_footprint_kb,
metrics->current_private_footprint_kb);
EXPECT_EQ(expected_swap_kb, metrics->current_swap_kb);
EXPECT_EQ(expected_vm_size_kb, metrics->current_vm_size_kb);
}
// TODO(yuzus): Once OOPIF unit test infrastructure is ready, add a test case
// with OOPIF enabled.
TEST_F(OomInterventionImplTest, V1DetectionAdsNavigation) {
OomInterventionMetrics mock_metrics = {};
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
// Set value more than the threshold to trigger intervention.
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) + 1;
intervention_->SetMetrics(mock_metrics);
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad("about:blank");
Page* page = web_view->MainFrameImpl()->GetFrame()->GetPage();
web_view->MainFrameImpl()
->GetFrame()
->GetDocument()
->body()
->SetInnerHTMLFromString(
"<iframe name='ad' src='data:text/html,'></iframe><iframe "
"name='non-ad' src='data:text/html,'>");
WebFrame* ad_iframe = web_view_helper_.LocalMainFrame()->FindFrameByName(
WebString::FromUTF8("ad"));
WebFrame* non_ad_iframe = web_view_helper_.LocalMainFrame()->FindFrameByName(
WebString::FromUTF8("non-ad"));
LocalFrame* local_adframe = ToLocalFrame(WebFrame::ToCoreFrame(*ad_iframe));
local_adframe->SetIsAdSubframe(blink::mojom::AdFrameType::kRootAd);
LocalFrame* local_non_adframe =
ToLocalFrame(WebFrame::ToCoreFrame(*non_ad_iframe));
EXPECT_TRUE(local_adframe->IsAdSubframe());
EXPECT_FALSE(local_non_adframe->IsAdSubframe());
RunDetection(true, true, false);
EXPECT_EQ(local_adframe->GetDocument()->Url().GetString(), "about:blank");
EXPECT_NE(local_non_adframe->GetDocument()->Url().GetString(), "about:blank");
EXPECT_TRUE(page->Paused());
}
TEST_F(OomInterventionImplTest, V2DetectionV8PurgeMemory) {
OomInterventionMetrics mock_metrics = {};
mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
// Set value more than the threshold to trigger intervention.
mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) + 1;
intervention_->SetMetrics(mock_metrics);
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad("about:blank");
Page* page = web_view->MainFrameImpl()->GetFrame()->GetPage();
LocalFrame* frame = ToLocalFrame(page->MainFrame());
EXPECT_FALSE(frame->GetDocument()->ExecutionContext::IsContextDestroyed());
RunDetection(true, true, true);
EXPECT_TRUE(frame->GetDocument()->ExecutionContext::IsContextDestroyed());
}
} // namespace blink