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