Index: content/browser/gpu/gpu_memory_test.cc |
diff --git a/content/browser/gpu/gpu_memory_test.cc b/content/browser/gpu/gpu_memory_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7eecc3b3cdbcf9fd0531b273f55ce296e6b11f74 |
--- /dev/null |
+++ b/content/browser/gpu/gpu_memory_test.cc |
@@ -0,0 +1,317 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/callback.h" |
+#include "base/command_line.h" |
+#include "base/path_service.h" |
+#include "content/public/browser/gpu_data_manager.h" |
+#include "content/public/browser/gpu_data_manager_observer.h" |
+#include "content/public/browser/web_contents.h" |
+#include "content/public/common/content_paths.h" |
+#include "content/public/common/content_switches.h" |
+#include "content/public/test/browser_test_utils.h" |
+#include "content/public/test/test_utils.h" |
+#include "content/shell/shell.h" |
+#include "content/test/content_browser_test.h" |
+#include "content/test/content_browser_test_utils.h" |
+#include "gpu/command_buffer/service/gpu_switches.h" |
+#include "net/base/net_util.h" |
+ |
+namespace { |
+ |
+// Run the tests with a memory limit of 256MB, and give |
+// and extra 16MB of wiggle-room for over-allocation. |
piman
2013/01/08 00:04:17
nit: s/16/24/ given the value below
ccameron
2013/01/08 00:41:24
Done.
|
+const char* kMemoryLimitSwitch = "256"; |
+const size_t kMemoryLimit = 256; |
+const size_t kSingleTabLimit = 128; |
+const size_t kSingleTabMinimum = 64; |
+const size_t kWiggleRoom = 24; |
+ |
+// Observer to report GPU memory usage when requested. |
+class GpuMemoryBytesAllocatedObserver |
+ : public content::GpuDataManagerObserver { |
+ public: |
+ GpuMemoryBytesAllocatedObserver() |
+ : bytes_allocated_(0) { |
+ } |
+ |
+ virtual ~GpuMemoryBytesAllocatedObserver() { |
+ } |
+ |
+ virtual void OnGpuInfoUpdate() OVERRIDE {} |
+ |
+ virtual void OnVideoMemoryUsageStatsUpdate( |
+ const content::GPUVideoMemoryUsageStats& video_memory_usage_stats) |
+ OVERRIDE { |
+ bytes_allocated_ = video_memory_usage_stats.bytes_allocated; |
+ message_loop_runner_->Quit(); |
+ } |
+ |
+ size_t GetBytesAllocated() { |
+ message_loop_runner_ = new content::MessageLoopRunner; |
+ content::GpuDataManager::GetInstance()->AddObserver(this); |
+ content::GpuDataManager::GetInstance()-> |
+ RequestVideoMemoryUsageStatsUpdate(); |
+ message_loop_runner_->Run(); |
+ // Will break after OnVideoMemoryUsageStatsUpdate |
piman
2013/01/08 00:04:17
nit: not sure what this comment means. Consider re
ccameron
2013/01/08 00:41:24
This was to say the Run() call will stop after the
|
+ content::GpuDataManager::GetInstance()->RemoveObserver(this); |
+ message_loop_runner_ = NULL; |
+ return bytes_allocated_; |
+ } |
+ |
+ private: |
+ size_t bytes_allocated_; |
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_; |
+}; |
+ |
+class GpuMemoryTest : public content::ContentBrowserTest { |
+ public: |
+ GpuMemoryTest() : allow_tests_to_run_(false) { |
+ } |
+ virtual ~GpuMemoryTest() { |
+ } |
+ |
+ virtual void SetUpInProcessBrowserTestFixture() { |
+ FilePath test_dir; |
+ ASSERT_TRUE(PathService::Get(content::DIR_TEST_DATA, &test_dir)); |
+ gpu_test_dir_ = test_dir.AppendASCII("gpu"); |
+ } |
+ |
+ virtual void SetUpCommandLine(CommandLine* command_line) { |
+ command_line->AppendSwitch(switches::kEnableLogging); |
+ command_line->AppendSwitch(switches::kForceCompositingMode); |
+ command_line->AppendSwitchASCII(switches::kForceGpuMemAvailableMb, |
+ kMemoryLimitSwitch); |
+ // Only run this on GPU bots for now. These tests should work with |
+ // any GPU process, but may be slow. |
+ if (command_line->HasSwitch(switches::kUseGpuInTests)) { |
+ allow_tests_to_run_ = true; |
+ } |
+ // Don't enable these tests on Android just yet (they use lots of memory and |
+ // may not be stable). |
+#if defined(OS_ANDROID) |
+ allow_tests_to_run_ = false; |
+#endif |
+ } |
+ |
+ enum PageType { |
+ PAGE_CSS3D, |
+ PAGE_WEBGL, |
+ }; |
+ |
+ void LoadPage(content::Shell* shell_to_load, |
+ PageType page_type, |
+ size_t mb_to_use) { |
+ FilePath url; |
+ switch (page_type) { |
+ case PAGE_CSS3D: |
+ url = gpu_test_dir_.AppendASCII("mem_css3d.html"); |
+ break; |
+ case PAGE_WEBGL: |
+ url = gpu_test_dir_.AppendASCII("mem_webgl.html"); |
+ break; |
+ default: |
piman
2013/01/08 00:04:17
nit: just remove and have the compiler warn about
ccameron
2013/01/08 00:41:24
Done.
|
+ NOTREACHED(); |
+ break; |
+ } |
+ |
+ content::NavigateToURL(shell_to_load, net::FilePathToFileURL(url)); |
+ std::ostringstream js_call; |
+ js_call << "useGpuMemory("; |
+ js_call << mb_to_use; |
+ js_call << ");"; |
+ content::DOMMessageQueue message_queue; |
+ std::string message; |
+ ASSERT_TRUE(content::ExecuteScript( |
+ shell_to_load->web_contents(), js_call.str())); |
+ ASSERT_TRUE(message_queue.WaitForMessage(&message)); |
+ EXPECT_EQ("\"DONE\"", message); |
+ } |
+ |
+ size_t GetMemoryUsageMbytes() { |
+ GpuMemoryBytesAllocatedObserver observer; |
+ observer.GetBytesAllocated(); |
+ return observer.GetBytesAllocated()/1048576; |
piman
2013/01/08 00:04:17
nit: spaces around /
ccameron
2013/01/08 00:41:24
Done.
|
+ } |
+ |
+ void ExpectMemoryUsageGE(size_t mbytes_expected) { |
+ // TODO: This should wait until all effects of memory management complete. |
+ // We will need to wait until all |
+ // 1. pending commits from the main thread to the impl thread in the |
+ // compositor complete (for visible compositors). |
+ // 2. allocations that the renderer's impl thread will make due to the |
+ // compositor and WebGL are completed. |
+ // 3. pending GpuMemoryManager::Manage() calls to manage are made. |
+ // 4. renderers' OnMemoryAllocationChanged callbacks in response to |
+ // manager are made. |
+ // Each step in this sequence can cause trigger the next (as a 1-2-3-4-1 |
+ // cycle), so we will need to pump this cycle until it stabilizes. |
+ |
+ // The above is a big undertaking, so, instead, we wait for the condition |
+ // to be satisfied, risking that we will give a false PASS if it happens |
+ // to be being transiently satisfied. |
+ WaitForMemoryStableInRange(mbytes_expected, static_cast<size_t>(-1)); |
piman
2013/01/08 00:04:17
nit: std::memory_limits<size_t>::max()
ccameron
2013/01/08 00:41:24
Done.
|
+ } |
+ |
+ void ExpectMemoryUsageLE(size_t mbytes_expected) { |
+ WaitForMemoryStableInRange(0, mbytes_expected); |
+ } |
+ |
+ content::Shell* CreateShell() { |
+ // The ContentBrowserTest will create one shell by default, use that one |
+ // first so that we don't confuse the memory manager into thinking there |
+ // are more windows than there are. |
+ content::Shell* new_shell = shells_.empty() ? shell() : CreateBrowser(); |
+ shells_.push_back(new_shell); |
+ return new_shell; |
+ } |
+ |
+ bool AllowTestsToRun() const { |
+ return allow_tests_to_run_; |
+ } |
+ |
+ private: |
+ void WaitForMemoryStableInRange(size_t low, size_t high) { |
+ // Wait until we get the same vaue in the expected interval 20 times in |
piman
2013/01/08 00:04:17
typo: vaue->value
ccameron
2013/01/08 00:41:24
Done.
|
+ // a row. By waiting for the value to settle, we increase the chance that |
+ // if we are in the process of plowing through the interval, we will |
+ // not give a false pass. |
+ // TODO: Implement the scheme discussed in ExpectMemoryUsageGE instead. |
+ const size_t iterations_to_consider_stable = 20; |
+ size_t same_value_observed_count = 0; |
+ size_t mbytes_last_observed = 0; |
+ while (same_value_observed_count < iterations_to_consider_stable) { |
+ size_t mbytes_observed = GetMemoryUsageMbytes(); |
+ if (low <= mbytes_observed && mbytes_observed <= high) { |
+ if (mbytes_observed != mbytes_last_observed) |
+ same_value_observed_count = 0; |
+ else |
+ same_value_observed_count++; |
+ mbytes_last_observed = mbytes_observed; |
+ } |
+ } |
piman
2013/01/08 00:04:17
This can't return failure, it will just not return
ccameron
2013/01/08 00:41:24
Yes, the failure method is time-out. The comments
|
+ } |
+ |
+ std::vector<content::Shell*> shells_; |
+ FilePath gpu_test_dir_; |
+ bool allow_tests_to_run_; |
+}; |
+ |
+// When trying to load something that doesn't fit into our total GPU memory |
+// limit, we shouldn't exceed that limit. |
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest, SingleWindowDoesNotExceedLimit) { |
+ if (!AllowTestsToRun()) |
+ return; |
+ |
+ content::Shell* shell1 = CreateShell(); |
+ LoadPage(shell1, PAGE_CSS3D, kMemoryLimit); |
+ // Make sure that the CSS3D page triggers allocation of at least 64MB, |
+ // otherwise the test doesn't test anything. |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ // Make sure that we stay below the memory limit. |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+} |
+ |
+// This should remain true if we load the same big page in three windows. |
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest, MultipleWindowsDoNotExceedLimit) { |
+ if (!AllowTestsToRun()) |
+ return; |
+ |
+ // Load three instances of a heavy page in visible windows. |
+ content::Shell* shell1 = CreateShell(); |
+ LoadPage(shell1, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+ content::Shell* shell2 = CreateShell(); |
+ LoadPage(shell2, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kMemoryLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+ content::Shell* shell3 = CreateShell(); |
+ LoadPage(shell3, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kMemoryLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+} |
+ |
+// This should remain true if we load the same big page in multiple tabs. |
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest, MultipleTabsDoNotExceedLimit) { |
+ if (!AllowTestsToRun()) |
+ return; |
+ |
+ content::Shell* shell1 = CreateShell(); |
+ // Load and then background two tabs, then load a foregrounded tab. |
+ LoadPage(shell1, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ shell1->web_contents()->WasHidden(); |
+ ExpectMemoryUsageLE(0u); |
+ |
+ content::Shell* shell2 = CreateShell(); |
+ LoadPage(shell2, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ shell2->web_contents()->WasHidden(); |
+ ExpectMemoryUsageLE(0u); |
+ |
+ content::Shell* shell3 = CreateShell(); |
+ LoadPage(shell3, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+} |
+ |
+// Load a page with heavy WebGL, then background it, and make sure that |
+// the WebGL page's allocation causes the CSS page to use less memory |
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest, WebGLMakesCompositorUseLessMemory) { |
+ if (!AllowTestsToRun()) |
+ return; |
+ |
+ content::Shell* shell1 = CreateShell(); |
+ LoadPage(shell1, PAGE_WEBGL, kMemoryLimit - kSingleTabMinimum); |
+ // Make sure that the WEBGL page triggers allocation of at least what |
+ // WebGL allocated, otherwise the test doesn't test anything. |
+ ExpectMemoryUsageGE(kMemoryLimit - kSingleTabMinimum); |
+ // Background that tab, and load heavy content in a new foreground tab. |
+ shell1->web_contents()->WasHidden(); |
+ content::Shell* shell2 = CreateShell(); |
+ LoadPage(shell2, PAGE_CSS3D, kMemoryLimit); |
+ // Make sure we are still under the limit, but that we get close |
+ ExpectMemoryUsageGE(kMemoryLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+} |
+ |
+// Load a big CSS page, then open a new window with WebGL, and make |
+// sure that the compositor gave up some of its resources. |
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest, CompositorUsesLessWhenWebGLIsOpened) { |
+ if (!AllowTestsToRun()) |
+ return; |
+ |
+ // Open the CSS page. |
+ content::Shell* shell1 = CreateShell(); |
+ LoadPage(shell1, PAGE_CSS3D, kMemoryLimit); |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+ |
+ // Open the WebGL page. |
+ content::Shell* shell2 = CreateShell(); |
+ LoadPage(shell2, PAGE_WEBGL, kMemoryLimit - kSingleTabMinimum); |
+ // Make sure we are still under the limit, but that we get close |
+ ExpectMemoryUsageGE(kMemoryLimit - kWiggleRoom); |
+ ExpectMemoryUsageLE(kMemoryLimit + kWiggleRoom); |
+} |
+ |
+// Make sure that we don't waste memory on backgrounded tabs that can't |
+// be rendered fully. |
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest, BackgroundingBigTabsDropsTheirMemory) { |
+ if (!AllowTestsToRun()) |
+ return; |
+ |
+ content::Shell* shell = CreateShell(); |
+ LoadPage(shell, PAGE_CSS3D, kMemoryLimit); |
+ // Make sure that the CSS3D page triggers allocation of at least 64MB, |
+ // otherwise the test doesn't test anything. |
+ ExpectMemoryUsageGE(kSingleTabLimit - kWiggleRoom); |
+ // Background the tab. Because it is too big to be kept in memory, |
+ // its allocation should drop to zero. |
+ shell->web_contents()->WasHidden(); |
+ ExpectMemoryUsageLE(0u); |
+} |
+ |
+} // namespace |