OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/memory/oom_priority_manager.h" | 5 #include "chrome/browser/memory/oom_priority_manager.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <set> | 8 #include <set> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
111 } | 111 } |
112 } | 112 } |
113 } | 113 } |
114 | 114 |
115 void OomPriorityManager::Stop() { | 115 void OomPriorityManager::Stop() { |
116 update_timer_.Stop(); | 116 update_timer_.Stop(); |
117 recent_tab_discard_timer_.Stop(); | 117 recent_tab_discard_timer_.Stop(); |
118 memory_pressure_listener_.reset(); | 118 memory_pressure_listener_.reset(); |
119 } | 119 } |
120 | 120 |
121 std::vector<base::string16> OomPriorityManager::GetTabTitles() { | 121 // Things we need to collect on the browser thread (because TabStripModel isn't |
122 TabStatsList stats = GetTabStatsOnUIThread(); | 122 // thread safe): |
123 std::vector<base::string16> titles; | 123 // 1) whether or not a tab is pinned |
124 titles.reserve(stats.size()); | 124 // 2) last time a tab was selected |
125 TabStatsList::iterator it = stats.begin(); | 125 // 3) is the tab currently selected |
126 for (; it != stats.end(); ++it) { | 126 TabStatsList OomPriorityManager::GetTabStats() { |
127 base::string16 str; | 127 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
128 str.reserve(4096); | 128 TabStatsList stats_list; |
129 #if defined(OS_CHROMEOS) | 129 stats_list.reserve(32); // 99% of users have < 30 tabs open |
130 int score = delegate_->GetOomScore(it->child_process_host_id); | 130 |
131 str += base::IntToString16(score); | 131 // We go through each window to get all the tabs. Depending on the platform, |
132 str += base::ASCIIToUTF16(" - "); | 132 // windows are either native or ash or both. We want to make sure to go |
133 #endif | 133 // through them all, starting with the active window first (we use |
134 str += it->title; | 134 // chrome::GetActiveDesktop to get the current used type). |
135 str += base::ASCIIToUTF16(it->is_app ? " app" : ""); | 135 AddTabStats(BrowserList::GetInstance(chrome::GetActiveDesktop()), true, |
136 str += base::ASCIIToUTF16(it->is_internal_page ? " internal_page" : ""); | 136 &stats_list); |
137 str += base::ASCIIToUTF16(it->is_playing_audio ? " playing_audio" : ""); | 137 if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_NATIVE) { |
138 str += base::ASCIIToUTF16(it->is_pinned ? " pinned" : ""); | 138 AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE), |
139 str += base::ASCIIToUTF16(it->is_discarded ? " discarded" : ""); | 139 false, &stats_list); |
140 titles.push_back(str); | 140 } else if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH) { |
| 141 AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH), false, |
| 142 &stats_list); |
141 } | 143 } |
142 return titles; | 144 |
| 145 // Sort the data we collected so that least desirable to be |
| 146 // killed is first, most desirable is last. |
| 147 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); |
| 148 return stats_list; |
143 } | 149 } |
144 | 150 |
145 // TODO(jamescook): This should consider tabs with references to other tabs, | 151 // TODO(jamescook): This should consider tabs with references to other tabs, |
146 // such as tabs created with JavaScript window.open(). We might want to | 152 // such as tabs created with JavaScript window.open(). We might want to |
147 // discard the entire set together, or use that in the priority computation. | 153 // discard the entire set together, or use that in the priority computation. |
148 bool OomPriorityManager::DiscardTab() { | 154 bool OomPriorityManager::DiscardTab() { |
149 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 155 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
150 TabStatsList stats = GetTabStatsOnUIThread(); | 156 TabStatsList stats = GetTabStats(); |
151 if (stats.empty()) | 157 if (stats.empty()) |
152 return false; | 158 return false; |
153 // Loop until we find a non-discarded tab to kill. | 159 // Loop until we find a non-discarded tab to kill. |
154 for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin(); | 160 for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin(); |
155 stats_rit != stats.rend(); ++stats_rit) { | 161 stats_rit != stats.rend(); ++stats_rit) { |
156 int64 least_important_tab_id = stats_rit->tab_contents_id; | 162 int64 least_important_tab_id = stats_rit->tab_contents_id; |
157 if (DiscardTabById(least_important_tab_id)) | 163 if (DiscardTabById(least_important_tab_id)) |
158 return true; | 164 return true; |
159 } | 165 } |
160 return false; | 166 return false; |
161 } | 167 } |
162 | 168 |
| 169 bool OomPriorityManager::DiscardTabById(int64 target_web_contents_id) { |
| 170 for (chrome::BrowserIterator it; !it.done(); it.Next()) { |
| 171 Browser* browser = *it; |
| 172 TabStripModel* model = browser->tab_strip_model(); |
| 173 for (int idx = 0; idx < model->count(); idx++) { |
| 174 // Can't discard tabs that are already discarded or active. |
| 175 if (model->IsTabDiscarded(idx) || (model->active_index() == idx)) |
| 176 continue; |
| 177 WebContents* web_contents = model->GetWebContentsAt(idx); |
| 178 int64 web_contents_id = IdFromWebContents(web_contents); |
| 179 if (web_contents_id == target_web_contents_id) { |
| 180 VLOG(1) << "Discarding tab " << idx << " id " << target_web_contents_id; |
| 181 // Record statistics before discarding because we want to capture the |
| 182 // memory state that lead to the discard. |
| 183 RecordDiscardStatistics(); |
| 184 model->DiscardWebContentsAt(idx); |
| 185 recent_tab_discard_ = true; |
| 186 return true; |
| 187 } |
| 188 } |
| 189 } |
| 190 return false; |
| 191 } |
| 192 |
163 void OomPriorityManager::LogMemoryAndDiscardTab() { | 193 void OomPriorityManager::LogMemoryAndDiscardTab() { |
164 LogMemory("Tab Discards Memory details", | 194 LogMemory("Tab Discards Memory details", |
165 base::Bind(&OomPriorityManager::PurgeMemoryAndDiscardTab)); | 195 base::Bind(&OomPriorityManager::PurgeMemoryAndDiscardTab)); |
166 } | 196 } |
167 | 197 |
168 void OomPriorityManager::LogMemory(const std::string& title, | 198 void OomPriorityManager::LogMemory(const std::string& title, |
169 const base::Closure& callback) { | 199 const base::Closure& callback) { |
170 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 200 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
171 OomMemoryDetails::Log(title, callback); | 201 OomMemoryDetails::Log(title, callback); |
172 } | 202 } |
(...skipping 23 matching lines...) Expand all Loading... |
196 // Prefix-match against the table above. Use strncmp to avoid allocating | 226 // Prefix-match against the table above. Use strncmp to avoid allocating |
197 // memory to convert the URL prefix constants into std::strings. | 227 // memory to convert the URL prefix constants into std::strings. |
198 for (size_t i = 0; i < arraysize(kInternalPagePrefixes); ++i) { | 228 for (size_t i = 0; i < arraysize(kInternalPagePrefixes); ++i) { |
199 if (!strncmp(url.spec().c_str(), kInternalPagePrefixes[i], | 229 if (!strncmp(url.spec().c_str(), kInternalPagePrefixes[i], |
200 strlen(kInternalPagePrefixes[i]))) | 230 strlen(kInternalPagePrefixes[i]))) |
201 return true; | 231 return true; |
202 } | 232 } |
203 return false; | 233 return false; |
204 } | 234 } |
205 | 235 |
206 bool OomPriorityManager::DiscardTabById(int64 target_web_contents_id) { | |
207 for (chrome::BrowserIterator it; !it.done(); it.Next()) { | |
208 Browser* browser = *it; | |
209 TabStripModel* model = browser->tab_strip_model(); | |
210 for (int idx = 0; idx < model->count(); idx++) { | |
211 // Can't discard tabs that are already discarded or active. | |
212 if (model->IsTabDiscarded(idx) || (model->active_index() == idx)) | |
213 continue; | |
214 WebContents* web_contents = model->GetWebContentsAt(idx); | |
215 int64 web_contents_id = IdFromWebContents(web_contents); | |
216 if (web_contents_id == target_web_contents_id) { | |
217 LOG(WARNING) << "Discarding tab " << idx << " id " | |
218 << target_web_contents_id; | |
219 // Record statistics before discarding because we want to capture the | |
220 // memory state that lead to the discard. | |
221 RecordDiscardStatistics(); | |
222 model->DiscardWebContentsAt(idx); | |
223 recent_tab_discard_ = true; | |
224 return true; | |
225 } | |
226 } | |
227 } | |
228 return false; | |
229 } | |
230 | |
231 void OomPriorityManager::RecordDiscardStatistics() { | 236 void OomPriorityManager::RecordDiscardStatistics() { |
232 // Record a raw count so we can compare to discard reloads. | 237 // Record a raw count so we can compare to discard reloads. |
233 discard_count_++; | 238 discard_count_++; |
234 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.DiscardCount", discard_count_, 1, | 239 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.DiscardCount", discard_count_, 1, |
235 1000, 50); | 240 1000, 50); |
236 | 241 |
237 // TODO(jamescook): Maybe incorporate extension count? | 242 // TODO(jamescook): Maybe incorporate extension count? |
238 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.TabCount", GetTabCount(), 1, 100, | 243 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.Discard.TabCount", GetTabCount(), 1, 100, |
239 50); | 244 50); |
240 #if defined(OS_CHROMEOS) | 245 #if defined(OS_CHROMEOS) |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
355 // We were probably suspended, move our event timers forward in time so | 360 // We were probably suspended, move our event timers forward in time so |
356 // when we subtract them out later we are counting "uptime". | 361 // when we subtract them out later we are counting "uptime". |
357 start_time_ += suspend_time; | 362 start_time_ += suspend_time; |
358 if (!last_discard_time_.is_null()) | 363 if (!last_discard_time_.is_null()) |
359 last_discard_time_ += suspend_time; | 364 last_discard_time_ += suspend_time; |
360 } | 365 } |
361 } | 366 } |
362 last_adjust_time_ = TimeTicks::Now(); | 367 last_adjust_time_ = TimeTicks::Now(); |
363 | 368 |
364 #if defined(OS_CHROMEOS) | 369 #if defined(OS_CHROMEOS) |
365 TabStatsList stats_list = GetTabStatsOnUIThread(); | 370 TabStatsList stats_list = GetTabStats(); |
366 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj. | 371 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj. |
367 delegate_->AdjustOomPriorities(stats_list); | 372 delegate_->AdjustOomPriorities(stats_list); |
368 #endif | 373 #endif |
369 } | 374 } |
370 | 375 |
371 // Things we need to collect on the browser thread (because TabStripModel isn't | |
372 // thread safe): | |
373 // 1) whether or not a tab is pinned | |
374 // 2) last time a tab was selected | |
375 // 3) is the tab currently selected | |
376 TabStatsList OomPriorityManager::GetTabStatsOnUIThread() { | |
377 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
378 TabStatsList stats_list; | |
379 stats_list.reserve(32); // 99% of users have < 30 tabs open | |
380 | |
381 // We go through each window to get all the tabs. Depending on the platform, | |
382 // windows are either native or ash or both. We want to make sure to go | |
383 // through them all, starting with the active window first (we use | |
384 // chrome::GetActiveDesktop to get the current used type). | |
385 AddTabStats(BrowserList::GetInstance(chrome::GetActiveDesktop()), true, | |
386 &stats_list); | |
387 if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_NATIVE) { | |
388 AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE), | |
389 false, &stats_list); | |
390 } else if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH) { | |
391 AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH), false, | |
392 &stats_list); | |
393 } | |
394 | |
395 // Sort the data we collected so that least desirable to be | |
396 // killed is first, most desirable is last. | |
397 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); | |
398 return stats_list; | |
399 } | |
400 | |
401 void OomPriorityManager::AddTabStats(BrowserList* browser_list, | 376 void OomPriorityManager::AddTabStats(BrowserList* browser_list, |
402 bool active_desktop, | 377 bool active_desktop, |
403 TabStatsList* stats_list) { | 378 TabStatsList* stats_list) { |
404 // If it's the active desktop, the first window will be the active one. | 379 // If it's the active desktop, the first window will be the active one. |
405 // Otherwise, we assume no active windows. | 380 // Otherwise, we assume no active windows. |
406 bool browser_active = active_desktop; | 381 bool browser_active = active_desktop; |
407 for (BrowserList::const_reverse_iterator browser_iterator = | 382 for (BrowserList::const_reverse_iterator browser_iterator = |
408 browser_list->begin_last_active(); | 383 browser_list->begin_last_active(); |
409 browser_iterator != browser_list->end_last_active(); | 384 browser_iterator != browser_list->end_last_active(); |
410 ++browser_iterator) { | 385 ++browser_iterator) { |
411 Browser* browser = *browser_iterator; | 386 Browser* browser = *browser_iterator; |
412 bool is_browser_for_app = browser->is_app(); | 387 bool is_browser_for_app = browser->is_app(); |
413 const TabStripModel* model = browser->tab_strip_model(); | 388 const TabStripModel* model = browser->tab_strip_model(); |
414 for (int i = 0; i < model->count(); i++) { | 389 for (int i = 0; i < model->count(); i++) { |
415 WebContents* contents = model->GetWebContentsAt(i); | 390 WebContents* contents = model->GetWebContentsAt(i); |
416 if (!contents->IsCrashed()) { | 391 if (!contents->IsCrashed()) { |
417 TabStats stats; | 392 TabStats stats; |
418 stats.is_app = is_browser_for_app; | 393 stats.is_app = is_browser_for_app; |
419 stats.is_internal_page = | 394 stats.is_internal_page = |
420 IsInternalPage(contents->GetLastCommittedURL()); | 395 IsInternalPage(contents->GetLastCommittedURL()); |
421 stats.is_playing_audio = chrome::IsPlayingAudio(contents); | 396 stats.is_playing_audio = chrome::IsPlayingAudio(contents); |
422 stats.is_pinned = model->IsTabPinned(i); | 397 stats.is_pinned = model->IsTabPinned(i); |
423 stats.is_selected = browser_active && model->IsTabSelected(i); | 398 stats.is_selected = browser_active && model->IsTabSelected(i); |
424 stats.is_discarded = model->IsTabDiscarded(i); | 399 stats.is_discarded = model->IsTabDiscarded(i); |
425 stats.last_active = contents->GetLastActiveTime(); | 400 stats.last_active = contents->GetLastActiveTime(); |
426 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); | 401 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); |
427 stats.child_process_host_id = contents->GetRenderProcessHost()->GetID(); | 402 stats.child_process_host_id = contents->GetRenderProcessHost()->GetID(); |
| 403 #if defined(OS_CHROMEOS) |
| 404 stats.oom_score = delegate_->GetOomScore(stats.child_process_host_id); |
| 405 #endif |
428 stats.title = contents->GetTitle(); | 406 stats.title = contents->GetTitle(); |
429 stats.tab_contents_id = IdFromWebContents(contents); | 407 stats.tab_contents_id = IdFromWebContents(contents); |
430 stats_list->push_back(stats); | 408 stats_list->push_back(stats); |
431 } | 409 } |
432 } | 410 } |
433 // We process the active browser window in the first iteration. | 411 // We process the active browser window in the first iteration. |
434 browser_active = false; | 412 browser_active = false; |
435 } | 413 } |
436 } | 414 } |
437 | 415 |
438 void OomPriorityManager::OnMemoryPressure( | 416 void OomPriorityManager::OnMemoryPressure( |
439 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { | 417 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { |
440 // For the moment we only do something when we reach a critical state. | 418 // For the moment we only do something when we reach a critical state. |
441 if (memory_pressure_level == | 419 if (memory_pressure_level == |
442 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { | 420 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { |
443 LogMemoryAndDiscardTab(); | 421 LogMemoryAndDiscardTab(); |
444 } | 422 } |
445 // TODO(skuhne): If more memory pressure levels are introduced, we might | 423 // TODO(skuhne): If more memory pressure levels are introduced, we might |
446 // consider to call PurgeBrowserMemory() before CRITICAL is reached. | 424 // consider to call PurgeBrowserMemory() before CRITICAL is reached. |
447 } | 425 } |
448 | 426 |
449 } // namespace memory | 427 } // namespace memory |
OLD | NEW |