CrOS: Add Discard Tab command link to about:discards

Work in process on discarding a tab (for low memory conditions on CrOS).  Also clean up the strings we use to describe system memory, and eliminate memory as an input for "interestingness" of tabs, since the OOM killer already includes memory as an input.

BUG=none
TEST=Open "about:discards".  Click the "Discard Tab Now" link - the tab at the bottom of the list should close.

Review URL: http://codereview.chromium.org/7983042

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@102397 0039d316-1c4b-4281-b951-d872f2087c98
parent e7ea0c62
...@@ -676,8 +676,13 @@ std::string AboutCryptohome(const std::string& query) { ...@@ -676,8 +676,13 @@ std::string AboutCryptohome(const std::string& query) {
return GetCryptohomeHtmlInfo(refresh); return GetCryptohomeHtmlInfo(refresh);
} }
std::string AboutDiscards() { std::string AboutDiscards(const std::string& path) {
std::string output; std::string output;
const std::string kRunCommand("run");
if (path == kRunCommand) {
output.append(WrapWithTag("p", "Discarding a tab..."));
g_browser_process->oom_priority_manager()->DiscardTab();
}
AppendHeader(&output, 0, "About discards"); AppendHeader(&output, 0, "About discards");
AppendBody(&output); AppendBody(&output);
output.append("<h3>About discards</h3>"); output.append("<h3>About discards</h3>");
...@@ -698,28 +703,30 @@ std::string AboutDiscards() { ...@@ -698,28 +703,30 @@ std::string AboutDiscards() {
} else { } else {
output.append("<p>None found. Wait 10 seconds, then refresh.</p>"); output.append("<p>None found. Wait 10 seconds, then refresh.</p>");
} }
output.append(
"<a href='chrome://discards/" + kRunCommand + "'>Discard tab now</a>");
base::SystemMemoryInfoKB meminfo; base::SystemMemoryInfoKB meminfo;
base::GetSystemMemoryInfo(&meminfo); base::GetSystemMemoryInfo(&meminfo);
output.append("<h3>System memory information in MB</h3>"); output.append("<h3>System memory information in MB</h3>");
output.append("<table>"); output.append("<table>");
output.append(AddStringRow( output.append(AddStringRow(
"Total Memory", base::IntToString(meminfo.total / 1024))); "Total", base::IntToString(meminfo.total / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Free Memory", base::IntToString(meminfo.free / 1024))); "Free", base::IntToString(meminfo.free / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Buffered Memory", base::IntToString(meminfo.buffers / 1024))); "Buffered", base::IntToString(meminfo.buffers / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Cached Memory", base::IntToString(meminfo.cached / 1024))); "Cached", base::IntToString(meminfo.cached / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Committed Memory", base::IntToString( "Committed", base::IntToString(
(meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached) / 1024))); (meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached) / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Active Anon Memory", base::IntToString(meminfo.active_anon / 1024))); "Active Anon", base::IntToString(meminfo.active_anon / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Inactive Anon Memory", base::IntToString(meminfo.inactive_anon / 1024))); "Inactive Anon", base::IntToString(meminfo.inactive_anon / 1024)));
output.append(AddStringRow( output.append(AddStringRow(
"Shared Memory", base::IntToString(meminfo.shmem / 1024))); "Shared", base::IntToString(meminfo.shmem / 1024)));
output.append("</table>"); output.append("</table>");
AppendFooter(&output); AppendFooter(&output);
...@@ -1406,7 +1413,7 @@ void AboutSource::StartDataRequest(const std::string& path, ...@@ -1406,7 +1413,7 @@ void AboutSource::StartDataRequest(const std::string& path,
} else if (host == chrome::kChromeUICryptohomeHost) { } else if (host == chrome::kChromeUICryptohomeHost) {
response = AboutCryptohome(path); response = AboutCryptohome(path);
} else if (host == chrome::kChromeUIDiscardsHost) { } else if (host == chrome::kChromeUIDiscardsHost) {
response = AboutDiscards(); response = AboutDiscards(path);
#endif #endif
} else if (host == chrome::kChromeUIDNSHost) { } else if (host == chrome::kChromeUIDNSHost) {
AboutDnsHandler::Start(this, request_id); AboutDnsHandler::Start(this, request_id);
......
...@@ -9,10 +9,12 @@ ...@@ -9,10 +9,12 @@
#include "base/process.h" #include "base/process.h"
#include "base/process_util.h" #include "base/process_util.h"
#include "base/string_number_conversions.h"
#include "base/string16.h" #include "base/string16.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "base/timer.h" #include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list.h"
...@@ -23,6 +25,7 @@ ...@@ -23,6 +25,7 @@
#include "content/browser/renderer_host/render_widget_host.h" #include "content/browser/renderer_host/render_widget_host.h"
#include "content/browser/tab_contents/tab_contents.h" #include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/zygote_host_linux.h" #include "content/browser/zygote_host_linux.h"
#include "content/common/content_notification_types.h"
#include "content/common/notification_service.h" #include "content/common/notification_service.h"
#if !defined(OS_CHROMEOS) #if !defined(OS_CHROMEOS)
...@@ -34,33 +37,38 @@ using base::TimeTicks; ...@@ -34,33 +37,38 @@ using base::TimeTicks;
using base::ProcessHandle; using base::ProcessHandle;
using base::ProcessMetrics; using base::ProcessMetrics;
namespace {
// Returns a unique ID for a TabContents. Do not cast back to a pointer, as
// the TabContents could be deleted if the user closed the tab.
int64 IdFromTabContents(TabContents* tab_contents) {
return reinterpret_cast<int64>(tab_contents);
}
} // namespace
namespace browser { namespace browser {
// The default interval in seconds after which to adjust the oom_score_adj // The default interval in seconds after which to adjust the oom_score_adj
// value. // value.
#define ADJUSTMENT_INTERVAL_SECONDS 10 #define ADJUSTMENT_INTERVAL_SECONDS 10
// The default interval in minutes for considering activation times
// "equal".
#define BUCKET_INTERVAL_MINUTES 10
// The default interval in milliseconds to wait before setting the score of // The default interval in milliseconds to wait before setting the score of
// currently focused tab. // currently focused tab.
#define FOCUSED_TAB_SCORE_ADJUST_INTERVAL_MS 500 #define FOCUSED_TAB_SCORE_ADJUST_INTERVAL_MS 500
OomPriorityManager::RendererStats::RendererStats() OomPriorityManager::TabStats::TabStats()
: is_pinned(false), : is_pinned(false),
is_selected(false), is_selected(false),
memory_used(0), renderer_handle(0),
renderer_handle(0) { tab_contents_id(0) {
} }
OomPriorityManager::RendererStats::~RendererStats() { OomPriorityManager::TabStats::~TabStats() {
} }
OomPriorityManager::OomPriorityManager() OomPriorityManager::OomPriorityManager()
: focused_tab_pid_(0) { : focused_tab_pid_(0) {
renderer_stats_.reserve(32); // 99% of users have < 30 tabs open
registrar_.Add(this, registrar_.Add(this,
content::NOTIFICATION_RENDERER_PROCESS_CLOSED, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
NotificationService::AllSources()); NotificationService::AllSources());
...@@ -90,25 +98,48 @@ void OomPriorityManager::Stop() { ...@@ -90,25 +98,48 @@ void OomPriorityManager::Stop() {
} }
std::vector<string16> OomPriorityManager::GetTabTitles() { std::vector<string16> OomPriorityManager::GetTabTitles() {
base::AutoLock renderer_stats_autolock(renderer_stats_lock_); TabStatsList stats = GetTabStatsOnUIThread();
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
std::vector<string16> titles; std::vector<string16> titles;
titles.reserve(renderer_stats_.size()); titles.reserve(stats.size());
StatsList::iterator it = renderer_stats_.begin(); TabStatsList::iterator it = stats.begin();
for ( ; it != renderer_stats_.end(); ++it) { for ( ; it != stats.end(); ++it) {
titles.push_back(it->title); string16 str = it->title;
str += ASCIIToUTF16(" (");
int score = pid_to_oom_score_[it->renderer_handle];
str += base::IntToString16(score);
str += ASCIIToUTF16(")");
titles.push_back(str);
} }
return titles; return titles;
} }
void OomPriorityManager::DiscardTab() {
TabStatsList stats = GetTabStatsOnUIThread();
if (stats.empty())
return;
std::sort(stats.begin(), stats.end(), CompareTabStats);
TabStatsList::const_reverse_iterator rit = stats.rbegin();
int64 least_important_tab_id = rit->tab_contents_id;
for (BrowserList::const_iterator browser_iterator = BrowserList::begin();
browser_iterator != BrowserList::end(); ++browser_iterator) {
Browser* browser = *browser_iterator;
TabStripModel* model = browser->tabstrip_model();
for (int idx = 0; idx < model->count(); idx++) {
TabContents* tab_contents = model->GetTabContentsAt(idx)->tab_contents();
int64 tab_contents_id = IdFromTabContents(tab_contents);
if (tab_contents_id == least_important_tab_id) {
model->CloseTabContentsAt(idx,
TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
}
}
}
}
// Returns true if |first| is considered less desirable to be killed // Returns true if |first| is considered less desirable to be killed
// than |second|. // than |second|.
bool OomPriorityManager::CompareRendererStats(RendererStats first, bool OomPriorityManager::CompareTabStats(TabStats first,
RendererStats second) { TabStats second) {
// The size of the slop in comparing activation times. [This is
// allocated here to avoid static initialization at startup time.]
static const int64 kTimeBucketInterval =
TimeDelta::FromMinutes(BUCKET_INTERVAL_MINUTES).ToInternalValue();
// Being currently selected is most important. // Being currently selected is most important.
if (first.is_selected != second.is_selected) if (first.is_selected != second.is_selected)
return first.is_selected == true; return first.is_selected == true;
...@@ -117,18 +148,12 @@ bool OomPriorityManager::CompareRendererStats(RendererStats first, ...@@ -117,18 +148,12 @@ bool OomPriorityManager::CompareRendererStats(RendererStats first,
if (first.is_pinned != second.is_pinned) if (first.is_pinned != second.is_pinned)
return first.is_pinned == true; return first.is_pinned == true;
// We want to be a little "fuzzy" when we compare these, because // Being more recently selected is more important.
// it's not really possible for the times to be identical, but if return first.last_selected > second.last_selected;
// the user selected two tabs at about the same time, we still want
// to take the one that uses more memory.
if (abs((first.last_selected - second.last_selected).ToInternalValue()) <
kTimeBucketInterval)
return first.last_selected > second.last_selected;
return first.memory_used < second.memory_used;
} }
void OomPriorityManager::AdjustFocusedTabScore() { void OomPriorityManager::AdjustFocusedTabScoreOnFileThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
ZygoteHost::GetInstance()->AdjustRendererOOMScore( ZygoteHost::GetInstance()->AdjustRendererOOMScore(
focused_tab_pid_, chrome::kLowestRendererOomScore); focused_tab_pid_, chrome::kLowestRendererOomScore);
...@@ -138,11 +163,12 @@ void OomPriorityManager::AdjustFocusedTabScore() { ...@@ -138,11 +163,12 @@ void OomPriorityManager::AdjustFocusedTabScore() {
void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() { void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() {
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE, BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(this, &OomPriorityManager::AdjustFocusedTabScore)); NewRunnableMethod(
this, &OomPriorityManager::AdjustFocusedTabScoreOnFileThread));
} }
void OomPriorityManager::Observe(int type, const NotificationSource& source, void OomPriorityManager::Observe(int type, const NotificationSource& source,
const NotificationDetails& details) { const NotificationDetails& details) {
base::ProcessHandle handle = 0; base::ProcessHandle handle = 0;
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
switch (type) { switch (type) {
...@@ -193,71 +219,49 @@ void OomPriorityManager::Observe(int type, const NotificationSource& source, ...@@ -193,71 +219,49 @@ void OomPriorityManager::Observe(int type, const NotificationSource& source,
// 1) whether or not a tab is pinned // 1) whether or not a tab is pinned
// 2) last time a tab was selected // 2) last time a tab was selected
// 3) is the tab currently selected // 3) is the tab currently selected
//
// We also need to collect:
// 4) size in memory of a tab
// But we do that in DoAdjustOomPriorities on the FILE thread so that
// we avoid jank, because it accesses /proc.
void OomPriorityManager::AdjustOomPriorities() { void OomPriorityManager::AdjustOomPriorities() {
if (BrowserList::size() == 0) if (BrowserList::size() == 0)
return; return;
TabStatsList stats_list = GetTabStatsOnUIThread();
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(this,
&OomPriorityManager::AdjustOomPrioritiesOnFileThread,
stats_list));
}
{ OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
base::AutoLock renderer_stats_autolock(renderer_stats_lock_); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
renderer_stats_.clear(); TabStatsList stats_list;
for (BrowserList::const_iterator browser_iterator = BrowserList::begin(); stats_list.reserve(32); // 99% of users have < 30 tabs open
browser_iterator != BrowserList::end(); ++browser_iterator) { for (BrowserList::const_iterator browser_iterator = BrowserList::begin();
Browser* browser = *browser_iterator; browser_iterator != BrowserList::end(); ++browser_iterator) {
const TabStripModel* model = browser->tabstrip_model(); Browser* browser = *browser_iterator;
for (int i = 0; i < model->count(); i++) { const TabStripModel* model = browser->tabstrip_model();
TabContents* contents = model->GetTabContentsAt(i)->tab_contents(); for (int i = 0; i < model->count(); i++) {
if (!contents->is_crashed()) { TabContents* contents = model->GetTabContentsAt(i)->tab_contents();
RendererStats stats; if (!contents->is_crashed()) {
stats.last_selected = contents->last_selected_time(); TabStats stats;
stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); stats.last_selected = contents->last_selected_time();
stats.is_pinned = model->IsTabPinned(i); stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
stats.memory_used = 0; // Calculated in DoAdjustOomPriorities. stats.is_pinned = model->IsTabPinned(i);
stats.is_selected = model->IsTabSelected(i); stats.is_selected = model->IsTabSelected(i);
stats.title = contents->GetTitle(); stats.title = contents->GetTitle();
renderer_stats_.push_back(stats); stats.tab_contents_id = IdFromTabContents(contents);
} stats_list.push_back(stats);
} }
} }
} }
// Sort the data we collected so that least desirable to be
BrowserThread::PostTask( // killed is first, most desirable is last.
BrowserThread::FILE, FROM_HERE, std::sort(stats_list.begin(), stats_list.end(), CompareTabStats);
NewRunnableMethod(this, &OomPriorityManager::DoAdjustOomPriorities)); return stats_list;
} }
void OomPriorityManager::DoAdjustOomPriorities() { void OomPriorityManager::AdjustOomPrioritiesOnFileThread(
TabStatsList stats_list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
base::AutoLock renderer_stats_autolock(renderer_stats_lock_);
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_); base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
for (StatsList::iterator stats_iter = renderer_stats_.begin();
stats_iter != renderer_stats_.end(); ++stats_iter) {
scoped_ptr<ProcessMetrics> metrics(ProcessMetrics::CreateProcessMetrics(
stats_iter->renderer_handle));
base::WorkingSetKBytes working_set_kbytes;
if (metrics->GetWorkingSetKBytes(&working_set_kbytes)) {
// We use the proportional set size (PSS) to calculate memory
// usage "badness" on Linux.
stats_iter->memory_used = working_set_kbytes.shared * 1024;
} else {
// and if for some reason we can't get PSS, we revert to using
// resident set size (RSS). This will be zero if the process
// has already gone away, but we can live with that, since the
// process is gone anyhow.
stats_iter->memory_used = metrics->GetWorkingSetSize();
}
}
// Now we sort the data we collected so that least desirable to be
// killed is first, most desirable is last.
std::sort(renderer_stats_.begin(),
renderer_stats_.end(),
OomPriorityManager::CompareRendererStats);
// Now we assign priorities based on the sorted list. We're // Now we assign priorities based on the sorted list. We're
// assigning priorities in the range of kLowestRendererOomScore to // assigning priorities in the range of kLowestRendererOomScore to
...@@ -277,13 +281,13 @@ void OomPriorityManager::DoAdjustOomPriorities() { ...@@ -277,13 +281,13 @@ void OomPriorityManager::DoAdjustOomPriorities() {
const int kPriorityRange = chrome::kHighestRendererOomScore - const int kPriorityRange = chrome::kHighestRendererOomScore -
chrome::kLowestRendererOomScore; chrome::kLowestRendererOomScore;
float priority_increment = float priority_increment =
static_cast<float>(kPriorityRange) / renderer_stats_.size(); static_cast<float>(kPriorityRange) / stats_list.size();
float priority = chrome::kLowestRendererOomScore; float priority = chrome::kLowestRendererOomScore;
std::set<base::ProcessHandle> already_seen; std::set<base::ProcessHandle> already_seen;
int score = 0; int score = 0;
ProcessScoreMap::iterator it; ProcessScoreMap::iterator it;
for (StatsList::iterator iterator = renderer_stats_.begin(); for (TabStatsList::iterator iterator = stats_list.begin();
iterator != renderer_stats_.end(); ++iterator) { iterator != stats_list.end(); ++iterator) {
if (already_seen.find(iterator->renderer_handle) == already_seen.end()) { if (already_seen.find(iterator->renderer_handle) == already_seen.end()) {
already_seen.insert(iterator->renderer_handle); already_seen.insert(iterator->renderer_handle);
// If a process has the same score as the newly calculated value, // If a process has the same score as the newly calculated value,
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
#include "content/common/notification_observer.h" #include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h" #include "content/common/notification_registrar.h"
class TabContents;
namespace browser { namespace browser {
// The OomPriorityManager periodically checks (see // The OomPriorityManager periodically checks (see
...@@ -26,11 +28,8 @@ namespace browser { ...@@ -26,11 +28,8 @@ namespace browser {
// algorithm embedded here for priority in being killed upon OOM // algorithm embedded here for priority in being killed upon OOM
// conditions. // conditions.
// //
// The algorithm used favors killing tabs that are not pinned, have // The algorithm used favors killing tabs that are not selected, not pinned,
// been idle for longest, and take up the most memory, in that order // and have been idle for longest, in that order of priority.
// of priority. We round the idle times to the nearest few minutes
// (see BUCKET_INTERVAL_MINUTES in the source) so that we can bucket
// them, as no two tabs will have exactly the same idle time.
class OomPriorityManager : public NotificationObserver { class OomPriorityManager : public NotificationObserver {
public: public:
OomPriorityManager(); OomPriorityManager();
...@@ -43,35 +42,38 @@ class OomPriorityManager : public NotificationObserver { ...@@ -43,35 +42,38 @@ class OomPriorityManager : public NotificationObserver {
// to least interesting (OK to kill). // to least interesting (OK to kill).
std::vector<string16> GetTabTitles(); std::vector<string16> GetTabTitles();
// Discards a tab to free the memory occupied by its renderer.
// Tab still exists in the tab-strip; clicking on it will reload it.
void DiscardTab();
private: private:
struct RendererStats { struct TabStats {
RendererStats(); TabStats();
~RendererStats(); ~TabStats();
bool is_pinned; bool is_pinned;
bool is_selected; bool is_selected;
base::TimeTicks last_selected; base::TimeTicks last_selected;
size_t memory_used;
base::ProcessHandle renderer_handle; base::ProcessHandle renderer_handle;
string16 title; string16 title;
int64 tab_contents_id; // unique ID per TabContents
}; };
typedef std::vector<RendererStats> StatsList; typedef std::vector<TabStats> TabStatsList;
typedef base::hash_map<base::ProcessHandle, int> ProcessScoreMap;
// Posts DoAdjustOomPriorities task to the file thread. Called when TabStatsList GetTabStatsOnUIThread();
// the timer fires.
// Called when the timer fires, sets oom_adjust_score for all renderers.
void AdjustOomPriorities(); void AdjustOomPriorities();
// Called by AdjustOomPriorities.
void AdjustOomPrioritiesOnFileThread(TabStatsList stats_list);
// Posts AdjustFocusedTabScore task to the file thread. // Posts AdjustFocusedTabScore task to the file thread.
void OnFocusTabScoreAdjustmentTimeout(); void OnFocusTabScoreAdjustmentTimeout();
// Called by AdjustOomPriorities. Runs on the file thread. // Sets the score of the focused tab to the least value.
void DoAdjustOomPriorities(); void AdjustFocusedTabScoreOnFileThread();
// Called when a tab comes into focus. Runs on the file thread. static bool CompareTabStats(TabStats first, TabStats second);
// Sets the score of only the currently focused tab to the least value.
void AdjustFocusedTabScore();
static bool CompareRendererStats(RendererStats first, RendererStats second);
virtual void Observe(int type, virtual void Observe(int type,
const NotificationSource& source, const NotificationSource& source,
...@@ -79,14 +81,13 @@ class OomPriorityManager : public NotificationObserver { ...@@ -79,14 +81,13 @@ class OomPriorityManager : public NotificationObserver {
base::RepeatingTimer<OomPriorityManager> timer_; base::RepeatingTimer<OomPriorityManager> timer_;
base::OneShotTimer<OomPriorityManager> focus_tab_score_adjust_timer_; base::OneShotTimer<OomPriorityManager> focus_tab_score_adjust_timer_;
// renderer_stats_ is used on both UI and file threads. NotificationRegistrar registrar_;
base::Lock renderer_stats_lock_;
StatsList renderer_stats_;
// This lock is for pid_to_oom_score_ and focus_tab_pid_. // This lock is for pid_to_oom_score_ and focus_tab_pid_.
base::Lock pid_to_oom_score_lock_; base::Lock pid_to_oom_score_lock_;
// map maintaining the process - oom_score mapping. // map maintaining the process - oom_score mapping.
typedef base::hash_map<base::ProcessHandle, int> ProcessScoreMap;
ProcessScoreMap pid_to_oom_score_; ProcessScoreMap pid_to_oom_score_;
NotificationRegistrar registrar_;
base::ProcessHandle focused_tab_pid_; base::ProcessHandle focused_tab_pid_;
DISALLOW_COPY_AND_ASSIGN(OomPriorityManager); DISALLOW_COPY_AND_ASSIGN(OomPriorityManager);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment