Home | History | Annotate | Download | only in browser
      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