blob: 088b684abe35311c245604fcefafbb95b2d36392 [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/resource_coordinator/tab_manager_delegate_chromeos.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/process/process_handle.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/resource_coordinator/test_lifecycle_unit.h"
#include "chrome/browser/resource_coordinator/time.h"
#include "chromeos/dbus/fake_debug_daemon_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace resource_coordinator {
namespace {
class TabManagerDelegateTest : public testing::Test {
public:
TabManagerDelegateTest() {}
~TabManagerDelegateTest() override {}
private:
content::TestBrowserThreadBundle thread_bundle_;
};
constexpr bool kIsFocused = true;
constexpr bool kNotFocused = false;
} // namespace
TEST_F(TabManagerDelegateTest, CandidatesSorted) {
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
kIsFocused, 100);
arc_processes.emplace_back(2, 20, "visible1", arc::mojom::ProcessState::TOP,
kNotFocused, 200);
arc_processes.emplace_back(
3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
arc_processes.emplace_back(4, 40, "visible2", arc::mojom::ProcessState::TOP,
kNotFocused, 150);
TestLifecycleUnit focused_lifecycle_unit(base::TimeTicks::Max());
TestLifecycleUnit protected_lifecycle_unit(
base::TimeTicks() + base::TimeDelta::FromSeconds(5), 0, false);
TestLifecycleUnit non_focused_lifecycle_unit(base::TimeTicks() +
base::TimeDelta::FromSeconds(1));
TestLifecycleUnit other_non_focused_lifecycle_unit(
base::TimeTicks() + base::TimeDelta::FromSeconds(2));
LifecycleUnitVector lifecycle_units{
&focused_lifecycle_unit, &protected_lifecycle_unit,
&non_focused_lifecycle_unit, &other_non_focused_lifecycle_unit};
std::vector<TabManagerDelegate::Candidate> candidates;
candidates =
TabManagerDelegate::GetSortedCandidates(lifecycle_units, arc_processes);
ASSERT_EQ(8U, candidates.size());
// focused LifecycleUnit
EXPECT_EQ(candidates[0].lifecycle_unit(), &focused_lifecycle_unit);
// focused app.
ASSERT_TRUE(candidates[1].app());
EXPECT_EQ("focused", candidates[1].app()->process_name());
// visible app 1, last_activity_time larger than visible app 2.
ASSERT_TRUE(candidates[2].app());
EXPECT_EQ("visible1", candidates[2].app()->process_name());
// visible app 2, last_activity_time less than visible app 1.
ASSERT_TRUE(candidates[3].app());
EXPECT_EQ("visible2", candidates[3].app()->process_name());
// background service.
ASSERT_TRUE(candidates[4].app());
EXPECT_EQ("service", candidates[4].app()->process_name());
// protected LifecycleUnit
EXPECT_EQ(candidates[5].lifecycle_unit(), &protected_lifecycle_unit);
// non-focused LifecycleUnits, sorted by last focused time.
EXPECT_EQ(candidates[6].lifecycle_unit(), &other_non_focused_lifecycle_unit);
EXPECT_EQ(candidates[7].lifecycle_unit(), &non_focused_lifecycle_unit);
}
// Occasionally, Chrome sees both FOCUSED_TAB and FOCUSED_APP at the same time.
// Test that Chrome treats the former as a more important process.
TEST_F(TabManagerDelegateTest, CandidatesSortedWithFocusedAppAndTab) {
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
kIsFocused, 100);
TestLifecycleUnit focused_lifecycle_unit(base::TimeTicks::Max());
LifecycleUnitVector lifecycle_units{&focused_lifecycle_unit};
const std::vector<TabManagerDelegate::Candidate> candidates =
TabManagerDelegate::GetSortedCandidates(lifecycle_units, arc_processes);
ASSERT_EQ(2U, candidates.size());
// FOCUSED_TAB should be the first one.
EXPECT_EQ(&focused_lifecycle_unit, candidates[0].lifecycle_unit());
ASSERT_TRUE(candidates[1].app());
EXPECT_EQ("focused", candidates[1].app()->process_name());
}
TEST_F(TabManagerDelegateTest, CandidatesSortedWithNewProcessTypes) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({features::kNewProcessTypes},
{features::kTabRanker});
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
kIsFocused, 100);
arc_processes.emplace_back(2, 20, "visible1", arc::mojom::ProcessState::TOP,
kNotFocused, 200);
arc_processes.emplace_back(
3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
arc_processes.emplace_back(4, 40, "cached",
arc::mojom::ProcessState::CACHED_EMPTY,
kNotFocused, 150);
TestLifecycleUnit focused_tab(base::TimeTicks::Max());
TestLifecycleUnit protected_tab(
base::TimeTicks() + base::TimeDelta::FromSeconds(1), 0, false);
TestLifecycleUnit background_tab(base::TimeTicks() +
base::TimeDelta::FromSeconds(5));
LifecycleUnitVector lifecycle_units{&focused_tab, &protected_tab,
&background_tab};
std::vector<TabManagerDelegate::Candidate> candidates;
candidates =
TabManagerDelegate::GetSortedCandidates(lifecycle_units, arc_processes);
ASSERT_EQ(7U, candidates.size());
EXPECT_EQ(&focused_tab, candidates[0].lifecycle_unit());
EXPECT_EQ(ProcessType::FOCUSED_TAB, candidates[0].process_type());
ASSERT_TRUE(candidates[1].app());
EXPECT_EQ("focused", candidates[1].app()->process_name());
EXPECT_EQ(ProcessType::FOCUSED_APP, candidates[1].process_type());
ASSERT_TRUE(candidates[2].app());
EXPECT_EQ("visible1", candidates[2].app()->process_name());
EXPECT_EQ(ProcessType::PROTECTED_BACKGROUND, candidates[2].process_type());
EXPECT_EQ(&protected_tab, candidates[3].lifecycle_unit());
EXPECT_EQ(ProcessType::PROTECTED_BACKGROUND, candidates[3].process_type());
ASSERT_TRUE(candidates[4].app());
EXPECT_EQ("service", candidates[4].app()->process_name());
EXPECT_EQ(ProcessType::BACKGROUND, candidates[4].process_type());
EXPECT_EQ(&background_tab, candidates[5].lifecycle_unit());
EXPECT_EQ(ProcessType::BACKGROUND, candidates[5].process_type());
ASSERT_TRUE(candidates[6].app());
EXPECT_EQ("cached", candidates[6].app()->process_name());
EXPECT_EQ(ProcessType::CACHED_APP, candidates[6].process_type());
}
class MockTabManagerDelegate : public TabManagerDelegate {
public:
MockTabManagerDelegate()
: TabManagerDelegate(nullptr),
always_return_true_from_is_recently_killed_(false) {}
explicit MockTabManagerDelegate(TabManagerDelegate::MemoryStat* mem_stat)
: TabManagerDelegate(nullptr, mem_stat),
always_return_true_from_is_recently_killed_(false) {}
// unit test.
std::vector<int> GetKilledArcProcesses() { return killed_arc_processes_; }
// unit test.
LifecycleUnitVector GetKilledTabs() { return killed_tabs_; }
// unit test.
void Clear() {
killed_arc_processes_.clear();
killed_tabs_.clear();
}
// unit test.
void set_always_return_true_from_is_recently_killed(
bool always_return_true_from_is_recently_killed) {
always_return_true_from_is_recently_killed_ =
always_return_true_from_is_recently_killed;
}
void AddLifecycleUnit(LifecycleUnit* lifecycle_unit) {
lifecycle_units_.push_back(lifecycle_unit);
}
bool IsRecentlyKilledArcProcess(const std::string& process_name,
const base::TimeTicks& now) override {
if (always_return_true_from_is_recently_killed_)
return true;
return TabManagerDelegate::IsRecentlyKilledArcProcess(process_name, now);
}
protected:
bool KillArcProcess(const int nspid) override {
killed_arc_processes_.push_back(nspid);
return true;
}
bool KillTab(LifecycleUnit* lifecycle_unit,
::mojom::LifecycleUnitDiscardReason reason) override {
killed_tabs_.push_back(lifecycle_unit);
return true;
}
LifecycleUnitVector GetLifecycleUnits() override { return lifecycle_units_; }
chromeos::DebugDaemonClient* GetDebugDaemonClient() override {
return &debugd_client_;
}
private:
LifecycleUnitVector lifecycle_units_;
chromeos::FakeDebugDaemonClient debugd_client_;
std::vector<int> killed_arc_processes_;
LifecycleUnitVector killed_tabs_;
bool always_return_true_from_is_recently_killed_;
};
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) {
MockTabManagerDelegate tab_manager_delegate;
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
kIsFocused, 100);
arc_processes.emplace_back(2, 20, "visible1", arc::mojom::ProcessState::TOP,
kNotFocused, 200);
arc_processes.emplace_back(
3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
arc_processes.emplace_back(4, 40, "visible2", arc::mojom::ProcessState::TOP,
kNotFocused, 150);
arc_processes.emplace_back(5, 50, "persistent",
arc::mojom::ProcessState::PERSISTENT, kNotFocused,
600);
arc_processes.emplace_back(6, 60, "persistent_ui",
arc::mojom::ProcessState::PERSISTENT_UI,
kNotFocused, 700);
TestLifecycleUnit tab1(base::TimeTicks() + base::TimeDelta::FromSeconds(3),
11);
tab_manager_delegate.AddLifecycleUnit(&tab1);
TestLifecycleUnit tab2(base::TimeTicks() + base::TimeDelta::FromSeconds(1),
11);
tab_manager_delegate.AddLifecycleUnit(&tab2);
TestLifecycleUnit tab3(base::TimeTicks() + base::TimeDelta::FromSeconds(5),
12);
tab_manager_delegate.AddLifecycleUnit(&tab3);
TestLifecycleUnit tab4(base::TimeTicks() + base::TimeDelta::FromSeconds(4),
12);
tab_manager_delegate.AddLifecycleUnit(&tab4);
TestLifecycleUnit tab5(base::TimeTicks() + base::TimeDelta::FromSeconds(2),
12);
tab_manager_delegate.AddLifecycleUnit(&tab5);
// Sorted order (by GetSortedCandidates):
// app "focused" pid: 10
// app "persistent" pid: 50
// app "persistent_ui" pid: 60
// app "visible1" pid: 20
// app "visible2" pid: 40
// app "service" pid: 30
// tab3 pid: 12
// tab4 pid: 12
// tab1 pid: 11
// tab5 pid: 12
// tab2 pid: 11
tab_manager_delegate.AdjustOomPrioritiesImpl(std::move(arc_processes));
auto& oom_score_map = tab_manager_delegate.oom_score_map_;
// 6 PIDs for apps + 2 PIDs for tabs.
EXPECT_EQ(6U + 2U, oom_score_map.size());
// Non-killable part. AdjustOomPrioritiesImpl() does make a focused app/tab
// kernel-killable, but does not do that for PERSISTENT and PERSISTENT_UI
// apps.
EXPECT_EQ(TabManagerDelegate::kPersistentArcAppOomScore, oom_score_map[50]);
EXPECT_EQ(TabManagerDelegate::kPersistentArcAppOomScore, oom_score_map[60]);
// Higher priority part.
EXPECT_EQ(300, oom_score_map[10]);
EXPECT_EQ(388, oom_score_map[20]);
EXPECT_EQ(475, oom_score_map[40]);
EXPECT_EQ(563, oom_score_map[30]);
// Lower priority part.
EXPECT_EQ(650, oom_score_map[12]);
EXPECT_EQ(720, oom_score_map[11]);
}
TEST_F(TabManagerDelegateTest, IsRecentlyKilledArcProcess) {
constexpr char kProcessName1[] = "org.chromium.arc.test1";
constexpr char kProcessName2[] = "org.chromium.arc.test2";
// Not owned.
MockMemoryStat* memory_stat = new MockMemoryStat();
// Instantiate the mock instance.
MockTabManagerDelegate tab_manager_delegate(memory_stat);
// When the process name is not in the map, IsRecentlyKilledArcProcess should
// return false.
const base::TimeTicks now = NowTicks();
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName1, now));
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName2, now));
// Update the map to tell the manager that the process was killed very
// recently.
tab_manager_delegate.recently_killed_arc_processes_[kProcessName1] = now;
EXPECT_TRUE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName1, now));
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName2, now));
tab_manager_delegate.recently_killed_arc_processes_[kProcessName1] =
now - base::TimeDelta::FromMicroseconds(1);
EXPECT_TRUE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName1, now));
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName2, now));
tab_manager_delegate.recently_killed_arc_processes_[kProcessName1] =
now - TabManagerDelegate::GetArcRespawnKillDelay();
EXPECT_TRUE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName1, now));
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName2, now));
// Update the map to tell the manager that the process was killed
// (GetArcRespawnKillDelay() + 1) seconds ago. In this case,
// IsRecentlyKilledArcProcess(kProcessName1) should return false.
tab_manager_delegate.recently_killed_arc_processes_[kProcessName1] =
now - TabManagerDelegate::GetArcRespawnKillDelay() -
base::TimeDelta::FromSeconds(1);
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName1, now));
EXPECT_FALSE(
tab_manager_delegate.IsRecentlyKilledArcProcess(kProcessName2, now));
}
TEST_F(TabManagerDelegateTest, DoNotKillRecentlyKilledArcProcesses) {
// Not owned.
MockMemoryStat* memory_stat = new MockMemoryStat();
// Instantiate the mock instance.
MockTabManagerDelegate tab_manager_delegate(memory_stat);
tab_manager_delegate.set_always_return_true_from_is_recently_killed(true);
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(
1, 10, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
memory_stat->SetTargetMemoryToFreeKB(250000);
memory_stat->SetProcessPss(30, 10000);
tab_manager_delegate.LowMemoryKillImpl(
base::TimeTicks::Now(), ::mojom::LifecycleUnitDiscardReason::URGENT,
std::move(arc_processes));
auto killed_arc_processes = tab_manager_delegate.GetKilledArcProcesses();
EXPECT_EQ(0U, killed_arc_processes.size());
}
TEST_F(TabManagerDelegateTest, KillMultipleProcesses) {
// Not owned.
MockMemoryStat* memory_stat = new MockMemoryStat();
// Instantiate the mock instance.
MockTabManagerDelegate tab_manager_delegate(memory_stat);
std::vector<arc::ArcProcess> arc_processes;
arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
kIsFocused, 100);
arc_processes.emplace_back(2, 20, "visible1", arc::mojom::ProcessState::TOP,
kNotFocused, 200);
arc_processes.emplace_back(
3, 30, "service", arc::mojom::ProcessState::SERVICE, kNotFocused, 500);
arc_processes.emplace_back(4, 40, "visible2",
arc::mojom::ProcessState::IMPORTANT_FOREGROUND,
kNotFocused, 150);
arc_processes.emplace_back(5, 50, "not-visible",
arc::mojom::ProcessState::IMPORTANT_BACKGROUND,
kNotFocused, 300);
arc_processes.emplace_back(6, 60, "persistent",
arc::mojom::ProcessState::PERSISTENT, kNotFocused,
400);
TestLifecycleUnit tab1(base::TimeTicks() + base::TimeDelta::FromSeconds(3),
11);
tab_manager_delegate.AddLifecycleUnit(&tab1);
TestLifecycleUnit tab2(base::TimeTicks() + base::TimeDelta::FromSeconds(1),
11);
tab_manager_delegate.AddLifecycleUnit(&tab2);
TestLifecycleUnit tab3(base::TimeTicks() + base::TimeDelta::FromSeconds(5),
12);
tab_manager_delegate.AddLifecycleUnit(&tab3);
TestLifecycleUnit tab4(base::TimeTicks() + base::TimeDelta::FromSeconds(4),
12);
tab_manager_delegate.AddLifecycleUnit(&tab4);
TestLifecycleUnit tab5(base::TimeTicks() + base::TimeDelta::FromSeconds(2),
12);
tab_manager_delegate.AddLifecycleUnit(&tab5);
// Sorted order (by GetSortedCandidates):
// app "focused" pid: 10 nspid 1
// app "persistent" pid: 60 nspid 6
// app "visible1" pid: 20 nspid 2
// app "visible2" pid: 40 nspid 4
// app "not-visible" pid: 50 nspid 5
// app "service" pid: 30 nspid 3
// tab3 pid: 12 id 3
// tab4 pid: 12 id 4
// tab1 pid: 11 id 1
// tab5 pid: 12 id 5
// tab2 pid: 11 id 2
memory_stat->SetTargetMemoryToFreeKB(250000);
// Entities to be killed.
memory_stat->SetProcessPss(11, 50000);
memory_stat->SetProcessPss(12, 30000);
memory_stat->SetProcessPss(30, 10000);
memory_stat->SetProcessPss(50, 60000);
// Should not be used.
memory_stat->SetProcessPss(60, 500000);
memory_stat->SetProcessPss(40, 50000);
memory_stat->SetProcessPss(20, 30000);
memory_stat->SetProcessPss(10, 100000);
tab_manager_delegate.LowMemoryKillImpl(
base::TimeTicks::Now(), ::mojom::LifecycleUnitDiscardReason::PROACTIVE,
std::move(arc_processes));
auto killed_arc_processes = tab_manager_delegate.GetKilledArcProcesses();
auto killed_tabs = tab_manager_delegate.GetKilledTabs();
// Killed apps and their nspid.
ASSERT_EQ(2U, killed_arc_processes.size());
EXPECT_EQ(3, killed_arc_processes[0]);
EXPECT_EQ(5, killed_arc_processes[1]);
// Killed tabs and their content id.
// Note that process with pid 11 is counted twice and pid 12 is counted 3
// times. But so far I don't have a good way to estimate the memory freed
// if multiple tabs share one process.
ASSERT_EQ(5U, killed_tabs.size());
EXPECT_EQ(&tab2, killed_tabs[0]);
EXPECT_EQ(&tab5, killed_tabs[1]);
EXPECT_EQ(&tab1, killed_tabs[2]);
EXPECT_EQ(&tab4, killed_tabs[3]);
EXPECT_EQ(&tab3, killed_tabs[4]);
// Check that killed apps are in the map.
const TabManagerDelegate::KilledArcProcessesMap& processes_map =
tab_manager_delegate.recently_killed_arc_processes_;
EXPECT_EQ(2U, processes_map.size());
EXPECT_EQ(1U, processes_map.count("service"));
EXPECT_EQ(1U, processes_map.count("not-visible"));
}
} // namespace resource_coordinator