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

Side by Side Diff: chrome/test/functional/perf.py

Issue 222873002: Remove pyauto tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: sync Created 6 years, 8 months 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/test/functional/perf.cfg ('k') | chrome/test/functional/perf/endure_graphs/config.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Basic pyauto performance tests.
7
8 For tests that need to be run for multiple iterations (e.g., so that average
9 and standard deviation values can be reported), the default number of iterations
10 run for each of these tests is specified by |_DEFAULT_NUM_ITERATIONS|.
11 That value can optionally be tweaked by setting an environment variable
12 'NUM_ITERATIONS' to a positive integer, representing the number of iterations
13 to run. An additional, initial iteration will also be run to "warm up" the
14 environment, and the result from that initial iteration will be ignored.
15
16 Some tests rely on repeatedly appending tabs to Chrome. Occasionally, these
17 automation calls time out, thereby affecting the timing measurements (see issue
18 crosbug.com/20503). To work around this, the tests discard timing measurements
19 that involve automation timeouts. The value |_DEFAULT_MAX_TIMEOUT_COUNT|
20 specifies the threshold number of timeouts that can be tolerated before the test
21 fails. To tweak this value, set environment variable 'MAX_TIMEOUT_COUNT' to the
22 desired threshold value.
23 """
24
25 import BaseHTTPServer
26 import commands
27 import errno
28 import itertools
29 import logging
30 import math
31 import os
32 import posixpath
33 import re
34 import SimpleHTTPServer
35 import SocketServer
36 import signal
37 import subprocess
38 import sys
39 import tempfile
40 import threading
41 import time
42 import timeit
43 import urllib
44 import urllib2
45 import urlparse
46
47 import pyauto_functional # Must be imported before pyauto.
48 import pyauto
49 import simplejson # Must be imported after pyauto; located in third_party.
50
51 from netflix import NetflixTestHelper
52 import pyauto_utils
53 import test_utils
54 from youtube import YoutubeTestHelper
55
56
57 _CHROME_BASE_DIR = os.path.abspath(os.path.join(
58 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir))
59
60
61 def FormatChromePath(posix_path, **kwargs):
62 """Convert a path relative to the Chromium root into an OS-specific path.
63
64 Args:
65 posix_path: a path string that may be a format().
66 Example: 'src/third_party/{module_name}/__init__.py'
67 kwargs: args for the format replacement.
68 Example: {'module_name': 'pylib'}
69
70 Returns:
71 an absolute path in the current Chromium tree with formatting applied.
72 """
73 formated_path = posix_path.format(**kwargs)
74 path_parts = formated_path.split('/')
75 return os.path.join(_CHROME_BASE_DIR, *path_parts)
76
77
78 def StandardDeviation(values):
79 """Returns the standard deviation of |values|."""
80 avg = Mean(values)
81 if len(values) < 2 or not avg:
82 return 0.0
83 temp_vals = [math.pow(x - avg, 2) for x in values]
84 return math.sqrt(sum(temp_vals) / (len(temp_vals) - 1))
85
86
87 def Mean(values):
88 """Returns the arithmetic mean of |values|."""
89 if not values or None in values:
90 return None
91 return sum(values) / float(len(values))
92
93
94 def GeometricMean(values):
95 """Returns the geometric mean of |values|."""
96 if not values or None in values or [x for x in values if x < 0.0]:
97 return None
98 if 0.0 in values:
99 return 0.0
100 return math.exp(Mean([math.log(x) for x in values]))
101
102
103 class BasePerfTest(pyauto.PyUITest):
104 """Base class for performance tests."""
105
106 _DEFAULT_NUM_ITERATIONS = 10 # Keep synced with desktopui_PyAutoPerfTests.py.
107 _DEFAULT_MAX_TIMEOUT_COUNT = 10
108 _PERF_OUTPUT_MARKER_PRE = '_PERF_PRE_'
109 _PERF_OUTPUT_MARKER_POST = '_PERF_POST_'
110
111 def setUp(self):
112 """Performs necessary setup work before running each test."""
113 self._num_iterations = self._DEFAULT_NUM_ITERATIONS
114 if 'NUM_ITERATIONS' in os.environ:
115 self._num_iterations = int(os.environ['NUM_ITERATIONS'])
116 self._max_timeout_count = self._DEFAULT_MAX_TIMEOUT_COUNT
117 if 'MAX_TIMEOUT_COUNT' in os.environ:
118 self._max_timeout_count = int(os.environ['MAX_TIMEOUT_COUNT'])
119 self._timeout_count = 0
120
121 # For users who want to see local perf graphs for Chrome when running the
122 # tests on their own machines.
123 self._local_perf_dir = None
124 if 'LOCAL_PERF_DIR' in os.environ:
125 self._local_perf_dir = os.environ['LOCAL_PERF_DIR']
126 if not os.path.exists(self._local_perf_dir):
127 self.fail('LOCAL_PERF_DIR environment variable specified as %s, '
128 'but this directory does not exist.' % self._local_perf_dir)
129 # When outputting perf graph information on-the-fly for Chrome, this
130 # variable lets us know whether a perf measurement is for a new test
131 # execution, or the current test execution.
132 self._seen_graph_lines = {}
133
134 pyauto.PyUITest.setUp(self)
135
136 # Flush all buffers to disk and wait until system calms down. Must be done
137 # *after* calling pyauto.PyUITest.setUp, since that is where Chrome is
138 # killed and re-initialized for a new test.
139 # TODO(dennisjeffrey): Implement wait for idle CPU on Windows/Mac.
140 if self.IsLinux(): # IsLinux() also implies IsChromeOS().
141 os.system('sync')
142 self._WaitForIdleCPU(60.0, 0.05)
143
144 def _IsPIDRunning(self, pid):
145 """Checks if a given process id is running.
146
147 Args:
148 pid: The process id of the process to check.
149
150 Returns:
151 True if the process is running. False if not.
152 """
153 try:
154 # Note that this sends the signal 0, which should not interfere with the
155 # process.
156 os.kill(pid, 0)
157 except OSError, err:
158 if err.errno == errno.ESRCH:
159 return False
160
161 try:
162 with open('/proc/%s/status' % pid) as proc_file:
163 if 'zombie' in proc_file.read():
164 return False
165 except IOError:
166 return False
167 return True
168
169 def _GetAllDescendentProcesses(self, pid):
170 pstree_out = subprocess.check_output(['pstree', '-p', '%s' % pid])
171 children = re.findall('\((\d+)\)', pstree_out)
172 return [int(pid) for pid in children]
173
174 def _WaitForChromeExit(self, browser_info, timeout):
175 pid = browser_info['browser_pid']
176 chrome_pids = self._GetAllDescendentProcesses(pid)
177 initial_time = time.time()
178 while time.time() - initial_time < timeout:
179 if any([self._IsPIDRunning(pid) for pid in chrome_pids]):
180 time.sleep(1)
181 else:
182 logging.info('_WaitForChromeExit() took: %s seconds',
183 time.time() - initial_time)
184 return
185 self.fail('_WaitForChromeExit() did not finish within %s seconds' %
186 timeout)
187
188 def tearDown(self):
189 if self._IsPGOMode():
190 browser_info = self.GetBrowserInfo()
191 pid = browser_info['browser_pid']
192 # session_manager kills chrome without waiting for it to cleanly exit.
193 # Until that behavior is changed, we stop it and wait for Chrome to exit
194 # cleanly before restarting it. See:
195 # crbug.com/264717
196 subprocess.call(['sudo', 'pkill', '-STOP', 'session_manager'])
197 os.kill(pid, signal.SIGINT)
198 self._WaitForChromeExit(browser_info, 120)
199 subprocess.call(['sudo', 'pkill', '-CONT', 'session_manager'])
200
201 pyauto.PyUITest.tearDown(self)
202
203 def _IsPGOMode(self):
204 return 'USE_PGO' in os.environ
205
206 def _WaitForIdleCPU(self, timeout, utilization):
207 """Waits for the CPU to become idle (< utilization).
208
209 Args:
210 timeout: The longest time in seconds to wait before throwing an error.
211 utilization: The CPU usage below which the system should be considered
212 idle (between 0 and 1.0 independent of cores/hyperthreads).
213 """
214 time_passed = 0.0
215 fraction_non_idle_time = 1.0
216 logging.info('Starting to wait up to %fs for idle CPU...', timeout)
217 while fraction_non_idle_time >= utilization:
218 cpu_usage_start = self._GetCPUUsage()
219 time.sleep(2)
220 time_passed += 2.0
221 cpu_usage_end = self._GetCPUUsage()
222 fraction_non_idle_time = \
223 self._GetFractionNonIdleCPUTime(cpu_usage_start, cpu_usage_end)
224 logging.info('Current CPU utilization = %f.', fraction_non_idle_time)
225 if time_passed > timeout:
226 self._LogProcessActivity()
227 message = ('CPU did not idle after %fs wait (utilization = %f).' % (
228 time_passed, fraction_non_idle_time))
229
230 # crosbug.com/37389
231 if self._IsPGOMode():
232 logging.info(message)
233 logging.info('Still continuing because we are in PGO mode.')
234 return
235
236 self.fail(message)
237 logging.info('Wait for idle CPU took %fs (utilization = %f).',
238 time_passed, fraction_non_idle_time)
239
240 def _LogProcessActivity(self):
241 """Logs the output of top on Linux/Mac/CrOS.
242
243 TODO: use taskmgr or similar on Windows.
244 """
245 if self.IsLinux() or self.IsMac(): # IsLinux() also implies IsChromeOS().
246 logging.info('Logging current process activity using top.')
247 cmd = 'top -b -d1 -n1'
248 if self.IsMac():
249 cmd = 'top -l1'
250 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
251 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
252 output = p.stdout.read()
253 logging.info(output)
254 else:
255 logging.info('Process activity logging not implemented on this OS.')
256
257 def _AppendTab(self, url):
258 """Appends a tab and increments a counter if the automation call times out.
259
260 Args:
261 url: The string url to which the appended tab should be navigated.
262 """
263 if not self.AppendTab(pyauto.GURL(url)):
264 self._timeout_count += 1
265
266 def _MeasureElapsedTime(self, python_command, num_invocations=1):
267 """Measures time (in msec) to execute a python command one or more times.
268
269 Args:
270 python_command: A callable.
271 num_invocations: An integer number of times to invoke the given command.
272
273 Returns:
274 The time required to execute the python command the specified number of
275 times, in milliseconds as a float.
276 """
277 assert callable(python_command)
278 def RunCommand():
279 for _ in range(num_invocations):
280 python_command()
281 timer = timeit.Timer(stmt=RunCommand)
282 return timer.timeit(number=1) * 1000 # Convert seconds to milliseconds.
283
284 def _OutputPerfForStandaloneGraphing(self, graph_name, description, value,
285 units, units_x, is_stacked):
286 """Outputs perf measurement data to a local folder to be graphed.
287
288 This function only applies to Chrome desktop, and assumes that environment
289 variable 'LOCAL_PERF_DIR' has been specified and refers to a valid directory
290 on the local machine.
291
292 Args:
293 graph_name: A string name for the graph associated with this performance
294 value.
295 description: A string description of the performance value. Should not
296 include spaces.
297 value: Either a single numeric value representing a performance
298 measurement, or else a list of (x, y) tuples representing one or more
299 long-running performance measurements, where 'x' is an x-axis value
300 (such as an iteration number) and 'y' is the corresponding performance
301 measurement. If a list of tuples is given, then the |units_x|
302 argument must also be specified.
303 units: A string representing the units of the performance measurement(s).
304 Should not include spaces.
305 units_x: A string representing the units of the x-axis values associated
306 with the performance measurements, such as 'iteration' if the x values
307 are iteration numbers. If this argument is specified, then the
308 |value| argument must be a list of (x, y) tuples.
309 is_stacked: True to draw a "stacked" graph. First-come values are
310 stacked at bottom by default.
311 """
312 revision_num_file = os.path.join(self._local_perf_dir, 'last_revision.dat')
313 if os.path.exists(revision_num_file):
314 with open(revision_num_file) as f:
315 revision = int(f.read())
316 else:
317 revision = 0
318
319 if not self._seen_graph_lines:
320 # We're about to output data for a new test run.
321 revision += 1
322
323 # Update graphs.dat.
324 existing_graphs = []
325 graphs_file = os.path.join(self._local_perf_dir, 'graphs.dat')
326 if os.path.exists(graphs_file):
327 with open(graphs_file) as f:
328 existing_graphs = simplejson.loads(f.read())
329 is_new_graph = True
330 for graph in existing_graphs:
331 if graph['name'] == graph_name:
332 is_new_graph = False
333 break
334 if is_new_graph:
335 new_graph = {
336 'name': graph_name,
337 'units': units,
338 'important': False,
339 }
340 if units_x:
341 new_graph['units_x'] = units_x
342 existing_graphs.append(new_graph)
343 with open(graphs_file, 'w') as f:
344 f.write(simplejson.dumps(existing_graphs))
345 os.chmod(graphs_file, 0755)
346
347 # Update data file for this particular graph.
348 existing_lines = []
349 data_file = os.path.join(self._local_perf_dir, graph_name + '-summary.dat')
350 if os.path.exists(data_file):
351 with open(data_file) as f:
352 existing_lines = f.readlines()
353 existing_lines = map(
354 simplejson.loads, map(lambda x: x.strip(), existing_lines))
355
356 seen_key = graph_name
357 # We assume that the first line |existing_lines[0]| is the latest.
358 if units_x:
359 new_line = {
360 'rev': revision,
361 'traces': { description: [] }
362 }
363 if seen_key in self._seen_graph_lines:
364 # We've added points previously for this graph line in the current
365 # test execution, so retrieve the original set of points specified in
366 # the most recent revision in the data file.
367 new_line = existing_lines[0]
368 if not description in new_line['traces']:
369 new_line['traces'][description] = []
370 for x_value, y_value in value:
371 new_line['traces'][description].append([str(x_value), str(y_value)])
372 else:
373 new_line = {
374 'rev': revision,
375 'traces': { description: [str(value), str(0.0)] }
376 }
377
378 if is_stacked:
379 new_line['stack'] = True
380 if 'stack_order' not in new_line:
381 new_line['stack_order'] = []
382 if description not in new_line['stack_order']:
383 new_line['stack_order'].append(description)
384
385 if seen_key in self._seen_graph_lines:
386 # Update results for the most recent revision.
387 existing_lines[0] = new_line
388 else:
389 # New results for a new revision.
390 existing_lines.insert(0, new_line)
391 self._seen_graph_lines[seen_key] = True
392
393 existing_lines = map(simplejson.dumps, existing_lines)
394 with open(data_file, 'w') as f:
395 f.write('\n'.join(existing_lines))
396 os.chmod(data_file, 0755)
397
398 with open(revision_num_file, 'w') as f:
399 f.write(str(revision))
400
401 def _OutputPerfGraphValue(self, description, value, units,
402 graph_name, units_x=None, is_stacked=False):
403 """Outputs a performance value to have it graphed on the performance bots.
404
405 The output format differs, depending on whether the current platform is
406 Chrome desktop or ChromeOS.
407
408 For ChromeOS, the performance bots have a 30-character limit on the length
409 of the key associated with a performance value. A key on ChromeOS is
410 considered to be of the form "units_description" (for example,
411 "milliseconds_NewTabPage"), and is created from the |units| and
412 |description| passed as input to this function. Any characters beyond the
413 length 30 limit are truncated before results are stored in the autotest
414 database.
415
416 Args:
417 description: A string description of the performance value. Should not
418 include spaces.
419 value: Either a numeric value representing a performance measurement, or
420 a list of values to be averaged. Lists may also contain (x, y) tuples
421 representing one or more performance measurements, where 'x' is an
422 x-axis value (such as an iteration number) and 'y' is the
423 corresponding performance measurement. If a list of tuples is given,
424 the |units_x| argument must also be specified.
425 units: A string representing the units of the performance measurement(s).
426 Should not include spaces.
427 graph_name: A string name for the graph associated with this performance
428 value. Only used on Chrome desktop.
429 units_x: A string representing the units of the x-axis values associated
430 with the performance measurements, such as 'iteration' if the x values
431 are iteration numbers. If this argument is specified, then the
432 |value| argument must be a list of (x, y) tuples.
433 is_stacked: True to draw a "stacked" graph. First-come values are
434 stacked at bottom by default.
435 """
436 if (isinstance(value, list) and value[0] is not None and
437 isinstance(value[0], tuple)):
438 assert units_x
439 if units_x:
440 assert isinstance(value, list)
441
442 if self.IsChromeOS():
443 # Autotest doesn't support result lists.
444 autotest_value = value
445 if (isinstance(value, list) and value[0] is not None and
446 not isinstance(value[0], tuple)):
447 autotest_value = Mean(value)
448
449 if units_x:
450 # TODO(dennisjeffrey): Support long-running performance measurements on
451 # ChromeOS in a way that can be graphed: crosbug.com/21881.
452 pyauto_utils.PrintPerfResult(graph_name, description, autotest_value,
453 units + ' ' + units_x)
454 else:
455 # Output short-running performance results in a format understood by
456 # autotest.
457 perf_key = '%s_%s' % (units, description)
458 if len(perf_key) > 30:
459 logging.warning('The description "%s" will be truncated to "%s" '
460 '(length 30) when added to the autotest database.',
461 perf_key, perf_key[:30])
462 print '\n%s(\'%s\', %f)%s' % (self._PERF_OUTPUT_MARKER_PRE,
463 perf_key, autotest_value,
464 self._PERF_OUTPUT_MARKER_POST)
465
466 # Also output results in the format recognized by buildbot, for cases
467 # in which these tests are run on chromeOS through buildbot. Since
468 # buildbot supports result lists, it's ok for |value| to be a list here.
469 pyauto_utils.PrintPerfResult(graph_name, description, value, units)
470
471 sys.stdout.flush()
472 else:
473 # TODO(dmikurube): Support stacked graphs in PrintPerfResult.
474 # See http://crbug.com/122119.
475 if units_x:
476 pyauto_utils.PrintPerfResult(graph_name, description, value,
477 units + ' ' + units_x)
478 else:
479 pyauto_utils.PrintPerfResult(graph_name, description, value, units)
480
481 if self._local_perf_dir:
482 self._OutputPerfForStandaloneGraphing(
483 graph_name, description, value, units, units_x, is_stacked)
484
485 def _OutputEventForStandaloneGraphing(self, description, event_list):
486 """Outputs event information to a local folder to be graphed.
487
488 See function _OutputEventGraphValue below for a description of an event.
489
490 This function only applies to Chrome Endure tests running on Chrome desktop,
491 and assumes that environment variable 'LOCAL_PERF_DIR' has been specified
492 and refers to a valid directory on the local machine.
493
494 Args:
495 description: A string description of the event. Should not include
496 spaces.
497 event_list: A list of (x, y) tuples representing one or more events
498 occurring during an endurance test, where 'x' is the time of the event
499 (in seconds since the start of the test), and 'y' is a dictionary
500 representing relevant data associated with that event (as key/value
501 pairs).
502 """
503 revision_num_file = os.path.join(self._local_perf_dir, 'last_revision.dat')
504 if os.path.exists(revision_num_file):
505 with open(revision_num_file) as f:
506 revision = int(f.read())
507 else:
508 revision = 0
509
510 if not self._seen_graph_lines:
511 # We're about to output data for a new test run.
512 revision += 1
513
514 existing_lines = []
515 data_file = os.path.join(self._local_perf_dir, '_EVENT_-summary.dat')
516 if os.path.exists(data_file):
517 with open(data_file) as f:
518 existing_lines = f.readlines()
519 existing_lines = map(eval, map(lambda x: x.strip(), existing_lines))
520
521 seen_event_type = description
522 value_list = []
523 if seen_event_type in self._seen_graph_lines:
524 # We've added events previously for this event type in the current
525 # test execution, so retrieve the original set of values specified in
526 # the most recent revision in the data file.
527 value_list = existing_lines[0]['events'][description]
528 for event_time, event_data in event_list:
529 value_list.append([str(event_time), event_data])
530 new_events = {
531 description: value_list
532 }
533
534 new_line = {
535 'rev': revision,
536 'events': new_events
537 }
538
539 if seen_event_type in self._seen_graph_lines:
540 # Update results for the most recent revision.
541 existing_lines[0] = new_line
542 else:
543 # New results for a new revision.
544 existing_lines.insert(0, new_line)
545 self._seen_graph_lines[seen_event_type] = True
546
547 existing_lines = map(str, existing_lines)
548 with open(data_file, 'w') as f:
549 f.write('\n'.join(existing_lines))
550 os.chmod(data_file, 0755)
551
552 with open(revision_num_file, 'w') as f:
553 f.write(str(revision))
554
555 def _OutputEventGraphValue(self, description, event_list):
556 """Outputs a set of events to have them graphed on the Chrome Endure bots.
557
558 An "event" can be anything recorded by a performance test that occurs at
559 particular times during a test execution. For example, a garbage collection
560 in the v8 heap can be considered an event. An event is distinguished from a
561 regular perf measurement in two ways: (1) an event is depicted differently
562 in the performance graphs than performance measurements; (2) an event can
563 be associated with zero or more data fields describing relevant information
564 associated with the event. For example, a garbage collection event will
565 occur at a particular time, and it may be associated with data such as
566 the number of collected bytes and/or the length of time it took to perform
567 the garbage collection.
568
569 This function only applies to Chrome Endure tests running on Chrome desktop.
570
571 Args:
572 description: A string description of the event. Should not include
573 spaces.
574 event_list: A list of (x, y) tuples representing one or more events
575 occurring during an endurance test, where 'x' is the time of the event
576 (in seconds since the start of the test), and 'y' is a dictionary
577 representing relevant data associated with that event (as key/value
578 pairs).
579 """
580 pyauto_utils.PrintPerfResult('_EVENT_', description, event_list, '')
581 if self._local_perf_dir:
582 self._OutputEventForStandaloneGraphing(description, event_list)
583
584 def _PrintSummaryResults(self, description, values, units, graph_name):
585 """Logs summary measurement information.
586
587 This function computes and outputs the average and standard deviation of
588 the specified list of value measurements. It also invokes
589 _OutputPerfGraphValue() with the computed *average* value, to ensure the
590 average value can be plotted in a performance graph.
591
592 Args:
593 description: A string description for the specified results.
594 values: A list of numeric value measurements.
595 units: A string specifying the units for the specified measurements.
596 graph_name: A string name for the graph associated with this performance
597 value. Only used on Chrome desktop.
598 """
599 logging.info('Overall results for: %s', description)
600 if values:
601 logging.info(' Average: %f %s', Mean(values), units)
602 logging.info(' Std dev: %f %s', StandardDeviation(values), units)
603 self._OutputPerfGraphValue(description, values, units, graph_name)
604 else:
605 logging.info('No results to report.')
606
607 def _RunNewTabTest(self, description, open_tab_command, graph_name,
608 num_tabs=1):
609 """Runs a perf test that involves opening new tab(s).
610
611 This helper function can be called from different tests to do perf testing
612 with different types of tabs. It is assumed that the |open_tab_command|
613 will open up a single tab.
614
615 Args:
616 description: A string description of the associated tab test.
617 open_tab_command: A callable that will open a single tab.
618 graph_name: A string name for the performance graph associated with this
619 test. Only used on Chrome desktop.
620 num_tabs: The number of tabs to open, i.e., the number of times to invoke
621 the |open_tab_command|.
622 """
623 assert callable(open_tab_command)
624
625 timings = []
626 for iteration in range(self._num_iterations + 1):
627 orig_timeout_count = self._timeout_count
628 elapsed_time = self._MeasureElapsedTime(open_tab_command,
629 num_invocations=num_tabs)
630 # Only count the timing measurement if no automation call timed out.
631 if self._timeout_count == orig_timeout_count:
632 # Ignore the first iteration.
633 if iteration:
634 timings.append(elapsed_time)
635 logging.info('Iteration %d of %d: %f milliseconds', iteration,
636 self._num_iterations, elapsed_time)
637 self.assertTrue(self._timeout_count <= self._max_timeout_count,
638 msg='Test exceeded automation timeout threshold.')
639 self.assertEqual(1 + num_tabs, self.GetTabCount(),
640 msg='Did not open %d new tab(s).' % num_tabs)
641 for _ in range(num_tabs):
642 self.CloseTab(tab_index=1)
643
644 self._PrintSummaryResults(description, timings, 'milliseconds', graph_name)
645
646 def _GetConfig(self):
647 """Load perf test configuration file.
648
649 Returns:
650 A dictionary that represents the config information.
651 """
652 config_file = os.path.join(os.path.dirname(__file__), 'perf.cfg')
653 config = {'username': None,
654 'password': None,
655 'google_account_url': 'https://accounts.google.com/',
656 'gmail_url': 'https://www.gmail.com',
657 'plus_url': 'https://plus.google.com',
658 'docs_url': 'https://docs.google.com'}
659 if os.path.exists(config_file):
660 try:
661 new_config = pyauto.PyUITest.EvalDataFrom(config_file)
662 for key in new_config:
663 if new_config.get(key) is not None:
664 config[key] = new_config.get(key)
665 except SyntaxError, e:
666 logging.info('Could not read %s: %s', config_file, str(e))
667 return config
668
669 def _LoginToGoogleAccount(self, account_key='test_google_account'):
670 """Logs in to a test Google account.
671
672 Login with user-defined credentials if they exist.
673 Else login with private test credentials if they exist.
674 Else fail.
675
676 Args:
677 account_key: The string key in private_tests_info.txt which is associated
678 with the test account login credentials to use. It will only
679 be used when fail to load user-defined credentials.
680
681 Raises:
682 RuntimeError: if could not get credential information.
683 """
684 private_file = os.path.join(pyauto.PyUITest.DataDir(), 'pyauto_private',
685 'private_tests_info.txt')
686 config_file = os.path.join(os.path.dirname(__file__), 'perf.cfg')
687 config = self._GetConfig()
688 google_account_url = config.get('google_account_url')
689 username = config.get('username')
690 password = config.get('password')
691 if username and password:
692 logging.info(
693 'Using google account credential from %s',
694 os.path.join(os.path.dirname(__file__), 'perf.cfg'))
695 elif os.path.exists(private_file):
696 creds = self.GetPrivateInfo()[account_key]
697 username = creds['username']
698 password = creds['password']
699 logging.info(
700 'User-defined credentials not found,' +
701 ' using private test credentials instead.')
702 else:
703 message = 'No user-defined or private test ' \
704 'credentials could be found. ' \
705 'Please specify credential information in %s.' \
706 % config_file
707 raise RuntimeError(message)
708 test_utils.GoogleAccountsLogin(
709 self, username, password, url=google_account_url)
710 self.NavigateToURL('about:blank') # Clear the existing tab.
711
712 def _GetCPUUsage(self):
713 """Returns machine's CPU usage.
714
715 This function uses /proc/stat to identify CPU usage, and therefore works
716 only on Linux/ChromeOS.
717
718 Returns:
719 A dictionary with 'user', 'nice', 'system' and 'idle' values.
720 Sample dictionary:
721 {
722 'user': 254544,
723 'nice': 9,
724 'system': 254768,
725 'idle': 2859878,
726 }
727 """
728 try:
729 f = open('/proc/stat')
730 cpu_usage_str = f.readline().split()
731 f.close()
732 except IOError, e:
733 self.fail('Could not retrieve CPU usage: ' + str(e))
734 return {
735 'user': int(cpu_usage_str[1]),
736 'nice': int(cpu_usage_str[2]),
737 'system': int(cpu_usage_str[3]),
738 'idle': int(cpu_usage_str[4])
739 }
740
741 def _GetFractionNonIdleCPUTime(self, cpu_usage_start, cpu_usage_end):
742 """Computes the fraction of CPU time spent non-idling.
743
744 This function should be invoked using before/after values from calls to
745 _GetCPUUsage().
746 """
747 time_non_idling_end = (cpu_usage_end['user'] + cpu_usage_end['nice'] +
748 cpu_usage_end['system'])
749 time_non_idling_start = (cpu_usage_start['user'] + cpu_usage_start['nice'] +
750 cpu_usage_start['system'])
751 total_time_end = (cpu_usage_end['user'] + cpu_usage_end['nice'] +
752 cpu_usage_end['system'] + cpu_usage_end['idle'])
753 total_time_start = (cpu_usage_start['user'] + cpu_usage_start['nice'] +
754 cpu_usage_start['system'] + cpu_usage_start['idle'])
755 return ((float(time_non_idling_end) - time_non_idling_start) /
756 (total_time_end - total_time_start))
757
758 def ExtraChromeFlags(self):
759 """Ensures Chrome is launched with custom flags.
760
761 Returns:
762 A list of extra flags to pass to Chrome when it is launched.
763 """
764 flags = super(BasePerfTest, self).ExtraChromeFlags()
765 # Window size impacts a variety of perf tests, ensure consistency.
766 flags.append('--window-size=1024,768')
767 if self._IsPGOMode():
768 flags = flags + ['--no-sandbox']
769 return flags
770
771
772 class TabPerfTest(BasePerfTest):
773 """Tests that involve opening tabs."""
774
775 def testNewTab(self):
776 """Measures time to open a new tab."""
777 self._RunNewTabTest('NewTabPage',
778 lambda: self._AppendTab('chrome://newtab'), 'open_tab')
779
780 def testNewTabFlash(self):
781 """Measures time to open a new tab navigated to a flash page."""
782 self.assertTrue(
783 os.path.exists(os.path.join(self.ContentDataDir(), 'plugin',
784 'flash.swf')),
785 msg='Missing required flash data file.')
786 url = self.GetFileURLForContentDataPath('plugin', 'flash.swf')
787 self._RunNewTabTest('NewTabFlashPage', lambda: self._AppendTab(url),
788 'open_tab')
789
790 def test20Tabs(self):
791 """Measures time to open 20 tabs."""
792 self._RunNewTabTest('20TabsNewTabPage',
793 lambda: self._AppendTab('chrome://newtab'),
794 'open_20_tabs', num_tabs=20)
795
796
797 class BenchmarkPerfTest(BasePerfTest):
798 """Benchmark performance tests."""
799
800 def testV8BenchmarkSuite(self):
801 """Measures score from v8 benchmark suite."""
802 url = self.GetFileURLForDataPath('v8_benchmark_v6', 'run.html')
803
804 def _RunBenchmarkOnce(url):
805 """Runs the v8 benchmark suite once and returns the results in a dict."""
806 self.assertTrue(self.AppendTab(pyauto.GURL(url)),
807 msg='Failed to append tab for v8 benchmark suite.')
808 js_done = """
809 var val = document.getElementById("status").innerHTML;
810 window.domAutomationController.send(val);
811 """
812 self.assertTrue(
813 self.WaitUntil(
814 lambda: 'Score:' in self.ExecuteJavascript(js_done, tab_index=1),
815 timeout=300, expect_retval=True, retry_sleep=1),
816 msg='Timed out when waiting for v8 benchmark score.')
817
818 js_get_results = """
819 var result = {};
820 result['final_score'] = document.getElementById("status").innerHTML;
821 result['all_results'] = document.getElementById("results").innerHTML;
822 window.domAutomationController.send(JSON.stringify(result));
823 """
824 results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
825 score_pattern = '(\w+): (\d+)'
826 final_score = re.search(score_pattern, results['final_score']).group(2)
827 result_dict = {'final_score': int(final_score)}
828 for match in re.finditer(score_pattern, results['all_results']):
829 benchmark_name = match.group(1)
830 benchmark_score = match.group(2)
831 result_dict[benchmark_name] = int(benchmark_score)
832 self.CloseTab(tab_index=1)
833 return result_dict
834
835 timings = {}
836 for iteration in xrange(self._num_iterations + 1):
837 result_dict = _RunBenchmarkOnce(url)
838 # Ignore the first iteration.
839 if iteration:
840 for key, val in result_dict.items():
841 timings.setdefault(key, []).append(val)
842 logging.info('Iteration %d of %d:\n%s', iteration,
843 self._num_iterations, self.pformat(result_dict))
844
845 for key, val in timings.items():
846 if key == 'final_score':
847 self._PrintSummaryResults('V8Benchmark', val, 'score',
848 'v8_benchmark_final')
849 else:
850 self._PrintSummaryResults('V8Benchmark-%s' % key, val, 'score',
851 'v8_benchmark_individual')
852
853 def testSunSpider(self):
854 """Runs the SunSpider javascript benchmark suite."""
855 url = self.GetFileURLForDataPath('sunspider', 'sunspider-driver.html')
856 self.assertTrue(self.AppendTab(pyauto.GURL(url)),
857 msg='Failed to append tab for SunSpider benchmark suite.')
858
859 js_is_done = """
860 var done = false;
861 if (document.getElementById("console"))
862 done = true;
863 window.domAutomationController.send(JSON.stringify(done));
864 """
865 self.assertTrue(
866 self.WaitUntil(
867 lambda: self.ExecuteJavascript(js_is_done, tab_index=1),
868 timeout=300, expect_retval='true', retry_sleep=1),
869 msg='Timed out when waiting for SunSpider benchmark score.')
870
871 js_get_results = """
872 window.domAutomationController.send(
873 document.getElementById("console").innerHTML);
874 """
875 # Append '<br>' to the result to simplify regular expression matching.
876 results = self.ExecuteJavascript(js_get_results, tab_index=1) + '<br>'
877 total = re.search('Total:\s*([\d.]+)ms', results).group(1)
878 logging.info('Total: %f ms', float(total))
879 self._OutputPerfGraphValue('SunSpider-total', float(total), 'ms',
880 'sunspider_total')
881
882 for match_category in re.finditer('\s\s(\w+):\s*([\d.]+)ms.+?<br><br>',
883 results):
884 category_name = match_category.group(1)
885 category_result = match_category.group(2)
886 logging.info('Benchmark "%s": %f ms', category_name,
887 float(category_result))
888 self._OutputPerfGraphValue('SunSpider-' + category_name,
889 float(category_result), 'ms',
890 'sunspider_individual')
891
892 for match_result in re.finditer('<br>\s\s\s\s([\w-]+):\s*([\d.]+)ms',
893 match_category.group(0)):
894 result_name = match_result.group(1)
895 result_value = match_result.group(2)
896 logging.info(' Result "%s-%s": %f ms', category_name, result_name,
897 float(result_value))
898 self._OutputPerfGraphValue(
899 'SunSpider-%s-%s' % (category_name, result_name),
900 float(result_value), 'ms', 'sunspider_individual')
901
902 def testDromaeoSuite(self):
903 """Measures results from Dromaeo benchmark suite."""
904 url = self.GetFileURLForDataPath('dromaeo', 'index.html')
905 self.assertTrue(self.AppendTab(pyauto.GURL(url + '?dromaeo')),
906 msg='Failed to append tab for Dromaeo benchmark suite.')
907
908 js_is_ready = """
909 var val = document.getElementById('pause').value;
910 window.domAutomationController.send(val);
911 """
912 self.assertTrue(
913 self.WaitUntil(
914 lambda: self.ExecuteJavascript(js_is_ready, tab_index=1),
915 timeout=30, expect_retval='Run', retry_sleep=1),
916 msg='Timed out when waiting for Dromaeo benchmark to load.')
917
918 js_run = """
919 $('#pause').val('Run').click();
920 window.domAutomationController.send('done');
921 """
922 self.ExecuteJavascript(js_run, tab_index=1)
923
924 js_is_done = """
925 var val = document.getElementById('timebar').innerHTML;
926 window.domAutomationController.send(val);
927 """
928 self.assertTrue(
929 self.WaitUntil(
930 lambda: 'Total' in self.ExecuteJavascript(js_is_done, tab_index=1),
931 timeout=900, expect_retval=True, retry_sleep=2),
932 msg='Timed out when waiting for Dromaeo benchmark to complete.')
933
934 js_get_results = """
935 var result = {};
936 result['total_result'] = $('#timebar strong').html();
937 result['all_results'] = {};
938 $('.result-item.done').each(function (i) {
939 var group_name = $(this).find('.test b').html().replace(':', '');
940 var group_results = {};
941 group_results['result'] =
942 $(this).find('span').html().replace('runs/s', '')
943
944 group_results['sub_groups'] = {}
945 $(this).find('li').each(function (i) {
946 var sub_name = $(this).find('b').html().replace(':', '');
947 group_results['sub_groups'][sub_name] =
948 $(this).text().match(/: ([\d.]+)/)[1]
949 });
950 result['all_results'][group_name] = group_results;
951 });
952 window.domAutomationController.send(JSON.stringify(result));
953 """
954 results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
955 total_result = results['total_result']
956 logging.info('Total result: ' + total_result)
957 self._OutputPerfGraphValue('Dromaeo-total', float(total_result),
958 'runsPerSec', 'dromaeo_total')
959
960 for group_name, group in results['all_results'].iteritems():
961 logging.info('Benchmark "%s": %s', group_name, group['result'])
962 self._OutputPerfGraphValue('Dromaeo-' + group_name.replace(' ', ''),
963 float(group['result']), 'runsPerSec',
964 'dromaeo_individual')
965 for benchmark_name, benchmark_score in group['sub_groups'].iteritems():
966 logging.info(' Result "%s": %s', benchmark_name, benchmark_score)
967
968 def testSpaceport(self):
969 """Measures results from Spaceport benchmark suite."""
970 # TODO(tonyg): Test is failing on bots. Diagnose and re-enable.
971 pass
972
973 # url = self.GetFileURLForDataPath('third_party', 'spaceport', 'index.html')
974 # self.assertTrue(self.AppendTab(pyauto.GURL(url + '?auto')),
975 # msg='Failed to append tab for Spaceport benchmark suite.')
976 #
977 # # The test reports results to console.log in the format "name: value".
978 # # Inject a bit of JS to intercept those.
979 # js_collect_console_log = """
980 # window.__pyautoresult = {};
981 # window.console.log = function(str) {
982 # if (!str) return;
983 # var key_val = str.split(': ');
984 # if (!key_val.length == 2) return;
985 # __pyautoresult[key_val[0]] = key_val[1];
986 # };
987 # window.domAutomationController.send('done');
988 # """
989 # self.ExecuteJavascript(js_collect_console_log, tab_index=1)
990 #
991 # def _IsDone():
992 # expected_num_results = 30 # The number of tests in benchmark.
993 # results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
994 # return expected_num_results == len(results)
995 #
996 # js_get_results = """
997 # window.domAutomationController.send(
998 # JSON.stringify(window.__pyautoresult));
999 # """
1000 # self.assertTrue(
1001 # self.WaitUntil(_IsDone, timeout=1200, expect_retval=True,
1002 # retry_sleep=5),
1003 # msg='Timed out when waiting for Spaceport benchmark to complete.')
1004 # results = eval(self.ExecuteJavascript(js_get_results, tab_index=1))
1005 #
1006 # for key in results:
1007 # suite, test = key.split('.')
1008 # value = float(results[key])
1009 # self._OutputPerfGraphValue(test, value, 'ObjectsAt30FPS', suite)
1010 # self._PrintSummaryResults('Overall', [float(x) for x in results.values()],
1011 # 'ObjectsAt30FPS', 'Overall')
1012
1013
1014 class LiveWebappLoadTest(BasePerfTest):
1015 """Tests that involve performance measurements of live webapps.
1016
1017 These tests connect to live webpages (e.g., Gmail, Calendar, Docs) and are
1018 therefore subject to network conditions. These tests are meant to generate
1019 "ball-park" numbers only (to see roughly how long things take to occur from a
1020 user's perspective), and are not expected to be precise.
1021 """
1022
1023 def testNewTabGmail(self):
1024 """Measures time to open a tab to a logged-in Gmail account.
1025
1026 Timing starts right before the new tab is opened, and stops as soon as the
1027 webpage displays the substring 'Last account activity:'.
1028 """
1029 EXPECTED_SUBSTRING = 'Last account activity:'
1030
1031 def _SubstringExistsOnPage():
1032 js = """
1033 var frame = document.getElementById("canvas_frame");
1034 var divs = frame.contentDocument.getElementsByTagName("div");
1035 for (var i = 0; i < divs.length; ++i) {
1036 if (divs[i].innerHTML.indexOf("%s") >= 0)
1037 window.domAutomationController.send("true");
1038 }
1039 window.domAutomationController.send("false");
1040 """ % EXPECTED_SUBSTRING
1041 return self.ExecuteJavascript(js, tab_index=1)
1042
1043 def _RunSingleGmailTabOpen():
1044 self._AppendTab('http://www.gmail.com')
1045 self.assertTrue(self.WaitUntil(_SubstringExistsOnPage, timeout=120,
1046 expect_retval='true', retry_sleep=0.10),
1047 msg='Timed out waiting for expected Gmail string.')
1048
1049 self._LoginToGoogleAccount()
1050 self._RunNewTabTest('NewTabGmail', _RunSingleGmailTabOpen,
1051 'open_tab_live_webapp')
1052
1053 def testNewTabCalendar(self):
1054 """Measures time to open a tab to a logged-in Calendar account.
1055
1056 Timing starts right before the new tab is opened, and stops as soon as the
1057 webpage displays the calendar print button (title 'Print my calendar').
1058 """
1059 EXPECTED_SUBSTRING = 'Month'
1060
1061 def _DivTitleStartsWith():
1062 js = """
1063 var divs = document.getElementsByTagName("div");
1064 for (var i = 0; i < divs.length; ++i) {
1065 if (divs[i].innerHTML == "%s")
1066 window.domAutomationController.send("true");
1067 }
1068 window.domAutomationController.send("false");
1069 """ % EXPECTED_SUBSTRING
1070 return self.ExecuteJavascript(js, tab_index=1)
1071
1072 def _RunSingleCalendarTabOpen():
1073 self._AppendTab('http://calendar.google.com')
1074 self.assertTrue(self.WaitUntil(_DivTitleStartsWith, timeout=120,
1075 expect_retval='true', retry_sleep=0.10),
1076 msg='Timed out waiting for expected Calendar string.')
1077
1078 self._LoginToGoogleAccount()
1079 self._RunNewTabTest('NewTabCalendar', _RunSingleCalendarTabOpen,
1080 'open_tab_live_webapp')
1081
1082 def testNewTabDocs(self):
1083 """Measures time to open a tab to a logged-in Docs account.
1084
1085 Timing starts right before the new tab is opened, and stops as soon as the
1086 webpage displays the expected substring 'last modified' (case insensitive).
1087 """
1088 EXPECTED_SUBSTRING = 'sort'
1089
1090 def _SubstringExistsOnPage():
1091 js = """
1092 var divs = document.getElementsByTagName("div");
1093 for (var i = 0; i < divs.length; ++i) {
1094 if (divs[i].innerHTML.toLowerCase().indexOf("%s") >= 0)
1095 window.domAutomationController.send("true");
1096 }
1097 window.domAutomationController.send("false");
1098 """ % EXPECTED_SUBSTRING
1099 return self.ExecuteJavascript(js, tab_index=1)
1100
1101 def _RunSingleDocsTabOpen():
1102 self._AppendTab('http://docs.google.com')
1103 self.assertTrue(self.WaitUntil(_SubstringExistsOnPage, timeout=120,
1104 expect_retval='true', retry_sleep=0.10),
1105 msg='Timed out waiting for expected Docs string.')
1106
1107 self._LoginToGoogleAccount()
1108 self._RunNewTabTest('NewTabDocs', _RunSingleDocsTabOpen,
1109 'open_tab_live_webapp')
1110
1111
1112 class NetflixPerfTest(BasePerfTest, NetflixTestHelper):
1113 """Test Netflix video performance."""
1114
1115 def __init__(self, methodName='runTest', **kwargs):
1116 pyauto.PyUITest.__init__(self, methodName, **kwargs)
1117 NetflixTestHelper.__init__(self, self)
1118
1119 def tearDown(self):
1120 self.SignOut()
1121 pyauto.PyUITest.tearDown(self)
1122
1123 def testNetflixDroppedFrames(self):
1124 """Measures the Netflix video dropped frames/second. Runs for 60 secs."""
1125 self.LoginAndStartPlaying()
1126 self.CheckNetflixPlaying(self.IS_PLAYING,
1127 'Player did not start playing the title.')
1128 # Ignore first 10 seconds of video playing so we get smooth videoplayback.
1129 time.sleep(10)
1130 init_dropped_frames = self._GetVideoDroppedFrames()
1131 dropped_frames = []
1132 prev_dropped_frames = 0
1133 for iteration in xrange(60):
1134 # Ignoring initial dropped frames of first 10 seconds.
1135 total_dropped_frames = self._GetVideoDroppedFrames() - init_dropped_frames
1136 dropped_frames_last_sec = total_dropped_frames - prev_dropped_frames
1137 dropped_frames.append(dropped_frames_last_sec)
1138 logging.info('Iteration %d of %d: %f dropped frames in the last second',
1139 iteration + 1, 60, dropped_frames_last_sec)
1140 prev_dropped_frames = total_dropped_frames
1141 # Play the video for some time.
1142 time.sleep(1)
1143 self._PrintSummaryResults('NetflixDroppedFrames', dropped_frames, 'frames',
1144 'netflix_dropped_frames')
1145
1146 def testNetflixCPU(self):
1147 """Measures the Netflix video CPU usage. Runs for 60 seconds."""
1148 self.LoginAndStartPlaying()
1149 self.CheckNetflixPlaying(self.IS_PLAYING,
1150 'Player did not start playing the title.')
1151 # Ignore first 10 seconds of video playing so we get smooth videoplayback.
1152 time.sleep(10)
1153 init_dropped_frames = self._GetVideoDroppedFrames()
1154 init_video_frames = self._GetVideoFrames()
1155 cpu_usage_start = self._GetCPUUsage()
1156 total_shown_frames = 0
1157 # Play the video for some time.
1158 time.sleep(60)
1159 total_video_frames = self._GetVideoFrames() - init_video_frames
1160 total_dropped_frames = self._GetVideoDroppedFrames() - init_dropped_frames
1161 cpu_usage_end = self._GetCPUUsage()
1162 fraction_non_idle_time = \
1163 self._GetFractionNonIdleCPUTime(cpu_usage_start, cpu_usage_end)
1164 # Counting extrapolation for utilization to play the video.
1165 extrapolation_value = fraction_non_idle_time * \
1166 (float(total_video_frames) + total_dropped_frames) / total_video_frames
1167 logging.info('Netflix CPU extrapolation: %f', extrapolation_value)
1168 self._OutputPerfGraphValue('NetflixCPUExtrapolation', extrapolation_value,
1169 'extrapolation', 'netflix_cpu_extrapolation')
1170
1171
1172 class YoutubePerfTest(BasePerfTest, YoutubeTestHelper):
1173 """Test Youtube video performance."""
1174
1175 def __init__(self, methodName='runTest', **kwargs):
1176 pyauto.PyUITest.__init__(self, methodName, **kwargs)
1177 YoutubeTestHelper.__init__(self, self)
1178
1179 def _VerifyVideoTotalBytes(self):
1180 """Returns true if video total bytes information is available."""
1181 return self.GetVideoTotalBytes() > 0
1182
1183 def _VerifyVideoLoadedBytes(self):
1184 """Returns true if video loaded bytes information is available."""
1185 return self.GetVideoLoadedBytes() > 0
1186
1187 def StartVideoForPerformance(self, video_id='zuzaxlddWbk'):
1188 """Start the test video with all required buffering."""
1189 self.PlayVideoAndAssert(video_id)
1190 self.ExecuteJavascript("""
1191 ytplayer.setPlaybackQuality('hd720');
1192 window.domAutomationController.send('');
1193 """)
1194 self.AssertPlayerState(state=self.is_playing,
1195 msg='Player did not enter the playing state')
1196 self.assertTrue(
1197 self.WaitUntil(self._VerifyVideoTotalBytes, expect_retval=True),
1198 msg='Failed to get video total bytes information.')
1199 self.assertTrue(
1200 self.WaitUntil(self._VerifyVideoLoadedBytes, expect_retval=True),
1201 msg='Failed to get video loaded bytes information')
1202 loaded_video_bytes = self.GetVideoLoadedBytes()
1203 total_video_bytes = self.GetVideoTotalBytes()
1204 self.PauseVideo()
1205 logging.info('total_video_bytes: %f', total_video_bytes)
1206 # Wait for the video to finish loading.
1207 while total_video_bytes > loaded_video_bytes:
1208 loaded_video_bytes = self.GetVideoLoadedBytes()
1209 logging.info('loaded_video_bytes: %f', loaded_video_bytes)
1210 time.sleep(1)
1211 self.PlayVideo()
1212 # Ignore first 10 seconds of video playing so we get smooth videoplayback.
1213 time.sleep(10)
1214
1215 def testYoutubeDroppedFrames(self):
1216 """Measures the Youtube video dropped frames/second. Runs for 60 secs.
1217
1218 This test measures Youtube video dropped frames for three different types
1219 of videos like slow, normal and fast motion.
1220 """
1221 youtube_video = {'Slow': 'VT1-sitWRtY',
1222 'Normal': '2tqK_3mKQUw',
1223 'Fast': '8ETDE0VGJY4',
1224 }
1225 for video_type in youtube_video:
1226 logging.info('Running %s video.', video_type)
1227 self.StartVideoForPerformance(youtube_video[video_type])
1228 init_dropped_frames = self.GetVideoDroppedFrames()
1229 total_dropped_frames = 0
1230 dropped_fps = []
1231 for iteration in xrange(60):
1232 frames = self.GetVideoDroppedFrames() - init_dropped_frames
1233 current_dropped_frames = frames - total_dropped_frames
1234 dropped_fps.append(current_dropped_frames)
1235 logging.info('Iteration %d of %d: %f dropped frames in the last '
1236 'second', iteration + 1, 60, current_dropped_frames)
1237 total_dropped_frames = frames
1238 # Play the video for some time
1239 time.sleep(1)
1240 graph_description = 'YoutubeDroppedFrames' + video_type
1241 self._PrintSummaryResults(graph_description, dropped_fps, 'frames',
1242 'youtube_dropped_frames')
1243
1244 def testYoutubeCPU(self):
1245 """Measures the Youtube video CPU usage. Runs for 60 seconds.
1246
1247 Measures the Youtube video CPU usage (between 0 and 1), extrapolated to
1248 totalframes in the video by taking dropped frames into account. For smooth
1249 videoplayback this number should be < 0.5..1.0 on a hyperthreaded CPU.
1250 """
1251 self.StartVideoForPerformance()
1252 init_dropped_frames = self.GetVideoDroppedFrames()
1253 logging.info('init_dropped_frames: %f', init_dropped_frames)
1254 cpu_usage_start = self._GetCPUUsage()
1255 total_shown_frames = 0
1256 for sec_num in xrange(60):
1257 # Play the video for some time.
1258 time.sleep(1)
1259 total_shown_frames = total_shown_frames + self.GetVideoFrames()
1260 logging.info('total_shown_frames: %f', total_shown_frames)
1261 total_dropped_frames = self.GetVideoDroppedFrames() - init_dropped_frames
1262 logging.info('total_dropped_frames: %f', total_dropped_frames)
1263 cpu_usage_end = self._GetCPUUsage()
1264 fraction_non_idle_time = self._GetFractionNonIdleCPUTime(
1265 cpu_usage_start, cpu_usage_end)
1266 logging.info('fraction_non_idle_time: %f', fraction_non_idle_time)
1267 total_frames = total_shown_frames + total_dropped_frames
1268 # Counting extrapolation for utilization to play the video.
1269 extrapolation_value = (fraction_non_idle_time *
1270 (float(total_frames) / total_shown_frames))
1271 logging.info('Youtube CPU extrapolation: %f', extrapolation_value)
1272 # Video is still running so log some more detailed data.
1273 self._LogProcessActivity()
1274 self._OutputPerfGraphValue('YoutubeCPUExtrapolation', extrapolation_value,
1275 'extrapolation', 'youtube_cpu_extrapolation')
1276
1277
1278 class FlashVideoPerfTest(BasePerfTest):
1279 """General flash video performance tests."""
1280
1281 def FlashVideo1080P(self):
1282 """Measures total dropped frames and average FPS for a 1080p flash video.
1283
1284 This is a temporary test to be run manually for now, needed to collect some
1285 performance statistics across different ChromeOS devices.
1286 """
1287 # Open up the test webpage; it's assumed the test will start automatically.
1288 webpage_url = 'http://www/~arscott/fl/FlashVideoTests.html'
1289 self.assertTrue(self.AppendTab(pyauto.GURL(webpage_url)),
1290 msg='Failed to append tab for webpage.')
1291
1292 # Wait until the test is complete.
1293 js_is_done = """
1294 window.domAutomationController.send(JSON.stringify(tests_done));
1295 """
1296 self.assertTrue(
1297 self.WaitUntil(
1298 lambda: self.ExecuteJavascript(js_is_done, tab_index=1) == 'true',
1299 timeout=300, expect_retval=True, retry_sleep=1),
1300 msg='Timed out when waiting for test result.')
1301
1302 # Retrieve and output the test results.
1303 js_results = """
1304 window.domAutomationController.send(JSON.stringify(tests_results));
1305 """
1306 test_result = eval(self.ExecuteJavascript(js_results, tab_index=1))
1307 test_result[0] = test_result[0].replace('true', 'True')
1308 test_result = eval(test_result[0]) # Webpage only does 1 test right now.
1309
1310 description = 'FlashVideo1080P'
1311 result = test_result['averageFPS']
1312 logging.info('Result for %s: %f FPS (average)', description, result)
1313 self._OutputPerfGraphValue(description, result, 'FPS',
1314 'flash_video_1080p_fps')
1315 result = test_result['droppedFrames']
1316 logging.info('Result for %s: %f dropped frames', description, result)
1317 self._OutputPerfGraphValue(description, result, 'DroppedFrames',
1318 'flash_video_1080p_dropped_frames')
1319
1320
1321 class WebGLTest(BasePerfTest):
1322 """Tests for WebGL performance."""
1323
1324 def _RunWebGLTest(self, url, description, graph_name):
1325 """Measures FPS using a specified WebGL demo.
1326
1327 Args:
1328 url: The string URL that, once loaded, will run the WebGL demo (default
1329 WebGL demo settings are used, since this test does not modify any
1330 settings in the demo).
1331 description: A string description for this demo, used as a performance
1332 value description. Should not contain any spaces.
1333 graph_name: A string name for the performance graph associated with this
1334 test. Only used on Chrome desktop.
1335 """
1336 self.assertTrue(self.AppendTab(pyauto.GURL(url)),
1337 msg='Failed to append tab for %s.' % description)
1338
1339 get_fps_js = """
1340 var fps_field = document.getElementById("fps");
1341 var result = -1;
1342 if (fps_field)
1343 result = fps_field.innerHTML;
1344 window.domAutomationController.send(JSON.stringify(result));
1345 """
1346
1347 # Wait until we start getting FPS values.
1348 self.assertTrue(
1349 self.WaitUntil(
1350 lambda: self.ExecuteJavascript(get_fps_js, tab_index=1) != '-1',
1351 timeout=300, retry_sleep=1),
1352 msg='Timed out when waiting for FPS values to be available.')
1353
1354 # Let the experiment run for 5 seconds before we start collecting perf
1355 # measurements.
1356 time.sleep(5)
1357
1358 # Collect the current FPS value each second for the next 30 seconds. The
1359 # final result of this test will be the average of these FPS values.
1360 fps_vals = []
1361 for iteration in xrange(30):
1362 fps = self.ExecuteJavascript(get_fps_js, tab_index=1)
1363 fps = float(fps.replace('"', ''))
1364 fps_vals.append(fps)
1365 logging.info('Iteration %d of %d: %f FPS', iteration + 1, 30, fps)
1366 time.sleep(1)
1367 self._PrintSummaryResults(description, fps_vals, 'fps', graph_name)
1368
1369 def testWebGLAquarium(self):
1370 """Measures performance using the WebGL Aquarium demo."""
1371 self._RunWebGLTest(
1372 self.GetFileURLForDataPath('pyauto_private', 'webgl', 'aquarium',
1373 'aquarium.html'),
1374 'WebGLAquarium', 'webgl_demo')
1375
1376 def testWebGLField(self):
1377 """Measures performance using the WebGL Field demo."""
1378 self._RunWebGLTest(
1379 self.GetFileURLForDataPath('pyauto_private', 'webgl', 'field',
1380 'field.html'),
1381 'WebGLField', 'webgl_demo')
1382
1383 def testWebGLSpaceRocks(self):
1384 """Measures performance using the WebGL SpaceRocks demo."""
1385 self._RunWebGLTest(
1386 self.GetFileURLForDataPath('pyauto_private', 'webgl', 'spacerocks',
1387 'spacerocks.html'),
1388 'WebGLSpaceRocks', 'webgl_demo')
1389
1390
1391 class GPUPerfTest(BasePerfTest):
1392 """Tests for GPU performance."""
1393
1394 def setUp(self):
1395 """Performs necessary setup work before running each test in this class."""
1396 self._gpu_info_dict = self.EvalDataFrom(os.path.join(self.DataDir(),
1397 'gpu', 'gpuperf.txt'))
1398 self._demo_name_url_dict = self._gpu_info_dict['demo_info']
1399 pyauto.PyUITest.setUp(self)
1400
1401 def _MeasureFpsOverTime(self, tab_index=0):
1402 """Measures FPS using a specified demo.
1403
1404 This function assumes that the demo is already loaded in the specified tab
1405 index.
1406
1407 Args:
1408 tab_index: The tab index, default is 0.
1409 """
1410 # Let the experiment run for 5 seconds before we start collecting FPS
1411 # values.
1412 time.sleep(5)
1413
1414 # Collect the current FPS value each second for the next 10 seconds.
1415 # Then return the average FPS value from among those collected.
1416 fps_vals = []
1417 for iteration in xrange(10):
1418 fps = self.GetFPS(tab_index=tab_index)
1419 fps_vals.append(fps['fps'])
1420 time.sleep(1)
1421 return Mean(fps_vals)
1422
1423 def _GetStdAvgAndCompare(self, avg_fps, description, ref_dict):
1424 """Computes the average and compare set of values with reference data.
1425
1426 Args:
1427 avg_fps: Average fps value.
1428 description: A string description for this demo, used as a performance
1429 value description.
1430 ref_dict: Dictionary which contains reference data for this test case.
1431
1432 Returns:
1433 True, if the actual FPS value is within 10% of the reference FPS value,
1434 or False, otherwise.
1435 """
1436 std_fps = 0
1437 status = True
1438 # Load reference data according to platform.
1439 platform_ref_dict = None
1440 if self.IsWin():
1441 platform_ref_dict = ref_dict['win']
1442 elif self.IsMac():
1443 platform_ref_dict = ref_dict['mac']
1444 elif self.IsLinux():
1445 platform_ref_dict = ref_dict['linux']
1446 else:
1447 self.assertFail(msg='This platform is unsupported.')
1448 std_fps = platform_ref_dict[description]
1449 # Compare reference data to average fps.
1450 # We allow the average FPS value to be within 10% of the reference
1451 # FPS value.
1452 if avg_fps < (0.9 * std_fps):
1453 logging.info('FPS difference exceeds threshold for: %s', description)
1454 logging.info(' Average: %f fps', avg_fps)
1455 logging.info('Reference Average: %f fps', std_fps)
1456 status = False
1457 else:
1458 logging.info('Average FPS is actually greater than 10 percent '
1459 'more than the reference FPS for: %s', description)
1460 logging.info(' Average: %f fps', avg_fps)
1461 logging.info(' Reference Average: %f fps', std_fps)
1462 return status
1463
1464 def testLaunchDemosParallelInSeparateTabs(self):
1465 """Measures performance of demos in different tabs in same browser."""
1466 # Launch all the demos parallel in separate tabs
1467 counter = 0
1468 all_demos_passed = True
1469 ref_dict = self._gpu_info_dict['separate_tab_ref_data']
1470 # Iterate through dictionary and append all url to browser
1471 for url in self._demo_name_url_dict.iterkeys():
1472 self.assertTrue(
1473 self.AppendTab(pyauto.GURL(self._demo_name_url_dict[url])),
1474 msg='Failed to append tab for %s.' % url)
1475 counter += 1
1476 # Assert number of tab count is equal to number of tabs appended.
1477 self.assertEqual(self.GetTabCount(), counter + 1)
1478 # Measures performance using different demos and compare it golden
1479 # reference.
1480 for url in self._demo_name_url_dict.iterkeys():
1481 avg_fps = self._MeasureFpsOverTime(tab_index=counter)
1482 # Get the reference value of fps and compare the results
1483 if not self._GetStdAvgAndCompare(avg_fps, url, ref_dict):
1484 all_demos_passed = False
1485 counter -= 1
1486 self.assertTrue(
1487 all_demos_passed,
1488 msg='One or more demos failed to yield an acceptable FPS value')
1489
1490 def testLaunchDemosInSeparateBrowser(self):
1491 """Measures performance by launching each demo in a separate tab."""
1492 # Launch demos in the browser
1493 ref_dict = self._gpu_info_dict['separate_browser_ref_data']
1494 all_demos_passed = True
1495 for url in self._demo_name_url_dict.iterkeys():
1496 self.NavigateToURL(self._demo_name_url_dict[url])
1497 # Measures performance using different demos.
1498 avg_fps = self._MeasureFpsOverTime()
1499 self.RestartBrowser()
1500 # Get the standard value of fps and compare the rseults
1501 if not self._GetStdAvgAndCompare(avg_fps, url, ref_dict):
1502 all_demos_passed = False
1503 self.assertTrue(
1504 all_demos_passed,
1505 msg='One or more demos failed to yield an acceptable FPS value')
1506
1507 def testLaunchDemosBrowseForwardBackward(self):
1508 """Measures performance of various demos in browser going back and forth."""
1509 ref_dict = self._gpu_info_dict['browse_back_forward_ref_data']
1510 url_array = []
1511 desc_array = []
1512 all_demos_passed = True
1513 # Get URL/Description from dictionary and put in individual array
1514 for url in self._demo_name_url_dict.iterkeys():
1515 url_array.append(self._demo_name_url_dict[url])
1516 desc_array.append(url)
1517 for index in range(len(url_array) - 1):
1518 # Launch demo in the Browser
1519 if index == 0:
1520 self.NavigateToURL(url_array[index])
1521 # Measures performance using the first demo.
1522 avg_fps = self._MeasureFpsOverTime()
1523 status1 = self._GetStdAvgAndCompare(avg_fps, desc_array[index],
1524 ref_dict)
1525 # Measures performance using the second demo.
1526 self.NavigateToURL(url_array[index + 1])
1527 avg_fps = self._MeasureFpsOverTime()
1528 status2 = self._GetStdAvgAndCompare(avg_fps, desc_array[index + 1],
1529 ref_dict)
1530 # Go Back to previous demo
1531 self.TabGoBack()
1532 # Measures performance for first demo when moved back
1533 avg_fps = self._MeasureFpsOverTime()
1534 status3 = self._GetStdAvgAndCompare(
1535 avg_fps, desc_array[index] + '_backward',
1536 ref_dict)
1537 # Go Forward to previous demo
1538 self.TabGoForward()
1539 # Measures performance for second demo when moved forward
1540 avg_fps = self._MeasureFpsOverTime()
1541 status4 = self._GetStdAvgAndCompare(
1542 avg_fps, desc_array[index + 1] + '_forward',
1543 ref_dict)
1544 if not all([status1, status2, status3, status4]):
1545 all_demos_passed = False
1546 self.assertTrue(
1547 all_demos_passed,
1548 msg='One or more demos failed to yield an acceptable FPS value')
1549
1550
1551 class HTML5BenchmarkTest(BasePerfTest):
1552 """Tests for HTML5 performance."""
1553
1554 def testHTML5Benchmark(self):
1555 """Measures performance using the benchmark at html5-benchmark.com."""
1556 self.NavigateToURL('http://html5-benchmark.com')
1557
1558 start_benchmark_js = """
1559 benchmark();
1560 window.domAutomationController.send("done");
1561 """
1562 self.ExecuteJavascript(start_benchmark_js)
1563
1564 js_final_score = """
1565 var score = "-1";
1566 var elem = document.getElementById("score");
1567 if (elem)
1568 score = elem.innerHTML;
1569 window.domAutomationController.send(score);
1570 """
1571 # Wait for the benchmark to complete, which is assumed to be when the value
1572 # of the 'score' DOM element changes to something other than '87485'.
1573 self.assertTrue(
1574 self.WaitUntil(
1575 lambda: self.ExecuteJavascript(js_final_score) != '87485',
1576 timeout=900, retry_sleep=1),
1577 msg='Timed out when waiting for final score to be available.')
1578
1579 score = self.ExecuteJavascript(js_final_score)
1580 logging.info('HTML5 Benchmark final score: %f', float(score))
1581 self._OutputPerfGraphValue('HTML5Benchmark', float(score), 'score',
1582 'html5_benchmark')
1583
1584
1585 class FileUploadDownloadTest(BasePerfTest):
1586 """Tests that involve measuring performance of upload and download."""
1587
1588 def setUp(self):
1589 """Performs necessary setup work before running each test in this class."""
1590 self._temp_dir = tempfile.mkdtemp()
1591 self._test_server = PerfTestServer(self._temp_dir)
1592 self._test_server_port = self._test_server.GetPort()
1593 self._test_server.Run()
1594 self.assertTrue(self.WaitUntil(self._IsTestServerRunning),
1595 msg='Failed to start local performance test server.')
1596 BasePerfTest.setUp(self)
1597
1598 def tearDown(self):
1599 """Performs necessary cleanup work after running each test in this class."""
1600 BasePerfTest.tearDown(self)
1601 self._test_server.ShutDown()
1602 pyauto_utils.RemovePath(self._temp_dir)
1603
1604 def _IsTestServerRunning(self):
1605 """Determines whether the local test server is ready to accept connections.
1606
1607 Returns:
1608 True, if a connection can be made to the local performance test server, or
1609 False otherwise.
1610 """
1611 conn = None
1612 try:
1613 conn = urllib2.urlopen('http://localhost:%d' % self._test_server_port)
1614 return True
1615 except IOError, e:
1616 return False
1617 finally:
1618 if conn:
1619 conn.close()
1620
1621 def testDownload100MBFile(self):
1622 """Measures the time to download a 100 MB file from a local server."""
1623 CREATE_100MB_URL = (
1624 'http://localhost:%d/create_file_of_size?filename=data&mb=100' %
1625 self._test_server_port)
1626 DOWNLOAD_100MB_URL = 'http://localhost:%d/data' % self._test_server_port
1627 DELETE_100MB_URL = ('http://localhost:%d/delete_file?filename=data' %
1628 self._test_server_port)
1629
1630 # Tell the local server to create a 100 MB file.
1631 self.NavigateToURL(CREATE_100MB_URL)
1632
1633 # Cleaning up downloaded files is done in the same way as in downloads.py.
1634 # We first identify all existing downloaded files, then remove only those
1635 # new downloaded files that appear during the course of this test.
1636 download_dir = self.GetDownloadDirectory().value()
1637 orig_downloads = []
1638 if os.path.isdir(download_dir):
1639 orig_downloads = os.listdir(download_dir)
1640
1641 def _CleanupAdditionalFilesInDir(directory, orig_files):
1642 """Removes the additional files in the specified directory.
1643
1644 This function will remove all files from |directory| that are not
1645 specified in |orig_files|.
1646
1647 Args:
1648 directory: A string directory path.
1649 orig_files: A list of strings representing the original set of files in
1650 the specified directory.
1651 """
1652 downloads_to_remove = []
1653 if os.path.isdir(directory):
1654 downloads_to_remove = [os.path.join(directory, name)
1655 for name in os.listdir(directory)
1656 if name not in orig_files]
1657 for file_name in downloads_to_remove:
1658 pyauto_utils.RemovePath(file_name)
1659
1660 def _DownloadFile(url):
1661 self.DownloadAndWaitForStart(url)
1662 self.WaitForAllDownloadsToComplete(timeout=2 * 60 * 1000) # 2 minutes.
1663
1664 timings = []
1665 for iteration in range(self._num_iterations + 1):
1666 elapsed_time = self._MeasureElapsedTime(
1667 lambda: _DownloadFile(DOWNLOAD_100MB_URL), num_invocations=1)
1668 # Ignore the first iteration.
1669 if iteration:
1670 timings.append(elapsed_time)
1671 logging.info('Iteration %d of %d: %f milliseconds', iteration,
1672 self._num_iterations, elapsed_time)
1673 self.SetDownloadShelfVisible(False)
1674 _CleanupAdditionalFilesInDir(download_dir, orig_downloads)
1675
1676 self._PrintSummaryResults('Download100MBFile', timings, 'milliseconds',
1677 'download_file')
1678
1679 # Tell the local server to delete the 100 MB file.
1680 self.NavigateToURL(DELETE_100MB_URL)
1681
1682 def testUpload50MBFile(self):
1683 """Measures the time to upload a 50 MB file to a local server."""
1684 # TODO(dennisjeffrey): Replace the use of XMLHttpRequest in this test with
1685 # FileManager automation to select the upload file when crosbug.com/17903
1686 # is complete.
1687 START_UPLOAD_URL = (
1688 'http://localhost:%d/start_upload?mb=50' % self._test_server_port)
1689
1690 EXPECTED_SUBSTRING = 'Upload complete'
1691
1692 def _IsUploadComplete():
1693 js = """
1694 result = "";
1695 var div = document.getElementById("upload_result");
1696 if (div)
1697 result = div.innerHTML;
1698 window.domAutomationController.send(result);
1699 """
1700 return self.ExecuteJavascript(js).find(EXPECTED_SUBSTRING) >= 0
1701
1702 def _RunSingleUpload():
1703 self.NavigateToURL(START_UPLOAD_URL)
1704 self.assertTrue(
1705 self.WaitUntil(_IsUploadComplete, timeout=120, expect_retval=True,
1706 retry_sleep=0.10),
1707 msg='Upload failed to complete before the timeout was hit.')
1708
1709 timings = []
1710 for iteration in range(self._num_iterations + 1):
1711 elapsed_time = self._MeasureElapsedTime(_RunSingleUpload)
1712 # Ignore the first iteration.
1713 if iteration:
1714 timings.append(elapsed_time)
1715 logging.info('Iteration %d of %d: %f milliseconds', iteration,
1716 self._num_iterations, elapsed_time)
1717
1718 self._PrintSummaryResults('Upload50MBFile', timings, 'milliseconds',
1719 'upload_file')
1720
1721
1722 class FlashTest(BasePerfTest):
1723 """Tests to measure flash performance."""
1724
1725 def _RunFlashTestForAverageFPS(self, webpage_url, description, graph_name):
1726 """Runs a single flash test that measures an average FPS value.
1727
1728 Args:
1729 webpage_url: The string URL to a webpage that will run the test.
1730 description: A string description for this test.
1731 graph_name: A string name for the performance graph associated with this
1732 test. Only used on Chrome desktop.
1733 """
1734 # Open up the test webpage; it's assumed the test will start automatically.
1735 self.assertTrue(self.AppendTab(pyauto.GURL(webpage_url)),
1736 msg='Failed to append tab for webpage.')
1737
1738 # Wait until the final result is computed, then retrieve and output it.
1739 js = """
1740 window.domAutomationController.send(
1741 JSON.stringify(final_average_fps));
1742 """
1743 self.assertTrue(
1744 self.WaitUntil(
1745 lambda: self.ExecuteJavascript(js, tab_index=1) != '-1',
1746 timeout=300, expect_retval=True, retry_sleep=1),
1747 msg='Timed out when waiting for test result.')
1748 result = float(self.ExecuteJavascript(js, tab_index=1))
1749 logging.info('Result for %s: %f FPS (average)', description, result)
1750 self._OutputPerfGraphValue(description, result, 'FPS', graph_name)
1751
1752 def testFlashGaming(self):
1753 """Runs a simple flash gaming benchmark test."""
1754 webpage_url = self.GetHttpURLForDataPath('pyauto_private', 'flash',
1755 'FlashGamingTest2.html')
1756 self._RunFlashTestForAverageFPS(webpage_url, 'FlashGaming', 'flash_fps')
1757
1758 def testFlashText(self):
1759 """Runs a simple flash text benchmark test."""
1760 webpage_url = self.GetHttpURLForDataPath('pyauto_private', 'flash',
1761 'FlashTextTest2.html')
1762 self._RunFlashTestForAverageFPS(webpage_url, 'FlashText', 'flash_fps')
1763
1764 def testScimarkGui(self):
1765 """Runs the ScimarkGui benchmark tests."""
1766 webpage_url = self.GetHttpURLForDataPath('pyauto_private', 'flash',
1767 'scimarkGui.html')
1768 self.assertTrue(self.AppendTab(pyauto.GURL(webpage_url)),
1769 msg='Failed to append tab for webpage.')
1770
1771 js = 'window.domAutomationController.send(JSON.stringify(tests_done));'
1772 self.assertTrue(
1773 self.WaitUntil(
1774 lambda: self.ExecuteJavascript(js, tab_index=1), timeout=300,
1775 expect_retval='true', retry_sleep=1),
1776 msg='Timed out when waiting for tests to complete.')
1777
1778 js_result = """
1779 var result = {};
1780 for (var i = 0; i < tests_results.length; ++i) {
1781 var test_name = tests_results[i][0];
1782 var mflops = tests_results[i][1];
1783 var mem = tests_results[i][2];
1784 result[test_name] = [mflops, mem]
1785 }
1786 window.domAutomationController.send(JSON.stringify(result));
1787 """
1788 result = eval(self.ExecuteJavascript(js_result, tab_index=1))
1789 for benchmark in result:
1790 mflops = float(result[benchmark][0])
1791 mem = float(result[benchmark][1])
1792 if benchmark.endswith('_mflops'):
1793 benchmark = benchmark[:benchmark.find('_mflops')]
1794 logging.info('Results for ScimarkGui_%s:', benchmark)
1795 logging.info(' %f MFLOPS', mflops)
1796 logging.info(' %f MB', mem)
1797 self._OutputPerfGraphValue('ScimarkGui-%s-MFLOPS' % benchmark, mflops,
1798 'MFLOPS', 'scimark_gui_mflops')
1799 self._OutputPerfGraphValue('ScimarkGui-%s-Mem' % benchmark, mem, 'MB',
1800 'scimark_gui_mem')
1801
1802
1803 class LiveGamePerfTest(BasePerfTest):
1804 """Tests to measure performance of live gaming webapps."""
1805
1806 def _RunLiveGamePerfTest(self, url, url_title_substring,
1807 description, graph_name):
1808 """Measures performance metrics for the specified live gaming webapp.
1809
1810 This function connects to the specified URL to launch the gaming webapp,
1811 waits for a period of time for the webapp to run, then collects some
1812 performance metrics about the running webapp.
1813
1814 Args:
1815 url: The string URL of the gaming webapp to analyze.
1816 url_title_substring: A string that is expected to be a substring of the
1817 webpage title for the specified gaming webapp. Used to verify that
1818 the webapp loads correctly.
1819 description: A string description for this game, used in the performance
1820 value description. Should not contain any spaces.
1821 graph_name: A string name for the performance graph associated with this
1822 test. Only used on Chrome desktop.
1823 """
1824 self.NavigateToURL(url)
1825 loaded_tab_title = self.GetActiveTabTitle()
1826 self.assertTrue(url_title_substring in loaded_tab_title,
1827 msg='Loaded tab title missing "%s": "%s"' %
1828 (url_title_substring, loaded_tab_title))
1829 cpu_usage_start = self._GetCPUUsage()
1830
1831 # Let the app run for 1 minute.
1832 time.sleep(60)
1833
1834 cpu_usage_end = self._GetCPUUsage()
1835 fraction_non_idle_time = self._GetFractionNonIdleCPUTime(
1836 cpu_usage_start, cpu_usage_end)
1837
1838 logging.info('Fraction of CPU time spent non-idle: %f',
1839 fraction_non_idle_time)
1840 self._OutputPerfGraphValue(description + 'CpuBusy', fraction_non_idle_time,
1841 'Fraction', graph_name + '_cpu_busy')
1842 v8_heap_stats = self.GetV8HeapStats()
1843 v8_heap_size = v8_heap_stats['v8_memory_used'] / (1024.0 * 1024.0)
1844 logging.info('Total v8 heap size: %f MB', v8_heap_size)
1845 self._OutputPerfGraphValue(description + 'V8HeapSize', v8_heap_size, 'MB',
1846 graph_name + '_v8_heap_size')
1847
1848 def testAngryBirds(self):
1849 """Measures performance for Angry Birds."""
1850 self._RunLiveGamePerfTest('http://chrome.angrybirds.com', 'Angry Birds',
1851 'AngryBirds', 'angry_birds')
1852
1853
1854 class BasePageCyclerTest(BasePerfTest):
1855 """Page class for page cycler tests.
1856
1857 Derived classes must implement StartUrl().
1858
1859 Environment Variables:
1860 PC_NO_AUTO: if set, avoids automatically loading pages.
1861 """
1862 MAX_ITERATION_SECONDS = 60
1863 TRIM_PERCENT = 20
1864 DEFAULT_USE_AUTO = True
1865
1866 # Page Cycler lives in src/data/page_cycler rather than src/chrome/test/data
1867 DATA_PATH = os.path.abspath(
1868 os.path.join(BasePerfTest.DataDir(), os.pardir, os.pardir,
1869 os.pardir, 'data', 'page_cycler'))
1870
1871 def setUp(self):
1872 """Performs necessary setup work before running each test."""
1873 super(BasePageCyclerTest, self).setUp()
1874 self.use_auto = 'PC_NO_AUTO' not in os.environ
1875
1876 @classmethod
1877 def DataPath(cls, subdir):
1878 return os.path.join(cls.DATA_PATH, subdir)
1879
1880 def ExtraChromeFlags(self):
1881 """Ensures Chrome is launched with custom flags.
1882
1883 Returns:
1884 A list of extra flags to pass to Chrome when it is launched.
1885 """
1886 # Extra flags required to run these tests.
1887 # The first two are needed for the test.
1888 # The plugins argument is to prevent bad scores due to pop-ups from
1889 # running an old version of something (like Flash).
1890 return (super(BasePageCyclerTest, self).ExtraChromeFlags() +
1891 ['--js-flags="--expose_gc"',
1892 '--enable-file-cookies',
1893 '--allow-outdated-plugins'])
1894
1895 def WaitUntilStarted(self, start_url):
1896 """Check that the test navigates away from the start_url."""
1897 js_is_started = """
1898 var is_started = document.location.href !== "%s";
1899 window.domAutomationController.send(JSON.stringify(is_started));
1900 """ % start_url
1901 self.assertTrue(
1902 self.WaitUntil(lambda: self.ExecuteJavascript(js_is_started) == 'true',
1903 timeout=10),
1904 msg='Timed out when waiting to leave start page.')
1905
1906 def WaitUntilDone(self, url, iterations):
1907 """Check cookies for "__pc_done=1" to know the test is over."""
1908 def IsDone():
1909 cookies = self.GetCookie(pyauto.GURL(url)) # window 0, tab 0
1910 return '__pc_done=1' in cookies
1911 self.assertTrue(
1912 self.WaitUntil(
1913 IsDone,
1914 timeout=(self.MAX_ITERATION_SECONDS * iterations),
1915 retry_sleep=1),
1916 msg='Timed out waiting for page cycler test to complete.')
1917
1918 def CollectPagesAndTimes(self, url):
1919 """Collect the results from the cookies."""
1920 pages, times = None, None
1921 cookies = self.GetCookie(pyauto.GURL(url)) # window 0, tab 0
1922 for cookie in cookies.split(';'):
1923 if '__pc_pages' in cookie:
1924 pages_str = cookie.split('=', 1)[1]
1925 pages = pages_str.split(',')
1926 elif '__pc_timings' in cookie:
1927 times_str = cookie.split('=', 1)[1]
1928 times = [float(t) for t in times_str.split(',')]
1929 self.assertTrue(pages and times,
1930 msg='Unable to find test results in cookies: %s' % cookies)
1931 return pages, times
1932
1933 def IteratePageTimes(self, pages, times, iterations):
1934 """Regroup the times by the page.
1935
1936 Args:
1937 pages: the list of pages
1938 times: e.g. [page1_iter1, page2_iter1, ..., page1_iter2, page2_iter2, ...]
1939 iterations: the number of times for each page
1940 Yields:
1941 (pageN, [pageN_iter1, pageN_iter2, ...])
1942 """
1943 num_pages = len(pages)
1944 num_times = len(times)
1945 expected_num_times = num_pages * iterations
1946 self.assertEqual(
1947 expected_num_times, num_times,
1948 msg=('num_times != num_pages * iterations: %s != %s * %s, times=%s' %
1949 (num_times, num_pages, iterations, times)))
1950 for i, page in enumerate(pages):
1951 yield page, list(itertools.islice(times, i, None, num_pages))
1952
1953 def CheckPageTimes(self, pages, times, iterations):
1954 """Assert that all the times are greater than zero."""
1955 failed_pages = []
1956 for page, times in self.IteratePageTimes(pages, times, iterations):
1957 failed_times = [t for t in times if t <= 0.0]
1958 if failed_times:
1959 failed_pages.append((page, failed_times))
1960 if failed_pages:
1961 self.fail('Pages with unexpected times: %s' % failed_pages)
1962
1963 def TrimTimes(self, times, percent):
1964 """Return a new list with |percent| number of times trimmed for each page.
1965
1966 Removes the largest and smallest values.
1967 """
1968 iterations = len(times)
1969 times = sorted(times)
1970 num_to_trim = int(iterations * float(percent) / 100.0)
1971 logging.debug('Before trimming %d: %s' % (num_to_trim, times))
1972 a = num_to_trim / 2
1973 b = iterations - (num_to_trim / 2 + num_to_trim % 2)
1974 trimmed_times = times[a:b]
1975 logging.debug('After trimming: %s', trimmed_times)
1976 return trimmed_times
1977
1978 def ComputeFinalResult(self, pages, times, iterations):
1979 """The final score that is calculated is a geometric mean of the
1980 arithmetic means of each page's load time, and we drop the
1981 upper/lower 20% of the times for each page so they don't skew the
1982 mean. The geometric mean is used for the final score because the
1983 time range for any given site may be very different, and we don't
1984 want slower sites to weight more heavily than others.
1985 """
1986 self.CheckPageTimes(pages, times, iterations)
1987 page_means = [
1988 Mean(self.TrimTimes(times, percent=self.TRIM_PERCENT))
1989 for _, times in self.IteratePageTimes(pages, times, iterations)]
1990 return GeometricMean(page_means)
1991
1992 def StartUrl(self, test_name, iterations):
1993 """Return the URL to used to start the test.
1994
1995 Derived classes must implement this.
1996 """
1997 raise NotImplemented
1998
1999 def RunPageCyclerTest(self, name, description):
2000 """Runs the specified PageCycler test.
2001
2002 Args:
2003 name: the page cycler test name (corresponds to a directory or test file)
2004 description: a string description for the test
2005 """
2006 iterations = self._num_iterations
2007 start_url = self.StartUrl(name, iterations)
2008 self.NavigateToURL(start_url)
2009 if self.use_auto:
2010 self.WaitUntilStarted(start_url)
2011 self.WaitUntilDone(start_url, iterations)
2012 pages, times = self.CollectPagesAndTimes(start_url)
2013 final_result = self.ComputeFinalResult(pages, times, iterations)
2014 logging.info('%s page cycler final result: %f' %
2015 (description, final_result))
2016 self._OutputPerfGraphValue(description + '_PageCycler', final_result,
2017 'milliseconds', graph_name='PageCycler')
2018
2019
2020 class PageCyclerTest(BasePageCyclerTest):
2021 """Tests to run various page cyclers.
2022
2023 Environment Variables:
2024 PC_NO_AUTO: if set, avoids automatically loading pages.
2025 """
2026
2027 def _PreReadDataDir(self, subdir):
2028 """This recursively reads all of the files in a given url directory.
2029
2030 The intent is to get them into memory before they are used by the benchmark.
2031
2032 Args:
2033 subdir: a subdirectory of the page cycler data directory.
2034 """
2035 def _PreReadDir(dirname, names):
2036 for rfile in names:
2037 with open(os.path.join(dirname, rfile)) as fp:
2038 fp.read()
2039 for root, dirs, files in os.walk(self.DataPath(subdir)):
2040 _PreReadDir(root, files)
2041
2042 def StartUrl(self, test_name, iterations):
2043 # Must invoke GetFileURLForPath before appending parameters to the URL,
2044 # otherwise those parameters will get quoted.
2045 start_url = self.GetFileURLForPath(self.DataPath(test_name), 'start.html')
2046 start_url += '?iterations=%d' % iterations
2047 if self.use_auto:
2048 start_url += '&auto=1'
2049 return start_url
2050
2051 def RunPageCyclerTest(self, dirname, description):
2052 """Runs the specified PageCycler test.
2053
2054 Args:
2055 dirname: directory containing the page cycler test
2056 description: a string description for the test
2057 """
2058 self._PreReadDataDir('common')
2059 self._PreReadDataDir(dirname)
2060 super(PageCyclerTest, self).RunPageCyclerTest(dirname, description)
2061
2062 def testMoreJSFile(self):
2063 self.RunPageCyclerTest('morejs', 'MoreJSFile')
2064
2065 def testAlexaFile(self):
2066 self.RunPageCyclerTest('alexa_us', 'Alexa_usFile')
2067
2068 def testBloatFile(self):
2069 self.RunPageCyclerTest('bloat', 'BloatFile')
2070
2071 def testDHTMLFile(self):
2072 self.RunPageCyclerTest('dhtml', 'DhtmlFile')
2073
2074 def testIntl1File(self):
2075 self.RunPageCyclerTest('intl1', 'Intl1File')
2076
2077 def testIntl2File(self):
2078 self.RunPageCyclerTest('intl2', 'Intl2File')
2079
2080 def testMozFile(self):
2081 self.RunPageCyclerTest('moz', 'MozFile')
2082
2083 def testMoz2File(self):
2084 self.RunPageCyclerTest('moz2', 'Moz2File')
2085
2086
2087 class MemoryTest(BasePerfTest):
2088 """Tests to measure memory consumption under different usage scenarios."""
2089
2090 def ExtraChromeFlags(self):
2091 """Launches Chrome with custom flags.
2092
2093 Returns:
2094 A list of extra flags to pass to Chrome when it is launched.
2095 """
2096 # Ensure Chrome assigns one renderer process to each tab.
2097 return super(MemoryTest, self).ExtraChromeFlags() + ['--process-per-tab']
2098
2099 def _RecordMemoryStats(self, description, when, duration):
2100 """Outputs memory statistics to be graphed.
2101
2102 Args:
2103 description: A string description for the test. Should not contain
2104 spaces. For example, 'MemCtrl'.
2105 when: A string description of when the memory stats are being recorded
2106 during test execution (since memory stats may be recorded multiple
2107 times during a test execution at certain "interesting" times). Should
2108 not contain spaces.
2109 duration: The number of seconds to sample data before outputting the
2110 memory statistics.
2111 """
2112 mem = self.GetMemoryStatsChromeOS(duration)
2113 measurement_types = [
2114 ('gem_obj', 'GemObj'),
2115 ('gtt', 'GTT'),
2116 ('mem_free', 'MemFree'),
2117 ('mem_available', 'MemAvail'),
2118 ('mem_shared', 'MemShare'),
2119 ('mem_cached', 'MemCache'),
2120 ('mem_anon', 'MemAnon'),
2121 ('mem_file', 'MemFile'),
2122 ('mem_slab', 'MemSlab'),
2123 ('browser_priv', 'BrowPriv'),
2124 ('browser_shared', 'BrowShar'),
2125 ('gpu_priv', 'GpuPriv'),
2126 ('gpu_shared', 'GpuShar'),
2127 ('renderer_priv', 'RendPriv'),
2128 ('renderer_shared', 'RendShar'),
2129 ]
2130 for type_key, type_string in measurement_types:
2131 if type_key not in mem:
2132 continue
2133 self._OutputPerfGraphValue(
2134 '%s-Min%s-%s' % (description, type_string, when),
2135 mem[type_key]['min'], 'KB', '%s-%s' % (description, type_string))
2136 self._OutputPerfGraphValue(
2137 '%s-Max%s-%s' % (description, type_string, when),
2138 mem[type_key]['max'], 'KB', '%s-%s' % (description, type_string))
2139 self._OutputPerfGraphValue(
2140 '%s-End%s-%s' % (description, type_string, when),
2141 mem[type_key]['end'], 'KB', '%s-%s' % (description, type_string))
2142
2143 def _RunTest(self, tabs, description, duration):
2144 """Runs a general memory test.
2145
2146 Args:
2147 tabs: A list of strings representing the URLs of the websites to open
2148 during this test.
2149 description: A string description for the test. Should not contain
2150 spaces. For example, 'MemCtrl'.
2151 duration: The number of seconds to sample data before outputting memory
2152 statistics.
2153 """
2154 self._RecordMemoryStats(description, '0Tabs0', duration)
2155
2156 for iteration_num in xrange(2):
2157 for site in tabs:
2158 self.AppendTab(pyauto.GURL(site))
2159
2160 self._RecordMemoryStats(description,
2161 '%dTabs%d' % (len(tabs), iteration_num + 1),
2162 duration)
2163
2164 for _ in xrange(len(tabs)):
2165 self.CloseTab(tab_index=1)
2166
2167 self._RecordMemoryStats(description, '0Tabs%d' % (iteration_num + 1),
2168 duration)
2169
2170 def testOpenCloseTabsControl(self):
2171 """Measures memory usage when opening/closing tabs to about:blank."""
2172 tabs = ['about:blank'] * 10
2173 self._RunTest(tabs, 'MemCtrl', 15)
2174
2175 def testOpenCloseTabsLiveSites(self):
2176 """Measures memory usage when opening/closing tabs to live sites."""
2177 tabs = [
2178 'http://www.google.com/gmail',
2179 'http://www.google.com/calendar',
2180 'http://www.google.com/plus',
2181 'http://www.google.com/youtube',
2182 'http://www.nytimes.com',
2183 'http://www.cnn.com',
2184 'http://www.facebook.com/zuck',
2185 'http://www.techcrunch.com',
2186 'http://www.theverge.com',
2187 'http://www.yahoo.com',
2188 ]
2189 # Log in to a test Google account to make connections to the above Google
2190 # websites more interesting.
2191 self._LoginToGoogleAccount()
2192 self._RunTest(tabs, 'MemLive', 20)
2193
2194
2195 class PerfTestServerRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
2196 """Request handler for the local performance test server."""
2197
2198 def _IgnoreHandler(self, unused_args):
2199 """A GET request handler that simply replies with status code 200.
2200
2201 Args:
2202 unused_args: A dictionary of arguments for the current GET request.
2203 The arguments are ignored.
2204 """
2205 self.send_response(200)
2206 self.end_headers()
2207
2208 def _CreateFileOfSizeHandler(self, args):
2209 """A GET handler that creates a local file with the specified size.
2210
2211 Args:
2212 args: A dictionary of arguments for the current GET request. Must
2213 contain 'filename' and 'mb' keys that refer to the name of the file
2214 to create and its desired size, respectively.
2215 """
2216 megabytes = None
2217 filename = None
2218 try:
2219 megabytes = int(args['mb'][0])
2220 filename = args['filename'][0]
2221 except (ValueError, KeyError, IndexError), e:
2222 logging.exception('Server error creating file: %s', e)
2223 assert megabytes and filename
2224 with open(os.path.join(self.server.docroot, filename), 'wb') as f:
2225 f.write('X' * 1024 * 1024 * megabytes)
2226 self.send_response(200)
2227 self.end_headers()
2228
2229 def _DeleteFileHandler(self, args):
2230 """A GET handler that deletes the specified local file.
2231
2232 Args:
2233 args: A dictionary of arguments for the current GET request. Must
2234 contain a 'filename' key that refers to the name of the file to
2235 delete, relative to the server's document root.
2236 """
2237 filename = None
2238 try:
2239 filename = args['filename'][0]
2240 except (KeyError, IndexError), e:
2241 logging.exception('Server error deleting file: %s', e)
2242 assert filename
2243 try:
2244 os.remove(os.path.join(self.server.docroot, filename))
2245 except OSError, e:
2246 logging.warning('OS error removing file: %s', e)
2247 self.send_response(200)
2248 self.end_headers()
2249
2250 def _StartUploadHandler(self, args):
2251 """A GET handler to serve a page that uploads the given amount of data.
2252
2253 When the page loads, the specified amount of data is automatically
2254 uploaded to the same local server that is handling the current request.
2255
2256 Args:
2257 args: A dictionary of arguments for the current GET request. Must
2258 contain an 'mb' key that refers to the size of the data to upload.
2259 """
2260 megabytes = None
2261 try:
2262 megabytes = int(args['mb'][0])
2263 except (ValueError, KeyError, IndexError), e:
2264 logging.exception('Server error starting upload: %s', e)
2265 assert megabytes
2266 script = """
2267 <html>
2268 <head>
2269 <script type='text/javascript'>
2270 function startUpload() {
2271 var megabytes = %s;
2272 var data = Array((1024 * 1024 * megabytes) + 1).join('X');
2273 var boundary = '***BOUNDARY***';
2274 var xhr = new XMLHttpRequest();
2275
2276 xhr.open('POST', 'process_upload', true);
2277 xhr.setRequestHeader(
2278 'Content-Type',
2279 'multipart/form-data; boundary="' + boundary + '"');
2280 xhr.setRequestHeader('Content-Length', data.length);
2281 xhr.onreadystatechange = function() {
2282 if (xhr.readyState == 4 && xhr.status == 200) {
2283 document.getElementById('upload_result').innerHTML =
2284 xhr.responseText;
2285 }
2286 };
2287 var body = '--' + boundary + '\\r\\n';
2288 body += 'Content-Disposition: form-data;' +
2289 'file_contents=' + data;
2290 xhr.send(body);
2291 }
2292 </script>
2293 </head>
2294
2295 <body onload="startUpload();">
2296 <div id='upload_result'>Uploading...</div>
2297 </body>
2298 </html>
2299 """ % megabytes
2300 self.send_response(200)
2301 self.end_headers()
2302 self.wfile.write(script)
2303
2304 def _ProcessUploadHandler(self, form):
2305 """A POST handler that discards uploaded data and sends a response.
2306
2307 Args:
2308 form: A dictionary containing posted form data, as returned by
2309 urlparse.parse_qs().
2310 """
2311 upload_processed = False
2312 file_size = 0
2313 if 'file_contents' in form:
2314 file_size = len(form['file_contents'][0])
2315 upload_processed = True
2316 self.send_response(200)
2317 self.end_headers()
2318 if upload_processed:
2319 self.wfile.write('Upload complete (%d bytes)' % file_size)
2320 else:
2321 self.wfile.write('No file contents uploaded')
2322
2323 GET_REQUEST_HANDLERS = {
2324 'create_file_of_size': _CreateFileOfSizeHandler,
2325 'delete_file': _DeleteFileHandler,
2326 'start_upload': _StartUploadHandler,
2327 'favicon.ico': _IgnoreHandler,
2328 }
2329
2330 POST_REQUEST_HANDLERS = {
2331 'process_upload': _ProcessUploadHandler,
2332 }
2333
2334 def translate_path(self, path):
2335 """Ensures files are served from the given document root.
2336
2337 Overridden from SimpleHTTPServer.SimpleHTTPRequestHandler.
2338 """
2339 path = urlparse.urlparse(path)[2]
2340 path = posixpath.normpath(urllib.unquote(path))
2341 words = path.split('/')
2342 words = filter(None, words) # Remove empty strings from |words|.
2343 path = self.server.docroot
2344 for word in words:
2345 _, word = os.path.splitdrive(word)
2346 _, word = os.path.split(word)
2347 if word in (os.curdir, os.pardir):
2348 continue
2349 path = os.path.join(path, word)
2350 return path
2351
2352 def do_GET(self):
2353 """Processes a GET request to the local server.
2354
2355 Overridden from SimpleHTTPServer.SimpleHTTPRequestHandler.
2356 """
2357 split_url = urlparse.urlsplit(self.path)
2358 base_path = split_url[2]
2359 if base_path.startswith('/'):
2360 base_path = base_path[1:]
2361 args = urlparse.parse_qs(split_url[3])
2362 if base_path in self.GET_REQUEST_HANDLERS:
2363 self.GET_REQUEST_HANDLERS[base_path](self, args)
2364 else:
2365 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
2366
2367 def do_POST(self):
2368 """Processes a POST request to the local server.
2369
2370 Overridden from SimpleHTTPServer.SimpleHTTPRequestHandler.
2371 """
2372 form = urlparse.parse_qs(
2373 self.rfile.read(int(self.headers.getheader('Content-Length'))))
2374 path = urlparse.urlparse(self.path)[2]
2375 if path.startswith('/'):
2376 path = path[1:]
2377 if path in self.POST_REQUEST_HANDLERS:
2378 self.POST_REQUEST_HANDLERS[path](self, form)
2379 else:
2380 self.send_response(200)
2381 self.send_header('Content-Type', 'text/plain')
2382 self.end_headers()
2383 self.wfile.write('No handler for POST request "%s".' % path)
2384
2385
2386 class ThreadedHTTPServer(SocketServer.ThreadingMixIn,
2387 BaseHTTPServer.HTTPServer):
2388 def __init__(self, server_address, handler_class):
2389 BaseHTTPServer.HTTPServer.__init__(self, server_address, handler_class)
2390
2391
2392 class PerfTestServer(object):
2393 """Local server for use by performance tests."""
2394
2395 def __init__(self, docroot):
2396 """Initializes the performance test server.
2397
2398 Args:
2399 docroot: The directory from which to serve files.
2400 """
2401 # The use of 0 means to start the server on an arbitrary available port.
2402 self._server = ThreadedHTTPServer(('', 0),
2403 PerfTestServerRequestHandler)
2404 self._server.docroot = docroot
2405 self._server_thread = threading.Thread(target=self._server.serve_forever)
2406
2407 def Run(self):
2408 """Starts the server thread."""
2409 self._server_thread.start()
2410
2411 def ShutDown(self):
2412 """Shuts down the server."""
2413 self._server.shutdown()
2414 self._server_thread.join()
2415
2416 def GetPort(self):
2417 """Identifies the port number to which the server is currently bound.
2418
2419 Returns:
2420 The numeric port number to which the server is currently bound.
2421 """
2422 return self._server.server_address[1]
2423
2424
2425 if __name__ == '__main__':
2426 pyauto_functional.Main()
OLDNEW
« no previous file with comments | « chrome/test/functional/perf.cfg ('k') | chrome/test/functional/perf/endure_graphs/config.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698