Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / memory / oom_priority_manager.cc
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/memory/oom_priority_manager.h"
6
7 #include <algorithm>
8 #include <set>
9 #include <vector>
10
11 #include "ash/multi_profile_uma.h"
12 #include "ash/session/session_state_delegate.h"
13 #include "ash/shell.h"
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/command_line.h"
17 #include "base/metrics/field_trial.h"
18 #include "base/metrics/histogram.h"
19 #include "base/process/process.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/synchronization/lock.h"
25 #include "base/threading/thread.h"
26 #include "base/time/time.h"
27 #include "build/build_config.h"
28 #include "chrome/browser/browser_process.h"
29 #include "chrome/browser/browser_process_platform_part_chromeos.h"
30 #include "chrome/browser/chromeos/memory/low_memory_observer.h"
31 #include "chrome/browser/memory_details.h"
32 #include "chrome/browser/ui/browser.h"
33 #include "chrome/browser/ui/browser_iterator.h"
34 #include "chrome/browser/ui/browser_list.h"
35 #include "chrome/browser/ui/host_desktop.h"
36 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
37 #include "chrome/browser/ui/tabs/tab_strip_model.h"
38 #include "chrome/browser/ui/tabs/tab_utils.h"
39 #include "chrome/common/chrome_constants.h"
40 #include "chrome/common/url_constants.h"
41 #include "chromeos/chromeos_switches.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "content/public/browser/notification_service.h"
44 #include "content/public/browser/notification_types.h"
45 #include "content/public/browser/render_process_host.h"
46 #include "content/public/browser/render_widget_host.h"
47 #include "content/public/browser/web_contents.h"
48 #include "content/public/browser/zygote_host_linux.h"
49 #include "ui/base/text/bytes_formatting.h"
50
51 using base::TimeDelta;
52 using base::TimeTicks;
53 using content::BrowserThread;
54 using content::WebContents;
55
56 namespace chromeos {
57
58 namespace {
59
60 // Record a size in megabytes, over a potential interval up to 32 GB.
61 #define UMA_HISTOGRAM_MEGABYTES(name, sample)                     \
62     UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50)
63
64 // The default interval in seconds after which to adjust the oom_score_adj
65 // value.
66 const int kAdjustmentIntervalSeconds = 10;
67
68 // For each period of this length we record a statistic to indicate whether
69 // or not the user experienced a low memory event. If you change this interval
70 // you must replace Tabs.Discard.DiscardInLastMinute with a new statistic.
71 const int kRecentTabDiscardIntervalSeconds = 60;
72
73 // If there has been no priority adjustment in this interval, we assume the
74 // machine was suspended and correct our timing statistics.
75 const int kSuspendThresholdSeconds = kAdjustmentIntervalSeconds * 4;
76
77 // When switching to a new tab the tab's renderer's OOM score needs to be
78 // updated to reflect its front-most status and protect it from discard.
79 // However, doing this immediately might slow down tab switch time, so wait
80 // a little while before doing the adjustment.
81 const int kFocusedTabScoreAdjustIntervalMs = 500;
82
83 // Returns a unique ID for a WebContents.  Do not cast back to a pointer, as
84 // the WebContents could be deleted if the user closed the tab.
85 int64 IdFromWebContents(WebContents* web_contents) {
86   return reinterpret_cast<int64>(web_contents);
87 }
88
89 // Records a statistics |sample| for UMA histogram |name| using a linear
90 // distribution of buckets.
91 void RecordLinearHistogram(const std::string& name,
92                            int sample,
93                            int maximum,
94                            size_t bucket_count) {
95   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
96   // instance and thus only work if |name| is constant.
97   base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
98       name,
99       1,  // Minimum. The 0 bin for underflow is automatically added.
100       maximum + 1,  // Ensure bucket size of |maximum| / |bucket_count|.
101       bucket_count + 2,  // Account for the underflow and overflow bins.
102       base::Histogram::kUmaTargetedHistogramFlag);
103   counter->Add(sample);
104 }
105
106 }  // namespace
107
108 ////////////////////////////////////////////////////////////////////////////////
109 // OomMemoryDetails logs details about all Chrome processes during an out-of-
110 // memory event in an attempt to identify the culprit, then discards a tab and
111 // deletes itself.
112 class OomMemoryDetails : public MemoryDetails {
113  public:
114   OomMemoryDetails();
115
116   // MemoryDetails overrides:
117   virtual void OnDetailsAvailable() override;
118
119  private:
120   virtual ~OomMemoryDetails() {}
121
122   TimeTicks start_time_;
123
124   DISALLOW_COPY_AND_ASSIGN(OomMemoryDetails);
125 };
126
127 OomMemoryDetails::OomMemoryDetails() {
128   AddRef();  // Released in OnDetailsAvailable().
129   start_time_ = TimeTicks::Now();
130 }
131
132 void OomMemoryDetails::OnDetailsAvailable() {
133   TimeDelta delta = TimeTicks::Now() - start_time_;
134   // These logs are collected by user feedback reports.  We want them to help
135   // diagnose user-reported problems with frequently discarded tabs.
136   std::string log_string = ToLogString();
137   base::SystemMemoryInfoKB memory;
138   if (base::GetSystemMemoryInfo(&memory) && memory.gem_size != -1) {
139     log_string += "Graphics ";
140     log_string += base::UTF16ToASCII(ui::FormatBytes(memory.gem_size));
141   }
142   LOG(WARNING) << "OOM details (" << delta.InMilliseconds() << " ms):\n"
143       << log_string;
144   if (g_browser_process &&
145       g_browser_process->platform_part()->oom_priority_manager()) {
146     OomPriorityManager* manager =
147         g_browser_process->platform_part()->oom_priority_manager();
148     manager->PurgeBrowserMemory();
149     manager->DiscardTab();
150   }
151   // Delete ourselves so we don't have to worry about OomPriorityManager
152   // deleting us when we're still working.
153   Release();
154 }
155
156 ////////////////////////////////////////////////////////////////////////////////
157 // OomPriorityManager
158
159 OomPriorityManager::TabStats::TabStats()
160   : is_app(false),
161     is_reloadable_ui(false),
162     is_playing_audio(false),
163     is_pinned(false),
164     is_selected(false),
165     is_discarded(false),
166     renderer_handle(0),
167     tab_contents_id(0) {
168 }
169
170 OomPriorityManager::TabStats::~TabStats() {
171 }
172
173 OomPriorityManager::OomPriorityManager()
174     : focused_tab_pid_(0),
175       low_memory_observer_(new LowMemoryObserver),
176       discard_count_(0),
177       recent_tab_discard_(false) {
178   registrar_.Add(this,
179       content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
180       content::NotificationService::AllBrowserContextsAndSources());
181   registrar_.Add(this,
182       content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
183       content::NotificationService::AllBrowserContextsAndSources());
184   registrar_.Add(this,
185       content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
186       content::NotificationService::AllBrowserContextsAndSources());
187 }
188
189 OomPriorityManager::~OomPriorityManager() {
190   Stop();
191 }
192
193 void OomPriorityManager::Start() {
194   if (!timer_.IsRunning()) {
195     timer_.Start(FROM_HERE,
196                  TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
197                  this,
198                  &OomPriorityManager::AdjustOomPriorities);
199   }
200   if (!recent_tab_discard_timer_.IsRunning()) {
201     recent_tab_discard_timer_.Start(
202         FROM_HERE,
203         TimeDelta::FromSeconds(kRecentTabDiscardIntervalSeconds),
204         this,
205         &OomPriorityManager::RecordRecentTabDiscard);
206   }
207   if (low_memory_observer_.get())
208     low_memory_observer_->Start();
209   start_time_ = TimeTicks::Now();
210 }
211
212 void OomPriorityManager::Stop() {
213   timer_.Stop();
214   recent_tab_discard_timer_.Stop();
215   if (low_memory_observer_.get())
216     low_memory_observer_->Stop();
217 }
218
219 std::vector<base::string16> OomPriorityManager::GetTabTitles() {
220   TabStatsList stats = GetTabStatsOnUIThread();
221   base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
222   std::vector<base::string16> titles;
223   titles.reserve(stats.size());
224   TabStatsList::iterator it = stats.begin();
225   for ( ; it != stats.end(); ++it) {
226     base::string16 str;
227     str.reserve(4096);
228     int score = pid_to_oom_score_[it->renderer_handle];
229     str += base::IntToString16(score);
230     str += base::ASCIIToUTF16(" - ");
231     str += it->title;
232     str += base::ASCIIToUTF16(it->is_app ? " app" : "");
233     str += base::ASCIIToUTF16(it->is_reloadable_ui ? " reloadable_ui" : "");
234     str += base::ASCIIToUTF16(it->is_playing_audio ? " playing_audio" : "");
235     str += base::ASCIIToUTF16(it->is_pinned ? " pinned" : "");
236     str += base::ASCIIToUTF16(it->is_discarded ? " discarded" : "");
237     titles.push_back(str);
238   }
239   return titles;
240 }
241
242 // TODO(jamescook): This should consider tabs with references to other tabs,
243 // such as tabs created with JavaScript window.open().  We might want to
244 // discard the entire set together, or use that in the priority computation.
245 bool OomPriorityManager::DiscardTab() {
246   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
247   TabStatsList stats = GetTabStatsOnUIThread();
248   if (stats.empty())
249     return false;
250   // Loop until we find a non-discarded tab to kill.
251   for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin();
252        stats_rit != stats.rend();
253        ++stats_rit) {
254     int64 least_important_tab_id = stats_rit->tab_contents_id;
255     if (DiscardTabById(least_important_tab_id))
256       return true;
257   }
258   return false;
259 }
260
261 void OomPriorityManager::LogMemoryAndDiscardTab() {
262   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
263   // Deletes itself upon completion.
264   OomMemoryDetails* details = new OomMemoryDetails();
265   details->StartFetch(MemoryDetails::SKIP_USER_METRICS);
266 }
267
268 ///////////////////////////////////////////////////////////////////////////////
269 // OomPriorityManager, private:
270
271 // static
272 bool OomPriorityManager::IsReloadableUI(const GURL& url) {
273   // There are many chrome:// UI URLs, but only look for the ones that users
274   // are likely to have open. Most of the benefit is the from NTP URL.
275   const char* const kReloadableUrlPrefixes[] = {
276       chrome::kChromeUIDownloadsURL,
277       chrome::kChromeUIHistoryURL,
278       chrome::kChromeUINewTabURL,
279       chrome::kChromeUISettingsURL,
280   };
281   // Prefix-match against the table above. Use strncmp to avoid allocating
282   // memory to convert the URL prefix constants into std::strings.
283   for (size_t i = 0; i < arraysize(kReloadableUrlPrefixes); ++i) {
284     if (!strncmp(url.spec().c_str(),
285                  kReloadableUrlPrefixes[i],
286                  strlen(kReloadableUrlPrefixes[i])))
287       return true;
288   }
289   return false;
290 }
291
292 bool OomPriorityManager::DiscardTabById(int64 target_web_contents_id) {
293   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
294     Browser* browser = *it;
295     TabStripModel* model = browser->tab_strip_model();
296     for (int idx = 0; idx < model->count(); idx++) {
297       // Can't discard tabs that are already discarded or active.
298       if (model->IsTabDiscarded(idx) || (model->active_index() == idx))
299         continue;
300       WebContents* web_contents = model->GetWebContentsAt(idx);
301       int64 web_contents_id = IdFromWebContents(web_contents);
302       if (web_contents_id == target_web_contents_id) {
303         LOG(WARNING) << "Discarding tab " << idx
304                      << " id " << target_web_contents_id;
305         // Record statistics before discarding because we want to capture the
306         // memory state that lead to the discard.
307         RecordDiscardStatistics();
308         model->DiscardWebContentsAt(idx);
309         recent_tab_discard_ = true;
310         return true;
311       }
312     }
313   }
314   return false;
315 }
316
317 void OomPriorityManager::RecordDiscardStatistics() {
318   // Record a raw count so we can compare to discard reloads.
319   discard_count_++;
320   UMA_HISTOGRAM_CUSTOM_COUNTS(
321       "Tabs.Discard.DiscardCount", discard_count_, 1, 1000, 50);
322
323   // TODO(jamescook): Maybe incorporate extension count?
324   UMA_HISTOGRAM_CUSTOM_COUNTS(
325       "Tabs.Discard.TabCount", GetTabCount(), 1, 100, 50);
326 #if !defined(USE_ATHENA)
327   // Record the discarded tab in relation to the amount of simultaneously
328   // logged in users.
329   ash::MultiProfileUMA::RecordDiscardedTab(
330       ash::Shell::GetInstance()->session_state_delegate()->
331           NumberOfLoggedInUsers());
332 #endif
333
334   // TODO(jamescook): If the time stats prove too noisy, then divide up users
335   // based on how heavily they use Chrome using tab count as a proxy.
336   // Bin into <= 1, <= 2, <= 4, <= 8, etc.
337   if (last_discard_time_.is_null()) {
338     // This is the first discard this session.
339     TimeDelta interval = TimeTicks::Now() - start_time_;
340     int interval_seconds = static_cast<int>(interval.InSeconds());
341     // Record time in seconds over an interval of approximately 1 day.
342     UMA_HISTOGRAM_CUSTOM_COUNTS(
343         "Tabs.Discard.InitialTime2", interval_seconds, 1, 100000, 50);
344   } else {
345     // Not the first discard, so compute time since last discard.
346     TimeDelta interval = TimeTicks::Now() - last_discard_time_;
347     int interval_ms = static_cast<int>(interval.InMilliseconds());
348     // Record time in milliseconds over an interval of approximately 1 day.
349     // Start at 100 ms to get extra resolution in the target 750 ms range.
350     UMA_HISTOGRAM_CUSTOM_COUNTS(
351         "Tabs.Discard.IntervalTime2", interval_ms, 100, 100000 * 1000, 50);
352   }
353   // Record Chrome's concept of system memory usage at the time of the discard.
354   base::SystemMemoryInfoKB memory;
355   if (base::GetSystemMemoryInfo(&memory)) {
356     // TODO(jamescook): Remove this after R25 is deployed to stable. It does
357     // not have sufficient resolution in the 2-4 GB range and does not properly
358     // account for graphics memory on ARM. Replace with MemAllocatedMB below.
359     int mem_anonymous_mb = (memory.active_anon + memory.inactive_anon) / 1024;
360     UMA_HISTOGRAM_MEGABYTES("Tabs.Discard.MemAnonymousMB", mem_anonymous_mb);
361
362     // Record graphics GEM object size in a histogram with 50 MB buckets.
363     int mem_graphics_gem_mb = 0;
364     if (memory.gem_size != -1)
365       mem_graphics_gem_mb = memory.gem_size / 1024 / 1024;
366     RecordLinearHistogram(
367         "Tabs.Discard.MemGraphicsMB", mem_graphics_gem_mb, 2500, 50);
368
369     // Record shared memory (used by renderer/GPU buffers).
370     int mem_shmem_mb = memory.shmem / 1024;
371     RecordLinearHistogram("Tabs.Discard.MemShmemMB", mem_shmem_mb, 2500, 50);
372
373     // On Intel, graphics objects are in anonymous pages, but on ARM they are
374     // not. For a total "allocated count" add in graphics pages on ARM.
375     int mem_allocated_mb = mem_anonymous_mb;
376 #if defined(ARCH_CPU_ARM_FAMILY)
377     mem_allocated_mb += mem_graphics_gem_mb;
378 #endif
379     UMA_HISTOGRAM_CUSTOM_COUNTS(
380         "Tabs.Discard.MemAllocatedMB", mem_allocated_mb, 256, 32768, 50);
381
382     int mem_available_mb =
383         (memory.active_file + memory.inactive_file + memory.free) / 1024;
384     UMA_HISTOGRAM_MEGABYTES("Tabs.Discard.MemAvailableMB", mem_available_mb);
385   }
386   // Set up to record the next interval.
387   last_discard_time_ = TimeTicks::Now();
388 }
389
390 void OomPriorityManager::RecordRecentTabDiscard() {
391   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
392   // If we change the interval we need to change the histogram name.
393   UMA_HISTOGRAM_BOOLEAN("Tabs.Discard.DiscardInLastMinute",
394                         recent_tab_discard_);
395   // Reset for the next interval.
396   recent_tab_discard_ = false;
397 }
398
399 void OomPriorityManager::PurgeBrowserMemory() {
400   // Based on experimental evidence, attempts to free memory from renderers
401   // have been too slow to use in OOM situations (V8 garbage collection) or
402   // do not lead to persistent decreased usage (image/bitmap caches). This
403   // function therefore only targets large blocks of memory in the browser.
404   for (TabContentsIterator it; !it.done(); it.Next()) {
405     WebContents* web_contents = *it;
406     // Screenshots can consume ~5 MB per web contents for platforms that do
407     // touch back/forward.
408     web_contents->GetController().ClearAllScreenshots();
409   }
410   // TODO(jamescook): Are there other things we could flush? Drive metadata?
411 }
412
413 int OomPriorityManager::GetTabCount() const {
414   int tab_count = 0;
415   for (chrome::BrowserIterator it; !it.done(); it.Next())
416     tab_count += it->tab_strip_model()->count();
417   return tab_count;
418 }
419
420 // Returns true if |first| is considered less desirable to be killed
421 // than |second|.
422 bool OomPriorityManager::CompareTabStats(TabStats first,
423                                          TabStats second) {
424   // Being currently selected is most important to protect.
425   if (first.is_selected != second.is_selected)
426     return first.is_selected;
427
428   // Tab with internal web UI like NTP or Settings are good choices to discard,
429   // so protect non-Web UI and let the other conditionals finish the sort.
430   if (first.is_reloadable_ui != second.is_reloadable_ui)
431     return !first.is_reloadable_ui;
432
433   // Being pinned is important to protect.
434   if (first.is_pinned != second.is_pinned)
435     return first.is_pinned;
436
437   // Being an app is important too, as you're the only visible surface in the
438   // window and we don't want to discard that.
439   if (first.is_app != second.is_app)
440     return first.is_app;
441
442   // Protect streaming audio and video conferencing tabs.
443   if (first.is_playing_audio != second.is_playing_audio)
444     return first.is_playing_audio;
445
446   // TODO(jamescook): Incorporate sudden_termination_allowed into the sort
447   // order.  We don't do this now because pages with unload handlers set
448   // sudden_termination_allowed false, and that covers too many common pages
449   // with ad networks and statistics scripts.  Ideally we would like to check
450   // for beforeUnload handlers, which are likely to present a dialog asking
451   // if the user wants to discard state.  crbug.com/123049
452
453   // Being more recently active is more important.
454   return first.last_active > second.last_active;
455 }
456
457 void OomPriorityManager::AdjustFocusedTabScoreOnFileThread() {
458   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
459   base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
460   content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
461       focused_tab_pid_, chrome::kLowestRendererOomScore);
462   pid_to_oom_score_[focused_tab_pid_] = chrome::kLowestRendererOomScore;
463 }
464
465 void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() {
466   BrowserThread::PostTask(
467       BrowserThread::FILE, FROM_HERE,
468       base::Bind(&OomPriorityManager::AdjustFocusedTabScoreOnFileThread,
469                  base::Unretained(this)));
470 }
471
472 void OomPriorityManager::Observe(int type,
473                                  const content::NotificationSource& source,
474                                  const content::NotificationDetails& details) {
475   base::ProcessHandle handle = 0;
476   base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
477   switch (type) {
478     case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: {
479       handle =
480           content::Details<content::RenderProcessHost::RendererClosedDetails>(
481               details)->handle;
482       pid_to_oom_score_.erase(handle);
483       break;
484     }
485     case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
486       handle = content::Source<content::RenderProcessHost>(source)->
487           GetHandle();
488       pid_to_oom_score_.erase(handle);
489       break;
490     }
491     case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
492       bool visible = *content::Details<bool>(details).ptr();
493       if (visible) {
494         focused_tab_pid_ =
495             content::Source<content::RenderWidgetHost>(source).ptr()->
496             GetProcess()->GetHandle();
497
498         // If the currently focused tab already has a lower score, do not
499         // set it. This can happen in case the newly focused tab is script
500         // connected to the previous tab.
501         ProcessScoreMap::iterator it;
502         it = pid_to_oom_score_.find(focused_tab_pid_);
503         if (it == pid_to_oom_score_.end()
504             || it->second != chrome::kLowestRendererOomScore) {
505           // By starting a timer we guarantee that the tab is focused for
506           // certain amount of time. Secondly, it also does not add overhead
507           // to the tab switching time.
508           if (focus_tab_score_adjust_timer_.IsRunning())
509             focus_tab_score_adjust_timer_.Reset();
510           else
511             focus_tab_score_adjust_timer_.Start(FROM_HERE,
512               TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs),
513               this, &OomPriorityManager::OnFocusTabScoreAdjustmentTimeout);
514         }
515       }
516       break;
517     }
518     default:
519       NOTREACHED() << L"Received unexpected notification";
520       break;
521   }
522 }
523
524 // Here we collect most of the information we need to sort the
525 // existing renderers in priority order, and hand out oom_score_adj
526 // scores based on that sort order.
527 //
528 // Things we need to collect on the browser thread (because
529 // TabStripModel isn't thread safe):
530 // 1) whether or not a tab is pinned
531 // 2) last time a tab was selected
532 // 3) is the tab currently selected
533 void OomPriorityManager::AdjustOomPriorities() {
534   if (BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH)->empty())
535     return;
536
537   // Check for a discontinuity in time caused by the machine being suspended.
538   if (!last_adjust_time_.is_null()) {
539     TimeDelta suspend_time = TimeTicks::Now() - last_adjust_time_;
540     if (suspend_time.InSeconds() > kSuspendThresholdSeconds) {
541       // We were probably suspended, move our event timers forward in time so
542       // when we subtract them out later we are counting "uptime".
543       start_time_ += suspend_time;
544       if (!last_discard_time_.is_null())
545         last_discard_time_ += suspend_time;
546     }
547   }
548   last_adjust_time_ = TimeTicks::Now();
549
550   TabStatsList stats_list = GetTabStatsOnUIThread();
551   BrowserThread::PostTask(
552       BrowserThread::FILE, FROM_HERE,
553       base::Bind(&OomPriorityManager::AdjustOomPrioritiesOnFileThread,
554                  base::Unretained(this), stats_list));
555 }
556
557 OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
558   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559   TabStatsList stats_list;
560   stats_list.reserve(32);  // 99% of users have < 30 tabs open
561   bool browser_active = true;
562   const BrowserList* ash_browser_list =
563       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
564   for (BrowserList::const_reverse_iterator browser_iterator =
565            ash_browser_list->begin_last_active();
566        browser_iterator != ash_browser_list->end_last_active();
567        ++browser_iterator) {
568     Browser* browser = *browser_iterator;
569     bool is_browser_for_app = browser->is_app();
570     const TabStripModel* model = browser->tab_strip_model();
571     for (int i = 0; i < model->count(); i++) {
572       WebContents* contents = model->GetWebContentsAt(i);
573       if (!contents->IsCrashed()) {
574         TabStats stats;
575         stats.is_app = is_browser_for_app;
576         stats.is_reloadable_ui =
577             IsReloadableUI(contents->GetLastCommittedURL());
578         stats.is_playing_audio = chrome::IsPlayingAudio(contents);
579         stats.is_pinned = model->IsTabPinned(i);
580         stats.is_selected = browser_active && model->IsTabSelected(i);
581         stats.is_discarded = model->IsTabDiscarded(i);
582         stats.last_active = contents->GetLastActiveTime();
583         stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
584         stats.title = contents->GetTitle();
585         stats.tab_contents_id = IdFromWebContents(contents);
586         stats_list.push_back(stats);
587       }
588     }
589     // We process the active browser window in the first iteration.
590     browser_active = false;
591   }
592   // Sort the data we collected so that least desirable to be
593   // killed is first, most desirable is last.
594   std::sort(stats_list.begin(), stats_list.end(), CompareTabStats);
595   return stats_list;
596 }
597
598 // static
599 std::vector<base::ProcessHandle> OomPriorityManager::GetProcessHandles(
600     const TabStatsList& stats_list) {
601   std::vector<base::ProcessHandle> process_handles;
602   std::set<base::ProcessHandle> already_seen;
603   for (TabStatsList::const_iterator iterator = stats_list.begin();
604        iterator != stats_list.end(); ++iterator) {
605     // stats_list contains entries for already-discarded tabs. If the PID
606     // (renderer_handle) is zero, we don't need to adjust the oom_score.
607     if (iterator->renderer_handle == 0)
608       continue;
609
610     bool inserted = already_seen.insert(iterator->renderer_handle).second;
611     if (!inserted) {
612       // We've already seen this process handle.
613       continue;
614     }
615
616     process_handles.push_back(iterator->renderer_handle);
617   }
618   return process_handles;
619 }
620
621 void OomPriorityManager::AdjustOomPrioritiesOnFileThread(
622     TabStatsList stats_list) {
623   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
624   base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
625
626   // Remove any duplicate PIDs. Order of the list is maintained, so each
627   // renderer process will take on the oom_score_adj of the most important
628   // (least likely to be killed) tab.
629   std::vector<base::ProcessHandle> process_handles =
630       GetProcessHandles(stats_list);
631
632   // Now we assign priorities based on the sorted list.  We're
633   // assigning priorities in the range of kLowestRendererOomScore to
634   // kHighestRendererOomScore (defined in chrome_constants.h).
635   // oom_score_adj takes values from -1000 to 1000.  Negative values
636   // are reserved for system processes, and we want to give some room
637   // below the range we're using to allow for things that want to be
638   // above the renderers in priority, so the defined range gives us
639   // some variation in priority without taking up the whole range.  In
640   // the end, however, it's a pretty arbitrary range to use.  Higher
641   // values are more likely to be killed by the OOM killer.
642   float priority = chrome::kLowestRendererOomScore;
643   const int kPriorityRange = chrome::kHighestRendererOomScore -
644                              chrome::kLowestRendererOomScore;
645   float priority_increment =
646       static_cast<float>(kPriorityRange) / process_handles.size();
647   for (std::vector<base::ProcessHandle>::iterator iterator =
648            process_handles.begin();
649        iterator != process_handles.end(); ++iterator) {
650     int score = static_cast<int>(priority + 0.5f);
651     ProcessScoreMap::iterator it = pid_to_oom_score_.find(*iterator);
652     // If a process has the same score as the newly calculated value,
653     // do not set it.
654     if (it == pid_to_oom_score_.end() || it->second != score) {
655       content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(*iterator,
656                                                                  score);
657       pid_to_oom_score_[*iterator] = score;
658     }
659     priority += priority_increment;
660   }
661 }
662
663 }  // namespace chromeos