| Index: chrome/test/functional/perf.py
|
| ===================================================================
|
| --- chrome/test/functional/perf.py (revision 261231)
|
| +++ chrome/test/functional/perf.py (working copy)
|
| @@ -1,2426 +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.
|
| -
|
| -"""Basic pyauto performance tests.
|
| -
|
| -For tests that need to be run for multiple iterations (e.g., so that average
|
| -and standard deviation values can be reported), the default number of iterations
|
| -run for each of these tests is specified by |_DEFAULT_NUM_ITERATIONS|.
|
| -That value can optionally be tweaked by setting an environment variable
|
| -'NUM_ITERATIONS' to a positive integer, representing the number of iterations
|
| -to run. An additional, initial iteration will also be run to "warm up" the
|
| -environment, and the result from that initial iteration will be ignored.
|
| -
|
| -Some tests rely on repeatedly appending tabs to Chrome. Occasionally, these
|
| -automation calls time out, thereby affecting the timing measurements (see issue
|
| -crosbug.com/20503). To work around this, the tests discard timing measurements
|
| -that involve automation timeouts. The value |_DEFAULT_MAX_TIMEOUT_COUNT|
|
| -specifies the threshold number of timeouts that can be tolerated before the test
|
| -fails. To tweak this value, set environment variable 'MAX_TIMEOUT_COUNT' to the
|
| -desired threshold value.
|
| -"""
|
| -
|
| -import BaseHTTPServer
|
| -import commands
|
| -import errno
|
| -import itertools
|
| -import logging
|
| -import math
|
| -import os
|
| -import posixpath
|
| -import re
|
| -import SimpleHTTPServer
|
| -import SocketServer
|
| -import signal
|
| -import subprocess
|
| -import sys
|
| -import tempfile
|
| -import threading
|
| -import time
|
| -import timeit
|
| -import urllib
|
| -import urllib2
|
| -import urlparse
|
| -
|
| -import pyauto_functional # Must be imported before pyauto.
|
| -import pyauto
|
| -import simplejson # Must be imported after pyauto; located in third_party.
|
| -
|
| -from netflix import NetflixTestHelper
|
| -import pyauto_utils
|
| -import test_utils
|
| -from youtube import YoutubeTestHelper
|
| -
|
| -
|
| -_CHROME_BASE_DIR = os.path.abspath(os.path.join(
|
| - os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir))
|
| -
|
| -
|
| -def FormatChromePath(posix_path, **kwargs):
|
| - """Convert a path relative to the Chromium root into an OS-specific path.
|
| -
|
| - Args:
|
| - posix_path: a path string that may be a format().
|
| - Example: 'src/third_party/{module_name}/__init__.py'
|
| - kwargs: args for the format replacement.
|
| - Example: {'module_name': 'pylib'}
|
| -
|
| - Returns:
|
| - an absolute path in the current Chromium tree with formatting applied.
|
| - """
|
| - formated_path = posix_path.format(**kwargs)
|
| - path_parts = formated_path.split('/')
|
| - return os.path.join(_CHROME_BASE_DIR, *path_parts)
|
| -
|
| -
|
| -def StandardDeviation(values):
|
| - """Returns the standard deviation of |values|."""
|
| - avg = Mean(values)
|
| - if len(values) < 2 or not avg:
|
| - return 0.0
|
| - temp_vals = [math.pow(x - avg, 2) for x in values]
|
| - return math.sqrt(sum(temp_vals) / (len(temp_vals) - 1))
|
| -
|
| -
|
| -def Mean(values):
|
| - """Returns the arithmetic mean of |values|."""
|
| - if not values or None in values:
|
| - return None
|
| - return sum(values) / float(len(values))
|
| -
|
| -
|
| -def GeometricMean(values):
|
| - """Returns the geometric mean of |values|."""
|
| - if not values or None in values or [x for x in values if x < 0.0]:
|
| - return None
|
| - if 0.0 in values:
|
| - return 0.0
|
| - return math.exp(Mean([math.log(x) for x in values]))
|
| -
|
| -
|
| -class BasePerfTest(pyauto.PyUITest):
|
| - """Base class for performance tests."""
|
| -
|
| - _DEFAULT_NUM_ITERATIONS = 10 # Keep synced with desktopui_PyAutoPerfTests.py.
|
| - _DEFAULT_MAX_TIMEOUT_COUNT = 10
|
| - _PERF_OUTPUT_MARKER_PRE = '_PERF_PRE_'
|
| - _PERF_OUTPUT_MARKER_POST = '_PERF_POST_'
|
| -
|
| - def setUp(self):
|
| - """Performs necessary setup work before running each test."""
|
| - self._num_iterations = self._DEFAULT_NUM_ITERATIONS
|
| - if 'NUM_ITERATIONS' in os.environ:
|
| - self._num_iterations = int(os.environ['NUM_ITERATIONS'])
|
| - self._max_timeout_count = self._DEFAULT_MAX_TIMEOUT_COUNT
|
| - if 'MAX_TIMEOUT_COUNT' in os.environ:
|
| - self._max_timeout_count = int(os.environ['MAX_TIMEOUT_COUNT'])
|
| - self._timeout_count = 0
|
| -
|
| - # For users who want to see local perf graphs for Chrome when running the
|
| - # tests on their own machines.
|
| - self._local_perf_dir = None
|
| - if 'LOCAL_PERF_DIR' in os.environ:
|
| - self._local_perf_dir = os.environ['LOCAL_PERF_DIR']
|
| - if not os.path.exists(self._local_perf_dir):
|
| - self.fail('LOCAL_PERF_DIR environment variable specified as %s, '
|
| - 'but this directory does not exist.' % self._local_perf_dir)
|
| - # When outputting perf graph information on-the-fly for Chrome, this
|
| - # variable lets us know whether a perf measurement is for a new test
|
| - # execution, or the current test execution.
|
| - self._seen_graph_lines = {}
|
| -
|
| - pyauto.PyUITest.setUp(self)
|
| -
|
| - # Flush all buffers to disk and wait until system calms down. Must be done
|
| - # *after* calling pyauto.PyUITest.setUp, since that is where Chrome is
|
| - # killed and re-initialized for a new test.
|
| - # TODO(dennisjeffrey): Implement wait for idle CPU on Windows/Mac.
|
| - if self.IsLinux(): # IsLinux() also implies IsChromeOS().
|
| - os.system('sync')
|
| - self._WaitForIdleCPU(60.0, 0.05)
|
| -
|
| - def _IsPIDRunning(self, pid):
|
| - """Checks if a given process id is running.
|
| -
|
| - Args:
|
| - pid: The process id of the process to check.
|
| -
|
| - Returns:
|
| - True if the process is running. False if not.
|
| - """
|
| - try:
|
| - # Note that this sends the signal 0, which should not interfere with the
|
| - # process.
|
| - os.kill(pid, 0)
|
| - except OSError, err:
|
| - if err.errno == errno.ESRCH:
|
| - return False
|
| -
|
| - try:
|
| - with open('/proc/%s/status' % pid) as proc_file:
|
| - if 'zombie' in proc_file.read():
|
| - return False
|
| - except IOError:
|
| - return False
|
| - return True
|
| -
|
| - def _GetAllDescendentProcesses(self, pid):
|
| - pstree_out = subprocess.check_output(['pstree', '-p', '%s' % pid])
|
| - children = re.findall('\((\d+)\)', pstree_out)
|
| - return [int(pid) for pid in children]
|
| -
|
| - def _WaitForChromeExit(self, browser_info, timeout):
|
| - pid = browser_info['browser_pid']
|
| - chrome_pids = self._GetAllDescendentProcesses(pid)
|
| - initial_time = time.time()
|
| - while time.time() - initial_time < timeout:
|
| - if any([self._IsPIDRunning(pid) for pid in chrome_pids]):
|
| - time.sleep(1)
|
| - else:
|
| - logging.info('_WaitForChromeExit() took: %s seconds',
|
| - time.time() - initial_time)
|
| - return
|
| - self.fail('_WaitForChromeExit() did not finish within %s seconds' %
|
| - timeout)
|
| -
|
| - def tearDown(self):
|
| - if self._IsPGOMode():
|
| - browser_info = self.GetBrowserInfo()
|
| - pid = browser_info['browser_pid']
|
| - # session_manager kills chrome without waiting for it to cleanly exit.
|
| - # Until that behavior is changed, we stop it and wait for Chrome to exit
|
| - # cleanly before restarting it. See:
|
| - # crbug.com/264717
|
| - subprocess.call(['sudo', 'pkill', '-STOP', 'session_manager'])
|
| - os.kill(pid, signal.SIGINT)
|
| - self._WaitForChromeExit(browser_info, 120)
|
| - subprocess.call(['sudo', 'pkill', '-CONT', 'session_manager'])
|
| -
|
| - pyauto.PyUITest.tearDown(self)
|
| -
|
| - def _IsPGOMode(self):
|
| - return 'USE_PGO' in os.environ
|
| -
|
| - def _WaitForIdleCPU(self, timeout, utilization):
|
| - """Waits for the CPU to become idle (< utilization).
|
| -
|
| - Args:
|
| - timeout: The longest time in seconds to wait before throwing an error.
|
| - utilization: The CPU usage below which the system should be considered
|
| - idle (between 0 and 1.0 independent of cores/hyperthreads).
|
| - """
|
| - time_passed = 0.0
|
| - fraction_non_idle_time = 1.0
|
| - logging.info('Starting to wait up to %fs for idle CPU...', timeout)
|
| - while fraction_non_idle_time >= utilization:
|
| - cpu_usage_start = self._GetCPUUsage()
|
| - time.sleep(2)
|
| - time_passed += 2.0
|
| - cpu_usage_end = self._GetCPUUsage()
|
| - fraction_non_idle_time = \
|
| - self._GetFractionNonIdleCPUTime(cpu_usage_start, cpu_usage_end)
|
| - logging.info('Current CPU utilization = %f.', fraction_non_idle_time)
|
| - if time_passed > timeout:
|
| - self._LogProcessActivity()
|
| - message = ('CPU did not idle after %fs wait (utilization = %f).' % (
|
| - time_passed, fraction_non_idle_time))
|
| -
|
| - # crosbug.com/37389
|
| - if self._IsPGOMode():
|
| - logging.info(message)
|
| - logging.info('Still continuing because we are in PGO mode.')
|
| - return
|
| -
|
| - self.fail(message)
|
| - logging.info('Wait for idle CPU took %fs (utilization = %f).',
|
| - time_passed, fraction_non_idle_time)
|
| -
|
| - def _LogProcessActivity(self):
|
| - """Logs the output of top on Linux/Mac/CrOS.
|
| -
|
| - TODO: use taskmgr or similar on Windows.
|
| - """
|
| - if self.IsLinux() or self.IsMac(): # IsLinux() also implies IsChromeOS().
|
| - logging.info('Logging current process activity using top.')
|
| - cmd = 'top -b -d1 -n1'
|
| - if self.IsMac():
|
| - cmd = 'top -l1'
|
| - p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
| - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
|
| - output = p.stdout.read()
|
| - logging.info(output)
|
| - else:
|
| - logging.info('Process activity logging not implemented on this OS.')
|
| -
|
| - def _AppendTab(self, url):
|
| - """Appends a tab and increments a counter if the automation call times out.
|
| -
|
| - Args:
|
| - url: The string url to which the appended tab should be navigated.
|
| - """
|
| - if not self.AppendTab(pyauto.GURL(url)):
|
| - self._timeout_count += 1
|
| -
|
| - def _MeasureElapsedTime(self, python_command, num_invocations=1):
|
| - """Measures time (in msec) to execute a python command one or more times.
|
| -
|
| - Args:
|
| - python_command: A callable.
|
| - num_invocations: An integer number of times to invoke the given command.
|
| -
|
| - Returns:
|
| - The time required to execute the python command the specified number of
|
| - times, in milliseconds as a float.
|
| - """
|
| - assert callable(python_command)
|
| - def RunCommand():
|
| - for _ in range(num_invocations):
|
| - python_command()
|
| - timer = timeit.Timer(stmt=RunCommand)
|
| - return timer.timeit(number=1) * 1000 # Convert seconds to milliseconds.
|
| -
|
| - def _OutputPerfForStandaloneGraphing(self, graph_name, description, value,
|
| - units, units_x, is_stacked):
|
| - """Outputs perf measurement data to a local folder to be graphed.
|
| -
|
| - This function only applies to Chrome desktop, and assumes that environment
|
| - variable 'LOCAL_PERF_DIR' has been specified and refers to a valid directory
|
| - on the local machine.
|
| -
|
| - Args:
|
| - graph_name: A string name for the graph associated with this performance
|
| - value.
|
| - description: A string description of the performance value. Should not
|
| - include spaces.
|
| - value: Either a single numeric value representing a performance
|
| - measurement, or else a list of (x, y) tuples representing one or more
|
| - long-running performance measurements, where 'x' is an x-axis value
|
| - (such as an iteration number) and 'y' is the corresponding performance
|
| - measurement. If a list of tuples is given, then the |units_x|
|
| - argument must also be specified.
|
| - units: A string representing the units of the performance measurement(s).
|
| - Should not include spaces.
|
| - units_x: A string representing the units of the x-axis values associated
|
| - with the performance measurements, such as 'iteration' if the x values
|
| - are iteration numbers. If this argument is specified, then the
|
| - |value| argument must be a list of (x, y) tuples.
|
| - is_stacked: True to draw a "stacked" graph. First-come values are
|
| - stacked at bottom by default.
|
| - """
|
| - revision_num_file = os.path.join(self._local_perf_dir, 'last_revision.dat')
|
| - if os.path.exists(revision_num_file):
|
| - with open(revision_num_file) as f:
|
| - revision = int(f.read())
|
| - else:
|
| - revision = 0
|
| -
|
| - if not self._seen_graph_lines:
|
| - # We're about to output data for a new test run.
|
| - revision += 1
|
| -
|
| - # Update graphs.dat.
|
| - existing_graphs = []
|
| - graphs_file = os.path.join(self._local_perf_dir, 'graphs.dat')
|
| - if os.path.exists(graphs_file):
|
| - with open(graphs_file) as f:
|
| - existing_graphs = simplejson.loads(f.read())
|
| - is_new_graph = True
|
| - for graph in existing_graphs:
|
| - if graph['name'] == graph_name:
|
| - is_new_graph = False
|
| - break
|
| - if is_new_graph:
|
| - new_graph = {
|
| - 'name': graph_name,
|
| - 'units': units,
|
| - 'important': False,
|
| - }
|
| - if units_x:
|
| - new_graph['units_x'] = units_x
|
| - existing_graphs.append(new_graph)
|
| - with open(graphs_file, 'w') as f:
|
| - f.write(simplejson.dumps(existing_graphs))
|
| - os.chmod(graphs_file, 0755)
|
| -
|
| - # Update data file for this particular graph.
|
| - existing_lines = []
|
| - data_file = os.path.join(self._local_perf_dir, graph_name + '-summary.dat')
|
| - if os.path.exists(data_file):
|
| - with open(data_file) as f:
|
| - existing_lines = f.readlines()
|
| - existing_lines = map(
|
| - simplejson.loads, map(lambda x: x.strip(), existing_lines))
|
| -
|
| - seen_key = graph_name
|
| - # We assume that the first line |existing_lines[0]| is the latest.
|
| - if units_x:
|
| - new_line = {
|
| - 'rev': revision,
|
| - 'traces': { description: [] }
|
| - }
|
| - if seen_key in self._seen_graph_lines:
|
| - # We've added points previously for this graph line in the current
|
| - # test execution, so retrieve the original set of points specified in
|
| - # the most recent revision in the data file.
|
| - new_line = existing_lines[0]
|
| - if not description in new_line['traces']:
|
| - new_line['traces'][description] = []
|
| - for x_value, y_value in value:
|
| - new_line['traces'][description].append([str(x_value), str(y_value)])
|
| - else:
|
| - new_line = {
|
| - 'rev': revision,
|
| - 'traces': { description: [str(value), str(0.0)] }
|
| - }
|
| -
|
| - if is_stacked:
|
| - new_line['stack'] = True
|
| - if 'stack_order' not in new_line:
|
| - new_line['stack_order'] = []
|
| - if description not in new_line['stack_order']:
|
| - new_line['stack_order'].append(description)
|
| -
|
| - if seen_key in self._seen_graph_lines:
|
| - # Update results for the most recent revision.
|
| - existing_lines[0] = new_line
|
| - else:
|
| - # New results for a new revision.
|
| - existing_lines.insert(0, new_line)
|
| - self._seen_graph_lines[seen_key] = True
|
| -
|
| - existing_lines = map(simplejson.dumps, existing_lines)
|
| - with open(data_file, 'w') as f:
|
| - f.write('\n'.join(existing_lines))
|
| - os.chmod(data_file, 0755)
|
| -
|
| - with open(revision_num_file, 'w') as f:
|
| - f.write(str(revision))
|
| -
|
| - def _OutputPerfGraphValue(self, description, value, units,
|
| - graph_name, units_x=None, is_stacked=False):
|
| - """Outputs a performance value to have it graphed on the performance bots.
|
| -
|
| - The output format differs, depending on whether the current platform is
|
| - Chrome desktop or ChromeOS.
|
| -
|
| - For ChromeOS, the performance bots have a 30-character limit on the length
|
| - of the key associated with a performance value. A key on ChromeOS is
|
| - considered to be of the form "units_description" (for example,
|
| - "milliseconds_NewTabPage"), and is created from the |units| and
|
| - |description| passed as input to this function. Any characters beyond the
|
| - length 30 limit are truncated before results are stored in the autotest
|
| - database.
|
| -
|
| - Args:
|
| - description: A string description of the performance value. Should not
|
| - include spaces.
|
| - value: Either a numeric value representing a performance measurement, or
|
| - a list of values to be averaged. Lists may also contain (x, y) tuples
|
| - representing one or more performance measurements, where 'x' is an
|
| - x-axis value (such as an iteration number) and 'y' is the
|
| - corresponding performance measurement. If a list of tuples is given,
|
| - the |units_x| argument must also be specified.
|
| - units: A string representing the units of the performance measurement(s).
|
| - Should not include spaces.
|
| - graph_name: A string name for the graph associated with this performance
|
| - value. Only used on Chrome desktop.
|
| - units_x: A string representing the units of the x-axis values associated
|
| - with the performance measurements, such as 'iteration' if the x values
|
| - are iteration numbers. If this argument is specified, then the
|
| - |value| argument must be a list of (x, y) tuples.
|
| - is_stacked: True to draw a "stacked" graph. First-come values are
|
| - stacked at bottom by default.
|
| - """
|
| - if (isinstance(value, list) and value[0] is not None and
|
| - isinstance(value[0], tuple)):
|
| - assert units_x
|
| - if units_x:
|
| - assert isinstance(value, list)
|
| -
|
| - if self.IsChromeOS():
|
| - # Autotest doesn't support result lists.
|
| - autotest_value = value
|
| - if (isinstance(value, list) and value[0] is not None and
|
| - not isinstance(value[0], tuple)):
|
| - autotest_value = Mean(value)
|
| -
|
| - if units_x:
|
| - # TODO(dennisjeffrey): Support long-running performance measurements on
|
| - # ChromeOS in a way that can be graphed: crosbug.com/21881.
|
| - pyauto_utils.PrintPerfResult(graph_name, description, autotest_value,
|
| - units + ' ' + units_x)
|
| - else:
|
| - # Output short-running performance results in a format understood by
|
| - # autotest.
|
| - perf_key = '%s_%s' % (units, description)
|
| - if len(perf_key) > 30:
|
| - logging.warning('The description "%s" will be truncated to "%s" '
|
| - '(length 30) when added to the autotest database.',
|
| - perf_key, perf_key[:30])
|
| - print '\n%s(\'%s\', %f)%s' % (self._PERF_OUTPUT_MARKER_PRE,
|
| - perf_key, autotest_value,
|
| - self._PERF_OUTPUT_MARKER_POST)
|
| -
|
| - # Also output results in the format recognized by buildbot, for cases
|
| - # in which these tests are run on chromeOS through buildbot. Since
|
| - # buildbot supports result lists, it's ok for |value| to be a list here.
|
| - pyauto_utils.PrintPerfResult(graph_name, description, value, units)
|
| -
|
| - sys.stdout.flush()
|
| - else:
|
| - # TODO(dmikurube): Support stacked graphs in PrintPerfResult.
|
| - # See http://crbug.com/122119.
|
| - if units_x:
|
| - pyauto_utils.PrintPerfResult(graph_name, description, value,
|
| - units + ' ' + units_x)
|
| - else:
|
| - pyauto_utils.PrintPerfResult(graph_name, description, value, units)
|
| -
|
| - if self._local_perf_dir:
|
| - self._OutputPerfForStandaloneGraphing(
|
| - graph_name, description, value, units, units_x, is_stacked)
|
| -
|
| - def _OutputEventForStandaloneGraphing(self, description, event_list):
|
| - """Outputs event information to a local folder to be graphed.
|
| -
|
| - See function _OutputEventGraphValue below for a description of an event.
|
| -
|
| - This function only applies to Chrome Endure tests running on Chrome desktop,
|
| - and assumes that environment variable 'LOCAL_PERF_DIR' has been specified
|
| - and refers to a valid directory on the local machine.
|
| -
|
| - Args:
|
| - description: A string description of the event. Should not include
|
| - spaces.
|
| - event_list: A list of (x, y) tuples representing one or more events
|
| - occurring during an endurance test, where 'x' is the time of the event
|
| - (in seconds since the start of the test), and 'y' is a dictionary
|
| - representing relevant data associated with that event (as key/value
|
| - pairs).
|
| - """
|
| - revision_num_file = os.path.join(self._local_perf_dir, 'last_revision.dat')
|
| - if os.path.exists(revision_num_file):
|
| - with open(revision_num_file) as f:
|
| - revision = int(f.read())
|
| - else:
|
| - revision = 0
|
| -
|
| - if not self._seen_graph_lines:
|
| - # We're about to output data for a new test run.
|
| - revision += 1
|
| -
|
| - existing_lines = []
|
| - data_file = os.path.join(self._local_perf_dir, '_EVENT_-summary.dat')
|
| - if os.path.exists(data_file):
|
| - with open(data_file) as f:
|
| - existing_lines = f.readlines()
|
| - existing_lines = map(eval, map(lambda x: x.strip(), existing_lines))
|
| -
|
| - seen_event_type = description
|
| - value_list = []
|
| - if seen_event_type in self._seen_graph_lines:
|
| - # We've added events previously for this event type in the current
|
| - # test execution, so retrieve the original set of values specified in
|
| - # the most recent revision in the data file.
|
| - value_list = existing_lines[0]['events'][description]
|
| - for event_time, event_data in event_list:
|
| - value_list.append([str(event_time), event_data])
|
| - new_events = {
|
| - description: value_list
|
| - }
|
| -
|
| - new_line = {
|
| - 'rev': revision,
|
| - 'events': new_events
|
| - }
|
| -
|
| - if seen_event_type in self._seen_graph_lines:
|
| - # Update results for the most recent revision.
|
| - existing_lines[0] = new_line
|
| - else:
|
| - # New results for a new revision.
|
| - existing_lines.insert(0, new_line)
|
| - self._seen_graph_lines[seen_event_type] = True
|
| -
|
| - existing_lines = map(str, existing_lines)
|
| - with open(data_file, 'w') as f:
|
| - f.write('\n'.join(existing_lines))
|
| - os.chmod(data_file, 0755)
|
| -
|
| - with open(revision_num_file, 'w') as f:
|
| - f.write(str(revision))
|
| -
|
| - def _OutputEventGraphValue(self, description, event_list):
|
| - """Outputs a set of events to have them graphed on the Chrome Endure bots.
|
| -
|
| - An "event" can be anything recorded by a performance test that occurs at
|
| - particular times during a test execution. For example, a garbage collection
|
| - in the v8 heap can be considered an event. An event is distinguished from a
|
| - regular perf measurement in two ways: (1) an event is depicted differently
|
| - in the performance graphs than performance measurements; (2) an event can
|
| - be associated with zero or more data fields describing relevant information
|
| - associated with the event. For example, a garbage collection event will
|
| - occur at a particular time, and it may be associated with data such as
|
| - the number of collected bytes and/or the length of time it took to perform
|
| - the garbage collection.
|
| -
|
| - This function only applies to Chrome Endure tests running on Chrome desktop.
|
| -
|
| - Args:
|
| - description: A string description of the event. Should not include
|
| - spaces.
|
| - event_list: A list of (x, y) tuples representing one or more events
|
| - occurring during an endurance test, where 'x' is the time of the event
|
| - (in seconds since the start of the test), and 'y' is a dictionary
|
| - representing relevant data associated with that event (as key/value
|
| - pairs).
|
| - """
|
| - pyauto_utils.PrintPerfResult('_EVENT_', description, event_list, '')
|
| - if self._local_perf_dir:
|
| - self._OutputEventForStandaloneGraphing(description, event_list)
|
| -
|
| - def _PrintSummaryResults(self, description, values, units, graph_name):
|
| - """Logs summary measurement information.
|
| -
|
| - This function computes and outputs the average and standard deviation of
|
| - the specified list of value measurements. It also invokes
|
| - _OutputPerfGraphValue() with the computed *average* value, to ensure the
|
| - average value can be plotted in a performance graph.
|
| -
|
| - Args:
|
| - description: A string description for the specified results.
|
| - values: A list of numeric value measurements.
|
| - units: A string specifying the units for the specified measurements.
|
| - graph_name: A string name for the graph associated with this performance
|
| - value. Only used on Chrome desktop.
|
| - """
|
| - logging.info('Overall results for: %s', description)
|
| - if values:
|
| - logging.info(' Average: %f %s', Mean(values), units)
|
| - logging.info(' Std dev: %f %s', StandardDeviation(values), units)
|
| - self._OutputPerfGraphValue(description, values, units, graph_name)
|
| - else:
|
| - logging.info('No results to report.')
|
| -
|
| - def _RunNewTabTest(self, description, open_tab_command, graph_name,
|
| - num_tabs=1):
|
| - """Runs a perf test that involves opening new tab(s).
|
| -
|
| - This helper function can be called from different tests to do perf testing
|
| - with different types of tabs. It is assumed that the |open_tab_command|
|
| - will open up a single tab.
|
| -
|
| - Args:
|
| - description: A string description of the associated tab test.
|
| - open_tab_command: A callable that will open a single tab.
|
| - graph_name: A string name for the performance graph associated with this
|
| - test. Only used on Chrome desktop.
|
| - num_tabs: The number of tabs to open, i.e., the number of times to invoke
|
| - the |open_tab_command|.
|
| - """
|
| - assert callable(open_tab_command)
|
| -
|
| - timings = []
|
| - for iteration in range(self._num_iterations + 1):
|
| - orig_timeout_count = self._timeout_count
|
| - elapsed_time = self._MeasureElapsedTime(open_tab_command,
|
| - num_invocations=num_tabs)
|
| - # Only count the timing measurement if no automation call timed out.
|
| - if self._timeout_count == orig_timeout_count:
|
| - # Ignore the first iteration.
|
| - if iteration:
|
| - timings.append(elapsed_time)
|
| - logging.info('Iteration %d of %d: %f milliseconds', iteration,
|
| - self._num_iterations, elapsed_time)
|
| - self.assertTrue(self._timeout_count <= self._max_timeout_count,
|
| - msg='Test exceeded automation timeout threshold.')
|
| - self.assertEqual(1 + num_tabs, self.GetTabCount(),
|
| - msg='Did not open %d new tab(s).' % num_tabs)
|
| - for _ in range(num_tabs):
|
| - self.CloseTab(tab_index=1)
|
| -
|
| - self._PrintSummaryResults(description, timings, 'milliseconds', graph_name)
|
| -
|
| - def _GetConfig(self):
|
| - """Load perf test configuration file.
|
| -
|
| - Returns:
|
| - A dictionary that represents the config information.
|
| - """
|
| - config_file = os.path.join(os.path.dirname(__file__), 'perf.cfg')
|
| - config = {'username': None,
|
| - 'password': None,
|
| - 'google_account_url': 'https://accounts.google.com/',
|
| - 'gmail_url': 'https://www.gmail.com',
|
| - 'plus_url': 'https://plus.google.com',
|
| - 'docs_url': 'https://docs.google.com'}
|
| - if os.path.exists(config_file):
|
| - try:
|
| - new_config = pyauto.PyUITest.EvalDataFrom(config_file)
|
| - for key in new_config:
|
| - if new_config.get(key) is not None:
|
| - config[key] = new_config.get(key)
|
| - except SyntaxError, e:
|
| - logging.info('Could not read %s: %s', config_file, str(e))
|
| - return config
|
| -
|
| - def _LoginToGoogleAccount(self, account_key='test_google_account'):
|
| - """Logs in to a test Google account.
|
| -
|
| - Login with user-defined credentials if they exist.
|
| - Else login with private test credentials if they exist.
|
| - Else fail.
|
| -
|
| - Args:
|
| - account_key: The string key in private_tests_info.txt which is associated
|
| - with the test account login credentials to use. It will only
|
| - be used when fail to load user-defined credentials.
|
| -
|
| - Raises:
|
| - RuntimeError: if could not get credential information.
|
| - """
|
| - private_file = os.path.join(pyauto.PyUITest.DataDir(), 'pyauto_private',
|
| - 'private_tests_info.txt')
|
| - config_file = os.path.join(os.path.dirname(__file__), 'perf.cfg')
|
| - config = self._GetConfig()
|
| - google_account_url = config.get('google_account_url')
|
| - username = config.get('username')
|
| - password = config.get('password')
|
| - if username and password:
|
| - logging.info(
|
| - 'Using google account credential from %s',
|
| - os.path.join(os.path.dirname(__file__), 'perf.cfg'))
|
| - elif os.path.exists(private_file):
|
| - creds = self.GetPrivateInfo()[account_key]
|
| - username = creds['username']
|
| - password = creds['password']
|
| - logging.info(
|
| - 'User-defined credentials not found,' +
|
| - ' using private test credentials instead.')
|
| - else:
|
| - message = 'No user-defined or private test ' \
|
| - 'credentials could be found. ' \
|
| - 'Please specify credential information in %s.' \
|
| - % config_file
|
| - raise RuntimeError(message)
|
| - test_utils.GoogleAccountsLogin(
|
| - self, username, password, url=google_account_url)
|
| - self.NavigateToURL('about:blank') # Clear the existing tab.
|
| -
|
| - def _GetCPUUsage(self):
|
| - """Returns machine's CPU usage.
|
| -
|
| - This function uses /proc/stat to identify CPU usage, and therefore works
|
| - only on Linux/ChromeOS.
|
| -
|
| - Returns:
|
| - A dictionary with 'user', 'nice', 'system' and 'idle' values.
|
| - Sample dictionary:
|
| - {
|
| - 'user': 254544,
|
| - 'nice': 9,
|
| - 'system': 254768,
|
| - 'idle': 2859878,
|
| - }
|
| - """
|
| - try:
|
| - f = open('/proc/stat')
|
| - cpu_usage_str = f.readline().split()
|
| - f.close()
|
| - except IOError, e:
|
| - self.fail('Could not retrieve CPU usage: ' + str(e))
|
| - return {
|
| - 'user': int(cpu_usage_str[1]),
|
| - 'nice': int(cpu_usage_str[2]),
|
| - 'system': int(cpu_usage_str[3]),
|
| - 'idle': int(cpu_usage_str[4])
|
| - }
|
| -
|
| - def _GetFractionNonIdleCPUTime(self, cpu_usage_start, cpu_usage_end):
|
| - """Computes the fraction of CPU time spent non-idling.
|
| -
|
| - This function should be invoked using before/after values from calls to
|
| - _GetCPUUsage().
|
| - """
|
| - time_non_idling_end = (cpu_usage_end['user'] + cpu_usage_end['nice'] +
|
| - cpu_usage_end['system'])
|
| - time_non_idling_start = (cpu_usage_start['user'] + cpu_usage_start['nice'] +
|
| - cpu_usage_start['system'])
|
| - total_time_end = (cpu_usage_end['user'] + cpu_usage_end['nice'] +
|
| - cpu_usage_end['system'] + cpu_usage_end['idle'])
|
| - total_time_start = (cpu_usage_start['user'] + cpu_usage_start['nice'] +
|
| - cpu_usage_start['system'] + cpu_usage_start['idle'])
|
| - return ((float(time_non_idling_end) - time_non_idling_start) /
|
| - (total_time_end - total_time_start))
|
| -
|
| - def ExtraChromeFlags(self):
|
| - """Ensures Chrome is launched with custom flags.
|
| -
|
| - Returns:
|
| - A list of extra flags to pass to Chrome when it is launched.
|
| - """
|
| - flags = super(BasePerfTest, self).ExtraChromeFlags()
|
| - # Window size impacts a variety of perf tests, ensure consistency.
|
| - flags.append('--window-size=1024,768')
|
| - if self._IsPGOMode():
|
| - flags = flags + ['--no-sandbox']
|
| - return flags
|
| -
|
| -
|
| -class TabPerfTest(BasePerfTest):
|
| - """Tests that involve opening tabs."""
|
| -
|
| - def testNewTab(self):
|
| - """Measures time to open a new tab."""
|
| - self._RunNewTabTest('NewTabPage',
|
| - lambda: self._AppendTab('chrome://newtab'), 'open_tab')
|
| -
|
| - def testNewTabFlash(self):
|
| - """Measures time to open a new tab navigated to a flash page."""
|
| - self.assertTrue(
|
| - os.path.exists(os.path.join(self.ContentDataDir(), 'plugin',
|
| - 'flash.swf')),
|
| - msg='Missing required flash data file.')
|
| - url = self.GetFileURLForContentDataPath('plugin', 'flash.swf')
|
| - self._RunNewTabTest('NewTabFlashPage', lambda: self._AppendTab(url),
|
| - 'open_tab')
|
| -
|
| - def test20Tabs(self):
|
| - """Measures time to open 20 tabs."""
|
| - self._RunNewTabTest('20TabsNewTabPage',
|
| - lambda: self._AppendTab('chrome://newtab'),
|
| - 'open_20_tabs', num_tabs=20)
|
| -
|
| -
|
| -class BenchmarkPerfTest(BasePerfTest):
|
| - """Benchmark performance tests."""
|
| -
|
| - def testV8BenchmarkSuite(self):
|
| - """Measures score from v8 benchmark suite."""
|
| - url = self.GetFileURLForDataPath('v8_benchmark_v6', 'run.html')
|
| -
|
| - def _RunBenchmarkOnce(url):
|
| - """Runs the v8 benchmark suite once and returns the results in a dict."""
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(url)),
|
| - msg='Failed to append tab for v8 benchmark suite.')
|
| - js_done = """
|
| - var val = document.getElementById("status").innerHTML;
|
| - window.domAutomationController.send(val);
|
| - """
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: 'Score:' in self.ExecuteJavascript(js_done, tab_index=1),
|
| - timeout=300, expect_retval=True, retry_sleep=1),
|
| - msg='Timed out when waiting for v8 benchmark score.')
|
| -
|
| - js_get_results = """
|
| - var result = {};
|
| - result['final_score'] = document.getElementById("status").innerHTML;
|
| - result['all_results'] = document.getElementById("results").innerHTML;
|
| - window.domAutomationController.send(JSON.stringify(result));
|
| - """
|
| - results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
|
| - score_pattern = '(\w+): (\d+)'
|
| - final_score = re.search(score_pattern, results['final_score']).group(2)
|
| - result_dict = {'final_score': int(final_score)}
|
| - for match in re.finditer(score_pattern, results['all_results']):
|
| - benchmark_name = match.group(1)
|
| - benchmark_score = match.group(2)
|
| - result_dict[benchmark_name] = int(benchmark_score)
|
| - self.CloseTab(tab_index=1)
|
| - return result_dict
|
| -
|
| - timings = {}
|
| - for iteration in xrange(self._num_iterations + 1):
|
| - result_dict = _RunBenchmarkOnce(url)
|
| - # Ignore the first iteration.
|
| - if iteration:
|
| - for key, val in result_dict.items():
|
| - timings.setdefault(key, []).append(val)
|
| - logging.info('Iteration %d of %d:\n%s', iteration,
|
| - self._num_iterations, self.pformat(result_dict))
|
| -
|
| - for key, val in timings.items():
|
| - if key == 'final_score':
|
| - self._PrintSummaryResults('V8Benchmark', val, 'score',
|
| - 'v8_benchmark_final')
|
| - else:
|
| - self._PrintSummaryResults('V8Benchmark-%s' % key, val, 'score',
|
| - 'v8_benchmark_individual')
|
| -
|
| - def testSunSpider(self):
|
| - """Runs the SunSpider javascript benchmark suite."""
|
| - url = self.GetFileURLForDataPath('sunspider', 'sunspider-driver.html')
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(url)),
|
| - msg='Failed to append tab for SunSpider benchmark suite.')
|
| -
|
| - js_is_done = """
|
| - var done = false;
|
| - if (document.getElementById("console"))
|
| - done = true;
|
| - window.domAutomationController.send(JSON.stringify(done));
|
| - """
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(js_is_done, tab_index=1),
|
| - timeout=300, expect_retval='true', retry_sleep=1),
|
| - msg='Timed out when waiting for SunSpider benchmark score.')
|
| -
|
| - js_get_results = """
|
| - window.domAutomationController.send(
|
| - document.getElementById("console").innerHTML);
|
| - """
|
| - # Append '<br>' to the result to simplify regular expression matching.
|
| - results = self.ExecuteJavascript(js_get_results, tab_index=1) + '<br>'
|
| - total = re.search('Total:\s*([\d.]+)ms', results).group(1)
|
| - logging.info('Total: %f ms', float(total))
|
| - self._OutputPerfGraphValue('SunSpider-total', float(total), 'ms',
|
| - 'sunspider_total')
|
| -
|
| - for match_category in re.finditer('\s\s(\w+):\s*([\d.]+)ms.+?<br><br>',
|
| - results):
|
| - category_name = match_category.group(1)
|
| - category_result = match_category.group(2)
|
| - logging.info('Benchmark "%s": %f ms', category_name,
|
| - float(category_result))
|
| - self._OutputPerfGraphValue('SunSpider-' + category_name,
|
| - float(category_result), 'ms',
|
| - 'sunspider_individual')
|
| -
|
| - for match_result in re.finditer('<br>\s\s\s\s([\w-]+):\s*([\d.]+)ms',
|
| - match_category.group(0)):
|
| - result_name = match_result.group(1)
|
| - result_value = match_result.group(2)
|
| - logging.info(' Result "%s-%s": %f ms', category_name, result_name,
|
| - float(result_value))
|
| - self._OutputPerfGraphValue(
|
| - 'SunSpider-%s-%s' % (category_name, result_name),
|
| - float(result_value), 'ms', 'sunspider_individual')
|
| -
|
| - def testDromaeoSuite(self):
|
| - """Measures results from Dromaeo benchmark suite."""
|
| - url = self.GetFileURLForDataPath('dromaeo', 'index.html')
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(url + '?dromaeo')),
|
| - msg='Failed to append tab for Dromaeo benchmark suite.')
|
| -
|
| - js_is_ready = """
|
| - var val = document.getElementById('pause').value;
|
| - window.domAutomationController.send(val);
|
| - """
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(js_is_ready, tab_index=1),
|
| - timeout=30, expect_retval='Run', retry_sleep=1),
|
| - msg='Timed out when waiting for Dromaeo benchmark to load.')
|
| -
|
| - js_run = """
|
| - $('#pause').val('Run').click();
|
| - window.domAutomationController.send('done');
|
| - """
|
| - self.ExecuteJavascript(js_run, tab_index=1)
|
| -
|
| - js_is_done = """
|
| - var val = document.getElementById('timebar').innerHTML;
|
| - window.domAutomationController.send(val);
|
| - """
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: 'Total' in self.ExecuteJavascript(js_is_done, tab_index=1),
|
| - timeout=900, expect_retval=True, retry_sleep=2),
|
| - msg='Timed out when waiting for Dromaeo benchmark to complete.')
|
| -
|
| - js_get_results = """
|
| - var result = {};
|
| - result['total_result'] = $('#timebar strong').html();
|
| - result['all_results'] = {};
|
| - $('.result-item.done').each(function (i) {
|
| - var group_name = $(this).find('.test b').html().replace(':', '');
|
| - var group_results = {};
|
| - group_results['result'] =
|
| - $(this).find('span').html().replace('runs/s', '')
|
| -
|
| - group_results['sub_groups'] = {}
|
| - $(this).find('li').each(function (i) {
|
| - var sub_name = $(this).find('b').html().replace(':', '');
|
| - group_results['sub_groups'][sub_name] =
|
| - $(this).text().match(/: ([\d.]+)/)[1]
|
| - });
|
| - result['all_results'][group_name] = group_results;
|
| - });
|
| - window.domAutomationController.send(JSON.stringify(result));
|
| - """
|
| - results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
|
| - total_result = results['total_result']
|
| - logging.info('Total result: ' + total_result)
|
| - self._OutputPerfGraphValue('Dromaeo-total', float(total_result),
|
| - 'runsPerSec', 'dromaeo_total')
|
| -
|
| - for group_name, group in results['all_results'].iteritems():
|
| - logging.info('Benchmark "%s": %s', group_name, group['result'])
|
| - self._OutputPerfGraphValue('Dromaeo-' + group_name.replace(' ', ''),
|
| - float(group['result']), 'runsPerSec',
|
| - 'dromaeo_individual')
|
| - for benchmark_name, benchmark_score in group['sub_groups'].iteritems():
|
| - logging.info(' Result "%s": %s', benchmark_name, benchmark_score)
|
| -
|
| - def testSpaceport(self):
|
| - """Measures results from Spaceport benchmark suite."""
|
| - # TODO(tonyg): Test is failing on bots. Diagnose and re-enable.
|
| - pass
|
| -
|
| -# url = self.GetFileURLForDataPath('third_party', 'spaceport', 'index.html')
|
| -# self.assertTrue(self.AppendTab(pyauto.GURL(url + '?auto')),
|
| -# msg='Failed to append tab for Spaceport benchmark suite.')
|
| -#
|
| -# # The test reports results to console.log in the format "name: value".
|
| -# # Inject a bit of JS to intercept those.
|
| -# js_collect_console_log = """
|
| -# window.__pyautoresult = {};
|
| -# window.console.log = function(str) {
|
| -# if (!str) return;
|
| -# var key_val = str.split(': ');
|
| -# if (!key_val.length == 2) return;
|
| -# __pyautoresult[key_val[0]] = key_val[1];
|
| -# };
|
| -# window.domAutomationController.send('done');
|
| -# """
|
| -# self.ExecuteJavascript(js_collect_console_log, tab_index=1)
|
| -#
|
| -# def _IsDone():
|
| -# expected_num_results = 30 # The number of tests in benchmark.
|
| -# results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
|
| -# return expected_num_results == len(results)
|
| -#
|
| -# js_get_results = """
|
| -# window.domAutomationController.send(
|
| -# JSON.stringify(window.__pyautoresult));
|
| -# """
|
| -# self.assertTrue(
|
| -# self.WaitUntil(_IsDone, timeout=1200, expect_retval=True,
|
| -# retry_sleep=5),
|
| -# msg='Timed out when waiting for Spaceport benchmark to complete.')
|
| -# results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
|
| -#
|
| -# for key in results:
|
| -# suite, test = key.split('.')
|
| -# value = float(results[key])
|
| -# self._OutputPerfGraphValue(test, value, 'ObjectsAt30FPS', suite)
|
| -# self._PrintSummaryResults('Overall', [float(x) for x in results.values()],
|
| -# 'ObjectsAt30FPS', 'Overall')
|
| -
|
| -
|
| -class LiveWebappLoadTest(BasePerfTest):
|
| - """Tests that involve performance measurements of live webapps.
|
| -
|
| - These tests connect to live webpages (e.g., Gmail, Calendar, Docs) and are
|
| - therefore subject to network conditions. These tests are meant to generate
|
| - "ball-park" numbers only (to see roughly how long things take to occur from a
|
| - user's perspective), and are not expected to be precise.
|
| - """
|
| -
|
| - def testNewTabGmail(self):
|
| - """Measures time to open a tab to a logged-in Gmail account.
|
| -
|
| - Timing starts right before the new tab is opened, and stops as soon as the
|
| - webpage displays the substring 'Last account activity:'.
|
| - """
|
| - EXPECTED_SUBSTRING = 'Last account activity:'
|
| -
|
| - def _SubstringExistsOnPage():
|
| - js = """
|
| - var frame = document.getElementById("canvas_frame");
|
| - var divs = frame.contentDocument.getElementsByTagName("div");
|
| - for (var i = 0; i < divs.length; ++i) {
|
| - if (divs[i].innerHTML.indexOf("%s") >= 0)
|
| - window.domAutomationController.send("true");
|
| - }
|
| - window.domAutomationController.send("false");
|
| - """ % EXPECTED_SUBSTRING
|
| - return self.ExecuteJavascript(js, tab_index=1)
|
| -
|
| - def _RunSingleGmailTabOpen():
|
| - self._AppendTab('http://www.gmail.com')
|
| - self.assertTrue(self.WaitUntil(_SubstringExistsOnPage, timeout=120,
|
| - expect_retval='true', retry_sleep=0.10),
|
| - msg='Timed out waiting for expected Gmail string.')
|
| -
|
| - self._LoginToGoogleAccount()
|
| - self._RunNewTabTest('NewTabGmail', _RunSingleGmailTabOpen,
|
| - 'open_tab_live_webapp')
|
| -
|
| - def testNewTabCalendar(self):
|
| - """Measures time to open a tab to a logged-in Calendar account.
|
| -
|
| - Timing starts right before the new tab is opened, and stops as soon as the
|
| - webpage displays the calendar print button (title 'Print my calendar').
|
| - """
|
| - EXPECTED_SUBSTRING = 'Month'
|
| -
|
| - def _DivTitleStartsWith():
|
| - js = """
|
| - var divs = document.getElementsByTagName("div");
|
| - for (var i = 0; i < divs.length; ++i) {
|
| - if (divs[i].innerHTML == "%s")
|
| - window.domAutomationController.send("true");
|
| - }
|
| - window.domAutomationController.send("false");
|
| - """ % EXPECTED_SUBSTRING
|
| - return self.ExecuteJavascript(js, tab_index=1)
|
| -
|
| - def _RunSingleCalendarTabOpen():
|
| - self._AppendTab('http://calendar.google.com')
|
| - self.assertTrue(self.WaitUntil(_DivTitleStartsWith, timeout=120,
|
| - expect_retval='true', retry_sleep=0.10),
|
| - msg='Timed out waiting for expected Calendar string.')
|
| -
|
| - self._LoginToGoogleAccount()
|
| - self._RunNewTabTest('NewTabCalendar', _RunSingleCalendarTabOpen,
|
| - 'open_tab_live_webapp')
|
| -
|
| - def testNewTabDocs(self):
|
| - """Measures time to open a tab to a logged-in Docs account.
|
| -
|
| - Timing starts right before the new tab is opened, and stops as soon as the
|
| - webpage displays the expected substring 'last modified' (case insensitive).
|
| - """
|
| - EXPECTED_SUBSTRING = 'sort'
|
| -
|
| - def _SubstringExistsOnPage():
|
| - js = """
|
| - var divs = document.getElementsByTagName("div");
|
| - for (var i = 0; i < divs.length; ++i) {
|
| - if (divs[i].innerHTML.toLowerCase().indexOf("%s") >= 0)
|
| - window.domAutomationController.send("true");
|
| - }
|
| - window.domAutomationController.send("false");
|
| - """ % EXPECTED_SUBSTRING
|
| - return self.ExecuteJavascript(js, tab_index=1)
|
| -
|
| - def _RunSingleDocsTabOpen():
|
| - self._AppendTab('http://docs.google.com')
|
| - self.assertTrue(self.WaitUntil(_SubstringExistsOnPage, timeout=120,
|
| - expect_retval='true', retry_sleep=0.10),
|
| - msg='Timed out waiting for expected Docs string.')
|
| -
|
| - self._LoginToGoogleAccount()
|
| - self._RunNewTabTest('NewTabDocs', _RunSingleDocsTabOpen,
|
| - 'open_tab_live_webapp')
|
| -
|
| -
|
| -class NetflixPerfTest(BasePerfTest, NetflixTestHelper):
|
| - """Test Netflix video performance."""
|
| -
|
| - def __init__(self, methodName='runTest', **kwargs):
|
| - pyauto.PyUITest.__init__(self, methodName, **kwargs)
|
| - NetflixTestHelper.__init__(self, self)
|
| -
|
| - def tearDown(self):
|
| - self.SignOut()
|
| - pyauto.PyUITest.tearDown(self)
|
| -
|
| - def testNetflixDroppedFrames(self):
|
| - """Measures the Netflix video dropped frames/second. Runs for 60 secs."""
|
| - self.LoginAndStartPlaying()
|
| - self.CheckNetflixPlaying(self.IS_PLAYING,
|
| - 'Player did not start playing the title.')
|
| - # Ignore first 10 seconds of video playing so we get smooth videoplayback.
|
| - time.sleep(10)
|
| - init_dropped_frames = self._GetVideoDroppedFrames()
|
| - dropped_frames = []
|
| - prev_dropped_frames = 0
|
| - for iteration in xrange(60):
|
| - # Ignoring initial dropped frames of first 10 seconds.
|
| - total_dropped_frames = self._GetVideoDroppedFrames() - init_dropped_frames
|
| - dropped_frames_last_sec = total_dropped_frames - prev_dropped_frames
|
| - dropped_frames.append(dropped_frames_last_sec)
|
| - logging.info('Iteration %d of %d: %f dropped frames in the last second',
|
| - iteration + 1, 60, dropped_frames_last_sec)
|
| - prev_dropped_frames = total_dropped_frames
|
| - # Play the video for some time.
|
| - time.sleep(1)
|
| - self._PrintSummaryResults('NetflixDroppedFrames', dropped_frames, 'frames',
|
| - 'netflix_dropped_frames')
|
| -
|
| - def testNetflixCPU(self):
|
| - """Measures the Netflix video CPU usage. Runs for 60 seconds."""
|
| - self.LoginAndStartPlaying()
|
| - self.CheckNetflixPlaying(self.IS_PLAYING,
|
| - 'Player did not start playing the title.')
|
| - # Ignore first 10 seconds of video playing so we get smooth videoplayback.
|
| - time.sleep(10)
|
| - init_dropped_frames = self._GetVideoDroppedFrames()
|
| - init_video_frames = self._GetVideoFrames()
|
| - cpu_usage_start = self._GetCPUUsage()
|
| - total_shown_frames = 0
|
| - # Play the video for some time.
|
| - time.sleep(60)
|
| - total_video_frames = self._GetVideoFrames() - init_video_frames
|
| - total_dropped_frames = self._GetVideoDroppedFrames() - init_dropped_frames
|
| - cpu_usage_end = self._GetCPUUsage()
|
| - fraction_non_idle_time = \
|
| - self._GetFractionNonIdleCPUTime(cpu_usage_start, cpu_usage_end)
|
| - # Counting extrapolation for utilization to play the video.
|
| - extrapolation_value = fraction_non_idle_time * \
|
| - (float(total_video_frames) + total_dropped_frames) / total_video_frames
|
| - logging.info('Netflix CPU extrapolation: %f', extrapolation_value)
|
| - self._OutputPerfGraphValue('NetflixCPUExtrapolation', extrapolation_value,
|
| - 'extrapolation', 'netflix_cpu_extrapolation')
|
| -
|
| -
|
| -class YoutubePerfTest(BasePerfTest, YoutubeTestHelper):
|
| - """Test Youtube video performance."""
|
| -
|
| - def __init__(self, methodName='runTest', **kwargs):
|
| - pyauto.PyUITest.__init__(self, methodName, **kwargs)
|
| - YoutubeTestHelper.__init__(self, self)
|
| -
|
| - def _VerifyVideoTotalBytes(self):
|
| - """Returns true if video total bytes information is available."""
|
| - return self.GetVideoTotalBytes() > 0
|
| -
|
| - def _VerifyVideoLoadedBytes(self):
|
| - """Returns true if video loaded bytes information is available."""
|
| - return self.GetVideoLoadedBytes() > 0
|
| -
|
| - def StartVideoForPerformance(self, video_id='zuzaxlddWbk'):
|
| - """Start the test video with all required buffering."""
|
| - self.PlayVideoAndAssert(video_id)
|
| - self.ExecuteJavascript("""
|
| - ytplayer.setPlaybackQuality('hd720');
|
| - window.domAutomationController.send('');
|
| - """)
|
| - self.AssertPlayerState(state=self.is_playing,
|
| - msg='Player did not enter the playing state')
|
| - self.assertTrue(
|
| - self.WaitUntil(self._VerifyVideoTotalBytes, expect_retval=True),
|
| - msg='Failed to get video total bytes information.')
|
| - self.assertTrue(
|
| - self.WaitUntil(self._VerifyVideoLoadedBytes, expect_retval=True),
|
| - msg='Failed to get video loaded bytes information')
|
| - loaded_video_bytes = self.GetVideoLoadedBytes()
|
| - total_video_bytes = self.GetVideoTotalBytes()
|
| - self.PauseVideo()
|
| - logging.info('total_video_bytes: %f', total_video_bytes)
|
| - # Wait for the video to finish loading.
|
| - while total_video_bytes > loaded_video_bytes:
|
| - loaded_video_bytes = self.GetVideoLoadedBytes()
|
| - logging.info('loaded_video_bytes: %f', loaded_video_bytes)
|
| - time.sleep(1)
|
| - self.PlayVideo()
|
| - # Ignore first 10 seconds of video playing so we get smooth videoplayback.
|
| - time.sleep(10)
|
| -
|
| - def testYoutubeDroppedFrames(self):
|
| - """Measures the Youtube video dropped frames/second. Runs for 60 secs.
|
| -
|
| - This test measures Youtube video dropped frames for three different types
|
| - of videos like slow, normal and fast motion.
|
| - """
|
| - youtube_video = {'Slow': 'VT1-sitWRtY',
|
| - 'Normal': '2tqK_3mKQUw',
|
| - 'Fast': '8ETDE0VGJY4',
|
| - }
|
| - for video_type in youtube_video:
|
| - logging.info('Running %s video.', video_type)
|
| - self.StartVideoForPerformance(youtube_video[video_type])
|
| - init_dropped_frames = self.GetVideoDroppedFrames()
|
| - total_dropped_frames = 0
|
| - dropped_fps = []
|
| - for iteration in xrange(60):
|
| - frames = self.GetVideoDroppedFrames() - init_dropped_frames
|
| - current_dropped_frames = frames - total_dropped_frames
|
| - dropped_fps.append(current_dropped_frames)
|
| - logging.info('Iteration %d of %d: %f dropped frames in the last '
|
| - 'second', iteration + 1, 60, current_dropped_frames)
|
| - total_dropped_frames = frames
|
| - # Play the video for some time
|
| - time.sleep(1)
|
| - graph_description = 'YoutubeDroppedFrames' + video_type
|
| - self._PrintSummaryResults(graph_description, dropped_fps, 'frames',
|
| - 'youtube_dropped_frames')
|
| -
|
| - def testYoutubeCPU(self):
|
| - """Measures the Youtube video CPU usage. Runs for 60 seconds.
|
| -
|
| - Measures the Youtube video CPU usage (between 0 and 1), extrapolated to
|
| - totalframes in the video by taking dropped frames into account. For smooth
|
| - videoplayback this number should be < 0.5..1.0 on a hyperthreaded CPU.
|
| - """
|
| - self.StartVideoForPerformance()
|
| - init_dropped_frames = self.GetVideoDroppedFrames()
|
| - logging.info('init_dropped_frames: %f', init_dropped_frames)
|
| - cpu_usage_start = self._GetCPUUsage()
|
| - total_shown_frames = 0
|
| - for sec_num in xrange(60):
|
| - # Play the video for some time.
|
| - time.sleep(1)
|
| - total_shown_frames = total_shown_frames + self.GetVideoFrames()
|
| - logging.info('total_shown_frames: %f', total_shown_frames)
|
| - total_dropped_frames = self.GetVideoDroppedFrames() - init_dropped_frames
|
| - logging.info('total_dropped_frames: %f', total_dropped_frames)
|
| - cpu_usage_end = self._GetCPUUsage()
|
| - fraction_non_idle_time = self._GetFractionNonIdleCPUTime(
|
| - cpu_usage_start, cpu_usage_end)
|
| - logging.info('fraction_non_idle_time: %f', fraction_non_idle_time)
|
| - total_frames = total_shown_frames + total_dropped_frames
|
| - # Counting extrapolation for utilization to play the video.
|
| - extrapolation_value = (fraction_non_idle_time *
|
| - (float(total_frames) / total_shown_frames))
|
| - logging.info('Youtube CPU extrapolation: %f', extrapolation_value)
|
| - # Video is still running so log some more detailed data.
|
| - self._LogProcessActivity()
|
| - self._OutputPerfGraphValue('YoutubeCPUExtrapolation', extrapolation_value,
|
| - 'extrapolation', 'youtube_cpu_extrapolation')
|
| -
|
| -
|
| -class FlashVideoPerfTest(BasePerfTest):
|
| - """General flash video performance tests."""
|
| -
|
| - def FlashVideo1080P(self):
|
| - """Measures total dropped frames and average FPS for a 1080p flash video.
|
| -
|
| - This is a temporary test to be run manually for now, needed to collect some
|
| - performance statistics across different ChromeOS devices.
|
| - """
|
| - # Open up the test webpage; it's assumed the test will start automatically.
|
| - webpage_url = 'http://www/~arscott/fl/FlashVideoTests.html'
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(webpage_url)),
|
| - msg='Failed to append tab for webpage.')
|
| -
|
| - # Wait until the test is complete.
|
| - js_is_done = """
|
| - window.domAutomationController.send(JSON.stringify(tests_done));
|
| - """
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(js_is_done, tab_index=1) == 'true',
|
| - timeout=300, expect_retval=True, retry_sleep=1),
|
| - msg='Timed out when waiting for test result.')
|
| -
|
| - # Retrieve and output the test results.
|
| - js_results = """
|
| - window.domAutomationController.send(JSON.stringify(tests_results));
|
| - """
|
| - test_result = eval(self.ExecuteJavascript(js_results, tab_index=1))
|
| - test_result[0] = test_result[0].replace('true', 'True')
|
| - test_result = eval(test_result[0]) # Webpage only does 1 test right now.
|
| -
|
| - description = 'FlashVideo1080P'
|
| - result = test_result['averageFPS']
|
| - logging.info('Result for %s: %f FPS (average)', description, result)
|
| - self._OutputPerfGraphValue(description, result, 'FPS',
|
| - 'flash_video_1080p_fps')
|
| - result = test_result['droppedFrames']
|
| - logging.info('Result for %s: %f dropped frames', description, result)
|
| - self._OutputPerfGraphValue(description, result, 'DroppedFrames',
|
| - 'flash_video_1080p_dropped_frames')
|
| -
|
| -
|
| -class WebGLTest(BasePerfTest):
|
| - """Tests for WebGL performance."""
|
| -
|
| - def _RunWebGLTest(self, url, description, graph_name):
|
| - """Measures FPS using a specified WebGL demo.
|
| -
|
| - Args:
|
| - url: The string URL that, once loaded, will run the WebGL demo (default
|
| - WebGL demo settings are used, since this test does not modify any
|
| - settings in the demo).
|
| - description: A string description for this demo, used as a performance
|
| - value description. Should not contain any spaces.
|
| - graph_name: A string name for the performance graph associated with this
|
| - test. Only used on Chrome desktop.
|
| - """
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(url)),
|
| - msg='Failed to append tab for %s.' % description)
|
| -
|
| - get_fps_js = """
|
| - var fps_field = document.getElementById("fps");
|
| - var result = -1;
|
| - if (fps_field)
|
| - result = fps_field.innerHTML;
|
| - window.domAutomationController.send(JSON.stringify(result));
|
| - """
|
| -
|
| - # Wait until we start getting FPS values.
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(get_fps_js, tab_index=1) != '-1',
|
| - timeout=300, retry_sleep=1),
|
| - msg='Timed out when waiting for FPS values to be available.')
|
| -
|
| - # Let the experiment run for 5 seconds before we start collecting perf
|
| - # measurements.
|
| - time.sleep(5)
|
| -
|
| - # Collect the current FPS value each second for the next 30 seconds. The
|
| - # final result of this test will be the average of these FPS values.
|
| - fps_vals = []
|
| - for iteration in xrange(30):
|
| - fps = self.ExecuteJavascript(get_fps_js, tab_index=1)
|
| - fps = float(fps.replace('"', ''))
|
| - fps_vals.append(fps)
|
| - logging.info('Iteration %d of %d: %f FPS', iteration + 1, 30, fps)
|
| - time.sleep(1)
|
| - self._PrintSummaryResults(description, fps_vals, 'fps', graph_name)
|
| -
|
| - def testWebGLAquarium(self):
|
| - """Measures performance using the WebGL Aquarium demo."""
|
| - self._RunWebGLTest(
|
| - self.GetFileURLForDataPath('pyauto_private', 'webgl', 'aquarium',
|
| - 'aquarium.html'),
|
| - 'WebGLAquarium', 'webgl_demo')
|
| -
|
| - def testWebGLField(self):
|
| - """Measures performance using the WebGL Field demo."""
|
| - self._RunWebGLTest(
|
| - self.GetFileURLForDataPath('pyauto_private', 'webgl', 'field',
|
| - 'field.html'),
|
| - 'WebGLField', 'webgl_demo')
|
| -
|
| - def testWebGLSpaceRocks(self):
|
| - """Measures performance using the WebGL SpaceRocks demo."""
|
| - self._RunWebGLTest(
|
| - self.GetFileURLForDataPath('pyauto_private', 'webgl', 'spacerocks',
|
| - 'spacerocks.html'),
|
| - 'WebGLSpaceRocks', 'webgl_demo')
|
| -
|
| -
|
| -class GPUPerfTest(BasePerfTest):
|
| - """Tests for GPU performance."""
|
| -
|
| - def setUp(self):
|
| - """Performs necessary setup work before running each test in this class."""
|
| - self._gpu_info_dict = self.EvalDataFrom(os.path.join(self.DataDir(),
|
| - 'gpu', 'gpuperf.txt'))
|
| - self._demo_name_url_dict = self._gpu_info_dict['demo_info']
|
| - pyauto.PyUITest.setUp(self)
|
| -
|
| - def _MeasureFpsOverTime(self, tab_index=0):
|
| - """Measures FPS using a specified demo.
|
| -
|
| - This function assumes that the demo is already loaded in the specified tab
|
| - index.
|
| -
|
| - Args:
|
| - tab_index: The tab index, default is 0.
|
| - """
|
| - # Let the experiment run for 5 seconds before we start collecting FPS
|
| - # values.
|
| - time.sleep(5)
|
| -
|
| - # Collect the current FPS value each second for the next 10 seconds.
|
| - # Then return the average FPS value from among those collected.
|
| - fps_vals = []
|
| - for iteration in xrange(10):
|
| - fps = self.GetFPS(tab_index=tab_index)
|
| - fps_vals.append(fps['fps'])
|
| - time.sleep(1)
|
| - return Mean(fps_vals)
|
| -
|
| - def _GetStdAvgAndCompare(self, avg_fps, description, ref_dict):
|
| - """Computes the average and compare set of values with reference data.
|
| -
|
| - Args:
|
| - avg_fps: Average fps value.
|
| - description: A string description for this demo, used as a performance
|
| - value description.
|
| - ref_dict: Dictionary which contains reference data for this test case.
|
| -
|
| - Returns:
|
| - True, if the actual FPS value is within 10% of the reference FPS value,
|
| - or False, otherwise.
|
| - """
|
| - std_fps = 0
|
| - status = True
|
| - # Load reference data according to platform.
|
| - platform_ref_dict = None
|
| - if self.IsWin():
|
| - platform_ref_dict = ref_dict['win']
|
| - elif self.IsMac():
|
| - platform_ref_dict = ref_dict['mac']
|
| - elif self.IsLinux():
|
| - platform_ref_dict = ref_dict['linux']
|
| - else:
|
| - self.assertFail(msg='This platform is unsupported.')
|
| - std_fps = platform_ref_dict[description]
|
| - # Compare reference data to average fps.
|
| - # We allow the average FPS value to be within 10% of the reference
|
| - # FPS value.
|
| - if avg_fps < (0.9 * std_fps):
|
| - logging.info('FPS difference exceeds threshold for: %s', description)
|
| - logging.info(' Average: %f fps', avg_fps)
|
| - logging.info('Reference Average: %f fps', std_fps)
|
| - status = False
|
| - else:
|
| - logging.info('Average FPS is actually greater than 10 percent '
|
| - 'more than the reference FPS for: %s', description)
|
| - logging.info(' Average: %f fps', avg_fps)
|
| - logging.info(' Reference Average: %f fps', std_fps)
|
| - return status
|
| -
|
| - def testLaunchDemosParallelInSeparateTabs(self):
|
| - """Measures performance of demos in different tabs in same browser."""
|
| - # Launch all the demos parallel in separate tabs
|
| - counter = 0
|
| - all_demos_passed = True
|
| - ref_dict = self._gpu_info_dict['separate_tab_ref_data']
|
| - # Iterate through dictionary and append all url to browser
|
| - for url in self._demo_name_url_dict.iterkeys():
|
| - self.assertTrue(
|
| - self.AppendTab(pyauto.GURL(self._demo_name_url_dict[url])),
|
| - msg='Failed to append tab for %s.' % url)
|
| - counter += 1
|
| - # Assert number of tab count is equal to number of tabs appended.
|
| - self.assertEqual(self.GetTabCount(), counter + 1)
|
| - # Measures performance using different demos and compare it golden
|
| - # reference.
|
| - for url in self._demo_name_url_dict.iterkeys():
|
| - avg_fps = self._MeasureFpsOverTime(tab_index=counter)
|
| - # Get the reference value of fps and compare the results
|
| - if not self._GetStdAvgAndCompare(avg_fps, url, ref_dict):
|
| - all_demos_passed = False
|
| - counter -= 1
|
| - self.assertTrue(
|
| - all_demos_passed,
|
| - msg='One or more demos failed to yield an acceptable FPS value')
|
| -
|
| - def testLaunchDemosInSeparateBrowser(self):
|
| - """Measures performance by launching each demo in a separate tab."""
|
| - # Launch demos in the browser
|
| - ref_dict = self._gpu_info_dict['separate_browser_ref_data']
|
| - all_demos_passed = True
|
| - for url in self._demo_name_url_dict.iterkeys():
|
| - self.NavigateToURL(self._demo_name_url_dict[url])
|
| - # Measures performance using different demos.
|
| - avg_fps = self._MeasureFpsOverTime()
|
| - self.RestartBrowser()
|
| - # Get the standard value of fps and compare the rseults
|
| - if not self._GetStdAvgAndCompare(avg_fps, url, ref_dict):
|
| - all_demos_passed = False
|
| - self.assertTrue(
|
| - all_demos_passed,
|
| - msg='One or more demos failed to yield an acceptable FPS value')
|
| -
|
| - def testLaunchDemosBrowseForwardBackward(self):
|
| - """Measures performance of various demos in browser going back and forth."""
|
| - ref_dict = self._gpu_info_dict['browse_back_forward_ref_data']
|
| - url_array = []
|
| - desc_array = []
|
| - all_demos_passed = True
|
| - # Get URL/Description from dictionary and put in individual array
|
| - for url in self._demo_name_url_dict.iterkeys():
|
| - url_array.append(self._demo_name_url_dict[url])
|
| - desc_array.append(url)
|
| - for index in range(len(url_array) - 1):
|
| - # Launch demo in the Browser
|
| - if index == 0:
|
| - self.NavigateToURL(url_array[index])
|
| - # Measures performance using the first demo.
|
| - avg_fps = self._MeasureFpsOverTime()
|
| - status1 = self._GetStdAvgAndCompare(avg_fps, desc_array[index],
|
| - ref_dict)
|
| - # Measures performance using the second demo.
|
| - self.NavigateToURL(url_array[index + 1])
|
| - avg_fps = self._MeasureFpsOverTime()
|
| - status2 = self._GetStdAvgAndCompare(avg_fps, desc_array[index + 1],
|
| - ref_dict)
|
| - # Go Back to previous demo
|
| - self.TabGoBack()
|
| - # Measures performance for first demo when moved back
|
| - avg_fps = self._MeasureFpsOverTime()
|
| - status3 = self._GetStdAvgAndCompare(
|
| - avg_fps, desc_array[index] + '_backward',
|
| - ref_dict)
|
| - # Go Forward to previous demo
|
| - self.TabGoForward()
|
| - # Measures performance for second demo when moved forward
|
| - avg_fps = self._MeasureFpsOverTime()
|
| - status4 = self._GetStdAvgAndCompare(
|
| - avg_fps, desc_array[index + 1] + '_forward',
|
| - ref_dict)
|
| - if not all([status1, status2, status3, status4]):
|
| - all_demos_passed = False
|
| - self.assertTrue(
|
| - all_demos_passed,
|
| - msg='One or more demos failed to yield an acceptable FPS value')
|
| -
|
| -
|
| -class HTML5BenchmarkTest(BasePerfTest):
|
| - """Tests for HTML5 performance."""
|
| -
|
| - def testHTML5Benchmark(self):
|
| - """Measures performance using the benchmark at html5-benchmark.com."""
|
| - self.NavigateToURL('http://html5-benchmark.com')
|
| -
|
| - start_benchmark_js = """
|
| - benchmark();
|
| - window.domAutomationController.send("done");
|
| - """
|
| - self.ExecuteJavascript(start_benchmark_js)
|
| -
|
| - js_final_score = """
|
| - var score = "-1";
|
| - var elem = document.getElementById("score");
|
| - if (elem)
|
| - score = elem.innerHTML;
|
| - window.domAutomationController.send(score);
|
| - """
|
| - # Wait for the benchmark to complete, which is assumed to be when the value
|
| - # of the 'score' DOM element changes to something other than '87485'.
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(js_final_score) != '87485',
|
| - timeout=900, retry_sleep=1),
|
| - msg='Timed out when waiting for final score to be available.')
|
| -
|
| - score = self.ExecuteJavascript(js_final_score)
|
| - logging.info('HTML5 Benchmark final score: %f', float(score))
|
| - self._OutputPerfGraphValue('HTML5Benchmark', float(score), 'score',
|
| - 'html5_benchmark')
|
| -
|
| -
|
| -class FileUploadDownloadTest(BasePerfTest):
|
| - """Tests that involve measuring performance of upload and download."""
|
| -
|
| - def setUp(self):
|
| - """Performs necessary setup work before running each test in this class."""
|
| - self._temp_dir = tempfile.mkdtemp()
|
| - self._test_server = PerfTestServer(self._temp_dir)
|
| - self._test_server_port = self._test_server.GetPort()
|
| - self._test_server.Run()
|
| - self.assertTrue(self.WaitUntil(self._IsTestServerRunning),
|
| - msg='Failed to start local performance test server.')
|
| - BasePerfTest.setUp(self)
|
| -
|
| - def tearDown(self):
|
| - """Performs necessary cleanup work after running each test in this class."""
|
| - BasePerfTest.tearDown(self)
|
| - self._test_server.ShutDown()
|
| - pyauto_utils.RemovePath(self._temp_dir)
|
| -
|
| - def _IsTestServerRunning(self):
|
| - """Determines whether the local test server is ready to accept connections.
|
| -
|
| - Returns:
|
| - True, if a connection can be made to the local performance test server, or
|
| - False otherwise.
|
| - """
|
| - conn = None
|
| - try:
|
| - conn = urllib2.urlopen('http://localhost:%d' % self._test_server_port)
|
| - return True
|
| - except IOError, e:
|
| - return False
|
| - finally:
|
| - if conn:
|
| - conn.close()
|
| -
|
| - def testDownload100MBFile(self):
|
| - """Measures the time to download a 100 MB file from a local server."""
|
| - CREATE_100MB_URL = (
|
| - 'http://localhost:%d/create_file_of_size?filename=data&mb=100' %
|
| - self._test_server_port)
|
| - DOWNLOAD_100MB_URL = 'http://localhost:%d/data' % self._test_server_port
|
| - DELETE_100MB_URL = ('http://localhost:%d/delete_file?filename=data' %
|
| - self._test_server_port)
|
| -
|
| - # Tell the local server to create a 100 MB file.
|
| - self.NavigateToURL(CREATE_100MB_URL)
|
| -
|
| - # Cleaning up downloaded files is done in the same way as in downloads.py.
|
| - # We first identify all existing downloaded files, then remove only those
|
| - # new downloaded files that appear during the course of this test.
|
| - download_dir = self.GetDownloadDirectory().value()
|
| - orig_downloads = []
|
| - if os.path.isdir(download_dir):
|
| - orig_downloads = os.listdir(download_dir)
|
| -
|
| - def _CleanupAdditionalFilesInDir(directory, orig_files):
|
| - """Removes the additional files in the specified directory.
|
| -
|
| - This function will remove all files from |directory| that are not
|
| - specified in |orig_files|.
|
| -
|
| - Args:
|
| - directory: A string directory path.
|
| - orig_files: A list of strings representing the original set of files in
|
| - the specified directory.
|
| - """
|
| - downloads_to_remove = []
|
| - if os.path.isdir(directory):
|
| - downloads_to_remove = [os.path.join(directory, name)
|
| - for name in os.listdir(directory)
|
| - if name not in orig_files]
|
| - for file_name in downloads_to_remove:
|
| - pyauto_utils.RemovePath(file_name)
|
| -
|
| - def _DownloadFile(url):
|
| - self.DownloadAndWaitForStart(url)
|
| - self.WaitForAllDownloadsToComplete(timeout=2 * 60 * 1000) # 2 minutes.
|
| -
|
| - timings = []
|
| - for iteration in range(self._num_iterations + 1):
|
| - elapsed_time = self._MeasureElapsedTime(
|
| - lambda: _DownloadFile(DOWNLOAD_100MB_URL), num_invocations=1)
|
| - # Ignore the first iteration.
|
| - if iteration:
|
| - timings.append(elapsed_time)
|
| - logging.info('Iteration %d of %d: %f milliseconds', iteration,
|
| - self._num_iterations, elapsed_time)
|
| - self.SetDownloadShelfVisible(False)
|
| - _CleanupAdditionalFilesInDir(download_dir, orig_downloads)
|
| -
|
| - self._PrintSummaryResults('Download100MBFile', timings, 'milliseconds',
|
| - 'download_file')
|
| -
|
| - # Tell the local server to delete the 100 MB file.
|
| - self.NavigateToURL(DELETE_100MB_URL)
|
| -
|
| - def testUpload50MBFile(self):
|
| - """Measures the time to upload a 50 MB file to a local server."""
|
| - # TODO(dennisjeffrey): Replace the use of XMLHttpRequest in this test with
|
| - # FileManager automation to select the upload file when crosbug.com/17903
|
| - # is complete.
|
| - START_UPLOAD_URL = (
|
| - 'http://localhost:%d/start_upload?mb=50' % self._test_server_port)
|
| -
|
| - EXPECTED_SUBSTRING = 'Upload complete'
|
| -
|
| - def _IsUploadComplete():
|
| - js = """
|
| - result = "";
|
| - var div = document.getElementById("upload_result");
|
| - if (div)
|
| - result = div.innerHTML;
|
| - window.domAutomationController.send(result);
|
| - """
|
| - return self.ExecuteJavascript(js).find(EXPECTED_SUBSTRING) >= 0
|
| -
|
| - def _RunSingleUpload():
|
| - self.NavigateToURL(START_UPLOAD_URL)
|
| - self.assertTrue(
|
| - self.WaitUntil(_IsUploadComplete, timeout=120, expect_retval=True,
|
| - retry_sleep=0.10),
|
| - msg='Upload failed to complete before the timeout was hit.')
|
| -
|
| - timings = []
|
| - for iteration in range(self._num_iterations + 1):
|
| - elapsed_time = self._MeasureElapsedTime(_RunSingleUpload)
|
| - # Ignore the first iteration.
|
| - if iteration:
|
| - timings.append(elapsed_time)
|
| - logging.info('Iteration %d of %d: %f milliseconds', iteration,
|
| - self._num_iterations, elapsed_time)
|
| -
|
| - self._PrintSummaryResults('Upload50MBFile', timings, 'milliseconds',
|
| - 'upload_file')
|
| -
|
| -
|
| -class FlashTest(BasePerfTest):
|
| - """Tests to measure flash performance."""
|
| -
|
| - def _RunFlashTestForAverageFPS(self, webpage_url, description, graph_name):
|
| - """Runs a single flash test that measures an average FPS value.
|
| -
|
| - Args:
|
| - webpage_url: The string URL to a webpage that will run the test.
|
| - description: A string description for this test.
|
| - graph_name: A string name for the performance graph associated with this
|
| - test. Only used on Chrome desktop.
|
| - """
|
| - # Open up the test webpage; it's assumed the test will start automatically.
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(webpage_url)),
|
| - msg='Failed to append tab for webpage.')
|
| -
|
| - # Wait until the final result is computed, then retrieve and output it.
|
| - js = """
|
| - window.domAutomationController.send(
|
| - JSON.stringify(final_average_fps));
|
| - """
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(js, tab_index=1) != '-1',
|
| - timeout=300, expect_retval=True, retry_sleep=1),
|
| - msg='Timed out when waiting for test result.')
|
| - result = float(self.ExecuteJavascript(js, tab_index=1))
|
| - logging.info('Result for %s: %f FPS (average)', description, result)
|
| - self._OutputPerfGraphValue(description, result, 'FPS', graph_name)
|
| -
|
| - def testFlashGaming(self):
|
| - """Runs a simple flash gaming benchmark test."""
|
| - webpage_url = self.GetHttpURLForDataPath('pyauto_private', 'flash',
|
| - 'FlashGamingTest2.html')
|
| - self._RunFlashTestForAverageFPS(webpage_url, 'FlashGaming', 'flash_fps')
|
| -
|
| - def testFlashText(self):
|
| - """Runs a simple flash text benchmark test."""
|
| - webpage_url = self.GetHttpURLForDataPath('pyauto_private', 'flash',
|
| - 'FlashTextTest2.html')
|
| - self._RunFlashTestForAverageFPS(webpage_url, 'FlashText', 'flash_fps')
|
| -
|
| - def testScimarkGui(self):
|
| - """Runs the ScimarkGui benchmark tests."""
|
| - webpage_url = self.GetHttpURLForDataPath('pyauto_private', 'flash',
|
| - 'scimarkGui.html')
|
| - self.assertTrue(self.AppendTab(pyauto.GURL(webpage_url)),
|
| - msg='Failed to append tab for webpage.')
|
| -
|
| - js = 'window.domAutomationController.send(JSON.stringify(tests_done));'
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - lambda: self.ExecuteJavascript(js, tab_index=1), timeout=300,
|
| - expect_retval='true', retry_sleep=1),
|
| - msg='Timed out when waiting for tests to complete.')
|
| -
|
| - js_result = """
|
| - var result = {};
|
| - for (var i = 0; i < tests_results.length; ++i) {
|
| - var test_name = tests_results[i][0];
|
| - var mflops = tests_results[i][1];
|
| - var mem = tests_results[i][2];
|
| - result[test_name] = [mflops, mem]
|
| - }
|
| - window.domAutomationController.send(JSON.stringify(result));
|
| - """
|
| - result = eval(self.ExecuteJavascript(js_result, tab_index=1))
|
| - for benchmark in result:
|
| - mflops = float(result[benchmark][0])
|
| - mem = float(result[benchmark][1])
|
| - if benchmark.endswith('_mflops'):
|
| - benchmark = benchmark[:benchmark.find('_mflops')]
|
| - logging.info('Results for ScimarkGui_%s:', benchmark)
|
| - logging.info(' %f MFLOPS', mflops)
|
| - logging.info(' %f MB', mem)
|
| - self._OutputPerfGraphValue('ScimarkGui-%s-MFLOPS' % benchmark, mflops,
|
| - 'MFLOPS', 'scimark_gui_mflops')
|
| - self._OutputPerfGraphValue('ScimarkGui-%s-Mem' % benchmark, mem, 'MB',
|
| - 'scimark_gui_mem')
|
| -
|
| -
|
| -class LiveGamePerfTest(BasePerfTest):
|
| - """Tests to measure performance of live gaming webapps."""
|
| -
|
| - def _RunLiveGamePerfTest(self, url, url_title_substring,
|
| - description, graph_name):
|
| - """Measures performance metrics for the specified live gaming webapp.
|
| -
|
| - This function connects to the specified URL to launch the gaming webapp,
|
| - waits for a period of time for the webapp to run, then collects some
|
| - performance metrics about the running webapp.
|
| -
|
| - Args:
|
| - url: The string URL of the gaming webapp to analyze.
|
| - url_title_substring: A string that is expected to be a substring of the
|
| - webpage title for the specified gaming webapp. Used to verify that
|
| - the webapp loads correctly.
|
| - description: A string description for this game, used in the performance
|
| - value description. Should not contain any spaces.
|
| - graph_name: A string name for the performance graph associated with this
|
| - test. Only used on Chrome desktop.
|
| - """
|
| - self.NavigateToURL(url)
|
| - loaded_tab_title = self.GetActiveTabTitle()
|
| - self.assertTrue(url_title_substring in loaded_tab_title,
|
| - msg='Loaded tab title missing "%s": "%s"' %
|
| - (url_title_substring, loaded_tab_title))
|
| - cpu_usage_start = self._GetCPUUsage()
|
| -
|
| - # Let the app run for 1 minute.
|
| - time.sleep(60)
|
| -
|
| - cpu_usage_end = self._GetCPUUsage()
|
| - fraction_non_idle_time = self._GetFractionNonIdleCPUTime(
|
| - cpu_usage_start, cpu_usage_end)
|
| -
|
| - logging.info('Fraction of CPU time spent non-idle: %f',
|
| - fraction_non_idle_time)
|
| - self._OutputPerfGraphValue(description + 'CpuBusy', fraction_non_idle_time,
|
| - 'Fraction', graph_name + '_cpu_busy')
|
| - v8_heap_stats = self.GetV8HeapStats()
|
| - v8_heap_size = v8_heap_stats['v8_memory_used'] / (1024.0 * 1024.0)
|
| - logging.info('Total v8 heap size: %f MB', v8_heap_size)
|
| - self._OutputPerfGraphValue(description + 'V8HeapSize', v8_heap_size, 'MB',
|
| - graph_name + '_v8_heap_size')
|
| -
|
| - def testAngryBirds(self):
|
| - """Measures performance for Angry Birds."""
|
| - self._RunLiveGamePerfTest('http://chrome.angrybirds.com', 'Angry Birds',
|
| - 'AngryBirds', 'angry_birds')
|
| -
|
| -
|
| -class BasePageCyclerTest(BasePerfTest):
|
| - """Page class for page cycler tests.
|
| -
|
| - Derived classes must implement StartUrl().
|
| -
|
| - Environment Variables:
|
| - PC_NO_AUTO: if set, avoids automatically loading pages.
|
| - """
|
| - MAX_ITERATION_SECONDS = 60
|
| - TRIM_PERCENT = 20
|
| - DEFAULT_USE_AUTO = True
|
| -
|
| - # Page Cycler lives in src/data/page_cycler rather than src/chrome/test/data
|
| - DATA_PATH = os.path.abspath(
|
| - os.path.join(BasePerfTest.DataDir(), os.pardir, os.pardir,
|
| - os.pardir, 'data', 'page_cycler'))
|
| -
|
| - def setUp(self):
|
| - """Performs necessary setup work before running each test."""
|
| - super(BasePageCyclerTest, self).setUp()
|
| - self.use_auto = 'PC_NO_AUTO' not in os.environ
|
| -
|
| - @classmethod
|
| - def DataPath(cls, subdir):
|
| - return os.path.join(cls.DATA_PATH, subdir)
|
| -
|
| - def ExtraChromeFlags(self):
|
| - """Ensures Chrome is launched with custom flags.
|
| -
|
| - Returns:
|
| - A list of extra flags to pass to Chrome when it is launched.
|
| - """
|
| - # Extra flags required to run these tests.
|
| - # The first two are needed for the test.
|
| - # The plugins argument is to prevent bad scores due to pop-ups from
|
| - # running an old version of something (like Flash).
|
| - return (super(BasePageCyclerTest, self).ExtraChromeFlags() +
|
| - ['--js-flags="--expose_gc"',
|
| - '--enable-file-cookies',
|
| - '--allow-outdated-plugins'])
|
| -
|
| - def WaitUntilStarted(self, start_url):
|
| - """Check that the test navigates away from the start_url."""
|
| - js_is_started = """
|
| - var is_started = document.location.href !== "%s";
|
| - window.domAutomationController.send(JSON.stringify(is_started));
|
| - """ % start_url
|
| - self.assertTrue(
|
| - self.WaitUntil(lambda: self.ExecuteJavascript(js_is_started) == 'true',
|
| - timeout=10),
|
| - msg='Timed out when waiting to leave start page.')
|
| -
|
| - def WaitUntilDone(self, url, iterations):
|
| - """Check cookies for "__pc_done=1" to know the test is over."""
|
| - def IsDone():
|
| - cookies = self.GetCookie(pyauto.GURL(url)) # window 0, tab 0
|
| - return '__pc_done=1' in cookies
|
| - self.assertTrue(
|
| - self.WaitUntil(
|
| - IsDone,
|
| - timeout=(self.MAX_ITERATION_SECONDS * iterations),
|
| - retry_sleep=1),
|
| - msg='Timed out waiting for page cycler test to complete.')
|
| -
|
| - def CollectPagesAndTimes(self, url):
|
| - """Collect the results from the cookies."""
|
| - pages, times = None, None
|
| - cookies = self.GetCookie(pyauto.GURL(url)) # window 0, tab 0
|
| - for cookie in cookies.split(';'):
|
| - if '__pc_pages' in cookie:
|
| - pages_str = cookie.split('=', 1)[1]
|
| - pages = pages_str.split(',')
|
| - elif '__pc_timings' in cookie:
|
| - times_str = cookie.split('=', 1)[1]
|
| - times = [float(t) for t in times_str.split(',')]
|
| - self.assertTrue(pages and times,
|
| - msg='Unable to find test results in cookies: %s' % cookies)
|
| - return pages, times
|
| -
|
| - def IteratePageTimes(self, pages, times, iterations):
|
| - """Regroup the times by the page.
|
| -
|
| - Args:
|
| - pages: the list of pages
|
| - times: e.g. [page1_iter1, page2_iter1, ..., page1_iter2, page2_iter2, ...]
|
| - iterations: the number of times for each page
|
| - Yields:
|
| - (pageN, [pageN_iter1, pageN_iter2, ...])
|
| - """
|
| - num_pages = len(pages)
|
| - num_times = len(times)
|
| - expected_num_times = num_pages * iterations
|
| - self.assertEqual(
|
| - expected_num_times, num_times,
|
| - msg=('num_times != num_pages * iterations: %s != %s * %s, times=%s' %
|
| - (num_times, num_pages, iterations, times)))
|
| - for i, page in enumerate(pages):
|
| - yield page, list(itertools.islice(times, i, None, num_pages))
|
| -
|
| - def CheckPageTimes(self, pages, times, iterations):
|
| - """Assert that all the times are greater than zero."""
|
| - failed_pages = []
|
| - for page, times in self.IteratePageTimes(pages, times, iterations):
|
| - failed_times = [t for t in times if t <= 0.0]
|
| - if failed_times:
|
| - failed_pages.append((page, failed_times))
|
| - if failed_pages:
|
| - self.fail('Pages with unexpected times: %s' % failed_pages)
|
| -
|
| - def TrimTimes(self, times, percent):
|
| - """Return a new list with |percent| number of times trimmed for each page.
|
| -
|
| - Removes the largest and smallest values.
|
| - """
|
| - iterations = len(times)
|
| - times = sorted(times)
|
| - num_to_trim = int(iterations * float(percent) / 100.0)
|
| - logging.debug('Before trimming %d: %s' % (num_to_trim, times))
|
| - a = num_to_trim / 2
|
| - b = iterations - (num_to_trim / 2 + num_to_trim % 2)
|
| - trimmed_times = times[a:b]
|
| - logging.debug('After trimming: %s', trimmed_times)
|
| - return trimmed_times
|
| -
|
| - def ComputeFinalResult(self, pages, times, iterations):
|
| - """The final score that is calculated is a geometric mean of the
|
| - arithmetic means of each page's load time, and we drop the
|
| - upper/lower 20% of the times for each page so they don't skew the
|
| - mean. The geometric mean is used for the final score because the
|
| - time range for any given site may be very different, and we don't
|
| - want slower sites to weight more heavily than others.
|
| - """
|
| - self.CheckPageTimes(pages, times, iterations)
|
| - page_means = [
|
| - Mean(self.TrimTimes(times, percent=self.TRIM_PERCENT))
|
| - for _, times in self.IteratePageTimes(pages, times, iterations)]
|
| - return GeometricMean(page_means)
|
| -
|
| - def StartUrl(self, test_name, iterations):
|
| - """Return the URL to used to start the test.
|
| -
|
| - Derived classes must implement this.
|
| - """
|
| - raise NotImplemented
|
| -
|
| - def RunPageCyclerTest(self, name, description):
|
| - """Runs the specified PageCycler test.
|
| -
|
| - Args:
|
| - name: the page cycler test name (corresponds to a directory or test file)
|
| - description: a string description for the test
|
| - """
|
| - iterations = self._num_iterations
|
| - start_url = self.StartUrl(name, iterations)
|
| - self.NavigateToURL(start_url)
|
| - if self.use_auto:
|
| - self.WaitUntilStarted(start_url)
|
| - self.WaitUntilDone(start_url, iterations)
|
| - pages, times = self.CollectPagesAndTimes(start_url)
|
| - final_result = self.ComputeFinalResult(pages, times, iterations)
|
| - logging.info('%s page cycler final result: %f' %
|
| - (description, final_result))
|
| - self._OutputPerfGraphValue(description + '_PageCycler', final_result,
|
| - 'milliseconds', graph_name='PageCycler')
|
| -
|
| -
|
| -class PageCyclerTest(BasePageCyclerTest):
|
| - """Tests to run various page cyclers.
|
| -
|
| - Environment Variables:
|
| - PC_NO_AUTO: if set, avoids automatically loading pages.
|
| - """
|
| -
|
| - def _PreReadDataDir(self, subdir):
|
| - """This recursively reads all of the files in a given url directory.
|
| -
|
| - The intent is to get them into memory before they are used by the benchmark.
|
| -
|
| - Args:
|
| - subdir: a subdirectory of the page cycler data directory.
|
| - """
|
| - def _PreReadDir(dirname, names):
|
| - for rfile in names:
|
| - with open(os.path.join(dirname, rfile)) as fp:
|
| - fp.read()
|
| - for root, dirs, files in os.walk(self.DataPath(subdir)):
|
| - _PreReadDir(root, files)
|
| -
|
| - def StartUrl(self, test_name, iterations):
|
| - # Must invoke GetFileURLForPath before appending parameters to the URL,
|
| - # otherwise those parameters will get quoted.
|
| - start_url = self.GetFileURLForPath(self.DataPath(test_name), 'start.html')
|
| - start_url += '?iterations=%d' % iterations
|
| - if self.use_auto:
|
| - start_url += '&auto=1'
|
| - return start_url
|
| -
|
| - def RunPageCyclerTest(self, dirname, description):
|
| - """Runs the specified PageCycler test.
|
| -
|
| - Args:
|
| - dirname: directory containing the page cycler test
|
| - description: a string description for the test
|
| - """
|
| - self._PreReadDataDir('common')
|
| - self._PreReadDataDir(dirname)
|
| - super(PageCyclerTest, self).RunPageCyclerTest(dirname, description)
|
| -
|
| - def testMoreJSFile(self):
|
| - self.RunPageCyclerTest('morejs', 'MoreJSFile')
|
| -
|
| - def testAlexaFile(self):
|
| - self.RunPageCyclerTest('alexa_us', 'Alexa_usFile')
|
| -
|
| - def testBloatFile(self):
|
| - self.RunPageCyclerTest('bloat', 'BloatFile')
|
| -
|
| - def testDHTMLFile(self):
|
| - self.RunPageCyclerTest('dhtml', 'DhtmlFile')
|
| -
|
| - def testIntl1File(self):
|
| - self.RunPageCyclerTest('intl1', 'Intl1File')
|
| -
|
| - def testIntl2File(self):
|
| - self.RunPageCyclerTest('intl2', 'Intl2File')
|
| -
|
| - def testMozFile(self):
|
| - self.RunPageCyclerTest('moz', 'MozFile')
|
| -
|
| - def testMoz2File(self):
|
| - self.RunPageCyclerTest('moz2', 'Moz2File')
|
| -
|
| -
|
| -class MemoryTest(BasePerfTest):
|
| - """Tests to measure memory consumption under different usage scenarios."""
|
| -
|
| - def ExtraChromeFlags(self):
|
| - """Launches Chrome with custom flags.
|
| -
|
| - Returns:
|
| - A list of extra flags to pass to Chrome when it is launched.
|
| - """
|
| - # Ensure Chrome assigns one renderer process to each tab.
|
| - return super(MemoryTest, self).ExtraChromeFlags() + ['--process-per-tab']
|
| -
|
| - def _RecordMemoryStats(self, description, when, duration):
|
| - """Outputs memory statistics to be graphed.
|
| -
|
| - Args:
|
| - description: A string description for the test. Should not contain
|
| - spaces. For example, 'MemCtrl'.
|
| - when: A string description of when the memory stats are being recorded
|
| - during test execution (since memory stats may be recorded multiple
|
| - times during a test execution at certain "interesting" times). Should
|
| - not contain spaces.
|
| - duration: The number of seconds to sample data before outputting the
|
| - memory statistics.
|
| - """
|
| - mem = self.GetMemoryStatsChromeOS(duration)
|
| - measurement_types = [
|
| - ('gem_obj', 'GemObj'),
|
| - ('gtt', 'GTT'),
|
| - ('mem_free', 'MemFree'),
|
| - ('mem_available', 'MemAvail'),
|
| - ('mem_shared', 'MemShare'),
|
| - ('mem_cached', 'MemCache'),
|
| - ('mem_anon', 'MemAnon'),
|
| - ('mem_file', 'MemFile'),
|
| - ('mem_slab', 'MemSlab'),
|
| - ('browser_priv', 'BrowPriv'),
|
| - ('browser_shared', 'BrowShar'),
|
| - ('gpu_priv', 'GpuPriv'),
|
| - ('gpu_shared', 'GpuShar'),
|
| - ('renderer_priv', 'RendPriv'),
|
| - ('renderer_shared', 'RendShar'),
|
| - ]
|
| - for type_key, type_string in measurement_types:
|
| - if type_key not in mem:
|
| - continue
|
| - self._OutputPerfGraphValue(
|
| - '%s-Min%s-%s' % (description, type_string, when),
|
| - mem[type_key]['min'], 'KB', '%s-%s' % (description, type_string))
|
| - self._OutputPerfGraphValue(
|
| - '%s-Max%s-%s' % (description, type_string, when),
|
| - mem[type_key]['max'], 'KB', '%s-%s' % (description, type_string))
|
| - self._OutputPerfGraphValue(
|
| - '%s-End%s-%s' % (description, type_string, when),
|
| - mem[type_key]['end'], 'KB', '%s-%s' % (description, type_string))
|
| -
|
| - def _RunTest(self, tabs, description, duration):
|
| - """Runs a general memory test.
|
| -
|
| - Args:
|
| - tabs: A list of strings representing the URLs of the websites to open
|
| - during this test.
|
| - description: A string description for the test. Should not contain
|
| - spaces. For example, 'MemCtrl'.
|
| - duration: The number of seconds to sample data before outputting memory
|
| - statistics.
|
| - """
|
| - self._RecordMemoryStats(description, '0Tabs0', duration)
|
| -
|
| - for iteration_num in xrange(2):
|
| - for site in tabs:
|
| - self.AppendTab(pyauto.GURL(site))
|
| -
|
| - self._RecordMemoryStats(description,
|
| - '%dTabs%d' % (len(tabs), iteration_num + 1),
|
| - duration)
|
| -
|
| - for _ in xrange(len(tabs)):
|
| - self.CloseTab(tab_index=1)
|
| -
|
| - self._RecordMemoryStats(description, '0Tabs%d' % (iteration_num + 1),
|
| - duration)
|
| -
|
| - def testOpenCloseTabsControl(self):
|
| - """Measures memory usage when opening/closing tabs to about:blank."""
|
| - tabs = ['about:blank'] * 10
|
| - self._RunTest(tabs, 'MemCtrl', 15)
|
| -
|
| - def testOpenCloseTabsLiveSites(self):
|
| - """Measures memory usage when opening/closing tabs to live sites."""
|
| - tabs = [
|
| - 'http://www.google.com/gmail',
|
| - 'http://www.google.com/calendar',
|
| - 'http://www.google.com/plus',
|
| - 'http://www.google.com/youtube',
|
| - 'http://www.nytimes.com',
|
| - 'http://www.cnn.com',
|
| - 'http://www.facebook.com/zuck',
|
| - 'http://www.techcrunch.com',
|
| - 'http://www.theverge.com',
|
| - 'http://www.yahoo.com',
|
| - ]
|
| - # Log in to a test Google account to make connections to the above Google
|
| - # websites more interesting.
|
| - self._LoginToGoogleAccount()
|
| - self._RunTest(tabs, 'MemLive', 20)
|
| -
|
| -
|
| -class PerfTestServerRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
| - """Request handler for the local performance test server."""
|
| -
|
| - def _IgnoreHandler(self, unused_args):
|
| - """A GET request handler that simply replies with status code 200.
|
| -
|
| - Args:
|
| - unused_args: A dictionary of arguments for the current GET request.
|
| - The arguments are ignored.
|
| - """
|
| - self.send_response(200)
|
| - self.end_headers()
|
| -
|
| - def _CreateFileOfSizeHandler(self, args):
|
| - """A GET handler that creates a local file with the specified size.
|
| -
|
| - Args:
|
| - args: A dictionary of arguments for the current GET request. Must
|
| - contain 'filename' and 'mb' keys that refer to the name of the file
|
| - to create and its desired size, respectively.
|
| - """
|
| - megabytes = None
|
| - filename = None
|
| - try:
|
| - megabytes = int(args['mb'][0])
|
| - filename = args['filename'][0]
|
| - except (ValueError, KeyError, IndexError), e:
|
| - logging.exception('Server error creating file: %s', e)
|
| - assert megabytes and filename
|
| - with open(os.path.join(self.server.docroot, filename), 'wb') as f:
|
| - f.write('X' * 1024 * 1024 * megabytes)
|
| - self.send_response(200)
|
| - self.end_headers()
|
| -
|
| - def _DeleteFileHandler(self, args):
|
| - """A GET handler that deletes the specified local file.
|
| -
|
| - Args:
|
| - args: A dictionary of arguments for the current GET request. Must
|
| - contain a 'filename' key that refers to the name of the file to
|
| - delete, relative to the server's document root.
|
| - """
|
| - filename = None
|
| - try:
|
| - filename = args['filename'][0]
|
| - except (KeyError, IndexError), e:
|
| - logging.exception('Server error deleting file: %s', e)
|
| - assert filename
|
| - try:
|
| - os.remove(os.path.join(self.server.docroot, filename))
|
| - except OSError, e:
|
| - logging.warning('OS error removing file: %s', e)
|
| - self.send_response(200)
|
| - self.end_headers()
|
| -
|
| - def _StartUploadHandler(self, args):
|
| - """A GET handler to serve a page that uploads the given amount of data.
|
| -
|
| - When the page loads, the specified amount of data is automatically
|
| - uploaded to the same local server that is handling the current request.
|
| -
|
| - Args:
|
| - args: A dictionary of arguments for the current GET request. Must
|
| - contain an 'mb' key that refers to the size of the data to upload.
|
| - """
|
| - megabytes = None
|
| - try:
|
| - megabytes = int(args['mb'][0])
|
| - except (ValueError, KeyError, IndexError), e:
|
| - logging.exception('Server error starting upload: %s', e)
|
| - assert megabytes
|
| - script = """
|
| - <html>
|
| - <head>
|
| - <script type='text/javascript'>
|
| - function startUpload() {
|
| - var megabytes = %s;
|
| - var data = Array((1024 * 1024 * megabytes) + 1).join('X');
|
| - var boundary = '***BOUNDARY***';
|
| - var xhr = new XMLHttpRequest();
|
| -
|
| - xhr.open('POST', 'process_upload', true);
|
| - xhr.setRequestHeader(
|
| - 'Content-Type',
|
| - 'multipart/form-data; boundary="' + boundary + '"');
|
| - xhr.setRequestHeader('Content-Length', data.length);
|
| - xhr.onreadystatechange = function() {
|
| - if (xhr.readyState == 4 && xhr.status == 200) {
|
| - document.getElementById('upload_result').innerHTML =
|
| - xhr.responseText;
|
| - }
|
| - };
|
| - var body = '--' + boundary + '\\r\\n';
|
| - body += 'Content-Disposition: form-data;' +
|
| - 'file_contents=' + data;
|
| - xhr.send(body);
|
| - }
|
| - </script>
|
| - </head>
|
| -
|
| - <body onload="startUpload();">
|
| - <div id='upload_result'>Uploading...</div>
|
| - </body>
|
| - </html>
|
| - """ % megabytes
|
| - self.send_response(200)
|
| - self.end_headers()
|
| - self.wfile.write(script)
|
| -
|
| - def _ProcessUploadHandler(self, form):
|
| - """A POST handler that discards uploaded data and sends a response.
|
| -
|
| - Args:
|
| - form: A dictionary containing posted form data, as returned by
|
| - urlparse.parse_qs().
|
| - """
|
| - upload_processed = False
|
| - file_size = 0
|
| - if 'file_contents' in form:
|
| - file_size = len(form['file_contents'][0])
|
| - upload_processed = True
|
| - self.send_response(200)
|
| - self.end_headers()
|
| - if upload_processed:
|
| - self.wfile.write('Upload complete (%d bytes)' % file_size)
|
| - else:
|
| - self.wfile.write('No file contents uploaded')
|
| -
|
| - GET_REQUEST_HANDLERS = {
|
| - 'create_file_of_size': _CreateFileOfSizeHandler,
|
| - 'delete_file': _DeleteFileHandler,
|
| - 'start_upload': _StartUploadHandler,
|
| - 'favicon.ico': _IgnoreHandler,
|
| - }
|
| -
|
| - POST_REQUEST_HANDLERS = {
|
| - 'process_upload': _ProcessUploadHandler,
|
| - }
|
| -
|
| - def translate_path(self, path):
|
| - """Ensures files are served from the given document root.
|
| -
|
| - Overridden from SimpleHTTPServer.SimpleHTTPRequestHandler.
|
| - """
|
| - path = urlparse.urlparse(path)[2]
|
| - path = posixpath.normpath(urllib.unquote(path))
|
| - words = path.split('/')
|
| - words = filter(None, words) # Remove empty strings from |words|.
|
| - path = self.server.docroot
|
| - for word in words:
|
| - _, word = os.path.splitdrive(word)
|
| - _, word = os.path.split(word)
|
| - if word in (os.curdir, os.pardir):
|
| - continue
|
| - path = os.path.join(path, word)
|
| - return path
|
| -
|
| - def do_GET(self):
|
| - """Processes a GET request to the local server.
|
| -
|
| - Overridden from SimpleHTTPServer.SimpleHTTPRequestHandler.
|
| - """
|
| - split_url = urlparse.urlsplit(self.path)
|
| - base_path = split_url[2]
|
| - if base_path.startswith('/'):
|
| - base_path = base_path[1:]
|
| - args = urlparse.parse_qs(split_url[3])
|
| - if base_path in self.GET_REQUEST_HANDLERS:
|
| - self.GET_REQUEST_HANDLERS[base_path](self, args)
|
| - else:
|
| - SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
| -
|
| - def do_POST(self):
|
| - """Processes a POST request to the local server.
|
| -
|
| - Overridden from SimpleHTTPServer.SimpleHTTPRequestHandler.
|
| - """
|
| - form = urlparse.parse_qs(
|
| - self.rfile.read(int(self.headers.getheader('Content-Length'))))
|
| - path = urlparse.urlparse(self.path)[2]
|
| - if path.startswith('/'):
|
| - path = path[1:]
|
| - if path in self.POST_REQUEST_HANDLERS:
|
| - self.POST_REQUEST_HANDLERS[path](self, form)
|
| - else:
|
| - self.send_response(200)
|
| - self.send_header('Content-Type', 'text/plain')
|
| - self.end_headers()
|
| - self.wfile.write('No handler for POST request "%s".' % path)
|
| -
|
| -
|
| -class ThreadedHTTPServer(SocketServer.ThreadingMixIn,
|
| - BaseHTTPServer.HTTPServer):
|
| - def __init__(self, server_address, handler_class):
|
| - BaseHTTPServer.HTTPServer.__init__(self, server_address, handler_class)
|
| -
|
| -
|
| -class PerfTestServer(object):
|
| - """Local server for use by performance tests."""
|
| -
|
| - def __init__(self, docroot):
|
| - """Initializes the performance test server.
|
| -
|
| - Args:
|
| - docroot: The directory from which to serve files.
|
| - """
|
| - # The use of 0 means to start the server on an arbitrary available port.
|
| - self._server = ThreadedHTTPServer(('', 0),
|
| - PerfTestServerRequestHandler)
|
| - self._server.docroot = docroot
|
| - self._server_thread = threading.Thread(target=self._server.serve_forever)
|
| -
|
| - def Run(self):
|
| - """Starts the server thread."""
|
| - self._server_thread.start()
|
| -
|
| - def ShutDown(self):
|
| - """Shuts down the server."""
|
| - self._server.shutdown()
|
| - self._server_thread.join()
|
| -
|
| - def GetPort(self):
|
| - """Identifies the port number to which the server is currently bound.
|
| -
|
| - Returns:
|
| - The numeric port number to which the server is currently bound.
|
| - """
|
| - return self._server.server_address[1]
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - pyauto_functional.Main()
|
|
|