blob: 929f37c9bfed9c59c40007f6928c1053c33445ce [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/memory/tab_manager_delegate_chromeos.h"
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process_handle.h"
#include "chrome/browser/chromeos/arc/arc_process.h"
#include "chrome/browser/memory/tab_manager.h"
#include "chrome/browser/memory/tab_stats.h"
#include "chrome/common/url_constants.h"
#include "components/arc/common/process.mojom.h"
#include "components/arc/test/fake_arc_bridge_service.h"
#include "components/exo/shell_surface.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/wm/public/activation_client.h"
#include "url/gurl.h"
namespace memory {
namespace {
const char kExoShellSurfaceWindowName[] = "ExoShellSurface";
const char kArcProcessNamePrefix[] = "org.chromium.arc.";
} // namespace
class TabManagerDelegateTest : public ash::test::AshTestBase {
public:
TabManagerDelegateTest() : application_id_(kArcProcessNamePrefix) {}
~TabManagerDelegateTest() override {}
void SetUp() override {
AshTestBase::SetUp();
arc_window_ = CreateTestWindowInShellWithId(0);
arc_window_->SetName(kExoShellSurfaceWindowName);
exo::ShellSurface::SetApplicationId(arc_window_,
&application_id_);
}
protected:
void ActivateArcWindow() {
GetActivationClient()->ActivateWindow(arc_window_);
}
void DeactivateArcWindow() {
GetActivationClient()->DeactivateWindow(arc_window_);
}
private:
aura::client::ActivationClient* GetActivationClient() {
return aura::client::GetActivationClient(
ash::Shell::GetPrimaryRootWindow());
}
aura::Window* arc_window_;
std::string application_id_;
};
TEST_F(TabManagerDelegateTest, CandidatesSorted) {
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "top", arc::mojom::ProcessState::TOP);
arc_processes.emplace_back(2, 20, "foreground",
arc::mojom::ProcessState::FOREGROUND_SERVICE);
arc_processes.emplace_back(3, 30, "service",
arc::mojom::ProcessState::SERVICE);
TabStats tab1, tab2, tab3, tab4, tab5;
tab1.tab_contents_id = 100;
tab1.is_pinned = true;
tab2.tab_contents_id = 200;
tab2.is_internal_page = true;
tab3.tab_contents_id = 300;
tab3.is_pinned = true;
tab3.is_media = true;
tab4.tab_contents_id = 400;
tab4.is_media = true;
tab5.tab_contents_id = 500;
tab5.is_app = true;
TabStatsList tab_list = {
tab1, tab2, tab3, tab4, tab5
};
std::vector<TabManagerDelegate::Candidate> candidates;
// Case 1: ARC window in the foreground.
ActivateArcWindow();
candidates = TabManagerDelegate::GetSortedCandidates(
tab_list, arc_processes);
EXPECT_EQ(8U, candidates.size());
EXPECT_EQ("service", candidates[0].app->process_name());
EXPECT_EQ("foreground", candidates[1].app->process_name());
// internal page.
EXPECT_EQ(200, candidates[2].tab->tab_contents_id);
// chrome app.
EXPECT_EQ(500, candidates[3].tab->tab_contents_id);
// pinned.
EXPECT_EQ(100, candidates[4].tab->tab_contents_id);
// media.
EXPECT_EQ(400, candidates[5].tab->tab_contents_id);
// pinned and media.
EXPECT_EQ(300, candidates[6].tab->tab_contents_id);
// ARC window is the active window, so top app has highest priority.
EXPECT_EQ("top", candidates[7].app->process_name());
// Case 2: ARC window in the background.
DeactivateArcWindow();
candidates = TabManagerDelegate::GetSortedCandidates(
tab_list, arc_processes);
EXPECT_EQ(8U, candidates.size());
EXPECT_EQ("service", candidates[0].app->process_name());
EXPECT_EQ("foreground", candidates[1].app->process_name());
// internal page.
EXPECT_EQ(200, candidates[2].tab->tab_contents_id);
// Chrome app and android app are tied, so both orders are correct.
if (candidates[3].is_arc_app) {
EXPECT_EQ("top", candidates[3].app->process_name());
// chrome app.
EXPECT_EQ(500, candidates[4].tab->tab_contents_id);
} else {
// chrome app.
EXPECT_EQ(500, candidates[3].tab->tab_contents_id);
EXPECT_EQ("top", candidates[4].app->process_name());
}
// pinned.
EXPECT_EQ(100, candidates[5].tab->tab_contents_id);
// media.
EXPECT_EQ(400, candidates[6].tab->tab_contents_id);
// pinned and media.
EXPECT_EQ(300, candidates[7].tab->tab_contents_id);
}
class MockTabManagerDelegate : public TabManagerDelegate {
public:
MockTabManagerDelegate(): TabManagerDelegate(nullptr) {
}
explicit MockTabManagerDelegate(TabManagerDelegate::MemoryStat* mem_stat)
: TabManagerDelegate(nullptr, mem_stat) {
}
// unit test.
std::vector<int> GetKilledArcProcesses() {
return killed_arc_processes_;
}
// unit test.
std::vector<int64_t> GetKilledTabs() {
return killed_tabs_;
}
// unit test.
void Clear() {
killed_arc_processes_.clear();
killed_tabs_.clear();
}
protected:
// Nullify the operation for unit test.
void SetOomScoreAdjForTabs(
const std::vector<std::pair<base::ProcessHandle, int>>& entries)
override {}
bool KillArcProcess(const int nspid) override {
killed_arc_processes_.push_back(nspid);
return true;
}
bool KillTab(int64_t tab_id) override {
killed_tabs_.push_back(tab_id);
return true;
}
private:
std::vector<int> killed_arc_processes_;
std::vector<int64_t> killed_tabs_;
};
class MockMemoryStat : public TabManagerDelegate::MemoryStat {
public:
MockMemoryStat() {}
~MockMemoryStat() override {}
int TargetMemoryToFreeKB() override {
return target_memory_to_free_kb_;
}
int EstimatedMemoryFreedKB(base::ProcessHandle pid) override {
return process_pss_[pid];
}
// unittest.
void SetTargetMemoryToFreeKB(const int target) {
target_memory_to_free_kb_ = target;
}
// unittest.
void SetProcessPss(base::ProcessHandle pid, int pss) {
process_pss_[pid] = pss;
}
private:
int target_memory_to_free_kb_;
std::map<base::ProcessHandle, int> process_pss_;
};
TEST_F(TabManagerDelegateTest, SetOomScoreAdj) {
arc::FakeArcBridgeService fake_arc_bridge_service;
MockTabManagerDelegate tab_manager_delegate;
ActivateArcWindow();
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "top", arc::mojom::ProcessState::TOP);
arc_processes.emplace_back(2, 20, "foreground",
arc::mojom::ProcessState::FOREGROUND_SERVICE);
arc_processes.emplace_back(3, 30, "service",
arc::mojom::ProcessState::SERVICE);
TabStats tab1, tab2, tab3, tab4, tab5;
tab1.is_pinned = true;
tab1.renderer_handle = 11;
tab2.is_internal_page = true;
tab2.renderer_handle = 11;
tab3.is_pinned = true;
tab3.is_media = true;
tab3.renderer_handle = 12;
tab4.is_media = true;
tab4.renderer_handle = 12;
tab5.is_app = true;
tab5.renderer_handle = 12;
TabStatsList tab_list = {tab1, tab2, tab3, tab4, tab5};
// Sorted order:
// app "service" pid: 30 oom_socre_adj: 825
// app "foreground" pid: 20 oom_score_adj: 650
// tab2 pid: 11 oom_socre_adj: 417
// tab5 pid: 12 oom_score_adj: 358
// tab1 pid: 11 oom_socre_adj: 417
// tab4 pid: 12 oom_socre_adj: 358
// tab3 pid: 12 oom_score_adj: 358
// app "top" pid: 10 oom_score_adj: 300
tab_manager_delegate.AdjustOomPrioritiesImpl(tab_list, arc_processes);
auto& oom_score_map = tab_manager_delegate.oom_score_map_;
EXPECT_EQ(5U, oom_score_map.size());
// Higher priority part.
EXPECT_EQ(300, oom_score_map[10]);
EXPECT_EQ(358, oom_score_map[12]);
EXPECT_EQ(417, oom_score_map[11]);
// Lower priority part.
EXPECT_EQ(650, oom_score_map[20]);
EXPECT_EQ(825, oom_score_map[30]);
}
TEST_F(TabManagerDelegateTest, KillMultipleProcesses) {
arc::FakeArcBridgeService fake_arc_bridge_service;
// Not owned.
MockMemoryStat* memory_stat = new MockMemoryStat();
// Instantiate the mock instance.
MockTabManagerDelegate tab_manager_delegate(memory_stat);
ActivateArcWindow();
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(10001, 100, "top", arc::mojom::ProcessState::TOP);
arc_processes.emplace_back(10002, 200, "foreground",
arc::mojom::ProcessState::FOREGROUND_SERVICE);
arc_processes.emplace_back(10003, 300, "service",
arc::mojom::ProcessState::SERVICE);
TabStats tab1, tab2, tab3, tab4, tab5;
tab1.is_pinned = true;
tab1.renderer_handle = 11;
tab1.tab_contents_id = 1;
tab2.is_internal_page = true;
tab2.renderer_handle = 11;
tab2.tab_contents_id = 2;
tab3.is_pinned = true;
tab3.is_media = true;
tab3.renderer_handle = 12;
tab3.tab_contents_id = 3;
tab4.is_media = true;
tab4.renderer_handle = 12;
tab4.tab_contents_id = 4;
tab5.is_app = true;
tab5.renderer_handle = 12;
tab5.tab_contents_id = 5;
TabStatsList tab_list = {tab1, tab2, tab3, tab4, tab5};
// Sorted order:
// app "service" pid: 30
// app "foreground" pid: 20
// tab2 pid: 11
// tab5 pid: 12
// tab1 pid: 11
// tab4 pid: 12
// tab3 pid: 12
// app "top" pid: 10
memory_stat->SetTargetMemoryToFreeKB(250000);
// The 3 entities to be killed.
memory_stat->SetProcessPss(300, 10000);
memory_stat->SetProcessPss(200, 200000);
memory_stat->SetProcessPss(11, 50000);
// Should not be used.
memory_stat->SetProcessPss(100, 30000);
memory_stat->SetProcessPss(12, 100000);
tab_manager_delegate.LowMemoryKillImpl(tab_list, arc_processes);
auto killed_arc_processes = tab_manager_delegate.GetKilledArcProcesses();
auto killed_tabs = tab_manager_delegate.GetKilledTabs();
EXPECT_EQ(2U, killed_arc_processes.size());
EXPECT_EQ(1U, killed_tabs.size());
// nspid.
EXPECT_EQ(10003, killed_arc_processes[0]);
EXPECT_EQ(10002, killed_arc_processes[1]);
// tab content id.
EXPECT_EQ(2, killed_tabs[0]);
}
} // namespace memory