Chromium Code Reviews| OLD | NEW | 
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python | 
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be | 
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. | 
| 5 | 5 | 
| 6 """Performance tests for Chrome Endure (long-running perf tests on Chrome). | 6 """Performance tests for Chrome Endure (long-running perf tests on Chrome). | 
| 7 | 7 | 
| 8 This module accepts the following environment variable inputs: | 8 This module accepts the following environment variable inputs: | 
| 9 TEST_LENGTH: The number of seconds in which to run each test. | 9 TEST_LENGTH: The number of seconds in which to run each test. | 
| 10 PERF_STATS_INTERVAL: The number of seconds to wait in-between each sampling | 10 PERF_STATS_INTERVAL: The number of seconds to wait in-between each sampling | 
| 11 of performance/memory statistics. | 11 of performance/memory statistics. | 
| 12 """ | 12 """ | 
| 13 | 13 | 
| 14 import logging | 14 import logging | 
| 15 import os | 15 import os | 
| 16 import re | 16 import re | 
| 17 import shutil | |
| 18 import tempfile | |
| 17 import time | 19 import time | 
| 18 | 20 | 
| 19 import perf | 21 import perf | 
| 20 import pyauto_functional # Must be imported before pyauto. | 22 import pyauto_functional # Must be imported before pyauto. | 
| 21 import pyauto | 23 import pyauto | 
| 22 import remote_inspector_client | 24 import remote_inspector_client | 
| 23 import selenium.common.exceptions | 25 import selenium.common.exceptions | 
| 24 from selenium.webdriver.support.ui import WebDriverWait | 26 from selenium.webdriver.support.ui import WebDriverWait | 
| 25 | 27 | 
| 28 DMPROF_DIR_PATH = os.path.join(os.path.dirname(__file__), | |
| 
 
Nirnimesh
2012/04/09 18:46:44
Unless you want these vars to be accessible from o
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
Done.
 
 | |
| 29 os.pardir, | |
| 30 os.pardir, | |
| 31 os.pardir, | |
| 32 'tools', | |
| 33 'deep_memory_profiler') | |
| 34 | |
| 35 DMPROF_PATH = os.path.join(DMPROF_DIR_PATH, | |
| 36 'dmprof.py') | |
| 37 | |
| 38 # TODO(dmikurube): Need to find an actual running chrome. | |
| 39 CHROME_BIN_PATH = os.path.join(os.path.dirname(__file__), | |
| 40 os.pardir, | |
| 41 os.pardir, | |
| 42 os.pardir, | |
| 43 'out', | |
| 44 'Debug', | |
| 45 'chrome') | |
| 
 
Nirnimesh
2012/04/09 18:46:44
What about non-linux?
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
Done.
 
 | |
| 26 | 46 | 
| 27 class ChromeEndureBaseTest(perf.BasePerfTest): | 47 class ChromeEndureBaseTest(perf.BasePerfTest): | 
| 28 """Implements common functionality for all Chrome Endure tests. | 48 """Implements common functionality for all Chrome Endure tests. | 
| 29 | 49 | 
| 30 All Chrome Endure test classes should inherit from this class. | 50 All Chrome Endure test classes should inherit from this class. | 
| 31 """ | 51 """ | 
| 32 | 52 | 
| 33 _DEFAULT_TEST_LENGTH_SEC = 60 * 60 * 6 # Tests run for 6 hours. | 53 _DEFAULT_TEST_LENGTH_SEC = 60 * 60 * 6 # Tests run for 6 hours. | 
| 34 _GET_PERF_STATS_INTERVAL = 60 * 10 # Measure perf stats every 10 minutes. | 54 _GET_PERF_STATS_INTERVAL = 60 * 10 # Measure perf stats every 10 minutes. | 
| 35 _ERROR_COUNT_THRESHOLD = 300 # Number of ChromeDriver errors to tolerate. | 55 _ERROR_COUNT_THRESHOLD = 300 # Number of ChromeDriver errors to tolerate. | 
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 115 last_perf_stats_time = time.time() | 135 last_perf_stats_time = time.time() | 
| 116 self._GetPerformanceStats( | 136 self._GetPerformanceStats( | 
| 117 webapp_name, test_description, tab_title_substring) | 137 webapp_name, test_description, tab_title_substring) | 
| 118 | 138 | 
| 119 if self._iteration_num % 10 == 0: | 139 if self._iteration_num % 10 == 0: | 
| 120 remaining_time = self._test_length_sec - (time.time() - | 140 remaining_time = self._test_length_sec - (time.time() - | 
| 121 self._test_start_time) | 141 self._test_start_time) | 
| 122 logging.info('Chrome interaction #%d. Time remaining in test: %d sec.' % | 142 logging.info('Chrome interaction #%d. Time remaining in test: %d sec.' % | 
| 123 (self._iteration_num, remaining_time)) | 143 (self._iteration_num, remaining_time)) | 
| 124 | 144 | 
| 145 if self._iteration_num % 20 == 5: | |
| 
 
Dai Mikurube (NOT FULLTIME)
2012/04/09 09:10:40
Temporary trigger.
 
Nirnimesh
2012/04/09 18:46:44
Add comments / TODO notes.
 
dennis_jeffrey
2012/04/09 19:12:38
We'll likely want to be able to turn profiling on/
 
Dai Mikurube (NOT FULLTIME)
2012/04/11 08:44:02
Ah, exactly.  Such an option is required.  How can
 
dennis_jeffrey
2012/04/12 00:51:52
Right now it's done through environment variables.
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
Using an environment variable DEEP_MEMORY_PROFILE_
 
 | |
| 146 self.HeapProfilerDump('In ChromeEndureControlTest') | |
| 147 | |
| 125 do_scenario() | 148 do_scenario() | 
| 126 | 149 | 
| 127 self._GetPerformanceStats( | 150 self._GetPerformanceStats( | 
| 128 webapp_name, test_description, tab_title_substring) | 151 webapp_name, test_description, tab_title_substring) | 
| 129 | 152 | 
| 130 def _GetProcessInfo(self, tab_title_substring): | 153 def _GetProcessInfo(self, tab_title_substring): | 
| 131 """Gets process info associated with an open browser/tab. | 154 """Gets process info associated with an open browser/tab. | 
| 132 | 155 | 
| 133 Args: | 156 Args: | 
| 134 tab_title_substring: A unique substring contained within the title of | 157 tab_title_substring: A unique substring contained within the title of | 
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 171 msg='Expected to find 1 %s tab process, but found %d ' | 194 msg='Expected to find 1 %s tab process, but found %d ' | 
| 172 'instead.\nCurrent process info:\n%s.' % ( | 195 'instead.\nCurrent process info:\n%s.' % ( | 
| 173 tab_title_substring, len(tab_proc_info), | 196 tab_title_substring, len(tab_proc_info), | 
| 174 self.pformat(info))) | 197 self.pformat(info))) | 
| 175 | 198 | 
| 176 browser_proc_info = browser_proc_info[0] | 199 browser_proc_info = browser_proc_info[0] | 
| 177 tab_proc_info = tab_proc_info[0] | 200 tab_proc_info = tab_proc_info[0] | 
| 178 return { | 201 return { | 
| 179 'browser_private_mem': browser_proc_info['working_set_mem']['priv'], | 202 'browser_private_mem': browser_proc_info['working_set_mem']['priv'], | 
| 180 'tab_private_mem': tab_proc_info['working_set_mem']['priv'], | 203 'tab_private_mem': tab_proc_info['working_set_mem']['priv'], | 
| 204 'browser_pid': browser_proc_info['pid'], | |
| 
 
dennis_jeffrey
2012/04/09 19:12:38
is this value used anywhere?
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
No for now.  Removed it.
 
 | |
| 205 'tab_pid': tab_proc_info['pid'], | |
| 181 } | 206 } | 
| 182 | 207 | 
| 183 def _GetPerformanceStats(self, webapp_name, test_description, | 208 def _GetPerformanceStats(self, webapp_name, test_description, | 
| 184 tab_title_substring): | 209 tab_title_substring): | 
| 185 """Gets performance statistics and outputs the results. | 210 """Gets performance statistics and outputs the results. | 
| 186 | 211 | 
| 187 Args: | 212 Args: | 
| 188 webapp_name: A string name for the webapp being tested. Should not | 213 webapp_name: A string name for the webapp being tested. Should not | 
| 189 include spaces. For example, 'Gmail', 'Docs', or 'Plus'. | 214 include spaces. For example, 'Gmail', 'Docs', or 'Plus'. | 
| 190 test_description: A string description of what the test does, used for | 215 test_description: A string description of what the test does, used for | 
| (...skipping 25 matching lines...) Expand all Loading... | |
| 216 logging.info(' Tab process private memory: %d KB' % | 241 logging.info(' Tab process private memory: %d KB' % | 
| 217 proc_info['tab_private_mem']) | 242 proc_info['tab_private_mem']) | 
| 218 self._tab_process_private_mem_results.append( | 243 self._tab_process_private_mem_results.append( | 
| 219 (elapsed_time, proc_info['tab_private_mem'])) | 244 (elapsed_time, proc_info['tab_private_mem'])) | 
| 220 | 245 | 
| 221 v8_info = self.GetV8HeapStats() # First window, first tab. | 246 v8_info = self.GetV8HeapStats() # First window, first tab. | 
| 222 v8_mem_used = v8_info['v8_memory_used'] / 1024.0 # Convert to KB. | 247 v8_mem_used = v8_info['v8_memory_used'] / 1024.0 # Convert to KB. | 
| 223 logging.info(' V8 memory used: %f KB' % v8_mem_used) | 248 logging.info(' V8 memory used: %f KB' % v8_mem_used) | 
| 224 self._v8_mem_used_results.append((elapsed_time, v8_mem_used)) | 249 self._v8_mem_used_results.append((elapsed_time, v8_mem_used)) | 
| 225 | 250 | 
| 251 # TODO(dmikurube): Add --single in dmprof.py? | |
| 
 
dennis_jeffrey
2012/04/09 19:12:38
So in general, a test will periodically call HeapP
 
Dai Mikurube (NOT FULLTIME)
2012/04/11 08:44:02
I'm in tries-and-errors about that.  Dumping may t
 
 | |
| 252 first_dump = '' | |
| 253 print '^%s/endure.%05d.\d+.heap$' % ( | |
| 
 
Nirnimesh
2012/04/09 18:46:44
Use the right logging call instead of print.
 
Dai Mikurube (NOT FULLTIME)
2012/04/16 09:43:20
Done.
 
 | |
| 254 self._deep_tempdir, proc_info['tab_pid']) | |
| 255 for filename in sorted(os.listdir(self._deep_tempdir)): | |
| 256 if re.match('^%s/endure.%05d.\d+.heap$' % ( | |
| 
 
Nirnimesh
2012/04/09 18:46:44
use os.path.join() instead of hardcoding /
 
Dai Mikurube (NOT FULLTIME)
2012/04/16 09:43:20
Done.
 
 | |
| 257 self._deep_tempdir, proc_info['tab_pid']), filename): | |
| 258 first_dump = filename | |
| 259 break | |
| 260 | |
| 261 # Test printing. | |
| 262 if first_dump: | |
| 
 
dennis_jeffrey
2012/04/09 19:12:38
What does it mean for a file to be the "first dump
 
Dai Mikurube (NOT FULLTIME)
2012/04/11 08:44:02
This is just a test printing, and will be removed
 
 | |
| 263 print 'First Dump: %s' % first_dump | |
| 
 
Nirnimesh
2012/04/09 18:46:44
logging.info()?
 
Dai Mikurube (NOT FULLTIME)
2012/04/16 09:43:20
Done.
 
 | |
| 264 else: | |
| 265 print 'No Dump!' | |
| 266 """ | |
| 267 with open('%s/endure.%05d.json' % (self._deep_tempdir, | |
| 
 
Nirnimesh
2012/04/09 18:46:44
use os.path.join
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
Done.
 
 | |
| 268 proc_info['tab_pid']), 'w+') as json_f: | |
| 269 p = subprocess.Popen( | |
| 
 
Nirnimesh
2012/04/09 18:46:44
use better varname instead of |p|
 
Dai Mikurube (NOT FULLTIME)
2012/04/11 08:44:02
Good catch.  Thanks.
 
 | |
| 270 '%s --json %s %s %s' % (DMPROF_PATH, | |
| 271 CHROME_BIN_PATH, | |
| 272 os.path.join(DMPROF_DIR_PATH, 'dmpolicy'), | |
| 273 @@@SNAPSHOT@@@), | |
| 274 shell=True, stdout=json_f) | |
| 275 p.wait() | |
| 
 
Nirnimesh
2012/04/09 18:46:44
you won't need this if you use subprocess.call() i
 
Dai Mikurube (NOT FULLTIME)
2012/04/16 09:43:20
Finally, this script is required to run on backgro
 
 | |
| 276 """ | |
| 277 | |
| 278 # 2) Parse .json (including only the latest snapshot) file here. | |
| 279 | |
| 226 # Output the results seen so far, to be graphed. | 280 # Output the results seen so far, to be graphed. | 
| 227 self._OutputPerfGraphValue( | 281 self._OutputPerfGraphValue( | 
| 228 'TotalDOMNodeCount', self._dom_node_count_results, 'nodes', | 282 'TotalDOMNodeCount', self._dom_node_count_results, 'nodes', | 
| 229 graph_name='%s%s-Nodes-DOM' % (webapp_name, test_description), | 283 graph_name='%s%s-Nodes-DOM' % (webapp_name, test_description), | 
| 230 units_x='seconds') | 284 units_x='seconds') | 
| 231 self._OutputPerfGraphValue( | 285 self._OutputPerfGraphValue( | 
| 232 'EventListenerCount', self._event_listener_count_results, 'listeners', | 286 'EventListenerCount', self._event_listener_count_results, 'listeners', | 
| 233 graph_name='%s%s-EventListeners' % (webapp_name, test_description), | 287 graph_name='%s%s-EventListeners' % (webapp_name, test_description), | 
| 234 units_x='seconds') | 288 units_x='seconds') | 
| 235 self._OutputPerfGraphValue( | 289 self._OutputPerfGraphValue( | 
| 236 'BrowserPrivateMemory', self._browser_process_private_mem_results, 'KB', | 290 'BrowserPrivateMemory', self._browser_process_private_mem_results, 'KB', | 
| 237 graph_name='%s%s-BrowserMem-Private' % (webapp_name, test_description), | 291 graph_name='%s%s-BrowserMem-Private' % (webapp_name, test_description), | 
| 238 units_x='seconds') | 292 units_x='seconds') | 
| 239 self._OutputPerfGraphValue( | 293 self._OutputPerfGraphValue( | 
| 240 'TabPrivateMemory', self._tab_process_private_mem_results, 'KB', | 294 'TabPrivateMemory', self._tab_process_private_mem_results, 'KB', | 
| 241 graph_name='%s%s-TabMem-Private' % (webapp_name, test_description), | 295 graph_name='%s%s-TabMem-Private' % (webapp_name, test_description), | 
| 242 units_x='seconds') | 296 units_x='seconds') | 
| 243 self._OutputPerfGraphValue( | 297 self._OutputPerfGraphValue( | 
| 244 'V8MemoryUsed', self._v8_mem_used_results, 'KB', | 298 'V8MemoryUsed', self._v8_mem_used_results, 'KB', | 
| 245 graph_name='%s%s-V8MemUsed' % (webapp_name, test_description), | 299 graph_name='%s%s-V8MemUsed' % (webapp_name, test_description), | 
| 246 units_x='seconds') | 300 units_x='seconds') | 
| 301 # 3) Do self._OutputPerfGraphValue for multiple lines, here. | |
| 247 | 302 | 
| 248 def _GetElement(self, find_by, value): | 303 def _GetElement(self, find_by, value): | 
| 249 """Gets a WebDriver element object from the webpage DOM. | 304 """Gets a WebDriver element object from the webpage DOM. | 
| 250 | 305 | 
| 251 Args: | 306 Args: | 
| 252 find_by: A callable that queries WebDriver for an element from the DOM. | 307 find_by: A callable that queries WebDriver for an element from the DOM. | 
| 253 value: A string value that can be passed to the |find_by| callable. | 308 value: A string value that can be passed to the |find_by| callable. | 
| 254 | 309 | 
| 255 Returns: | 310 Returns: | 
| 256 The identified WebDriver element object, if found in the DOM, or | 311 The identified WebDriver element object, if found in the DOM, or | 
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 303 return False | 358 return False | 
| 304 return True | 359 return True | 
| 305 | 360 | 
| 306 | 361 | 
| 307 class ChromeEndureControlTest(ChromeEndureBaseTest): | 362 class ChromeEndureControlTest(ChromeEndureBaseTest): | 
| 308 """Control tests for Chrome Endure.""" | 363 """Control tests for Chrome Endure.""" | 
| 309 | 364 | 
| 310 _webapp_name = 'Control' | 365 _webapp_name = 'Control' | 
| 311 _tab_title_substring = 'Chrome Endure Control Test' | 366 _tab_title_substring = 'Chrome Endure Control Test' | 
| 312 | 367 | 
| 368 def setUp(self): | |
| 
 
dennis_jeffrey
2012/04/09 19:12:38
This only enables profiling in the two control tes
 
Dai Mikurube (NOT FULLTIME)
2012/04/11 08:44:02
Finally, yes.  Is there an efficient way to enable
 
dennis_jeffrey
2012/04/12 00:51:52
Yes - if you enable profiling above in class Chrom
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
I see.  Thank you.
I did it in the uploaded code.
 
 | |
| 369 # TODO(dmikurube): Have to specify "--no-sandbox" for Chrome. | |
| 
 
Dai Mikurube (NOT FULLTIME)
2012/04/09 09:20:27
Note: ExtraChromeFlags would work.
 
 | |
| 370 self._deep_tempdir = tempfile.mkdtemp() | |
| 371 os.environ['HEAPPROFILE'] = '%s/endure' % self._deep_tempdir | |
| 
 
Nirnimesh
2012/04/09 18:46:44
os.path.join
 
Dai Mikurube (NOT FULLTIME)
2012/04/13 11:16:49
Done.
 
 | |
| 372 os.environ['DEEP_HEAP_PROFILE'] = 'True' | |
| 373 ChromeEndureBaseTest.setUp(self) | |
| 374 | |
| 375 def tearDown(self): | |
| 376 del os.environ['DEEP_HEAP_PROFILE'] | |
| 377 del os.environ['HEAPPROFILE'] | |
| 378 ChromeEndureBaseTest.tearDown(self) | |
| 379 # shutil.rmtree(self._deep_tempdir) | |
| 380 | |
| 313 def testControlAttachDetachDOMTree(self): | 381 def testControlAttachDetachDOMTree(self): | 
| 314 """Continually attach and detach a DOM tree from a basic document.""" | 382 """Continually attach and detach a DOM tree from a basic document.""" | 
| 315 test_description = 'AttachDetachDOMTree' | 383 test_description = 'AttachDetachDOMTree' | 
| 316 url = self.GetHttpURLForDataPath('chrome_endure', 'endurance_control.html') | 384 url = self.GetHttpURLForDataPath('chrome_endure', 'endurance_control.html') | 
| 317 self.NavigateToURL(url) | 385 self.NavigateToURL(url) | 
| 318 loaded_tab_title = self.GetActiveTabTitle() | 386 loaded_tab_title = self.GetActiveTabTitle() | 
| 319 self.assertTrue(self._tab_title_substring in loaded_tab_title, | 387 self.assertTrue(self._tab_title_substring in loaded_tab_title, | 
| 320 msg='Loaded tab title does not contain "%s": "%s"' % | 388 msg='Loaded tab title does not contain "%s": "%s"' % | 
| 321 (self._tab_title_substring, loaded_tab_title)) | 389 (self._tab_title_substring, loaded_tab_title)) | 
| 322 | 390 | 
| (...skipping 515 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 838 self._driver, self._wait, 'id("state")[text()="offline"]'): | 906 self._driver, self._wait, 'id("state")[text()="offline"]'): | 
| 839 self._num_errors += 1 | 907 self._num_errors += 1 | 
| 840 time.sleep(1) | 908 time.sleep(1) | 
| 841 | 909 | 
| 842 self._RunEndureTest(self._webapp_name, self._tab_title_substring, | 910 self._RunEndureTest(self._webapp_name, self._tab_title_substring, | 
| 843 test_description, scenario) | 911 test_description, scenario) | 
| 844 | 912 | 
| 845 | 913 | 
| 846 if __name__ == '__main__': | 914 if __name__ == '__main__': | 
| 847 pyauto_functional.Main() | 915 pyauto_functional.Main() | 
| OLD | NEW |