1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/memory/oom_priority_manager_delegate_chromeos.h"
7 #include "base/memory/memory_pressure_monitor_chromeos.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/synchronization/lock.h"
13 #include "chrome/browser/memory/tab_stats.h"
14 #include "chrome/browser/ui/browser_list.h"
15 #include "chrome/common/chrome_constants.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/notification_types.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/render_widget_host.h"
21 #include "content/public/browser/zygote_host_linux.h"
23 using base::TimeDelta
;
24 using content::BrowserThread
;
29 // When switching to a new tab the tab's renderer's OOM score needs to be
30 // updated to reflect its front-most status and protect it from discard.
31 // However, doing this immediately might slow down tab switch time, so wait
32 // a little while before doing the adjustment.
33 const int kFocusedTabScoreAdjustIntervalMs
= 500;
37 OomPriorityManagerDelegate::OomPriorityManagerDelegate()
38 : focused_tab_process_info_(std::make_pair(0, 0)) {
39 registrar_
.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED
,
40 content::NotificationService::AllBrowserContextsAndSources());
41 registrar_
.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED
,
42 content::NotificationService::AllBrowserContextsAndSources());
43 registrar_
.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED
,
44 content::NotificationService::AllBrowserContextsAndSources());
47 OomPriorityManagerDelegate::~OomPriorityManagerDelegate() {
50 int OomPriorityManagerDelegate::GetOomScore(int child_process_host_id
) {
51 base::AutoLock
oom_score_autolock(oom_score_lock_
);
52 int score
= oom_score_map_
[child_process_host_id
];
56 void OomPriorityManagerDelegate::AdjustFocusedTabScoreOnFileThread() {
57 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
58 base::AutoLock
oom_score_autolock(oom_score_lock_
);
59 base::ProcessHandle pid
= focused_tab_process_info_
.second
;
60 content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
61 pid
, chrome::kLowestRendererOomScore
);
62 oom_score_map_
[focused_tab_process_info_
.first
] =
63 chrome::kLowestRendererOomScore
;
66 void OomPriorityManagerDelegate::OnFocusTabScoreAdjustmentTimeout() {
67 BrowserThread::PostTask(
68 BrowserThread::FILE, FROM_HERE
,
69 base::Bind(&OomPriorityManagerDelegate::AdjustFocusedTabScoreOnFileThread
,
70 base::Unretained(this)));
72 void OomPriorityManagerDelegate::Observe(
74 const content::NotificationSource
& source
,
75 const content::NotificationDetails
& details
) {
76 base::AutoLock
oom_score_autolock(oom_score_lock_
);
78 case content::NOTIFICATION_RENDERER_PROCESS_CLOSED
:
79 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED
: {
80 content::RenderProcessHost
* host
=
81 content::Source
<content::RenderProcessHost
>(source
).ptr();
82 oom_score_map_
.erase(host
->GetID());
83 // Coming here we know that a renderer was just killed and memory should
84 // come back into the pool. However - the memory pressure observer did
85 // not yet update its status and therefore we ask it to redo the
86 // measurement, calling us again if we have to release more.
87 // Note: We do not only accelerate the discarding speed by doing another
88 // check in short succession - we also accelerate it because the timer
89 // driven MemoryPressureMonitor will continue to produce timed events
90 // on top. So the longer the cleanup phase takes, the more tabs will
91 // get discarded in parallel.
92 base::chromeos::MemoryPressureMonitor
* monitor
=
93 base::chromeos::MemoryPressureMonitor::Get();
95 monitor
->ScheduleEarlyCheck();
98 case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED
: {
99 bool visible
= *content::Details
<bool>(details
).ptr();
101 content::RenderProcessHost
* render_host
=
102 content::Source
<content::RenderWidgetHost
>(source
)
105 focused_tab_process_info_
=
106 std::make_pair(render_host
->GetID(), render_host
->GetHandle());
108 // If the currently focused tab already has a lower score, do not
109 // set it. This can happen in case the newly focused tab is script
110 // connected to the previous tab.
111 ProcessScoreMap::iterator it
;
112 it
= oom_score_map_
.find(focused_tab_process_info_
.first
);
113 if (it
== oom_score_map_
.end() ||
114 it
->second
!= chrome::kLowestRendererOomScore
) {
115 // By starting a timer we guarantee that the tab is focused for
116 // certain amount of time. Secondly, it also does not add overhead
117 // to the tab switching time.
118 if (focus_tab_score_adjust_timer_
.IsRunning())
119 focus_tab_score_adjust_timer_
.Reset();
121 focus_tab_score_adjust_timer_
.Start(
123 TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs
),
125 &OomPriorityManagerDelegate::OnFocusTabScoreAdjustmentTimeout
);
131 NOTREACHED() << "Received unexpected notification";
136 // Here we collect most of the information we need to sort the existing
137 // renderers in priority order, and hand out oom_score_adj scores based on that
140 // Things we need to collect on the browser thread (because
141 // TabStripModel isn't thread safe):
142 // 1) whether or not a tab is pinned
143 // 2) last time a tab was selected
144 // 3) is the tab currently selected
145 void OomPriorityManagerDelegate::AdjustOomPriorities(
146 const TabStatsList
& stats_list
) {
147 BrowserThread::PostTask(
148 BrowserThread::FILE, FROM_HERE
,
149 base::Bind(&OomPriorityManagerDelegate::AdjustOomPrioritiesOnFileThread
,
150 base::Unretained(this), stats_list
));
154 std::vector
<OomPriorityManagerDelegate::ProcessInfo
>
155 OomPriorityManagerDelegate::GetChildProcessInfos(
156 const TabStatsList
& stats_list
) {
157 std::vector
<ProcessInfo
> process_infos
;
158 std::set
<base::ProcessHandle
> already_seen
;
159 for (TabStatsList::const_iterator iterator
= stats_list
.begin();
160 iterator
!= stats_list
.end(); ++iterator
) {
161 // stats_list contains entries for already-discarded tabs. If the PID
162 // (renderer_handle) is zero, we don't need to adjust the oom_score.
163 if (iterator
->renderer_handle
== 0)
166 bool inserted
= already_seen
.insert(iterator
->renderer_handle
).second
;
168 // We've already seen this process handle.
172 process_infos
.push_back(std::make_pair(iterator
->child_process_host_id
,
173 iterator
->renderer_handle
));
175 return process_infos
;
178 void OomPriorityManagerDelegate::AdjustOomPrioritiesOnFileThread(
179 TabStatsList stats_list
) {
180 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
181 base::AutoLock
oom_score_autolock(oom_score_lock_
);
183 // Remove any duplicate PIDs. Order of the list is maintained, so each
184 // renderer process will take on the oom_score_adj of the most important
185 // (least likely to be killed) tab.
186 std::vector
<ProcessInfo
> process_infos
= GetChildProcessInfos(stats_list
);
188 // Now we assign priorities based on the sorted list. We're assigning
189 // priorities in the range of kLowestRendererOomScore to
190 // kHighestRendererOomScore (defined in chrome_constants.h). oom_score_adj
191 // takes values from -1000 to 1000. Negative values are reserved for system
192 // processes, and we want to give some room below the range we're using to
193 // allow for things that want to be above the renderers in priority, so the
194 // defined range gives us some variation in priority without taking up the
195 // whole range. In the end, however, it's a pretty arbitrary range to use.
196 // Higher values are more likely to be killed by the OOM killer.
197 float priority
= chrome::kLowestRendererOomScore
;
198 const int kPriorityRange
=
199 chrome::kHighestRendererOomScore
- chrome::kLowestRendererOomScore
;
200 float priority_increment
=
201 static_cast<float>(kPriorityRange
) / process_infos
.size();
202 for (const auto& process_info
: process_infos
) {
203 int score
= static_cast<int>(priority
+ 0.5f
);
204 ProcessScoreMap::iterator it
= oom_score_map_
.find(process_info
.first
);
205 // If a process has the same score as the newly calculated value,
207 if (it
== oom_score_map_
.end() || it
->second
!= score
) {
208 content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
209 process_info
.second
, score
);
210 oom_score_map_
[process_info
.first
] = score
;
212 priority
+= priority_increment
;
216 } // namespace memory