Index: chrome/test/functional/perf_endure.py |
=================================================================== |
--- chrome/test/functional/perf_endure.py (revision 261231) |
+++ chrome/test/functional/perf_endure.py (working copy) |
@@ -1,776 +0,0 @@ |
-#!/usr/bin/env python |
-# 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. |
- |
-"""Performance tests for Chrome Endure (long-running perf tests on Chrome). |
- |
-This module accepts the following environment variable inputs: |
- TEST_LENGTH: The number of seconds in which to run each test. |
- PERF_STATS_INTERVAL: The number of seconds to wait in-between each sampling |
- of performance/memory statistics. |
- |
-The following variables are related to the Deep Memory Profiler. |
- DEEP_MEMORY_PROFILE: Enable the Deep Memory Profiler if it's set to 'True'. |
- DEEP_MEMORY_PROFILE_SAVE: Don't clean up dump files if it's set to 'True'. |
- DEEP_MEMORY_PROFILE_UPLOAD: Upload dumped files if the variable has a Google |
- Storage bucket like gs://chromium-endure/. The 'gsutil' script in $PATH |
- is used by default, or set a variable 'GSUTIL' to specify a path to the |
- 'gsutil' script. A variable 'REVISION' (or 'BUILDBOT_GOT_REVISION') is |
- used as a subdirectory in the destination if it is set. |
- GSUTIL: A path to the 'gsutil' script. Not mandatory. |
- REVISION: A string that represents the revision or some build configuration. |
- Not mandatory. |
- BUILDBOT_GOT_REVISION: Similar to 'REVISION', but checked only if 'REVISION' |
- is not specified. Not mandatory. |
-""" |
- |
-from datetime import datetime |
-import json |
-import logging |
-import os |
-import re |
-import subprocess |
-import tempfile |
-import time |
- |
-import perf |
-import pyauto_functional # Must be imported before pyauto. |
-import pyauto |
-import pyauto_errors |
-import pyauto_utils |
-import remote_inspector_client |
-import selenium.common.exceptions |
-from selenium.webdriver.support.ui import WebDriverWait |
- |
- |
-class NotSupportedEnvironmentError(RuntimeError): |
- """Represent an error raised since the environment (OS) is not supported.""" |
- pass |
- |
- |
-class DeepMemoryProfiler(object): |
- """Controls Deep Memory Profiler (dmprof) for endurance tests.""" |
- DEEP_MEMORY_PROFILE = False |
- DEEP_MEMORY_PROFILE_SAVE = False |
- DEEP_MEMORY_PROFILE_UPLOAD = '' |
- |
- _WORKDIR_PATTERN = re.compile('endure\.[0-9]+\.[0-9]+\.[A-Za-z0-9]+') |
- _SAVED_WORKDIRS = 8 |
- |
- _DMPROF_DIR_PATH = os.path.abspath(os.path.join( |
- os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, |
- 'tools', 'deep_memory_profiler')) |
- _DMPROF_SCRIPT_PATH = os.path.join(_DMPROF_DIR_PATH, 'dmprof') |
- _POLICIES = ['l0', 'l1', 'l2', 't0'] |
- |
- def __init__(self): |
- self._enabled = self.GetEnvironmentVariable( |
- 'DEEP_MEMORY_PROFILE', bool, self.DEEP_MEMORY_PROFILE) |
- self._save = self.GetEnvironmentVariable( |
- 'DEEP_MEMORY_PROFILE_SAVE', bool, self.DEEP_MEMORY_PROFILE_SAVE) |
- self._upload = self.GetEnvironmentVariable( |
- 'DEEP_MEMORY_PROFILE_UPLOAD', str, self.DEEP_MEMORY_PROFILE_UPLOAD) |
- if self._upload and not self._upload.endswith('/'): |
- self._upload += '/' |
- |
- self._revision = '' |
- self._gsutil = '' |
- self._json_file = None |
- self._last_json_filename = '' |
- self._proc = None |
- self._last_time = {} |
- for policy in self._POLICIES: |
- self._last_time[policy] = -1.0 |
- |
- def __nonzero__(self): |
- return self._enabled |
- |
- @staticmethod |
- def GetEnvironmentVariable(env_name, converter, default): |
- """Returns a converted environment variable for Deep Memory Profiler. |
- |
- Args: |
- env_name: A string name of an environment variable. |
- converter: A function taking a string to convert an environment variable. |
- default: A value used if the environment variable is not specified. |
- |
- Returns: |
- A value converted from the environment variable with 'converter'. |
- """ |
- return converter(os.environ.get(env_name, default)) |
- |
- def SetUp(self, is_linux, revision, gsutil): |
- """Sets up Deep Memory Profiler settings for a Chrome process. |
- |
- It sets environment variables and makes a working directory. |
- """ |
- if not self._enabled: |
- return |
- |
- if not is_linux: |
- raise NotSupportedEnvironmentError( |
- 'Deep Memory Profiler is not supported in this environment (OS).') |
- |
- self._revision = revision |
- self._gsutil = gsutil |
- |
- # Remove old dumped files with keeping latest _SAVED_WORKDIRS workdirs. |
- # It keeps the latest workdirs not to miss data by failure in uploading |
- # and other operations. Dumped files are no longer available if they are |
- # removed. Re-execution doesn't generate the same files. |
- tempdir = tempfile.gettempdir() |
- saved_workdirs = 0 |
- for filename in sorted(os.listdir(tempdir), reverse=True): |
- if self._WORKDIR_PATTERN.match(filename): |
- saved_workdirs += 1 |
- if saved_workdirs > self._SAVED_WORKDIRS: |
- fullpath = os.path.abspath(os.path.join(tempdir, filename)) |
- logging.info('Removing an old workdir: %s' % fullpath) |
- pyauto_utils.RemovePath(fullpath) |
- |
- dir_prefix = 'endure.%s.' % datetime.today().strftime('%Y%m%d.%H%M%S') |
- self._workdir = tempfile.mkdtemp(prefix=dir_prefix, dir=tempdir) |
- os.environ['HEAPPROFILE'] = os.path.join(self._workdir, 'endure') |
- os.environ['HEAP_PROFILE_MMAP'] = '1' |
- os.environ['DEEP_HEAP_PROFILE'] = '1' |
- |
- def TearDown(self): |
- """Tear down Deep Memory Profiler settings for the Chrome process. |
- |
- It removes the environment variables and the temporary directory. |
- Call it after Chrome finishes. Chrome may dump last files at the end. |
- """ |
- if not self._enabled: |
- return |
- |
- del os.environ['DEEP_HEAP_PROFILE'] |
- del os.environ['HEAP_PROFILE_MMAP'] |
- del os.environ['HEAPPROFILE'] |
- if not self._save and self._workdir: |
- pyauto_utils.RemovePath(self._workdir) |
- |
- def LogFirstMessage(self): |
- """Logs first messages.""" |
- if not self._enabled: |
- return |
- |
- logging.info('Running with the Deep Memory Profiler.') |
- if self._save: |
- logging.info(' Dumped files won\'t be cleaned.') |
- else: |
- logging.info(' Dumped files will be cleaned up after every test.') |
- |
- def StartProfiler(self, proc_info, is_last, webapp_name, test_description): |
- """Starts Deep Memory Profiler in background.""" |
- if not self._enabled: |
- return |
- |
- logging.info(' Profiling with the Deep Memory Profiler...') |
- |
- # Wait for a running dmprof process for last _GetPerformanceStat call to |
- # cover last dump files. |
- if is_last: |
- logging.info(' Waiting for the last dmprof.') |
- self._WaitForDeepMemoryProfiler() |
- |
- if self._proc and self._proc.poll() is None: |
- logging.info(' Last dmprof is still running.') |
- else: |
- if self._json_file: |
- self._last_json_filename = self._json_file.name |
- self._json_file.close() |
- self._json_file = None |
- first_dump = '' |
- last_dump = '' |
- for filename in sorted(os.listdir(self._workdir)): |
- if re.match('^endure.%05d.\d+.heap$' % proc_info['tab_pid'], |
- filename): |
- logging.info(' Profiled dump file: %s' % filename) |
- last_dump = filename |
- if not first_dump: |
- first_dump = filename |
- if first_dump: |
- logging.info(' First dump file: %s' % first_dump) |
- matched = re.match('^endure.\d+.(\d+).heap$', last_dump) |
- last_sequence_id = matched.group(1) |
- self._json_file = open( |
- os.path.join(self._workdir, |
- 'endure.%05d.%s.json' % (proc_info['tab_pid'], |
- last_sequence_id)), 'w+') |
- self._proc = subprocess.Popen( |
- '%s json %s' % (self._DMPROF_SCRIPT_PATH, |
- os.path.join(self._workdir, first_dump)), |
- shell=True, stdout=self._json_file) |
- if is_last: |
- # Wait only when it is the last profiling. dmprof may take long time. |
- self._WaitForDeepMemoryProfiler() |
- |
- # Upload the dumped files. |
- if first_dump and self._upload and self._gsutil: |
- if self._revision: |
- destination_path = '%s%s/' % (self._upload, self._revision) |
- else: |
- destination_path = self._upload |
- destination_path += '%s-%s-%s.zip' % ( |
- webapp_name, |
- test_description, |
- os.path.basename(self._workdir)) |
- gsutil_command = '%s upload --gsutil %s %s %s' % ( |
- self._DMPROF_SCRIPT_PATH, |
- self._gsutil, |
- os.path.join(self._workdir, first_dump), |
- destination_path) |
- logging.info('Uploading: %s' % gsutil_command) |
- try: |
- returncode = subprocess.call(gsutil_command, shell=True) |
- logging.info(' Return code: %d' % returncode) |
- except OSError, e: |
- logging.error(' Error while uploading: %s', e) |
- else: |
- logging.info('Note that the dumped files are not uploaded.') |
- else: |
- logging.info(' No dump files.') |
- |
- def ParseResultAndOutputPerfGraphValues( |
- self, webapp_name, test_description, output_perf_graph_value): |
- """Parses Deep Memory Profiler result, and outputs perf graph values.""" |
- if not self._enabled: |
- return |
- |
- results = {} |
- for policy in self._POLICIES: |
- if self._last_json_filename: |
- json_data = {} |
- with open(self._last_json_filename) as json_f: |
- json_data = json.load(json_f) |
- if json_data['version'] == 'JSON_DEEP_1': |
- results[policy] = json_data['snapshots'] |
- elif json_data['version'] == 'JSON_DEEP_2': |
- results[policy] = json_data['policies'][policy]['snapshots'] |
- for policy, result in results.iteritems(): |
- if result and result[-1]['second'] > self._last_time[policy]: |
- started = False |
- for legend in json_data['policies'][policy]['legends']: |
- if legend == 'FROM_HERE_FOR_TOTAL': |
- started = True |
- elif legend == 'UNTIL_HERE_FOR_TOTAL': |
- break |
- elif started: |
- output_perf_graph_value( |
- legend.encode('utf-8'), [ |
- (int(round(snapshot['second'])), snapshot[legend] / 1024) |
- for snapshot in result |
- if snapshot['second'] > self._last_time[policy]], |
- 'KB', |
- graph_name='%s%s-%s-DMP' % ( |
- webapp_name, test_description, policy), |
- units_x='seconds', is_stacked=True) |
- self._last_time[policy] = result[-1]['second'] |
- |
- def _WaitForDeepMemoryProfiler(self): |
- """Waits for the Deep Memory Profiler to finish if running.""" |
- if not self._enabled or not self._proc: |
- return |
- |
- self._proc.wait() |
- self._proc = None |
- if self._json_file: |
- self._last_json_filename = self._json_file.name |
- self._json_file.close() |
- self._json_file = None |
- |
- |
-class ChromeEndureBaseTest(perf.BasePerfTest): |
- """Implements common functionality for all Chrome Endure tests. |
- |
- All Chrome Endure test classes should inherit from this class. |
- """ |
- |
- _DEFAULT_TEST_LENGTH_SEC = 60 * 60 * 6 # Tests run for 6 hours. |
- _GET_PERF_STATS_INTERVAL = 60 * 5 # Measure perf stats every 5 minutes. |
- # TODO(dennisjeffrey): Do we still need to tolerate errors? |
- _ERROR_COUNT_THRESHOLD = 50 # Number of errors to tolerate. |
- _REVISION = '' |
- _GSUTIL = 'gsutil' |
- |
- def setUp(self): |
- # The environment variables for the Deep Memory Profiler must be set |
- # before perf.BasePerfTest.setUp() to inherit them to Chrome. |
- self._dmprof = DeepMemoryProfiler() |
- self._revision = str(os.environ.get('REVISION', self._REVISION)) |
- if not self._revision: |
- self._revision = str(os.environ.get('BUILDBOT_GOT_REVISION', |
- self._REVISION)) |
- self._gsutil = str(os.environ.get('GSUTIL', self._GSUTIL)) |
- if self._dmprof: |
- self._dmprof.SetUp(self.IsLinux(), self._revision, self._gsutil) |
- |
- perf.BasePerfTest.setUp(self) |
- |
- self._test_length_sec = int( |
- os.environ.get('TEST_LENGTH', self._DEFAULT_TEST_LENGTH_SEC)) |
- self._get_perf_stats_interval = int( |
- os.environ.get('PERF_STATS_INTERVAL', self._GET_PERF_STATS_INTERVAL)) |
- |
- logging.info('Running test for %d seconds.', self._test_length_sec) |
- logging.info('Gathering perf stats every %d seconds.', |
- self._get_perf_stats_interval) |
- |
- if self._dmprof: |
- self._dmprof.LogFirstMessage() |
- |
- # Set up a remote inspector client associated with tab 0. |
- logging.info('Setting up connection to remote inspector...') |
- self._remote_inspector_client = ( |
- remote_inspector_client.RemoteInspectorClient()) |
- logging.info('Connection to remote inspector set up successfully.') |
- |
- self._test_start_time = 0 |
- self._num_errors = 0 |
- self._events_to_output = [] |
- |
- def tearDown(self): |
- logging.info('Terminating connection to remote inspector...') |
- self._remote_inspector_client.Stop() |
- logging.info('Connection to remote inspector terminated.') |
- |
- # Must be done at end of this function except for post-cleaning after |
- # Chrome finishes. |
- perf.BasePerfTest.tearDown(self) |
- |
- # Must be done after perf.BasePerfTest.tearDown() |
- if self._dmprof: |
- self._dmprof.TearDown() |
- |
- def ExtraChromeFlags(self): |
- """Ensures Chrome is launched with custom flags. |
- |
- Returns: |
- A list of extra flags to pass to Chrome when it is launched. |
- """ |
- # The same with setUp, but need to fetch the environment variable since |
- # ExtraChromeFlags is called before setUp. |
- deep_memory_profile = DeepMemoryProfiler.GetEnvironmentVariable( |
- 'DEEP_MEMORY_PROFILE', bool, DeepMemoryProfiler.DEEP_MEMORY_PROFILE) |
- |
- # Ensure Chrome enables remote debugging on port 9222. This is required to |
- # interact with Chrome's remote inspector. |
- # Also, enable the memory benchmarking V8 extension for heap dumps. |
- extra_flags = ['--remote-debugging-port=9222', |
- '--enable-memory-benchmarking'] |
- if deep_memory_profile: |
- extra_flags.append('--no-sandbox') |
- return perf.BasePerfTest.ExtraChromeFlags(self) + extra_flags |
- |
- def _OnTimelineEvent(self, event_info): |
- """Invoked by the Remote Inspector Client when a timeline event occurs. |
- |
- Args: |
- event_info: A dictionary containing raw information associated with a |
- timeline event received from Chrome's remote inspector. Refer to |
- chrome/src/third_party/WebKit/Source/WebCore/inspector/Inspector.json |
- for the format of this dictionary. |
- """ |
- elapsed_time = int(round(time.time() - self._test_start_time)) |
- |
- if event_info['type'] == 'GCEvent': |
- self._events_to_output.append({ |
- 'type': 'GarbageCollection', |
- 'time': elapsed_time, |
- 'data': |
- {'collected_bytes': event_info['data']['usedHeapSizeDelta']}, |
- }) |
- |
- def _RunEndureTest(self, webapp_name, tab_title_substring, test_description, |
- do_scenario, frame_xpath=''): |
- """The main test harness function to run a general Chrome Endure test. |
- |
- After a test has performed any setup work and has navigated to the proper |
- starting webpage, this function should be invoked to run the endurance test. |
- |
- Args: |
- webapp_name: A string name for the webapp being tested. Should not |
- include spaces. For example, 'Gmail', 'Docs', or 'Plus'. |
- tab_title_substring: A unique substring contained within the title of |
- the tab to use, for identifying the appropriate tab. |
- test_description: A string description of what the test does, used for |
- outputting results to be graphed. Should not contain spaces. For |
- example, 'ComposeDiscard' for Gmail. |
- do_scenario: A callable to be invoked that implements the scenario to be |
- performed by this test. The callable is invoked iteratively for the |
- duration of the test. |
- frame_xpath: The string xpath of the frame in which to inject javascript |
- to clear chromedriver's cache (a temporary workaround until the |
- WebDriver team changes how they handle their DOM node cache). |
- """ |
- self._num_errors = 0 |
- self._test_start_time = time.time() |
- last_perf_stats_time = time.time() |
- if self._dmprof: |
- self.HeapProfilerDump('renderer', 'Chrome Endure (first)') |
- self._GetPerformanceStats( |
- webapp_name, test_description, tab_title_substring) |
- self._iteration_num = 0 # Available to |do_scenario| if needed. |
- |
- self._remote_inspector_client.StartTimelineEventMonitoring( |
- self._OnTimelineEvent) |
- |
- while time.time() - self._test_start_time < self._test_length_sec: |
- self._iteration_num += 1 |
- |
- if self._num_errors >= self._ERROR_COUNT_THRESHOLD: |
- logging.error('Error count threshold (%d) reached. Terminating test ' |
- 'early.' % self._ERROR_COUNT_THRESHOLD) |
- break |
- |
- if time.time() - last_perf_stats_time >= self._get_perf_stats_interval: |
- last_perf_stats_time = time.time() |
- if self._dmprof: |
- self.HeapProfilerDump('renderer', 'Chrome Endure') |
- self._GetPerformanceStats( |
- webapp_name, test_description, tab_title_substring) |
- |
- if self._iteration_num % 10 == 0: |
- remaining_time = self._test_length_sec - (time.time() - |
- self._test_start_time) |
- logging.info('Chrome interaction #%d. Time remaining in test: %d sec.' % |
- (self._iteration_num, remaining_time)) |
- |
- do_scenario() |
- # Clear ChromeDriver's DOM node cache so its growth doesn't affect the |
- # results of Chrome Endure. |
- # TODO(dennisjeffrey): Once the WebDriver team implements changes to |
- # handle their DOM node cache differently, we need to revisit this. It |
- # may no longer be necessary at that point to forcefully delete the cache. |
- # Additionally, the Javascript below relies on an internal property of |
- # WebDriver that may change at any time. This is only a temporary |
- # workaround to stabilize the Chrome Endure test results. |
- js = """ |
- (function() { |
- delete document.$wdc_; |
- window.domAutomationController.send('done'); |
- })(); |
- """ |
- try: |
- self.ExecuteJavascript(js, frame_xpath=frame_xpath) |
- except pyauto_errors.AutomationCommandTimeout: |
- self._num_errors += 1 |
- logging.warning('Logging an automation timeout: delete chromedriver ' |
- 'cache.') |
- |
- self._remote_inspector_client.StopTimelineEventMonitoring() |
- |
- if self._dmprof: |
- self.HeapProfilerDump('renderer', 'Chrome Endure (last)') |
- self._GetPerformanceStats( |
- webapp_name, test_description, tab_title_substring, is_last=True) |
- |
- def _GetProcessInfo(self, tab_title_substring): |
- """Gets process info associated with an open browser/tab. |
- |
- Args: |
- tab_title_substring: A unique substring contained within the title of |
- the tab to use; needed for locating the tab info. |
- |
- Returns: |
- A dictionary containing information about the browser and specified tab |
- process: |
- { |
- 'browser_private_mem': integer, # Private memory associated with the |
- # browser process, in KB. |
- 'tab_private_mem': integer, # Private memory associated with the tab |
- # process, in KB. |
- 'tab_pid': integer, # Process ID of the tab process. |
- } |
- """ |
- browser_process_name = ( |
- self.GetBrowserInfo()['properties']['BrowserProcessExecutableName']) |
- info = self.GetProcessInfo() |
- |
- # Get the information associated with the browser process. |
- browser_proc_info = [] |
- for browser_info in info['browsers']: |
- if browser_info['process_name'] == browser_process_name: |
- for proc_info in browser_info['processes']: |
- if proc_info['child_process_type'] == 'Browser': |
- browser_proc_info.append(proc_info) |
- self.assertEqual(len(browser_proc_info), 1, |
- msg='Expected to find 1 Chrome browser process, but found ' |
- '%d instead.\nCurrent process info:\n%s.' % ( |
- len(browser_proc_info), self.pformat(info))) |
- |
- # Get the process information associated with the specified tab. |
- tab_proc_info = [] |
- for browser_info in info['browsers']: |
- for proc_info in browser_info['processes']: |
- if (proc_info['child_process_type'] == 'Tab' and |
- [x for x in proc_info['titles'] if tab_title_substring in x]): |
- tab_proc_info.append(proc_info) |
- self.assertEqual(len(tab_proc_info), 1, |
- msg='Expected to find 1 %s tab process, but found %d ' |
- 'instead.\nCurrent process info:\n%s.' % ( |
- tab_title_substring, len(tab_proc_info), |
- self.pformat(info))) |
- |
- browser_proc_info = browser_proc_info[0] |
- tab_proc_info = tab_proc_info[0] |
- return { |
- 'browser_private_mem': browser_proc_info['working_set_mem']['priv'], |
- 'tab_private_mem': tab_proc_info['working_set_mem']['priv'], |
- 'tab_pid': tab_proc_info['pid'], |
- } |
- |
- def _GetPerformanceStats(self, webapp_name, test_description, |
- tab_title_substring, is_last=False): |
- """Gets performance statistics and outputs the results. |
- |
- Args: |
- webapp_name: A string name for the webapp being tested. Should not |
- include spaces. For example, 'Gmail', 'Docs', or 'Plus'. |
- test_description: A string description of what the test does, used for |
- outputting results to be graphed. Should not contain spaces. For |
- example, 'ComposeDiscard' for Gmail. |
- tab_title_substring: A unique substring contained within the title of |
- the tab to use, for identifying the appropriate tab. |
- is_last: A boolean value which should be True if it's the last call of |
- _GetPerformanceStats. The default is False. |
- """ |
- logging.info('Gathering performance stats...') |
- elapsed_time = int(round(time.time() - self._test_start_time)) |
- |
- memory_counts = self._remote_inspector_client.GetMemoryObjectCounts() |
- proc_info = self._GetProcessInfo(tab_title_substring) |
- |
- if self._dmprof: |
- self._dmprof.StartProfiler( |
- proc_info, is_last, webapp_name, test_description) |
- |
- # DOM node count. |
- dom_node_count = memory_counts['DOMNodeCount'] |
- self._OutputPerfGraphValue( |
- 'TotalDOMNodeCount', [(elapsed_time, dom_node_count)], 'nodes', |
- graph_name='%s%s-Nodes-DOM' % (webapp_name, test_description), |
- units_x='seconds') |
- |
- # Event listener count. |
- event_listener_count = memory_counts['EventListenerCount'] |
- self._OutputPerfGraphValue( |
- 'EventListenerCount', [(elapsed_time, event_listener_count)], |
- 'listeners', |
- graph_name='%s%s-EventListeners' % (webapp_name, test_description), |
- units_x='seconds') |
- |
- # Browser process private memory. |
- self._OutputPerfGraphValue( |
- 'BrowserPrivateMemory', |
- [(elapsed_time, proc_info['browser_private_mem'])], 'KB', |
- graph_name='%s%s-BrowserMem-Private' % (webapp_name, test_description), |
- units_x='seconds') |
- |
- # Tab process private memory. |
- self._OutputPerfGraphValue( |
- 'TabPrivateMemory', |
- [(elapsed_time, proc_info['tab_private_mem'])], 'KB', |
- graph_name='%s%s-TabMem-Private' % (webapp_name, test_description), |
- units_x='seconds') |
- |
- # V8 memory used. |
- v8_info = self.GetV8HeapStats() # First window, first tab. |
- v8_mem_used = v8_info['v8_memory_used'] / 1024.0 # Convert to KB. |
- self._OutputPerfGraphValue( |
- 'V8MemoryUsed', [(elapsed_time, v8_mem_used)], 'KB', |
- graph_name='%s%s-V8MemUsed' % (webapp_name, test_description), |
- units_x='seconds') |
- |
- # V8 memory allocated. |
- v8_mem_allocated = v8_info['v8_memory_allocated'] / 1024.0 # Convert to KB. |
- self._OutputPerfGraphValue( |
- 'V8MemoryAllocated', [(elapsed_time, v8_mem_allocated)], 'KB', |
- graph_name='%s%s-V8MemAllocated' % (webapp_name, test_description), |
- units_x='seconds') |
- |
- if self._dmprof: |
- self._dmprof.ParseResultAndOutputPerfGraphValues( |
- webapp_name, test_description, self._OutputPerfGraphValue) |
- |
- logging.info(' Total DOM node count: %d nodes' % dom_node_count) |
- logging.info(' Event listener count: %d listeners' % event_listener_count) |
- logging.info(' Browser process private memory: %d KB' % |
- proc_info['browser_private_mem']) |
- logging.info(' Tab process private memory: %d KB' % |
- proc_info['tab_private_mem']) |
- logging.info(' V8 memory used: %f KB' % v8_mem_used) |
- logging.info(' V8 memory allocated: %f KB' % v8_mem_allocated) |
- |
- # Output any new timeline events that have occurred. |
- if self._events_to_output: |
- logging.info('Logging timeline events...') |
- event_type_to_value_list = {} |
- for event_info in self._events_to_output: |
- if not event_info['type'] in event_type_to_value_list: |
- event_type_to_value_list[event_info['type']] = [] |
- event_type_to_value_list[event_info['type']].append( |
- (event_info['time'], event_info['data'])) |
- for event_type, value_list in event_type_to_value_list.iteritems(): |
- self._OutputEventGraphValue(event_type, value_list) |
- self._events_to_output = [] |
- else: |
- logging.info('No new timeline events to log.') |
- |
- def _GetElement(self, find_by, value): |
- """Gets a WebDriver element object from the webpage DOM. |
- |
- Args: |
- find_by: A callable that queries WebDriver for an element from the DOM. |
- value: A string value that can be passed to the |find_by| callable. |
- |
- Returns: |
- The identified WebDriver element object, if found in the DOM, or |
- None, otherwise. |
- """ |
- try: |
- return find_by(value) |
- except selenium.common.exceptions.NoSuchElementException: |
- return None |
- |
- def _ClickElementByXpath(self, driver, xpath): |
- """Given the xpath for a DOM element, clicks on it using WebDriver. |
- |
- Args: |
- driver: A WebDriver object, as returned by self.NewWebDriver(). |
- xpath: The string xpath associated with the DOM element to click. |
- |
- Returns: |
- True, if the DOM element was found and clicked successfully, or |
- False, otherwise. |
- """ |
- try: |
- self.WaitForDomNode(xpath) |
- except (pyauto_errors.JSONInterfaceError, |
- pyauto_errors.JavascriptRuntimeError) as e: |
- logging.exception('PyAuto exception: %s' % e) |
- return False |
- |
- try: |
- element = self._GetElement(driver.find_element_by_xpath, xpath) |
- element.click() |
- except (selenium.common.exceptions.StaleElementReferenceException, |
- selenium.common.exceptions.TimeoutException) as e: |
- logging.exception('WebDriver exception: %s' % e) |
- return False |
- |
- return True |
- |
- |
-class ChromeEndureControlTest(ChromeEndureBaseTest): |
- """Control tests for Chrome Endure.""" |
- |
- _WEBAPP_NAME = 'Control' |
- _TAB_TITLE_SUBSTRING = 'Chrome Endure Control Test' |
- |
- def testControlAttachDetachDOMTree(self): |
- """Continually attach and detach a DOM tree from a basic document.""" |
- test_description = 'AttachDetachDOMTree' |
- url = self.GetHttpURLForDataPath('chrome_endure', 'endurance_control.html') |
- self.NavigateToURL(url) |
- loaded_tab_title = self.GetActiveTabTitle() |
- self.assertTrue(self._TAB_TITLE_SUBSTRING in loaded_tab_title, |
- msg='Loaded tab title does not contain "%s": "%s"' % |
- (self._TAB_TITLE_SUBSTRING, loaded_tab_title)) |
- |
- def scenario(): |
- # Just sleep. Javascript in the webpage itself does the work. |
- time.sleep(5) |
- |
- self._RunEndureTest(self._WEBAPP_NAME, self._TAB_TITLE_SUBSTRING, |
- test_description, scenario) |
- |
- def testControlAttachDetachDOMTreeWebDriver(self): |
- """Use WebDriver to attach and detach a DOM tree from a basic document.""" |
- test_description = 'AttachDetachDOMTreeWebDriver' |
- url = self.GetHttpURLForDataPath('chrome_endure', |
- 'endurance_control_webdriver.html') |
- self.NavigateToURL(url) |
- loaded_tab_title = self.GetActiveTabTitle() |
- self.assertTrue(self._TAB_TITLE_SUBSTRING in loaded_tab_title, |
- msg='Loaded tab title does not contain "%s": "%s"' % |
- (self._TAB_TITLE_SUBSTRING, loaded_tab_title)) |
- |
- driver = self.NewWebDriver() |
- |
- def scenario(driver): |
- # Click the "attach" button to attach a large DOM tree (with event |
- # listeners) to the document, wait half a second, click "detach" to detach |
- # the DOM tree from the document, wait half a second. |
- self._ClickElementByXpath(driver, 'id("attach")') |
- time.sleep(0.5) |
- self._ClickElementByXpath(driver, 'id("detach")') |
- time.sleep(0.5) |
- |
- self._RunEndureTest(self._WEBAPP_NAME, self._TAB_TITLE_SUBSTRING, |
- test_description, lambda: scenario(driver)) |
- |
- |
-class IndexedDBOfflineTest(ChromeEndureBaseTest): |
- """Long-running performance tests for IndexedDB, modeling offline usage.""" |
- |
- _WEBAPP_NAME = 'IndexedDBOffline' |
- _TAB_TITLE_SUBSTRING = 'IndexedDB Offline' |
- |
- def setUp(self): |
- ChromeEndureBaseTest.setUp(self) |
- |
- url = self.GetHttpURLForDataPath('indexeddb', 'endure', 'app.html') |
- self.NavigateToURL(url) |
- loaded_tab_title = self.GetActiveTabTitle() |
- self.assertTrue(self._TAB_TITLE_SUBSTRING in loaded_tab_title, |
- msg='Loaded tab title does not contain "%s": "%s"' % |
- (self._TAB_TITLE_SUBSTRING, loaded_tab_title)) |
- |
- self._driver = self.NewWebDriver() |
- |
- def testOfflineOnline(self): |
- """Simulates user input while offline and sync while online. |
- |
- This test alternates between a simulated "Offline" state (where user |
- input events are queued) and an "Online" state (where user input events |
- are dequeued, sync data is staged, and sync data is unstaged). |
- """ |
- test_description = 'OnlineOfflineSync' |
- |
- def scenario(): |
- # Click the "Online" button and let simulated sync run for 1 second. |
- if not self._ClickElementByXpath(self._driver, 'id("online")'): |
- self._num_errors += 1 |
- logging.warning('Logging an automation error: click "online" button.') |
- |
- try: |
- self.WaitForDomNode('id("state")[text()="online"]') |
- except (pyauto_errors.JSONInterfaceError, |
- pyauto_errors.JavascriptRuntimeError): |
- self._num_errors += 1 |
- logging.warning('Logging an automation error: wait for "online".') |
- |
- time.sleep(1) |
- |
- # Click the "Offline" button and let user input occur for 1 second. |
- if not self._ClickElementByXpath(self._driver, 'id("offline")'): |
- self._num_errors += 1 |
- logging.warning('Logging an automation error: click "offline" button.') |
- |
- try: |
- self.WaitForDomNode('id("state")[text()="offline"]') |
- except (pyauto_errors.JSONInterfaceError, |
- pyauto_errors.JavascriptRuntimeError): |
- self._num_errors += 1 |
- logging.warning('Logging an automation error: wait for "offline".') |
- |
- time.sleep(1) |
- |
- self._RunEndureTest(self._WEBAPP_NAME, self._TAB_TITLE_SUBSTRING, |
- test_description, scenario) |
- |
- |
-if __name__ == '__main__': |
- pyauto_functional.Main() |