2 # Copyright (C) 2012 Google Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 # Import for auto-install
43 if sys.platform not in ('cygwin', 'win32'):
44 # FIXME: webpagereplay doesn't work on win32. See https://bugs.webkit.org/show_bug.cgi?id=88279.
45 import webkitpy.thirdparty.autoinstalled.webpagereplay.replay
47 from webkitpy.layout_tests.controllers.test_result_writer import TestResultWriter
48 from webkitpy.layout_tests.port.driver import DriverInput
49 from webkitpy.layout_tests.port.driver import DriverOutput
52 _log = logging.getLogger(__name__)
55 class PerfTest(object):
56 def __init__(self, port, test_name, path_or_url):
58 self._test_name = test_name
59 self._path_or_url = path_or_url
62 return self._test_name
64 def path_or_url(self):
65 return self._path_or_url
67 def prepare(self, time_out_ms):
70 def run(self, driver, time_out_ms):
71 output = self.run_single(driver, self.path_or_url(), time_out_ms)
72 if self.run_failed(output):
74 return self.parse_output(output)
76 def run_single(self, driver, path_or_url, time_out_ms, should_run_pixel_test=False):
77 return driver.run_test(DriverInput(path_or_url, time_out_ms, image_hash=None, should_run_pixel_test=should_run_pixel_test), stop_when_done=False)
79 def run_failed(self, output):
80 if output.text == None or output.error:
83 _log.error('timeout: %s' % self.test_name())
85 _log.error('crash: %s' % self.test_name())
90 _log.error('error: %s\n%s' % (self.test_name(), output.error))
94 _lines_to_ignore_in_parser_result = [
95 re.compile(r'^Running \d+ times$'),
96 re.compile(r'^Ignoring warm-up '),
97 re.compile(r'^Info:'),
98 re.compile(r'^\d+(.\d+)?(\s*(runs\/s|ms|fps))?$'),
99 # Following are for handle existing test like Dromaeo
100 re.compile(re.escape("""main frame - has 1 onunload handler(s)""")),
101 re.compile(re.escape("""frame "<!--framePath //<!--frame0-->-->" - has 1 onunload handler(s)""")),
102 re.compile(re.escape("""frame "<!--framePath //<!--frame0-->/<!--frame0-->-->" - has 1 onunload handler(s)""")),
103 # Following is for html5.html
104 re.compile(re.escape("""Blocked access to external URL http://www.whatwg.org/specs/web-apps/current-work/"""))]
106 def _should_ignore_line_in_parser_test_result(self, line):
109 for regex in self._lines_to_ignore_in_parser_result:
110 if regex.search(line):
114 _description_regex = re.compile(r'^Description: (?P<description>.*)$', re.IGNORECASE)
115 _result_classes = ['Time', 'JS Heap', 'Malloc']
116 _result_class_regex = re.compile(r'^(?P<resultclass>' + r'|'.join(_result_classes) + '):')
117 _statistics_keys = ['avg', 'median', 'stdev', 'min', 'max', 'unit', 'values']
118 _score_regex = re.compile(r'^(?P<key>' + r'|'.join(_statistics_keys) + r')\s+(?P<value>([0-9\.]+(,\s+)?)+)\s*(?P<unit>.*)')
120 def parse_output(self, output):
123 ordered_results_keys = []
124 test_name = re.sub(r'\.\w+$', '', self._test_name)
125 description_string = ""
127 for line in re.split('\n', output.text):
128 description = self._description_regex.match(line)
130 description_string = description.group('description')
133 result_class_match = self._result_class_regex.match(line)
134 if result_class_match:
135 result_class = result_class_match.group('resultclass')
138 score = self._score_regex.match(line)
140 key = score.group('key')
142 value = [float(number) for number in score.group('value').split(', ')]
144 value = float(score.group('value'))
145 unit = score.group('unit')
147 if result_class != 'Time':
148 name += ':' + result_class.replace(' ', '')
149 if name not in ordered_results_keys:
150 ordered_results_keys.append(name)
151 results.setdefault(name, {})
152 results[name]['unit'] = unit
153 results[name][key] = value
156 if not self._should_ignore_line_in_parser_test_result(line):
163 if set(self._statistics_keys) != set(results[test_name].keys() + ['values']):
164 # values is not provided by Dromaeo tests.
165 _log.error("The test didn't report all statistics.")
168 for result_name in ordered_results_keys:
169 if result_name == test_name:
170 self.output_statistics(result_name, results[result_name], description_string)
172 self.output_statistics(result_name, results[result_name])
175 def output_statistics(self, test_name, results, description_string=None):
176 unit = results['unit']
177 if description_string:
178 _log.info('DESCRIPTION: %s' % description_string)
179 _log.info('RESULT %s= %s %s' % (test_name.replace(':', ': ').replace('/', ': '), results['avg'], unit))
180 _log.info(', '.join(['%s= %s %s' % (key, results[key], unit) for key in self._statistics_keys[1:5]]))
183 class ChromiumStylePerfTest(PerfTest):
184 _chromium_style_result_regex = re.compile(r'^RESULT\s+(?P<name>[^=]+)\s*=\s+(?P<value>\d+(\.\d+)?)\s*(?P<unit>\w+)$')
186 def __init__(self, port, test_name, path_or_url):
187 super(ChromiumStylePerfTest, self).__init__(port, test_name, path_or_url)
189 def parse_output(self, output):
192 for line in re.split('\n', output.text):
193 resultLine = ChromiumStylePerfTest._chromium_style_result_regex.match(line)
195 # FIXME: Store the unit
196 results[self.test_name() + ':' + resultLine.group('name').replace(' ', '')] = float(resultLine.group('value'))
198 elif not len(line) == 0:
201 return results if results and not test_failed else None
204 class PageLoadingPerfTest(PerfTest):
205 def __init__(self, port, test_name, path_or_url):
206 super(PageLoadingPerfTest, self).__init__(port, test_name, path_or_url)
208 def run(self, driver, time_out_ms):
211 for i in range(0, 20):
212 output = self.run_single(driver, self.path_or_url(), time_out_ms)
213 if not output or self.run_failed(output):
217 test_times.append(output.test_time * 1000)
219 sorted_test_times = sorted(test_times)
221 # Compute the mean and variance using Knuth's online algorithm (has good numerical stability).
224 for i, time in enumerate(sorted_test_times):
227 mean += delta / sweep
228 squareSum += delta * (time - mean)
230 middle = int(len(test_times) / 2)
231 results = {'values': test_times,
233 'min': sorted_test_times[0],
234 'max': sorted_test_times[-1],
235 'median': sorted_test_times[middle] if len(sorted_test_times) % 2 else (sorted_test_times[middle - 1] + sorted_test_times[middle]) / 2,
236 'stdev': math.sqrt(squareSum / (len(sorted_test_times) - 1)),
238 self.output_statistics(self.test_name(), results, '')
239 return {self.test_name(): results}
242 class ReplayServer(object):
243 def __init__(self, archive, record):
246 # FIXME: Should error if local proxy isn't set to forward requests to localhost:8080 and localhost:8443
248 replay_path = webkitpy.thirdparty.autoinstalled.webpagereplay.replay.__file__
249 args = ['python', replay_path, '--no-dns_forwarding', '--port', '8080', '--ssl_port', '8443', '--use_closest_match', '--log_level', 'warning']
251 args.append('--record')
254 self._process = subprocess.Popen(args)
256 def wait_until_ready(self):
257 for i in range(0, 3):
259 connection = socket.create_connection(('localhost', '8080'), timeout=1)
269 self._process.send_signal(signal.SIGINT)
277 class ReplayPerfTest(PageLoadingPerfTest):
278 def __init__(self, port, test_name, path_or_url):
279 super(ReplayPerfTest, self).__init__(port, test_name, path_or_url)
281 def _start_replay_server(self, archive, record):
283 return ReplayServer(archive, record)
284 except OSError as error:
285 if error.errno == errno.ENOENT:
286 _log.error("Replay tests require web-page-replay.")
290 def prepare(self, time_out_ms):
291 filesystem = self._port.host.filesystem
292 path_without_ext = filesystem.splitext(self.path_or_url())[0]
294 self._archive_path = filesystem.join(path_without_ext + '.wpr')
295 self._expected_image_path = filesystem.join(path_without_ext + '-expected.png')
296 self._url = filesystem.read_text_file(self.path_or_url()).split('\n')[0]
298 if filesystem.isfile(self._archive_path) and filesystem.isfile(self._expected_image_path):
299 _log.info("Replay ready for %s" % self._archive_path)
302 _log.info("Preparing replay for %s" % self.test_name())
304 driver = self._port.create_driver(worker_number=1, no_timeout=True)
306 output = self.run_single(driver, self._archive_path, time_out_ms, record=True)
310 if not output or not filesystem.isfile(self._archive_path):
311 _log.error("Failed to prepare a replay for %s" % self.test_name())
314 _log.info("Prepared replay for %s" % self.test_name())
318 def run_single(self, driver, url, time_out_ms, record=False):
319 server = self._start_replay_server(self._archive_path, record)
321 _log.error("Web page replay didn't start.")
325 _log.debug("Waiting for Web page replay to start.")
326 if not server.wait_until_ready():
327 _log.error("Web page replay didn't start.")
330 _log.debug("Web page replay started. Loading the page.")
331 output = super(ReplayPerfTest, self).run_single(driver, self._url, time_out_ms, should_run_pixel_test=True)
332 if self.run_failed(output):
336 _log.error("Loading the page did not generate image results")
337 _log.error(output.text)
340 filesystem = self._port.host.filesystem
341 dirname = filesystem.dirname(self._archive_path)
342 filename = filesystem.split(self._archive_path)[1]
343 writer = TestResultWriter(filesystem, self._port, dirname, filename)
345 writer.write_image_files(actual_image=None, expected_image=output.image)
347 writer.write_image_files(actual_image=output.image, expected_image=None)
354 class PerfTestFactory(object):
357 (re.compile(r'^inspector/'), ChromiumStylePerfTest),
358 (re.compile(r'^PageLoad/'), PageLoadingPerfTest),
359 (re.compile(r'(.+)\.replay$'), ReplayPerfTest),
363 def create_perf_test(cls, port, test_name, path):
364 for (pattern, test_class) in cls._pattern_map:
365 if pattern.match(test_name):
366 return test_class(port, test_name, path)
367 return PerfTest(port, test_name, path)