CrOS - about:discards shows tab to discard in OOM situations

Added a simple, non-localized debug page to show the output of
the OOM-adjust algorithm, in preparation for discarding a tab in
low memory situations.  See "Out of memory handling" Chrome OS
design doc for details.

BUG=chromium-os:18375
TEST=open 3 tabs and navigate to web pages, wait 10 seconds, then open a tab to "about:discards".  It should list 3 tabs and itself.  Order is unimportant for now.

Review URL: http://codereview.chromium.org/7754002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@100170 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/browser_about_handler.cc b/chrome/browser/browser_about_handler.cc
index e460e21..1e37ef36 100644
--- a/chrome/browser/browser_about_handler.cc
+++ b/chrome/browser/browser_about_handler.cc
@@ -77,6 +77,7 @@
 #include "chrome/browser/chromeos/customization_document.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/version_loader.h"
+#include "chrome/browser/oom_priority_manager.h"
 #include "content/browser/zygote_host_linux.h"
 #elif defined(OS_LINUX)
 #include "content/browser/zygote_host_linux.h"
@@ -158,6 +159,7 @@
   chrome::kChromeUIActiveDownloadsHost,
   chrome::kChromeUIChooseMobileNetworkHost,
   chrome::kChromeUICryptohomeHost,
+  chrome::kChromeUIDiscardsHost,
   chrome::kChromeUIImageBurnerHost,
   chrome::kChromeUIKeyboardOverlayHost,
   chrome::kChromeUILoginHost,
@@ -191,8 +193,9 @@
   chrome::kChromeUISandboxHost,
 #endif
 #if defined(OS_CHROMEOS)
-  chrome::kChromeUINetworkHost,
   chrome::kChromeUICryptohomeHost,
+  chrome::kChromeUIDiscardsHost,
+  chrome::kChromeUINetworkHost,
   chrome::kChromeUIOSCreditsHost,
 #endif
 };
@@ -255,7 +258,7 @@
         request_id_(request_id) {
   }
 
-  virtual void OnDetailsAvailable();
+  virtual void OnDetailsAvailable() OVERRIDE;
 
  private:
   ~AboutMemoryHandler() {}
@@ -443,6 +446,11 @@
 // Html output helper functions
 // TODO(stevenjb): L10N this.
 
+// Helper function to wrap HTML with a tag.
+std::string WrapWithTag(const std::string& tag, const std::string& text) {
+  return "<" + tag + ">" + text + "</" + tag + ">";
+}
+
 // Helper function to wrap Html with <th> tag.
 std::string WrapWithTH(const std::string& text) {
   return "<th>" + text + "</th>";
@@ -670,6 +678,31 @@
   return GetCryptohomeHtmlInfo(refresh);
 }
 
+std::string AboutDiscards() {
+  std::string output;
+  AppendHeader(&output, 0, "About discards");
+  AppendBody(&output);
+  output.append("<h3>About discards</h3>");
+  output.append(
+      "<p>Tabs sorted from most interesting to least interesting. The least "
+      "interesting tab may be discarded if we run out of physical memory.</p>");
+
+  std::vector<string16> titles = browser::OomPriorityManager::GetTabTitles();
+  if (!titles.empty()) {
+    output.append("<ol>");
+    std::vector<string16>::iterator it = titles.begin();
+    for ( ; it != titles.end(); ++it) {
+      std::string title = UTF16ToUTF8(*it);
+      output.append(WrapWithTag("li", title));
+    }
+    output.append("</ol>");
+  } else {
+    output.append("<p>None found.  Wait 10 seconds, then refresh.</p>");
+  }
+  AppendFooter(&output);
+  return output;
+}
+
 #endif  // OS_CHROMEOS
 
 // AboutDnsHandler bounces the request back to the IO thread to collect
@@ -1346,26 +1379,62 @@
                                    int request_id) {
   std::string response;
   std::string host = source_name();
-  if (host == chrome::kChromeUIDNSHost) {
+  // Add your data source here, in alphabetical order.
+  if (host == chrome::kChromeUIChromeURLsHost) {
+    response = ChromeURLs();
+  } else if (host == chrome::kChromeUICreditsHost) {
+    int idr = (path == kCreditsJsPath) ? IDR_CREDITS_JS : IDR_CREDITS_HTML;
+    response = ResourceBundle::GetSharedInstance().GetRawDataResource(
+        idr).as_string();
+#if defined(OS_CHROMEOS)
+  } else if (host == chrome::kChromeUICryptohomeHost) {
+    response = AboutCryptohome(path);
+  } else if (host == chrome::kChromeUIDiscardsHost) {
+    response = AboutDiscards();
+#endif
+  } else if (host == chrome::kChromeUIDNSHost) {
     AboutDnsHandler::Start(this, request_id);
     return;
   } else if (host == chrome::kChromeUIHistogramsHost) {
     response = AboutHistograms(path);
+#if defined(OS_LINUX)
+  } else if (host == chrome::kChromeUILinuxProxyConfigHost) {
+    response = AboutLinuxProxyConfig();
+#endif
   } else if (host == chrome::kChromeUIMemoryHost) {
     response = GetAboutMemoryRedirectResponse(profile());
   } else if (host == chrome::kChromeUIMemoryRedirectHost) {
     AboutMemory(path, this, request_id);
     return;
+#if defined(OS_CHROMEOS)
+  } else if (host == chrome::kChromeUINetworkHost) {
+    response = AboutNetwork(path);
+  } else if (host == chrome::kChromeUIOSCreditsHost) {
+    response = ResourceBundle::GetSharedInstance().GetRawDataResource(
+        IDR_OS_CREDITS_HTML).as_string();
+#endif
+#if defined(OS_LINUX)
+  } else if (host == chrome::kChromeUISandboxHost) {
+    response = AboutSandbox();
+#endif
+  } else if (host == chrome::kChromeUIStatsHost) {
+    response = AboutStats(path);
 #ifdef TRACK_ALL_TASK_OBJECTS
   } else if (host == chrome::kChromeUITaskManagerHost) {
     response = AboutObjects(path);
 #endif
-  } else if (host == chrome::kChromeUIStatsHost) {
-    response = AboutStats(path);
 #if defined(USE_TCMALLOC)
   } else if (host == chrome::kChromeUITCMallocHost) {
     response = AboutTcmalloc();
 #endif
+  } else if (host == chrome::kChromeUITermsHost) {
+#if defined(OS_CHROMEOS)
+    ChromeOSTermsHandler::Start(this, path, request_id);
+    return;
+#else
+    response = ResourceBundle::GetSharedInstance().GetRawDataResource(
+        IDR_TERMS_HTML).as_string();
+#endif
   } else if (host == chrome::kChromeUIVersionHost) {
     if (path == kStringsJsPath) {
 #if defined(OS_CHROMEOS)
@@ -1379,35 +1448,6 @@
     } else {
       response = AboutVersionStaticContent(path);
     }
-  } else if (host == chrome::kChromeUICreditsHost) {
-    int idr = (path == kCreditsJsPath) ? IDR_CREDITS_JS : IDR_CREDITS_HTML;
-    response = ResourceBundle::GetSharedInstance().GetRawDataResource(
-        idr).as_string();
-  } else if (host == chrome::kChromeUIChromeURLsHost) {
-    response = ChromeURLs();
-#if defined(OS_CHROMEOS)
-  } else if (host == chrome::kChromeUIOSCreditsHost) {
-    response = ResourceBundle::GetSharedInstance().GetRawDataResource(
-        IDR_OS_CREDITS_HTML).as_string();
-  } else if (host == chrome::kChromeUINetworkHost) {
-    response = AboutNetwork(path);
-  } else if (host == chrome::kChromeUICryptohomeHost) {
-    response = AboutCryptohome(path);
-#endif
-  } else if (host == chrome::kChromeUITermsHost) {
-#if defined(OS_CHROMEOS)
-    ChromeOSTermsHandler::Start(this, path, request_id);
-    return;
-#else
-    response = ResourceBundle::GetSharedInstance().GetRawDataResource(
-        IDR_TERMS_HTML).as_string();
-#endif
-#if defined(OS_LINUX)
-  } else if (host == chrome::kChromeUILinuxProxyConfigHost) {
-    response = AboutLinuxProxyConfig();
-  } else if (host == chrome::kChromeUISandboxHost) {
-    response = AboutSandbox();
-#endif
   }
 
   FinishDataRequest(response, request_id);
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index cbac055..a069e422 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -701,7 +701,7 @@
       shutdown_watcher_(new ShutdownWatcherHelper()) {
   // If we're running tests (ui_task is non-null).
   if (parameters.ui_task)
-    browser_defaults::enable_help_app = false;  
+    browser_defaults::enable_help_app = false;
 }
 
 ChromeBrowserMainParts::~ChromeBrowserMainParts() {
@@ -1765,11 +1765,8 @@
   // Run the Out of Memory priority manager while in this scope.  Wait
   // until here to start so that we give the most amount of time for
   // the other services to start up before we start adjusting the oom
-  // priority.  In reality, it doesn't matter much where in this scope
-  // this is started, but it must be started in this scope so it will
-  // also be terminated when this scope exits.
-  scoped_ptr<browser::OomPriorityManager> oom_priority_manager(
-      new browser::OomPriorityManager);
+  // priority.
+  browser::OomPriorityManager::Create();
 #endif
 
   // Create the instance of the cloud print proxy service so that it can launch
@@ -1899,13 +1896,16 @@
   }
 #endif
 
+#if defined(OS_CHROMEOS)
+  browser::OomPriorityManager::Destroy();
+#endif
+
   // Some tests don't set parameters.ui_task, so they started translate
   // language fetch that was never completed so we need to cleanup here
   // otherwise it will be done by the destructor in a wrong thread.
   if (parameters().ui_task == NULL && translate_manager != NULL)
     translate_manager->CleanupPendingUlrFetcher();
 
-
   process_singleton.Cleanup();
 
   // Stop all tasks that might run on WatchDogThread.
diff --git a/chrome/browser/oom_priority_manager.cc b/chrome/browser/oom_priority_manager.cc
index d3db051..24832c1 100644
--- a/chrome/browser/oom_priority_manager.cc
+++ b/chrome/browser/oom_priority_manager.cc
@@ -4,11 +4,15 @@
 
 #include "chrome/browser/oom_priority_manager.h"
 
-#include <list>
+#include <algorithm>
+#include <vector>
 
 #include "base/process.h"
 #include "base/process_util.h"
+#include "base/string16.h"
+#include "base/synchronization/lock.h"
 #include "base/threading/thread.h"
+#include "base/timer.h"
 #include "build/build_config.h"
 #include "chrome/browser/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -38,31 +42,86 @@
 // "equal".
 #define BUCKET_INTERVAL_MINUTES 10
 
-OomPriorityManager::OomPriorityManager() {
+class OomPriorityManagerImpl {
+ public:
+  OomPriorityManagerImpl();
+  ~OomPriorityManagerImpl();
+
+  void StartTimer();
+  void StopTimer();
+
+  std::vector<string16> GetTabTitles();
+
+  struct RendererStats {
+    bool is_pinned;
+    bool is_selected;
+    base::TimeTicks last_selected;
+    size_t memory_used;
+    base::ProcessHandle renderer_handle;
+    string16 title;
+  };
+  typedef std::vector<RendererStats> StatsList;
+
+  // Posts DoAdjustOomPriorities task to the file thread.  Called when
+  // the timer fires.
+  void AdjustOomPriorities();
+
+  // Called by AdjustOomPriorities.  Runs on the file thread.
+  void DoAdjustOomPriorities();
+
+  static bool CompareRendererStats(RendererStats first, RendererStats second);
+
+  base::RepeatingTimer<OomPriorityManagerImpl> timer_;
+  // renderer_stats_ is used on both UI and file threads.
+  base::Lock renderer_stats_lock_;
+  StatsList renderer_stats_;
+
+  DISALLOW_COPY_AND_ASSIGN(OomPriorityManagerImpl);
+};
+
+}  // namespace browser
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(browser::OomPriorityManagerImpl);
+
+namespace browser {
+
+OomPriorityManagerImpl::OomPriorityManagerImpl() {
+  renderer_stats_.reserve(32);  // 99% of users have < 30 tabs open
   StartTimer();
 }
 
-OomPriorityManager::~OomPriorityManager() {
+OomPriorityManagerImpl::~OomPriorityManagerImpl() {
   StopTimer();
 }
 
-void OomPriorityManager::StartTimer() {
+void OomPriorityManagerImpl::StartTimer() {
   if (!timer_.IsRunning()) {
     timer_.Start(FROM_HERE,
                  TimeDelta::FromSeconds(ADJUSTMENT_INTERVAL_SECONDS),
                  this,
-                 &OomPriorityManager::AdjustOomPriorities);
+                 &OomPriorityManagerImpl::AdjustOomPriorities);
   }
 }
 
-void OomPriorityManager::StopTimer() {
+void OomPriorityManagerImpl::StopTimer() {
   timer_.Stop();
 }
 
+std::vector<string16> OomPriorityManagerImpl::GetTabTitles() {
+  base::AutoLock renderer_stats_autolock(renderer_stats_lock_);
+  std::vector<string16> titles;
+  titles.reserve(renderer_stats_.size());
+  StatsList::iterator it = renderer_stats_.begin();
+  for ( ; it != renderer_stats_.end(); ++it) {
+    titles.push_back(it->title);
+  }
+  return titles;
+}
+
 // Returns true if |first| is considered less desirable to be killed
 // than |second|.
-bool OomPriorityManager::CompareRendererStats(RendererStats first,
-                                              RendererStats second) {
+bool OomPriorityManagerImpl::CompareRendererStats(RendererStats first,
+                                                  RendererStats second) {
   // The size of the slop in comparing activation times.  [This is
   // allocated here to avoid static initialization at startup time.]
   static const int64 kTimeBucketInterval =
@@ -101,36 +160,41 @@
 // 4) size in memory of a tab
 // But we do that in DoAdjustOomPriorities on the FILE thread so that
 // we avoid jank, because it accesses /proc.
-void OomPriorityManager::AdjustOomPriorities() {
+void OomPriorityManagerImpl::AdjustOomPriorities() {
   if (BrowserList::size() == 0)
     return;
 
-  StatsList renderer_stats;
-  for (BrowserList::const_iterator browser_iterator = BrowserList::begin();
-       browser_iterator != BrowserList::end(); ++browser_iterator) {
-    Browser* browser = *browser_iterator;
-    const TabStripModel* model = browser->tabstrip_model();
-    for (int i = 0; i < model->count(); i++) {
-      TabContents* contents = model->GetTabContentsAt(i)->tab_contents();
-      RendererStats stats;
-      stats.last_selected = contents->last_selected_time();
-      stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
-      stats.is_pinned = model->IsTabPinned(i);
-      stats.memory_used = 0;  // This gets calculated in DoAdjustOomPriorities.
-      stats.is_selected = model->IsTabSelected(i);
-      renderer_stats.push_back(stats);
+  {
+    base::AutoLock renderer_stats_autolock(renderer_stats_lock_);
+    renderer_stats_.clear();
+    for (BrowserList::const_iterator browser_iterator = BrowserList::begin();
+         browser_iterator != BrowserList::end(); ++browser_iterator) {
+      Browser* browser = *browser_iterator;
+      const TabStripModel* model = browser->tabstrip_model();
+      for (int i = 0; i < model->count(); i++) {
+        TabContents* contents = model->GetTabContentsAt(i)->tab_contents();
+        RendererStats stats;
+        stats.last_selected = contents->last_selected_time();
+        stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
+        stats.is_pinned = model->IsTabPinned(i);
+        stats.memory_used = 0;  // Calculated in DoAdjustOomPriorities.
+        stats.is_selected = model->IsTabSelected(i);
+        stats.title = contents->GetTitle();
+        renderer_stats_.push_back(stats);
+      }
     }
   }
 
   BrowserThread::PostTask(
       BrowserThread::FILE, FROM_HERE,
-      NewRunnableMethod(this, &OomPriorityManager::DoAdjustOomPriorities,
-                        renderer_stats));
+      NewRunnableMethod(this, &OomPriorityManagerImpl::DoAdjustOomPriorities));
 }
 
-void OomPriorityManager::DoAdjustOomPriorities(StatsList renderer_stats) {
-  for (StatsList::iterator stats_iter = renderer_stats.begin();
-       stats_iter != renderer_stats.end(); ++stats_iter) {
+void OomPriorityManagerImpl::DoAdjustOomPriorities() {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+  base::AutoLock renderer_stats_autolock(renderer_stats_lock_);
+  for (StatsList::iterator stats_iter = renderer_stats_.begin();
+       stats_iter != renderer_stats_.end(); ++stats_iter) {
     scoped_ptr<ProcessMetrics> metrics(ProcessMetrics::CreateProcessMetrics(
         stats_iter->renderer_handle));
 
@@ -150,7 +214,9 @@
 
   // Now we sort the data we collected so that least desirable to be
   // killed is first, most desirable is last.
-  renderer_stats.sort(OomPriorityManager::CompareRendererStats);
+  std::sort(renderer_stats_.begin(),
+            renderer_stats_.end(),
+            OomPriorityManagerImpl::CompareRendererStats);
 
   // Now we assign priorities based on the sorted list.  We're
   // assigning priorities in the range of kLowestRendererOomScore to
@@ -170,11 +236,11 @@
   const int kPriorityRange = chrome::kHighestRendererOomScore -
                              chrome::kLowestRendererOomScore;
   float priority_increment =
-      static_cast<float>(kPriorityRange) / renderer_stats.size();
+      static_cast<float>(kPriorityRange) / renderer_stats_.size();
   float priority = chrome::kLowestRendererOomScore;
   std::set<base::ProcessHandle> already_seen;
-  for (StatsList::iterator iterator = renderer_stats.begin();
-       iterator != renderer_stats.end(); ++iterator) {
+  for (StatsList::iterator iterator = renderer_stats_.begin();
+       iterator != renderer_stats_.end(); ++iterator) {
     if (already_seen.find(iterator->renderer_handle) == already_seen.end()) {
       already_seen.insert(iterator->renderer_handle);
       ZygoteHost::GetInstance()->AdjustRendererOOMScore(
@@ -184,4 +250,27 @@
   }
 }
 
+//////////////////////////////////////////////////////////////////////////////
+// Pointer-to-impl glue
+
+OomPriorityManagerImpl* OomPriorityManager::impl_ = NULL;
+
+// static
+void OomPriorityManager::Create() {
+  impl_ = new OomPriorityManagerImpl();
+}
+
+// static
+void OomPriorityManager::Destroy() {
+  delete impl_;
+  impl_ = NULL;
+}
+
+// static
+std::vector<string16> OomPriorityManager::GetTabTitles() {
+  if (!impl_)
+    return std::vector<string16>();
+  return impl_->GetTabTitles();
+}
+
 }  // namespace browser
diff --git a/chrome/browser/oom_priority_manager.h b/chrome/browser/oom_priority_manager.h
index 3090947d..a5eeef6 100644
--- a/chrome/browser/oom_priority_manager.h
+++ b/chrome/browser/oom_priority_manager.h
@@ -5,13 +5,14 @@
 #ifndef CHROME_BROWSER_OOM_PRIORITY_MANAGER_H_
 #define CHROME_BROWSER_OOM_PRIORITY_MANAGER_H_
 
-#include <list>
+#include <vector>
 
-#include "base/timer.h"
-#include "base/process.h"
+#include "base/string16.h"
 
 namespace browser {
 
+class OomPriorityManagerImpl;
+
 // The OomPriorityManager periodically checks (see
 // ADJUSTMENT_INTERVAL_SECONDS in the source) the status of renderers
 // and adjusts the out of memory (OOM) adjustment value (in
@@ -26,37 +27,18 @@
 // them, as no two tabs will have exactly the same idle time.
 class OomPriorityManager {
  public:
-  OomPriorityManager();
-  ~OomPriorityManager();
+  // We need to explicitly manage our destruction, so don't use Singleton.
+  static void Create();
+  static void Destroy();
+
+  // Returns list of tab titles sorted from most interesting (don't kill)
+  // to least interesting (OK to kill).
+  static std::vector<string16> GetTabTitles();
 
  private:
-  struct RendererStats {
-    bool is_pinned;
-    bool is_selected;
-    base::TimeTicks last_selected;
-    size_t memory_used;
-    base::ProcessHandle renderer_handle;
-  };
-  typedef std::list<RendererStats> StatsList;
-
-  void StartTimer();
-  void StopTimer();
-
-  // Posts DoAdjustOomPriorities task to the file thread.  Called when
-  // the timer fires.
-  void AdjustOomPriorities();
-
-  // Called by AdjustOomPriorities.  Runs on the file thread.
-  void DoAdjustOomPriorities(StatsList list);
-
-  static bool CompareRendererStats(RendererStats first, RendererStats second);
-
-  base::RepeatingTimer<OomPriorityManager> timer_;
-
-  DISALLOW_COPY_AND_ASSIGN(OomPriorityManager);
+  static OomPriorityManagerImpl* impl_;
 };
-}  // namespace browser
 
-DISABLE_RUNNABLE_METHOD_REFCOUNT(browser::OomPriorityManager);
+}  // namespace browser
 
 #endif  // CHROME_BROWSER_OOM_PRIORITY_MANAGER_H_
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 78e3298..4050249 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -69,6 +69,7 @@
 const char kChromeUIChooseMobileNetworkURL[] =
     "chrome://choose-mobile-network/";
 const char kChromeUICollectedCookiesURL[] = "chrome://collected-cookies/";
+const char kChromeUIDiscardsURL[] = "chrome://discards/";
 const char kChromeUIEnterpriseEnrollmentURL[] =
     "chrome://enterprise-enrollment/";
 const char kChromeUIHttpAuthURL[] = "chrome://http-auth/";
@@ -176,6 +177,7 @@
 const char kChromeUIChooseMobileNetworkHost[] = "choose-mobile-network";
 const char kChromeUICollectedCookiesHost[] = "collected-cookies";
 const char kChromeUICryptohomeHost[] = "cryptohome";
+const char kChromeUIDiscardsHost[] = "discards";
 const char kChromeUIEnterpriseEnrollmentHost[] = "enterprise-enrollment";
 const char kChromeUIHttpAuthHost[] = "http-auth";
 const char kChromeUIImageBurnerHost[] = "imageburner";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index e6f8313..670bba4 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -70,6 +70,7 @@
 extern const char kChromeUIActiveDownloadsURL[];
 extern const char kChromeUIChooseMobileNetworkURL[];
 extern const char kChromeUICollectedCookiesURL[];
+extern const char kChromeUIDiscardsURL[];
 extern const char kChromeUIHttpAuthURL[];
 extern const char kChromeUIImageBurnerURL[];
 extern const char kChromeUIKeyboardOverlayURL[];
@@ -174,6 +175,7 @@
 extern const char kChromeUIChooseMobileNetworkHost[];
 extern const char kChromeUICollectedCookiesHost[];
 extern const char kChromeUICryptohomeHost[];
+extern const char kChromeUIDiscardsHost[];
 extern const char kChromeUIEnterpriseEnrollmentHost[];
 extern const char kChromeUIHttpAuthHost[];
 extern const char kChromeUIImageBurnerHost[];