This source file includes following definitions.
- IdFromWebContents
- RecordLinearHistogram
- OnDetailsAvailable
- tab_contents_id
- recent_tab_discard_
- Start
- Stop
- GetTabTitles
- DiscardTab
- LogMemoryAndDiscardTab
- IsReloadableUI
- DiscardTabById
- RecordDiscardStatistics
- RecordRecentTabDiscard
- PurgeBrowserMemory
- GetTabCount
- CompareTabStats
- AdjustFocusedTabScoreOnFileThread
- OnFocusTabScoreAdjustmentTimeout
- Observe
- AdjustOomPriorities
- GetTabStatsOnUIThread
- GetProcessHandles
- AdjustOomPrioritiesOnFileThread
#include "chrome/browser/chromeos/memory/oom_priority_manager.h"
#include <algorithm>
#include <set>
#include <vector>
#include "ash/multi_profile_uma.h"
#include "ash/session_state_delegate.h"
#include "ash/shell.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/process/process.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chromeos/memory/low_memory_observer.h"
#include "chrome/browser/memory_details.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_utils.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/url_constants.h"
#include "chromeos/chromeos_switches.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/zygote_host_linux.h"
#include "ui/base/text/bytes_formatting.h"
using base::TimeDelta;
using base::TimeTicks;
using content::BrowserThread;
using content::WebContents;
namespace chromeos {
namespace {
#define HISTOGRAM_MEGABYTES(name, sample) \
UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50)
const int kAdjustmentIntervalSeconds = 10;
const int kRecentTabDiscardIntervalSeconds = 60;
const int kSuspendThresholdSeconds = kAdjustmentIntervalSeconds * 4;
const int kFocusedTabScoreAdjustIntervalMs = 500;
int64 IdFromWebContents(WebContents* web_contents) {
return reinterpret_cast<int64>(web_contents);
}
void RecordLinearHistogram(const std::string& name,
int sample,
int maximum,
size_t bucket_count) {
base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
name,
1,
maximum + 1,
bucket_count + 2,
base::Histogram::kUmaTargetedHistogramFlag);
counter->Add(sample);
}
}
class OomMemoryDetails : public MemoryDetails {
public:
OomMemoryDetails();
virtual void OnDetailsAvailable() OVERRIDE;
private:
virtual ~OomMemoryDetails() {}
TimeTicks start_time_;
DISALLOW_COPY_AND_ASSIGN(OomMemoryDetails);
};
OomMemoryDetails::OomMemoryDetails() {
AddRef();
start_time_ = TimeTicks::Now();
}
void OomMemoryDetails::OnDetailsAvailable() {
TimeDelta delta = TimeTicks::Now() - start_time_;
std::string log_string = ToLogString();
base::SystemMemoryInfoKB memory;
if (base::GetSystemMemoryInfo(&memory) && memory.gem_size != -1) {
log_string += "Graphics ";
log_string += base::UTF16ToASCII(ui::FormatBytes(memory.gem_size));
}
LOG(WARNING) << "OOM details (" << delta.InMilliseconds() << " ms):\n"
<< log_string;
if (g_browser_process &&
g_browser_process->platform_part()->oom_priority_manager()) {
OomPriorityManager* manager =
g_browser_process->platform_part()->oom_priority_manager();
manager->PurgeBrowserMemory();
manager->DiscardTab();
}
Release();
}
OomPriorityManager::TabStats::TabStats()
: is_app(false),
is_reloadable_ui(false),
is_playing_audio(false),
is_pinned(false),
is_selected(false),
is_discarded(false),
renderer_handle(0),
tab_contents_id(0) {
}
OomPriorityManager::TabStats::~TabStats() {
}
OomPriorityManager::OomPriorityManager()
: focused_tab_pid_(0),
low_memory_observer_(new LowMemoryObserver),
discard_count_(0),
recent_tab_discard_(false) {
registrar_.Add(this,
content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this,
content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this,
content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
content::NotificationService::AllBrowserContextsAndSources());
}
OomPriorityManager::~OomPriorityManager() {
Stop();
}
void OomPriorityManager::Start() {
if (!timer_.IsRunning()) {
timer_.Start(FROM_HERE,
TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
this,
&OomPriorityManager::AdjustOomPriorities);
}
if (!recent_tab_discard_timer_.IsRunning()) {
recent_tab_discard_timer_.Start(
FROM_HERE,
TimeDelta::FromSeconds(kRecentTabDiscardIntervalSeconds),
this,
&OomPriorityManager::RecordRecentTabDiscard);
}
if (low_memory_observer_.get())
low_memory_observer_->Start();
start_time_ = TimeTicks::Now();
}
void OomPriorityManager::Stop() {
timer_.Stop();
recent_tab_discard_timer_.Stop();
if (low_memory_observer_.get())
low_memory_observer_->Stop();
}
std::vector<base::string16> OomPriorityManager::GetTabTitles() {
TabStatsList stats = GetTabStatsOnUIThread();
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
std::vector<base::string16> titles;
titles.reserve(stats.size());
TabStatsList::iterator it = stats.begin();
for ( ; it != stats.end(); ++it) {
base::string16 str;
str.reserve(4096);
int score = pid_to_oom_score_[it->renderer_handle];
str += base::IntToString16(score);
str += base::ASCIIToUTF16(" - ");
str += it->title;
str += base::ASCIIToUTF16(it->is_app ? " app" : "");
str += base::ASCIIToUTF16(it->is_reloadable_ui ? " reloadable_ui" : "");
str += base::ASCIIToUTF16(it->is_playing_audio ? " playing_audio" : "");
str += base::ASCIIToUTF16(it->is_pinned ? " pinned" : "");
str += base::ASCIIToUTF16(it->is_discarded ? " discarded" : "");
titles.push_back(str);
}
return titles;
}
bool OomPriorityManager::DiscardTab() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TabStatsList stats = GetTabStatsOnUIThread();
if (stats.empty())
return false;
for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin();
stats_rit != stats.rend();
++stats_rit) {
int64 least_important_tab_id = stats_rit->tab_contents_id;
if (DiscardTabById(least_important_tab_id))
return true;
}
return false;
}
void OomPriorityManager::LogMemoryAndDiscardTab() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
OomMemoryDetails* details = new OomMemoryDetails();
details->StartFetch(MemoryDetails::SKIP_USER_METRICS);
}
bool OomPriorityManager::IsReloadableUI(const GURL& url) {
const char* kReloadableUrlPrefixes[] = {
chrome::kChromeUIDownloadsURL,
chrome::kChromeUIHistoryURL,
chrome::kChromeUINewTabURL,
chrome::kChromeUISettingsURL,
};
for (size_t i = 0; i < arraysize(kReloadableUrlPrefixes); ++i) {
if (!strncmp(url.spec().c_str(),
kReloadableUrlPrefixes[i],
strlen(kReloadableUrlPrefixes[i])))
return true;
}
return false;
}
bool OomPriorityManager::DiscardTabById(int64 target_web_contents_id) {
for (chrome::BrowserIterator it; !it.done(); it.Next()) {
Browser* browser = *it;
TabStripModel* model = browser->tab_strip_model();
for (int idx = 0; idx < model->count(); idx++) {
if (model->IsTabDiscarded(idx) || (model->active_index() == idx))
continue;
WebContents* web_contents = model->GetWebContentsAt(idx);
int64 web_contents_id = IdFromWebContents(web_contents);
if (web_contents_id == target_web_contents_id) {
LOG(WARNING) << "Discarding tab " << idx
<< " id " << target_web_contents_id;
RecordDiscardStatistics();
model->DiscardWebContentsAt(idx);
recent_tab_discard_ = true;
return true;
}
}
}
return false;
}
void OomPriorityManager::RecordDiscardStatistics() {
discard_count_++;
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Tabs.Discard.DiscardCount", discard_count_, 1, 1000, 50);
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Tabs.Discard.TabCount", GetTabCount(), 1, 100, 50);
ash::MultiProfileUMA::RecordDiscardedTab(
ash::Shell::GetInstance()->session_state_delegate()->
NumberOfLoggedInUsers());
if (last_discard_time_.is_null()) {
TimeDelta interval = TimeTicks::Now() - start_time_;
int interval_seconds = static_cast<int>(interval.InSeconds());
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Tabs.Discard.InitialTime2", interval_seconds, 1, 100000, 50);
} else {
TimeDelta interval = TimeTicks::Now() - last_discard_time_;
int interval_ms = static_cast<int>(interval.InMilliseconds());
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Tabs.Discard.IntervalTime2", interval_ms, 100, 100000 * 1000, 50);
}
base::SystemMemoryInfoKB memory;
if (base::GetSystemMemoryInfo(&memory)) {
int mem_anonymous_mb = (memory.active_anon + memory.inactive_anon) / 1024;
HISTOGRAM_MEGABYTES("Tabs.Discard.MemAnonymousMB", mem_anonymous_mb);
int mem_graphics_gem_mb = 0;
if (memory.gem_size != -1)
mem_graphics_gem_mb = memory.gem_size / 1024 / 1024;
RecordLinearHistogram(
"Tabs.Discard.MemGraphicsMB", mem_graphics_gem_mb, 2500, 50);
int mem_shmem_mb = memory.shmem / 1024;
RecordLinearHistogram("Tabs.Discard.MemShmemMB", mem_shmem_mb, 2500, 50);
int mem_allocated_mb = mem_anonymous_mb;
#if defined(ARCH_CPU_ARM_FAMILY)
mem_allocated_mb += mem_graphics_gem_mb;
#endif
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Tabs.Discard.MemAllocatedMB", mem_allocated_mb, 256, 32768, 50);
int mem_available_mb =
(memory.active_file + memory.inactive_file + memory.free) / 1024;
HISTOGRAM_MEGABYTES("Tabs.Discard.MemAvailableMB", mem_available_mb);
}
last_discard_time_ = TimeTicks::Now();
}
void OomPriorityManager::RecordRecentTabDiscard() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UMA_HISTOGRAM_BOOLEAN("Tabs.Discard.DiscardInLastMinute",
recent_tab_discard_);
recent_tab_discard_ = false;
}
void OomPriorityManager::PurgeBrowserMemory() {
for (TabContentsIterator it; !it.done(); it.Next()) {
WebContents* web_contents = *it;
web_contents->GetController().ClearAllScreenshots();
}
}
int OomPriorityManager::GetTabCount() const {
int tab_count = 0;
for (chrome::BrowserIterator it; !it.done(); it.Next())
tab_count += it->tab_strip_model()->count();
return tab_count;
}
bool OomPriorityManager::CompareTabStats(TabStats first,
TabStats second) {
if (first.is_selected != second.is_selected)
return first.is_selected;
if (first.is_reloadable_ui != second.is_reloadable_ui)
return !first.is_reloadable_ui;
if (first.is_pinned != second.is_pinned)
return first.is_pinned;
if (first.is_app != second.is_app)
return first.is_app;
if (first.is_playing_audio != second.is_playing_audio)
return first.is_playing_audio;
return first.last_active > second.last_active;
}
void OomPriorityManager::AdjustFocusedTabScoreOnFileThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
focused_tab_pid_, chrome::kLowestRendererOomScore);
pid_to_oom_score_[focused_tab_pid_] = chrome::kLowestRendererOomScore;
}
void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&OomPriorityManager::AdjustFocusedTabScoreOnFileThread,
base::Unretained(this)));
}
void OomPriorityManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
base::ProcessHandle handle = 0;
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: {
handle =
content::Details<content::RenderProcessHost::RendererClosedDetails>(
details)->handle;
pid_to_oom_score_.erase(handle);
break;
}
case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
handle = content::Source<content::RenderProcessHost>(source)->
GetHandle();
pid_to_oom_score_.erase(handle);
break;
}
case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
bool visible = *content::Details<bool>(details).ptr();
if (visible) {
focused_tab_pid_ =
content::Source<content::RenderWidgetHost>(source).ptr()->
GetProcess()->GetHandle();
ProcessScoreMap::iterator it;
it = pid_to_oom_score_.find(focused_tab_pid_);
if (it == pid_to_oom_score_.end()
|| it->second != chrome::kLowestRendererOomScore) {
if (focus_tab_score_adjust_timer_.IsRunning())
focus_tab_score_adjust_timer_.Reset();
else
focus_tab_score_adjust_timer_.Start(FROM_HERE,
TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs),
this, &OomPriorityManager::OnFocusTabScoreAdjustmentTimeout);
}
}
break;
}
default:
NOTREACHED() << L"Received unexpected notification";
break;
}
}
void OomPriorityManager::AdjustOomPriorities() {
if (BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH)->empty())
return;
if (!last_adjust_time_.is_null()) {
TimeDelta suspend_time = TimeTicks::Now() - last_adjust_time_;
if (suspend_time.InSeconds() > kSuspendThresholdSeconds) {
start_time_ += suspend_time;
if (!last_discard_time_.is_null())
last_discard_time_ += suspend_time;
}
}
last_adjust_time_ = TimeTicks::Now();
TabStatsList stats_list = GetTabStatsOnUIThread();
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&OomPriorityManager::AdjustOomPrioritiesOnFileThread,
base::Unretained(this), stats_list));
}
OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TabStatsList stats_list;
stats_list.reserve(32);
bool browser_active = true;
const BrowserList* ash_browser_list =
BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
for (BrowserList::const_reverse_iterator browser_iterator =
ash_browser_list->begin_last_active();
browser_iterator != ash_browser_list->end_last_active();
++browser_iterator) {
Browser* browser = *browser_iterator;
bool is_browser_for_app = browser->is_app();
const TabStripModel* model = browser->tab_strip_model();
for (int i = 0; i < model->count(); i++) {
WebContents* contents = model->GetWebContentsAt(i);
if (!contents->IsCrashed()) {
TabStats stats;
stats.is_app = is_browser_for_app;
stats.is_reloadable_ui =
IsReloadableUI(contents->GetLastCommittedURL());
stats.is_playing_audio = chrome::IsPlayingAudio(contents);
stats.is_pinned = model->IsTabPinned(i);
stats.is_selected = browser_active && model->IsTabSelected(i);
stats.is_discarded = model->IsTabDiscarded(i);
stats.last_active = contents->GetLastActiveTime();
stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
stats.title = contents->GetTitle();
stats.tab_contents_id = IdFromWebContents(contents);
stats_list.push_back(stats);
}
}
browser_active = false;
}
std::sort(stats_list.begin(), stats_list.end(), CompareTabStats);
return stats_list;
}
std::vector<base::ProcessHandle> OomPriorityManager::GetProcessHandles(
const TabStatsList& stats_list) {
std::vector<base::ProcessHandle> process_handles;
std::set<base::ProcessHandle> already_seen;
for (TabStatsList::const_iterator iterator = stats_list.begin();
iterator != stats_list.end(); ++iterator) {
if (iterator->renderer_handle == 0)
continue;
bool inserted = already_seen.insert(iterator->renderer_handle).second;
if (!inserted) {
continue;
}
process_handles.push_back(iterator->renderer_handle);
}
return process_handles;
}
void OomPriorityManager::AdjustOomPrioritiesOnFileThread(
TabStatsList stats_list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
std::vector<base::ProcessHandle> process_handles =
GetProcessHandles(stats_list);
float priority = chrome::kLowestRendererOomScore;
const int kPriorityRange = chrome::kHighestRendererOomScore -
chrome::kLowestRendererOomScore;
float priority_increment =
static_cast<float>(kPriorityRange) / process_handles.size();
for (std::vector<base::ProcessHandle>::iterator iterator =
process_handles.begin();
iterator != process_handles.end(); ++iterator) {
int score = static_cast<int>(priority + 0.5f);
ProcessScoreMap::iterator it = pid_to_oom_score_.find(*iterator);
if (it == pid_to_oom_score_.end() || it->second != score) {
content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(*iterator,
score);
pid_to_oom_score_[*iterator] = score;
}
priority += priority_increment;
}
}
}