Chromium Code Reviews| 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 |