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