| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """The page cycler measurement. |
| |
| This measurement registers a window load handler in which is forces a layout and |
| then records the value of performance.now(). This call to now() measures the |
| time from navigationStart (immediately after the previous page's beforeunload |
| event) until after the layout in the page's load event. In addition, two garbage |
| collections are performed in between the page loads (in the beforeunload event). |
| This extra garbage collection time is not included in the measurement times. |
| |
| Finally, various memory and IO statistics are gathered at the very end of |
| cycling all pages. |
| """ |
| |
| import collections |
| import os |
| |
| from metrics import cpu |
| from metrics import io |
| from metrics import memory |
| from metrics import speedindex |
| from metrics import v8_object_stats |
| from telemetry.core import util |
| from telemetry.page import page_measurement |
| |
| class PageCycler(page_measurement.PageMeasurement): |
| def __init__(self, *args, **kwargs): |
| super(PageCycler, self).__init__(*args, **kwargs) |
| |
| with open(os.path.join(os.path.dirname(__file__), |
| 'page_cycler.js'), 'r') as f: |
| self._page_cycler_js = f.read() |
| |
| self._record_v8_object_stats = False |
| self._report_speed_index = False |
| self._speedindex_metric = speedindex.SpeedIndexMetric() |
| self._memory_metric = None |
| self._cpu_metric = None |
| self._v8_object_stats_metric = None |
| self._cold_run_start_index = None |
| self._has_loaded_page = collections.defaultdict(int) |
| |
| def AddCommandLineOptions(self, parser): |
| # The page cyclers should default to 10 iterations. In order to change the |
| # default of an option, we must remove and re-add it. |
| # TODO: Remove this after transition to run_benchmark. |
| pageset_repeat_option = parser.get_option('--pageset-repeat') |
| pageset_repeat_option.default = 10 |
| parser.remove_option('--pageset-repeat') |
| parser.add_option(pageset_repeat_option) |
| |
| parser.add_option('--v8-object-stats', |
| action='store_true', |
| help='Enable detailed V8 object statistics.') |
| |
| parser.add_option('--report-speed-index', |
| action='store_true', |
| help='Enable the speed index metric.') |
| |
| parser.add_option('--cold-load-percent', type='int', |
| help='%d of page visits for which a cold load is forced') |
| |
| def DidStartBrowser(self, browser): |
| """Initialize metrics once right after the browser has been launched.""" |
| self._memory_metric = memory.MemoryMetric(browser) |
| self._cpu_metric = cpu.CpuMetric(browser) |
| if self._record_v8_object_stats: |
| self._v8_object_stats_metric = v8_object_stats.V8ObjectStatsMetric() |
| |
| def DidStartHTTPServer(self, tab): |
| # Avoid paying for a cross-renderer navigation on the first page on legacy |
| # page cyclers which use the filesystem. |
| tab.Navigate(tab.browser.http_server.UrlOf('nonexistent.html')) |
| |
| def WillNavigateToPage(self, page, tab): |
| page.script_to_evaluate_on_commit = self._page_cycler_js |
| if self.ShouldRunCold(page.url): |
| tab.ClearCache() |
| if self._report_speed_index: |
| self._speedindex_metric.Start(page, tab) |
| |
| def DidNavigateToPage(self, page, tab): |
| self._memory_metric.Start(page, tab) |
| # TODO(qyearsley): Uncomment the following line and move it to |
| # WillNavigateToPage once the cpu metric has been changed. |
| # This is being temporarily commented out to let the page cycler |
| # results return to how they were before the cpu metric was added. |
| # self._cpu_metric.Start(page, tab) See crbug.com/301714. |
| if self._record_v8_object_stats: |
| self._v8_object_stats_metric.Start(page, tab) |
| |
| def CustomizeBrowserOptions(self, options): |
| memory.MemoryMetric.CustomizeBrowserOptions(options) |
| io.IOMetric.CustomizeBrowserOptions(options) |
| options.AppendExtraBrowserArgs('--js-flags=--expose_gc') |
| |
| if options.v8_object_stats: |
| self._record_v8_object_stats = True |
| v8_object_stats.V8ObjectStatsMetric.CustomizeBrowserOptions(options) |
| |
| if options.report_speed_index: |
| self._report_speed_index = True |
| |
| cold_runs_percent_set = (options.cold_load_percent != None) |
| # Handle requests for cold cache runs |
| if (cold_runs_percent_set and |
| (options.repeat_options.page_repeat_secs or |
| options.repeat_options.pageset_repeat_secs)): |
| raise Exception('--cold-load-percent is incompatible with timed repeat') |
| |
| if (cold_runs_percent_set and |
| (options.cold_load_percent < 0 or options.cold_load_percent > 100)): |
| raise Exception('--cold-load-percent must be in the range [0-100]') |
| |
| # Make sure _cold_run_start_index is an integer multiple of page_repeat. |
| # Without this, --pageset_shuffle + --page_repeat could lead to |
| # assertion failures on _started_warm in WillNavigateToPage. |
| if cold_runs_percent_set: |
| number_warm_pageset_runs = int( |
| (int(options.repeat_options.pageset_repeat_iters) - 1) * |
| (100 - options.cold_load_percent) / 100) |
| number_warm_runs = (number_warm_pageset_runs * |
| options.repeat_options.page_repeat_iters) |
| self._cold_run_start_index = (number_warm_runs + |
| options.repeat_options.page_repeat_iters) |
| self.discard_first_result = (not options.cold_load_percent or |
| self.discard_first_result) |
| else: |
| self._cold_run_start_index = ( |
| options.repeat_options.pageset_repeat_iters * |
| options.repeat_options.page_repeat_iters) |
| |
| def MeasurePage(self, page, tab, results): |
| tab.WaitForJavaScriptExpression('__pc_load_time', 60) |
| |
| chart_name_prefix = ('cold_' if self.IsRunCold(page.url) else |
| 'warm_') |
| |
| results.Add('page_load_time', 'ms', |
| int(float(tab.EvaluateJavaScript('__pc_load_time'))), |
| chart_name=chart_name_prefix+'times') |
| |
| self._has_loaded_page[page.url] += 1 |
| |
| self._memory_metric.Stop(page, tab) |
| self._memory_metric.AddResults(tab, results) |
| # TODO(qyearsley): Uncomment the following line when CPU metric is |
| # changed. See crbug.com/301714. |
| # self._cpu_metric.Stop(page, tab) |
| # self._cpu_metric.AddResults(tab, results) |
| if self._record_v8_object_stats: |
| self._v8_object_stats_metric.Stop(page, tab) |
| self._v8_object_stats_metric.AddResults(tab, results) |
| |
| if self._report_speed_index: |
| def SpeedIndexIsFinished(): |
| return self._speedindex_metric.IsFinished(tab) |
| util.WaitFor(SpeedIndexIsFinished, 60) |
| self._speedindex_metric.Stop(page, tab) |
| self._speedindex_metric.AddResults( |
| tab, results, chart_name=chart_name_prefix+'speed_index') |
| |
| def DidRunTest(self, browser, results): |
| self._memory_metric.AddSummaryResults(results) |
| io.IOMetric().AddSummaryResults(browser, results) |
| |
| def IsRunCold(self, url): |
| return (self.ShouldRunCold(url) or |
| self._has_loaded_page[url] == 0) |
| |
| def ShouldRunCold(self, url): |
| # We do the warm runs first for two reasons. The first is so we can |
| # preserve any initial profile cache for as long as possible. |
| # The second is that, if we did cold runs first, we'd have a transition |
| # page set during which we wanted the run for each URL to both |
| # contribute to the cold data and warm the catch for the following |
| # warm run, and clearing the cache before the load of the following |
| # URL would eliminate the intended warmup for the previous URL. |
| return (self._has_loaded_page[url] >= self._cold_run_start_index) |
| |
| def results_are_the_same_on_every_page(self): |
| return False |