Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7840)

Unified Diff: chrome/test/functional/media/media_cn_perf.py

Issue 8802030: Introduce new PyAuto test to measure EPT, TTP metrics. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Cleanup. Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/test/functional/media/media_cn_perf.py
diff --git a/chrome/test/functional/media/media_cn_perf.py b/chrome/test/functional/media/media_cn_perf.py
new file mode 100755
index 0000000000000000000000000000000000000000..c1df63e63bf7566f47bbe467a268220f9da54fa4
--- /dev/null
+++ b/chrome/test/functional/media/media_cn_perf.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 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.
+
+"""Records metrics on playing media under constrained network conditions.
+
+Spins up a Constrained Network Server (CNS) and runs through a test matrix of
+bandwidth, latency, and packet loss settings. Each run records a
+time-to-playback (TTP) and extra-play-time (EPT) metric in a format consumable
+by the Chromium perf bots.
+
+See design document "Testing Video Performance Over Constrained Networks" for
+more information -- http://goo.gl/EdAWU
+
+Since even a small number of different settings yields a large test matrix, the
+design is threaded... however PyAuto is not, so a global lock is used when calls
+into PyAuto are necessary. The number of threads can be set by _TEST_THREADS.
+
+The CNS code is located under: <root>/src/media/tools/constrained_network_server
+"""
+
+import itertools
+import logging
+import os
+import Queue
+import signal
+import subprocess
+import threading
+
+import pyauto_media
+import pyauto
+import pyauto_paths
+import pyauto_utils
+
+
+# Settings for each network constraint.
+_BANDWIDTH_SETTINGS_KBPS = {'Off': 0, 'Low': 256, 'Medium': 2000, 'High': 5000}
+_LATENCY_SETTINGS_MS = {'Off': 0, 'Low': 43, 'Medium': 105, 'High': 180}
+_PACKET_LOSS_SETTINGS_PERCENT = {'Off': 0, 'Medium': 2, 'High': 5}
+
+# Test constraints are all possible combination of the above settings. Each
Nirnimesh 2011/12/06 22:15:05 Leave 2 blank spaces after every period, ie before
DaleCurtis 2011/12/06 23:42:54 Ugh, if you insist. In protest, I'd like to point
Nirnimesh 2011/12/06 23:58:36 I don't like it either. All my initial pyauto code
+# tuple must be of the form (Bandwidth, Latency, Packet Loss).
Nirnimesh 2011/12/06 22:15:05 s/tuple/Tuple
DaleCurtis 2011/12/06 23:42:54 Tuple is not a proper noun and this is a continuat
+_TEST_CONSTRAINTS = itertools.product(
+ _BANDWIDTH_SETTINGS_KBPS.values(),
+ _LATENCY_SETTINGS_MS.values(),
+ _PACKET_LOSS_SETTINGS_PERCENT.values())
+
+_TEST_CONSTRAINT_NAMES = itertools.product(
+ _BANDWIDTH_SETTINGS_KBPS.keys(),
+ _LATENCY_SETTINGS_MS.keys(),
+ _PACKET_LOSS_SETTINGS_PERCENT.keys())
+
+# HTML test path; relative to src/chrome/test/data. Loads a test video and
+# records metrics in JavaScript.
+_TEST_HTML_PATH = os.path.join('media', 'html', 'media_cn.html')
+
+# Number of threads to use during testing. TODO(dalecurtis): Should be set on
Nirnimesh 2011/12/06 22:15:05 move TODO to next line
DaleCurtis 2011/12/06 23:42:54 Done.
+# the command line.
+_TEST_THREADS = 3
+
+# File name of video to collect metrics for. TODO(dalecurtis): Should be set on
Nirnimesh 2011/12/06 22:15:05 move TODO to next line
DaleCurtis 2011/12/06 23:42:54 Done.
+# the command line.
+_TEST_VIDEO = 'dancing.webm'
+
+# Path to CNS executable relative to source root.
+_CNS_PATH = os.path.join(
+ 'media', 'tools', 'constrained_network_server', 'cns.py')
+
+# Port to start the CNS on.
+_CNS_PORT = 9000
+
+# Base CNS URL, only requires & separated parameter names appended.
+_CNS_BASE_URL = 'http://127.0.0.1:%d/ServeConstrained?' % _CNS_PORT
+
+
+class TestWorker(threading.Thread):
+ """Worker thread. For each queue entry: opens tab, runs test, closes tab."""
+
+ # Atomic, monotonically increasing task identifier. Used to ID tabs.
+ _task_id = itertools.count()
+
+ def __init__(self, pyauto_test, tasks, automation_lock, url):
+ """Sets up TestWorker class variables.
+
+ Args:
+ pyauto_test: Reference to a pyauto.PyUITest instance.
+ tasks: Queue containing (settings, name) tuples.
+ automation_lock: Global automation lock for pyauto calls.
+ url: File URL to HTML/JavaScript test code.
+ """
+ threading.Thread.__init__(self)
+ self._tasks = tasks
+ self._automation_lock = automation_lock
+ self._pyauto = pyauto_test
+ self._url = url
+ self.start()
+
+ def _FindTab(self, url):
+ """Returns the tab index for the tab belonging to this thread."""
Nirnimesh 2011/12/06 22:15:05 s/thread/url/ ?
DaleCurtis 2011/12/06 23:42:54 Done.
+ for tab in self._pyauto.GetBrowserInfo()['windows'][0]['tabs']:
+ if tab['url'] == url:
+ return tab['index']
+
+ def _HaveMetrics(self, unique_url):
+ """Returns if metrics are ready or not. Set self.{_ept,_ttp} < 0 pre-run."""
+ with self._automation_lock:
+ tab = self._FindTab(unique_url)
+
+ if self._ept < 0:
+ self._ept = self._pyauto.GetDOMValue('extra_play_time', tab_index=tab)
+ if self._ttp < 0:
+ self._ttp = self._pyauto.GetDOMValue('time_to_playback', tab_index=tab)
+ return self._ept >= 0 and self._ttp >= 0
+
+ def run(self):
+ """Opens tab, starts HTML test, and records metrics for each queue entry.
+
+ No exception handling is done to make sure the main thread exits properly
+ during Chrome crashes or other failures. Doing otherwise has the potential
+ to leave the CNS server running in the background.
+
+ For a clean shutdown, put the magic exit value (None, None) in the queue.
+ """
+ while True:
+ settings, name = self._tasks.get()
+
+ # Listen for magic exit values.
Nirnimesh 2011/12/06 22:15:05 s/Listen/Check/
DaleCurtis 2011/12/06 23:42:54 Done.
+ if (settings, name) == (None, None):
+ break
+
+ # Build video source URL. Values <= 0 mean the setting is disabled.
+ video_url = [_CNS_BASE_URL, 'f=' + _TEST_VIDEO]
+ if settings[0] > 0:
+ video_url.append('bandwidth=%d' % settings[0])
+ if settings[1] > 0:
+ video_url.append('latency=%d' % settings[1])
+ if settings[2] > 0:
+ video_url.append('loss=%d' % settings[2])
+ video_url = '&'.join(video_url)
+
+ # Make the test URL unique so we can figure out our tab index later.
Nirnimesh 2011/12/06 22:15:05 why not save the tab index?
DaleCurtis 2011/12/06 23:42:54 When we talked via chat you said the tab_index cha
Nirnimesh 2011/12/06 23:58:36 I see. Yes, that's correct. To that end, why do y
DaleCurtis 2011/12/07 01:30:05 I could probably get away with not doing so, but w
+ unique_url = '%s?%d' % (self._url, TestWorker._task_id.next())
+
+ # Start the test!
+ with self._automation_lock:
+ self._pyauto.AppendTab(pyauto.GURL(unique_url))
+ self._pyauto.CallJavascriptFunc(
+ 'startTest', [video_url], tab_index=self._FindTab(unique_url))
+
+ # Wait until the necessary metrics have been collected. Okay to not lock
Nirnimesh 2011/12/06 22:15:05 2 spaces after .
DaleCurtis 2011/12/06 23:42:54 Done.
+ # here since pyauto.WaitUntil doesn't call into Chrome.
Nirnimesh 2011/12/06 22:15:05 but _HaveMetrics calls into chrome..
DaleCurtis 2011/12/06 23:42:54 ...and makes those calls with the lock.
+ self._ept = self._ttp = -1
+ self._pyauto.WaitUntil(
+ self._HaveMetrics, args=[unique_url], retry_sleep=2)
+
+ # Record results. TODO(dalecurtis): Support reference builds.
Nirnimesh 2011/12/06 22:15:05 move todo to next line
DaleCurtis 2011/12/06 23:42:54 Done.
+ series_name = ''.join(name)
+ pyauto_utils.PrintPerfResult('ept', series_name, self._ept, '%')
+ pyauto_utils.PrintPerfResult('ttp', series_name, self._ttp, 'ms')
+
+ # Close the tab.
+ with self._automation_lock:
+ self._pyauto.GetBrowserWindow(0).GetTab(
+ self._FindTab(unique_url)).Close(True)
+
+ # TODO(dalecurtis): Check results for regressions.
+ self._tasks.task_done()
+
+
+class MediaConstrainedNetworkPerfTest(pyauto.PyUITest):
+ """PyAuto test container. See file doc string for more information."""
+
+ def setUp(self):
+ """Starts the Constrained Network Server (CNS)."""
+ cmd = ['python', os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH),
Nirnimesh 2011/12/06 22:15:05 s/python/sys.executable
DaleCurtis 2011/12/06 23:42:54 Done.
+ '--port', str(_CNS_PORT),
+ '--interface', 'lo',
+ '--www-root', os.path.join(self.DataDir(), 'media')]
+ process = subprocess.Popen(cmd, stderr=subprocess.PIPE)
+
+ # Wait for server to start up.
+ line = True
+ while line:
+ line = process.stderr.readline()
+ if 'STARTED' in line:
+ self._server_pid = process.pid
+ pyauto.PyUITest.setUp(self)
+ return
+ self.fail('Failed to start CNS.')
+
+ def tearDown(self):
+ """Stops the Constrained Network Server (CNS)."""
+ self.Kill(self._server_pid)
+ pyauto.PyUITest.tearDown(self)
Nirnimesh 2011/12/06 22:15:05 destruct in the opposite order. ie tearDown before
DaleCurtis 2011/12/06 23:42:54 Done.
+
+ def testConstrainedNetworkPerf(self):
+ """Starts CNS, spins up worker threads to run through _TEST_CONSTRAINTS."""
+ # Convert relative test path into an absolute path.
+ test_url = self.GetFileURLForDataPath(_TEST_HTML_PATH)
+
+ # PyAuto doesn't support threads, so we synchronize all automation calls.
Nirnimesh 2011/12/06 22:15:05 s/synchronize/guard around/ ?
DaleCurtis 2011/12/06 23:42:54 Synchronize works fine: http://en.wikipedia.org/wi
+ automation_lock = threading.Lock()
+
+ # Spin up worker threads.
+ tasks = Queue.Queue()
+ threads = []
+ for _ in xrange(_TEST_THREADS):
+ threads.append(TestWorker(self, tasks, automation_lock, test_url))
Nirnimesh 2011/12/06 22:15:05 Won't having multiple threads affect the numbers y
DaleCurtis 2011/12/06 23:42:54 Not in the limited testing I've done thus far. We'
+
+ for settings, name in zip(_TEST_CONSTRAINTS, _TEST_CONSTRAINT_NAMES):
+ tasks.put((settings, name))
+
+ # Add shutdown magic to end of queue.
+ for thread in threads:
+ tasks.put((None, None))
+
+ # Wait for threads to exit, gracefully or otherwise.
+ for thread in threads:
+ thread.join()
+
+
+if __name__ == '__main__':
+ # TODO(dalecurtis): Process command line parameters here.
+ pyauto_media.Main()

Powered by Google App Engine
This is Rietveld 408576698