1 // Copyright (c) 2011 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.
4
5 #include "chrome/browser/oom_priority_manager.h"
6
7 #include <list>
8
9 #include "base/process.h"
10 #include "base/process_util.h"
11 #include "base/threading/thread.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/tabs/tab_strip_model.h"
14 #include "chrome/browser/ui/browser_list.h"
15 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
16 #include "content/browser/browser_thread.h"
17 #include "content/browser/renderer_host/render_process_host.h"
18 #include "content/browser/tab_contents/tab_contents.h"
19 #include "content/browser/zygote_host_linux.h"
20
21 #if !defined(OS_CHROMEOS)
22 #error This file only meant to be compiled on ChromeOS
23 #endif
24
25 using base::TimeDelta;
26 using base::TimeTicks;
27 using base::ProcessHandle;
28 using base::ProcessMetrics;
29
30 namespace browser {
31
32 // The default interval in seconds after which to adjust the oom_adj
33 // value.
34 #define ADJUSTMENT_INTERVAL_SECONDS 10
35
36 // The default interval in minutes for considering activation times
37 // "equal".
38 #define BUCKET_INTERVAL_MINUTES 10
39
OomPriorityManager()40 OomPriorityManager::OomPriorityManager() {
41 StartTimer();
42 }
43
~OomPriorityManager()44 OomPriorityManager::~OomPriorityManager() {
45 StopTimer();
46 }
47
StartTimer()48 void OomPriorityManager::StartTimer() {
49 if (!timer_.IsRunning()) {
50 timer_.Start(TimeDelta::FromSeconds(ADJUSTMENT_INTERVAL_SECONDS),
51 this,
52 &OomPriorityManager::AdjustOomPriorities);
53 }
54 }
55
StopTimer()56 void OomPriorityManager::StopTimer() {
57 timer_.Stop();
58 }
59
60 // Returns true if |first| is considered less desirable to be killed
61 // than |second|.
CompareRendererStats(RendererStats first,RendererStats second)62 bool OomPriorityManager::CompareRendererStats(RendererStats first,
63 RendererStats second) {
64 // The size of the slop in comparing activation times. [This is
65 // allocated here to avoid static initialization at startup time.]
66 static const int64 kTimeBucketInterval =
67 TimeDelta::FromMinutes(BUCKET_INTERVAL_MINUTES).ToInternalValue();
68
69 // Being pinned is most important.
70 if (first.is_pinned != second.is_pinned)
71 return first.is_pinned == true;
72
73 // We want to be a little "fuzzy" when we compare these, because
74 // it's not really possible for the times to be identical, but if
75 // the user selected two tabs at about the same time, we still want
76 // to take the one that uses more memory.
77 if (abs((first.last_selected - second.last_selected).ToInternalValue()) <
78 kTimeBucketInterval)
79 return first.last_selected < second.last_selected;
80
81 return first.memory_used < second.memory_used;
82 }
83
84 // Here we collect most of the information we need to sort the
85 // existing renderers in priority order, and hand out oom_adj scores
86 // based on that sort order.
87 //
88 // Things we need to collect on the browser thread (because
89 // TabStripModel isn't thread safe):
90 // 1) whether or not a tab is pinned
91 // 2) last time a tab was selected
92 //
93 // We also need to collect:
94 // 3) size in memory of a tab
95 // But we do that in DoAdjustOomPriorities on the FILE thread so that
96 // we avoid jank, because it accesses /proc.
AdjustOomPriorities()97 void OomPriorityManager::AdjustOomPriorities() {
98 if (BrowserList::size() == 0)
99 return;
100
101 StatsList renderer_stats;
102 for (BrowserList::const_iterator browser_iterator = BrowserList::begin();
103 browser_iterator != BrowserList::end(); ++browser_iterator) {
104 Browser* browser = *browser_iterator;
105 const TabStripModel* model = browser->tabstrip_model();
106 for (int i = 0; i < model->count(); i++) {
107 TabContents* contents = model->GetTabContentsAt(i)->tab_contents();
108 RendererStats stats;
109 stats.last_selected = contents->last_selected_time();
110 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
111 stats.is_pinned = model->IsTabPinned(i);
112 stats.memory_used = 0; // This gets calculated in DoAdjustOomPriorities.
113 renderer_stats.push_back(stats);
114 }
115 }
116
117 BrowserThread::PostTask(
118 BrowserThread::FILE, FROM_HERE,
119 NewRunnableMethod(this, &OomPriorityManager::DoAdjustOomPriorities,
120 renderer_stats));
121 }
122
DoAdjustOomPriorities(StatsList renderer_stats)123 void OomPriorityManager::DoAdjustOomPriorities(StatsList renderer_stats) {
124 for (StatsList::iterator stats_iter = renderer_stats.begin();
125 stats_iter != renderer_stats.end(); ++stats_iter) {
126 scoped_ptr<ProcessMetrics> metrics(ProcessMetrics::CreateProcessMetrics(
127 stats_iter->renderer_handle));
128
129 base::WorkingSetKBytes working_set_kbytes;
130 if (metrics->GetWorkingSetKBytes(&working_set_kbytes)) {
131 // We use the proportional set size (PSS) to calculate memory
132 // usage "badness" on Linux.
133 stats_iter->memory_used = working_set_kbytes.shared * 1024;
134 } else {
135 // and if for some reason we can't get PSS, we revert to using
136 // resident set size (RSS). This will be zero if the process
137 // has already gone away, but we can live with that, since the
138 // process is gone anyhow.
139 stats_iter->memory_used = metrics->GetWorkingSetSize();
140 }
141 }
142
143 // Now we sort the data we collected so that least desirable to be
144 // killed is first, most desirable is last.
145 renderer_stats.sort(OomPriorityManager::CompareRendererStats);
146
147 // Now we assign priorities based on the sorted list. We're
148 // assigning priorities in the range of 5 to 10. oom_adj takes
149 // values from -17 to 15. Negative values are reserved for system
150 // processes, and we want to give some room on either side of the
151 // range we're using to allow for things that want to be above or
152 // below the renderers in priority, so 5 to 10 gives us some
153 // variation in priority without taking up the whole range. In the
154 // end, however, it's a pretty arbitrary range to use. Higher
155 // values are more likely to be killed by the OOM killer. We also
156 // remove any duplicate PIDs, leaving the most important of the
157 // duplicates.
158 const int kMinPriority = 5;
159 const int kMaxPriority = 10;
160 const int kPriorityRange = kMaxPriority - kMinPriority;
161 float priority_increment =
162 static_cast<float>(kPriorityRange) / renderer_stats.size();
163 float priority = kMinPriority;
164 std::set<base::ProcessHandle> already_seen;
165 for (StatsList::iterator iterator = renderer_stats.begin();
166 iterator != renderer_stats.end(); ++iterator) {
167 if (already_seen.find(iterator->renderer_handle) == already_seen.end()) {
168 already_seen.insert(iterator->renderer_handle);
169 ZygoteHost::GetInstance()->AdjustRendererOOMScore(
170 iterator->renderer_handle,
171 static_cast<int>(priority + 0.5f));
172 priority += priority_increment;
173 }
174 }
175 }
176
177 } // namespace browser
178