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