1 // Copyright (c) 2011 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/threading/thread.h" 12 #include "build/build_config.h" 13 #include "chrome/browser/tabs/tab_strip_model.h" 14 #include "chrome/browser/ui/browser_list.h" 15 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 16 #include "content/browser/browser_thread.h" 17 #include "content/browser/renderer_host/render_process_host.h" 18 #include "content/browser/tab_contents/tab_contents.h" 19 #include "content/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 25 using base::TimeDelta; 26 using base::TimeTicks; 27 using base::ProcessHandle; 28 using base::ProcessMetrics; 29 30 namespace 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 40 OomPriorityManager::OomPriorityManager() { 41 StartTimer(); 42 } 43 44 OomPriorityManager::~OomPriorityManager() { 45 StopTimer(); 46 } 47 48 void OomPriorityManager::StartTimer() { 49 if (!timer_.IsRunning()) { 50 timer_.Start(TimeDelta::FromSeconds(ADJUSTMENT_INTERVAL_SECONDS), 51 this, 52 &OomPriorityManager::AdjustOomPriorities); 53 } 54 } 55 56 void OomPriorityManager::StopTimer() { 57 timer_.Stop(); 58 } 59 60 // Returns true if |first| is considered less desirable to be killed 61 // than |second|. 62 bool 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. 97 void 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 123 void 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 ZygoteHost::GetInstance()->AdjustRendererOOMScore( 170 iterator->renderer_handle, 171 static_cast<int>(priority + 0.5f)); 172 priority += priority_increment; 173 } 174 } 175 } 176 177 } // namespace browser 178