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 "base/callback.h" 6 #include "base/command_line.h" 7 #include "base/path_service.h" 8 #include "content/public/browser/gpu_data_manager.h" 9 #include "content/public/browser/gpu_data_manager_observer.h" 10 #include "content/public/browser/web_contents.h" 11 #include "content/public/common/content_paths.h" 12 #include "content/public/common/content_switches.h" 13 #include "content/public/test/browser_test_utils.h" 14 #include "content/public/test/test_utils.h" 15 #include "content/shell/shell.h" 16 #include "content/test/content_browser_test.h" 17 #include "content/test/content_browser_test_utils.h" 18 #include "gpu/command_buffer/service/gpu_switches.h" 19 #include "gpu/config/gpu_test_config.h" 20 #include "net/base/net_util.h" 21 22 namespace content { 23 24 // Run the tests with a memory limit of 256MB, and give 25 // and extra 4MB of wiggle-room for over-allocation. 26 const char* kMemoryLimitMBSwitch = "256"; 27 const size_t kMemoryLimitMB = 256; 28 const size_t kSingleTabLimitMB = 128; 29 const size_t kWiggleRoomMB = 4; 30 31 // Observer to report GPU memory usage when requested. 32 class GpuMemoryBytesAllocatedObserver : public GpuDataManagerObserver { 33 public: 34 GpuMemoryBytesAllocatedObserver() 35 : bytes_allocated_(0) { 36 } 37 38 virtual ~GpuMemoryBytesAllocatedObserver() { 39 } 40 41 virtual void OnVideoMemoryUsageStatsUpdate( 42 const GPUVideoMemoryUsageStats& video_memory_usage_stats) OVERRIDE { 43 bytes_allocated_ = video_memory_usage_stats.bytes_allocated; 44 message_loop_runner_->Quit(); 45 } 46 47 size_t GetBytesAllocated() { 48 message_loop_runner_ = new MessageLoopRunner; 49 GpuDataManager::GetInstance()->AddObserver(this); 50 GpuDataManager::GetInstance()->RequestVideoMemoryUsageStatsUpdate(); 51 message_loop_runner_->Run(); 52 GpuDataManager::GetInstance()->RemoveObserver(this); 53 message_loop_runner_ = NULL; 54 return bytes_allocated_; 55 } 56 57 private: 58 size_t bytes_allocated_; 59 scoped_refptr<MessageLoopRunner> message_loop_runner_; 60 }; 61 62 class GpuMemoryTest : public ContentBrowserTest { 63 public: 64 GpuMemoryTest() 65 : allow_tests_to_run_(false), 66 has_used_first_shell_(false) { 67 } 68 virtual ~GpuMemoryTest() { 69 } 70 71 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 72 base::FilePath test_dir; 73 ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir)); 74 gpu_test_dir_ = test_dir.AppendASCII("gpu"); 75 } 76 77 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 78 command_line->AppendSwitch(switches::kEnableLogging); 79 command_line->AppendSwitch(switches::kForceCompositingMode); 80 command_line->AppendSwitchASCII(switches::kForceGpuMemAvailableMb, 81 kMemoryLimitMBSwitch); 82 // Only run this on GPU bots for now. These tests should work with 83 // any GPU process, but may be slow. 84 if (command_line->HasSwitch(switches::kUseGpuInTests)) { 85 allow_tests_to_run_ = true; 86 } 87 // Don't enable these tests on Android just yet (they use lots of memory and 88 // may not be stable). 89 #if defined(OS_ANDROID) 90 allow_tests_to_run_ = false; 91 #endif 92 } 93 94 enum PageType { 95 PAGE_CSS3D, 96 PAGE_WEBGL, 97 }; 98 99 // Load a page and consume a specified amount of GPU memory. 100 void LoadPage(Shell* tab_to_load, 101 PageType page_type, 102 size_t mb_to_use) { 103 base::FilePath url; 104 switch (page_type) { 105 case PAGE_CSS3D: 106 url = gpu_test_dir_.AppendASCII("mem_css3d.html"); 107 break; 108 case PAGE_WEBGL: 109 url = gpu_test_dir_.AppendASCII("mem_webgl.html"); 110 break; 111 } 112 113 NavigateToURL(tab_to_load, net::FilePathToFileURL(url)); 114 std::ostringstream js_call; 115 js_call << "useGpuMemory("; 116 js_call << mb_to_use; 117 js_call << ");"; 118 std::string message; 119 ASSERT_TRUE(ExecuteScriptInFrameAndExtractString( 120 tab_to_load->web_contents(), std::string(), js_call.str(), &message)); 121 EXPECT_EQ("DONE_USE_GPU_MEMORY", message); 122 } 123 124 // Create a new tab. 125 Shell* CreateNewTab() { 126 // The ContentBrowserTest will create one shell by default, use that one 127 // first so that we don't confuse the memory manager into thinking there 128 // are more windows than there are. 129 Shell* new_tab = has_used_first_shell_ ? CreateBrowser() : shell(); 130 has_used_first_shell_ = true; 131 tabs_.insert(new_tab); 132 visible_tabs_.insert(new_tab); 133 return new_tab; 134 } 135 136 void SetTabBackgrounded(Shell* tab_to_background) { 137 ASSERT_TRUE( 138 visible_tabs_.find(tab_to_background) != visible_tabs_.end()); 139 visible_tabs_.erase(tab_to_background); 140 tab_to_background->web_contents()->WasHidden(); 141 } 142 143 bool MemoryUsageInRange(size_t low, size_t high) { 144 FinishGpuMemoryChanges(); 145 size_t memory_usage_bytes = GetMemoryUsageMbytes(); 146 147 // If it's not immediately the case that low <= usage <= high, then 148 // allow 149 // Because we haven't implemented the full delay in FinishGpuMemoryChanges, 150 // keep re-reading the GPU memory usage for 2 seconds before declaring 151 // failure. 152 base::Time start_time = base::Time::Now(); 153 while (low > memory_usage_bytes || memory_usage_bytes > high) { 154 memory_usage_bytes = GetMemoryUsageMbytes(); 155 base::TimeDelta delta = base::Time::Now() - start_time; 156 if (delta.InMilliseconds() >= 2000) 157 break; 158 } 159 160 return (low <= memory_usage_bytes && memory_usage_bytes <= high); 161 } 162 163 bool AllowTestsToRun() const { 164 return allow_tests_to_run_; 165 } 166 167 private: 168 void FinishGpuMemoryChanges() { 169 // This should wait until all effects of memory management complete. 170 // We will need to wait until all 171 // 1. pending commits from the main thread to the impl thread in the 172 // compositor complete (for visible compositors). 173 // 2. allocations that the renderer's impl thread will make due to the 174 // compositor and WebGL are completed. 175 // 3. pending GpuMemoryManager::Manage() calls to manage are made. 176 // 4. renderers' OnMemoryAllocationChanged callbacks in response to 177 // manager are made. 178 // Each step in this sequence can cause trigger the next (as a 1-2-3-4-1 179 // cycle), so we will need to pump this cycle until it stabilizes. 180 181 // Pump the cycle 8 times (in principle it could take an infinite number 182 // of iterations to settle). 183 for (size_t pump_it = 0; pump_it < 8; ++pump_it) { 184 // Wait for a RequestAnimationFrame to complete from all visible tabs 185 // for stage 1 of the cycle. 186 for (std::set<Shell*>::iterator it = visible_tabs_.begin(); 187 it != visible_tabs_.end(); 188 ++it) { 189 std::string js_call( 190 "window.webkitRequestAnimationFrame(function() {" 191 " domAutomationController.setAutomationId(1);" 192 " domAutomationController.send(\"DONE_RAF\");" 193 "})"); 194 std::string message; 195 ASSERT_TRUE(ExecuteScriptInFrameAndExtractString( 196 (*it)->web_contents(), std::string(), js_call, &message)); 197 EXPECT_EQ("DONE_RAF", message); 198 } 199 // TODO(ccameron): send an IPC from Browser -> Renderer (delay it until 200 // painting finishes) -> GPU process (delay it until any pending manages 201 // happen) -> All Renderers -> Browser to flush parts 2, 3, and 4. 202 } 203 } 204 205 size_t GetMemoryUsageMbytes() { 206 GpuMemoryBytesAllocatedObserver observer; 207 observer.GetBytesAllocated(); 208 return observer.GetBytesAllocated() / 1048576; 209 } 210 211 bool allow_tests_to_run_; 212 std::set<Shell*> tabs_; 213 std::set<Shell*> visible_tabs_; 214 bool has_used_first_shell_; 215 base::FilePath gpu_test_dir_; 216 }; 217 218 #if defined(OS_LINUX) && !defined(NDEBUG) 219 // http://crbug.com/254724 220 #define IF_NOT_DEBUG_LINUX(x) DISABLED_ ## x 221 #else 222 #define IF_NOT_DEBUG_LINUX(x) x 223 #endif 224 225 // When trying to load something that doesn't fit into our total GPU memory 226 // limit, we shouldn't exceed that limit. 227 IN_PROC_BROWSER_TEST_F(GpuMemoryTest, 228 IF_NOT_DEBUG_LINUX(SingleWindowDoesNotExceedLimit)) { 229 if (!AllowTestsToRun()) 230 return; 231 232 Shell* tab = CreateNewTab(); 233 LoadPage(tab, PAGE_CSS3D, kMemoryLimitMB); 234 // Make sure that the CSS3D page maxes out a single tab's budget (otherwise 235 // the test doesn't test anything) but still stays under the limit. 236 EXPECT_TRUE(MemoryUsageInRange( 237 kSingleTabLimitMB - kWiggleRoomMB, 238 kMemoryLimitMB + kWiggleRoomMB)); 239 } 240 241 } // namespace content 242