oom_priority_manager.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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_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
51using base::TimeDelta;
52using base::TimeTicks;
53using content::BrowserThread;
54using content::WebContents;
55
56namespace chromeos {
57
58namespace {
59
60// Record a size in megabytes, over a potential interval up to 32 GB.
61#define 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.
66const 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.
71const 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.
75const 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.
81const 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.
85int64 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.
91void 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.
112class 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
127OomMemoryDetails::OomMemoryDetails() {
128  AddRef();  // Released in OnDetailsAvailable().
129  start_time_ = TimeTicks::Now();
130}
131
132void 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
159OomPriorityManager::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
170OomPriorityManager::TabStats::~TabStats() {
171}
172
173OomPriorityManager::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
189OomPriorityManager::~OomPriorityManager() {
190  Stop();
191}
192
193void 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
212void OomPriorityManager::Stop() {
213  timer_.Stop();
214  recent_tab_discard_timer_.Stop();
215  if (low_memory_observer_.get())
216    low_memory_observer_->Stop();
217}
218
219std::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.
245bool 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
261void 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
272bool 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* 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
292bool 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
317void 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
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
333  // TODO(jamescook): If the time stats prove too noisy, then divide up users
334  // based on how heavily they use Chrome using tab count as a proxy.
335  // Bin into <= 1, <= 2, <= 4, <= 8, etc.
336  if (last_discard_time_.is_null()) {
337    // This is the first discard this session.
338    TimeDelta interval = TimeTicks::Now() - start_time_;
339    int interval_seconds = static_cast<int>(interval.InSeconds());
340    // Record time in seconds over an interval of approximately 1 day.
341    UMA_HISTOGRAM_CUSTOM_COUNTS(
342        "Tabs.Discard.InitialTime2", interval_seconds, 1, 100000, 50);
343  } else {
344    // Not the first discard, so compute time since last discard.
345    TimeDelta interval = TimeTicks::Now() - last_discard_time_;
346    int interval_ms = static_cast<int>(interval.InMilliseconds());
347    // Record time in milliseconds over an interval of approximately 1 day.
348    // Start at 100 ms to get extra resolution in the target 750 ms range.
349    UMA_HISTOGRAM_CUSTOM_COUNTS(
350        "Tabs.Discard.IntervalTime2", interval_ms, 100, 100000 * 1000, 50);
351  }
352  // Record Chrome's concept of system memory usage at the time of the discard.
353  base::SystemMemoryInfoKB memory;
354  if (base::GetSystemMemoryInfo(&memory)) {
355    // TODO(jamescook): Remove this after R25 is deployed to stable. It does
356    // not have sufficient resolution in the 2-4 GB range and does not properly
357    // account for graphics memory on ARM. Replace with MemAllocatedMB below.
358    int mem_anonymous_mb = (memory.active_anon + memory.inactive_anon) / 1024;
359    HISTOGRAM_MEGABYTES("Tabs.Discard.MemAnonymousMB", mem_anonymous_mb);
360
361    // Record graphics GEM object size in a histogram with 50 MB buckets.
362    int mem_graphics_gem_mb = 0;
363    if (memory.gem_size != -1)
364      mem_graphics_gem_mb = memory.gem_size / 1024 / 1024;
365    RecordLinearHistogram(
366        "Tabs.Discard.MemGraphicsMB", mem_graphics_gem_mb, 2500, 50);
367
368    // Record shared memory (used by renderer/GPU buffers).
369    int mem_shmem_mb = memory.shmem / 1024;
370    RecordLinearHistogram("Tabs.Discard.MemShmemMB", mem_shmem_mb, 2500, 50);
371
372    // On Intel, graphics objects are in anonymous pages, but on ARM they are
373    // not. For a total "allocated count" add in graphics pages on ARM.
374    int mem_allocated_mb = mem_anonymous_mb;
375#if defined(ARCH_CPU_ARM_FAMILY)
376    mem_allocated_mb += mem_graphics_gem_mb;
377#endif
378    UMA_HISTOGRAM_CUSTOM_COUNTS(
379        "Tabs.Discard.MemAllocatedMB", mem_allocated_mb, 256, 32768, 50);
380
381    int mem_available_mb =
382        (memory.active_file + memory.inactive_file + memory.free) / 1024;
383    HISTOGRAM_MEGABYTES("Tabs.Discard.MemAvailableMB", mem_available_mb);
384  }
385  // Set up to record the next interval.
386  last_discard_time_ = TimeTicks::Now();
387}
388
389void OomPriorityManager::RecordRecentTabDiscard() {
390  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
391  // If we change the interval we need to change the histogram name.
392  UMA_HISTOGRAM_BOOLEAN("Tabs.Discard.DiscardInLastMinute",
393                        recent_tab_discard_);
394  // Reset for the next interval.
395  recent_tab_discard_ = false;
396}
397
398void OomPriorityManager::PurgeBrowserMemory() {
399  // Based on experimental evidence, attempts to free memory from renderers
400  // have been too slow to use in OOM situations (V8 garbage collection) or
401  // do not lead to persistent decreased usage (image/bitmap caches). This
402  // function therefore only targets large blocks of memory in the browser.
403  for (TabContentsIterator it; !it.done(); it.Next()) {
404    WebContents* web_contents = *it;
405    // Screenshots can consume ~5 MB per web contents for platforms that do
406    // touch back/forward.
407    web_contents->GetController().ClearAllScreenshots();
408  }
409  // TODO(jamescook): Are there other things we could flush? Drive metadata?
410}
411
412int OomPriorityManager::GetTabCount() const {
413  int tab_count = 0;
414  for (chrome::BrowserIterator it; !it.done(); it.Next())
415    tab_count += it->tab_strip_model()->count();
416  return tab_count;
417}
418
419// Returns true if |first| is considered less desirable to be killed
420// than |second|.
421bool OomPriorityManager::CompareTabStats(TabStats first,
422                                         TabStats second) {
423  // Being currently selected is most important to protect.
424  if (first.is_selected != second.is_selected)
425    return first.is_selected;
426
427  // Tab with internal web UI like NTP or Settings are good choices to discard,
428  // so protect non-Web UI and let the other conditionals finish the sort.
429  if (first.is_reloadable_ui != second.is_reloadable_ui)
430    return !first.is_reloadable_ui;
431
432  // Being pinned is important to protect.
433  if (first.is_pinned != second.is_pinned)
434    return first.is_pinned;
435
436  // Being an app is important too, as you're the only visible surface in the
437  // window and we don't want to discard that.
438  if (first.is_app != second.is_app)
439    return first.is_app;
440
441  // Protect streaming audio and video conferencing tabs.
442  if (first.is_playing_audio != second.is_playing_audio)
443    return first.is_playing_audio;
444
445  // TODO(jamescook): Incorporate sudden_termination_allowed into the sort
446  // order.  We don't do this now because pages with unload handlers set
447  // sudden_termination_allowed false, and that covers too many common pages
448  // with ad networks and statistics scripts.  Ideally we would like to check
449  // for beforeUnload handlers, which are likely to present a dialog asking
450  // if the user wants to discard state.  crbug.com/123049
451
452  // Being more recently active is more important.
453  return first.last_active > second.last_active;
454}
455
456void OomPriorityManager::AdjustFocusedTabScoreOnFileThread() {
457  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
458  base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
459  content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(
460      focused_tab_pid_, chrome::kLowestRendererOomScore);
461  pid_to_oom_score_[focused_tab_pid_] = chrome::kLowestRendererOomScore;
462}
463
464void OomPriorityManager::OnFocusTabScoreAdjustmentTimeout() {
465  BrowserThread::PostTask(
466      BrowserThread::FILE, FROM_HERE,
467      base::Bind(&OomPriorityManager::AdjustFocusedTabScoreOnFileThread,
468                 base::Unretained(this)));
469}
470
471void OomPriorityManager::Observe(int type,
472                                 const content::NotificationSource& source,
473                                 const content::NotificationDetails& details) {
474  base::ProcessHandle handle = 0;
475  base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
476  switch (type) {
477    case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: {
478      handle =
479          content::Details<content::RenderProcessHost::RendererClosedDetails>(
480              details)->handle;
481      pid_to_oom_score_.erase(handle);
482      break;
483    }
484    case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
485      handle = content::Source<content::RenderProcessHost>(source)->
486          GetHandle();
487      pid_to_oom_score_.erase(handle);
488      break;
489    }
490    case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
491      bool visible = *content::Details<bool>(details).ptr();
492      if (visible) {
493        focused_tab_pid_ =
494            content::Source<content::RenderWidgetHost>(source).ptr()->
495            GetProcess()->GetHandle();
496
497        // If the currently focused tab already has a lower score, do not
498        // set it. This can happen in case the newly focused tab is script
499        // connected to the previous tab.
500        ProcessScoreMap::iterator it;
501        it = pid_to_oom_score_.find(focused_tab_pid_);
502        if (it == pid_to_oom_score_.end()
503            || it->second != chrome::kLowestRendererOomScore) {
504          // By starting a timer we guarantee that the tab is focused for
505          // certain amount of time. Secondly, it also does not add overhead
506          // to the tab switching time.
507          if (focus_tab_score_adjust_timer_.IsRunning())
508            focus_tab_score_adjust_timer_.Reset();
509          else
510            focus_tab_score_adjust_timer_.Start(FROM_HERE,
511              TimeDelta::FromMilliseconds(kFocusedTabScoreAdjustIntervalMs),
512              this, &OomPriorityManager::OnFocusTabScoreAdjustmentTimeout);
513        }
514      }
515      break;
516    }
517    default:
518      NOTREACHED() << L"Received unexpected notification";
519      break;
520  }
521}
522
523// Here we collect most of the information we need to sort the
524// existing renderers in priority order, and hand out oom_score_adj
525// scores based on that sort order.
526//
527// Things we need to collect on the browser thread (because
528// TabStripModel isn't thread safe):
529// 1) whether or not a tab is pinned
530// 2) last time a tab was selected
531// 3) is the tab currently selected
532void OomPriorityManager::AdjustOomPriorities() {
533  if (BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH)->empty())
534    return;
535
536  // Check for a discontinuity in time caused by the machine being suspended.
537  if (!last_adjust_time_.is_null()) {
538    TimeDelta suspend_time = TimeTicks::Now() - last_adjust_time_;
539    if (suspend_time.InSeconds() > kSuspendThresholdSeconds) {
540      // We were probably suspended, move our event timers forward in time so
541      // when we subtract them out later we are counting "uptime".
542      start_time_ += suspend_time;
543      if (!last_discard_time_.is_null())
544        last_discard_time_ += suspend_time;
545    }
546  }
547  last_adjust_time_ = TimeTicks::Now();
548
549  TabStatsList stats_list = GetTabStatsOnUIThread();
550  BrowserThread::PostTask(
551      BrowserThread::FILE, FROM_HERE,
552      base::Bind(&OomPriorityManager::AdjustOomPrioritiesOnFileThread,
553                 base::Unretained(this), stats_list));
554}
555
556OomPriorityManager::TabStatsList OomPriorityManager::GetTabStatsOnUIThread() {
557  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
558  TabStatsList stats_list;
559  stats_list.reserve(32);  // 99% of users have < 30 tabs open
560  bool browser_active = true;
561  const BrowserList* ash_browser_list =
562      BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
563  for (BrowserList::const_reverse_iterator browser_iterator =
564           ash_browser_list->begin_last_active();
565       browser_iterator != ash_browser_list->end_last_active();
566       ++browser_iterator) {
567    Browser* browser = *browser_iterator;
568    bool is_browser_for_app = browser->is_app();
569    const TabStripModel* model = browser->tab_strip_model();
570    for (int i = 0; i < model->count(); i++) {
571      WebContents* contents = model->GetWebContentsAt(i);
572      if (!contents->IsCrashed()) {
573        TabStats stats;
574        stats.is_app = is_browser_for_app;
575        stats.is_reloadable_ui =
576            IsReloadableUI(contents->GetLastCommittedURL());
577        stats.is_playing_audio = chrome::IsPlayingAudio(contents);
578        stats.is_pinned = model->IsTabPinned(i);
579        stats.is_selected = browser_active && model->IsTabSelected(i);
580        stats.is_discarded = model->IsTabDiscarded(i);
581        stats.last_active = contents->GetLastActiveTime();
582        stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
583        stats.title = contents->GetTitle();
584        stats.tab_contents_id = IdFromWebContents(contents);
585        stats_list.push_back(stats);
586      }
587    }
588    // We process the active browser window in the first iteration.
589    browser_active = false;
590  }
591  // Sort the data we collected so that least desirable to be
592  // killed is first, most desirable is last.
593  std::sort(stats_list.begin(), stats_list.end(), CompareTabStats);
594  return stats_list;
595}
596
597// static
598std::vector<base::ProcessHandle> OomPriorityManager::GetProcessHandles(
599    const TabStatsList& stats_list) {
600  std::vector<base::ProcessHandle> process_handles;
601  std::set<base::ProcessHandle> already_seen;
602  for (TabStatsList::const_iterator iterator = stats_list.begin();
603       iterator != stats_list.end(); ++iterator) {
604    // stats_list contains entries for already-discarded tabs. If the PID
605    // (renderer_handle) is zero, we don't need to adjust the oom_score.
606    if (iterator->renderer_handle == 0)
607      continue;
608
609    bool inserted = already_seen.insert(iterator->renderer_handle).second;
610    if (!inserted) {
611      // We've already seen this process handle.
612      continue;
613    }
614
615    process_handles.push_back(iterator->renderer_handle);
616  }
617  return process_handles;
618}
619
620void OomPriorityManager::AdjustOomPrioritiesOnFileThread(
621    TabStatsList stats_list) {
622  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
623  base::AutoLock pid_to_oom_score_autolock(pid_to_oom_score_lock_);
624
625  // Remove any duplicate PIDs. Order of the list is maintained, so each
626  // renderer process will take on the oom_score_adj of the most important
627  // (least likely to be killed) tab.
628  std::vector<base::ProcessHandle> process_handles =
629      GetProcessHandles(stats_list);
630
631  // Now we assign priorities based on the sorted list.  We're
632  // assigning priorities in the range of kLowestRendererOomScore to
633  // kHighestRendererOomScore (defined in chrome_constants.h).
634  // oom_score_adj takes values from -1000 to 1000.  Negative values
635  // are reserved for system processes, and we want to give some room
636  // below the range we're using to allow for things that want to be
637  // above the renderers in priority, so the defined range gives us
638  // some variation in priority without taking up the whole range.  In
639  // the end, however, it's a pretty arbitrary range to use.  Higher
640  // values are more likely to be killed by the OOM killer.
641  float priority = chrome::kLowestRendererOomScore;
642  const int kPriorityRange = chrome::kHighestRendererOomScore -
643                             chrome::kLowestRendererOomScore;
644  float priority_increment =
645      static_cast<float>(kPriorityRange) / process_handles.size();
646  for (std::vector<base::ProcessHandle>::iterator iterator =
647           process_handles.begin();
648       iterator != process_handles.end(); ++iterator) {
649    int score = static_cast<int>(priority + 0.5f);
650    ProcessScoreMap::iterator it = pid_to_oom_score_.find(*iterator);
651    // If a process has the same score as the newly calculated value,
652    // do not set it.
653    if (it == pid_to_oom_score_.end() || it->second != score) {
654      content::ZygoteHost::GetInstance()->AdjustRendererOOMScore(*iterator,
655                                                                 score);
656      pid_to_oom_score_[*iterator] = score;
657    }
658    priority += priority_increment;
659  }
660}
661
662}  // namespace chromeos
663