1# Copyright 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""The page cycler measurement. 6 7This measurement registers a window load handler in which is forces a layout and 8then records the value of performance.now(). This call to now() measures the 9time from navigationStart (immediately after the previous page's beforeunload 10event) until after the layout in the page's load event. In addition, two garbage 11collections are performed in between the page loads (in the beforeunload event). 12This extra garbage collection time is not included in the measurement times. 13 14Finally, various memory and IO statistics are gathered at the very end of 15cycling all pages. 16""" 17 18import collections 19import os 20 21from metrics import cpu 22from metrics import iometric 23from metrics import memory 24from metrics import power 25from metrics import speedindex 26from metrics import v8_object_stats 27from telemetry.core import util 28from telemetry.page import page_measurement 29 30class PageCycler(page_measurement.PageMeasurement): 31 options = {'pageset_repeat': 10} 32 33 def __init__(self, *args, **kwargs): 34 super(PageCycler, self).__init__(*args, **kwargs) 35 36 with open(os.path.join(os.path.dirname(__file__), 37 'page_cycler.js'), 'r') as f: 38 self._page_cycler_js = f.read() 39 40 self._speedindex_metric = speedindex.SpeedIndexMetric() 41 self._memory_metric = None 42 self._power_metric = power.PowerMetric() 43 self._cpu_metric = None 44 self._v8_object_stats_metric = None 45 self._has_loaded_page = collections.defaultdict(int) 46 47 @classmethod 48 def AddCommandLineArgs(cls, parser): 49 parser.add_option('--v8-object-stats', 50 action='store_true', 51 help='Enable detailed V8 object statistics.') 52 53 parser.add_option('--report-speed-index', 54 action='store_true', 55 help='Enable the speed index metric.') 56 57 parser.add_option('--cold-load-percent', type='int', default=50, 58 help='%d of page visits for which a cold load is forced') 59 60 @classmethod 61 def ProcessCommandLineArgs(cls, parser, args): 62 cls._record_v8_object_stats = args.v8_object_stats 63 cls._report_speed_index = args.report_speed_index 64 65 cold_runs_percent_set = (args.cold_load_percent != None) 66 # Handle requests for cold cache runs 67 if (cold_runs_percent_set and 68 (args.cold_load_percent < 0 or args.cold_load_percent > 100)): 69 raise Exception('--cold-load-percent must be in the range [0-100]') 70 71 # Make sure _cold_run_start_index is an integer multiple of page_repeat. 72 # Without this, --pageset_shuffle + --page_repeat could lead to 73 # assertion failures on _started_warm in WillNavigateToPage. 74 if cold_runs_percent_set: 75 number_warm_pageset_runs = int( 76 (int(args.pageset_repeat) - 1) * (100 - args.cold_load_percent) / 100) 77 number_warm_runs = number_warm_pageset_runs * args.page_repeat 78 cls._cold_run_start_index = number_warm_runs + args.page_repeat 79 cls.discard_first_result = (not args.cold_load_percent or 80 cls.discard_first_result) 81 else: 82 cls._cold_run_start_index = args.pageset_repeat * args.page_repeat 83 84 def DidStartBrowser(self, browser): 85 """Initialize metrics once right after the browser has been launched.""" 86 self._memory_metric = memory.MemoryMetric(browser) 87 self._cpu_metric = cpu.CpuMetric(browser) 88 if self._record_v8_object_stats: 89 self._v8_object_stats_metric = v8_object_stats.V8ObjectStatsMetric() 90 91 def DidStartHTTPServer(self, tab): 92 # Avoid paying for a cross-renderer navigation on the first page on legacy 93 # page cyclers which use the filesystem. 94 tab.Navigate(tab.browser.http_server.UrlOf('nonexistent.html')) 95 96 def WillNavigateToPage(self, page, tab): 97 page.script_to_evaluate_on_commit = self._page_cycler_js 98 if self.ShouldRunCold(page.url): 99 tab.ClearCache(force=True) 100 if self._report_speed_index: 101 self._speedindex_metric.Start(page, tab) 102 self._cpu_metric.Start(page, tab) 103 self._power_metric.Start(page, tab) 104 105 def DidNavigateToPage(self, page, tab): 106 self._memory_metric.Start(page, tab) 107 if self._record_v8_object_stats: 108 self._v8_object_stats_metric.Start(page, tab) 109 110 def CustomizeBrowserOptions(self, options): 111 memory.MemoryMetric.CustomizeBrowserOptions(options) 112 power.PowerMetric.CustomizeBrowserOptions(options) 113 iometric.IOMetric.CustomizeBrowserOptions(options) 114 options.AppendExtraBrowserArgs('--js-flags=--expose_gc') 115 116 if self._record_v8_object_stats: 117 v8_object_stats.V8ObjectStatsMetric.CustomizeBrowserOptions(options) 118 if self._report_speed_index: 119 self._speedindex_metric.CustomizeBrowserOptions(options) 120 121 def MeasurePage(self, page, tab, results): 122 tab.WaitForJavaScriptExpression('__pc_load_time', 60) 123 124 chart_name_prefix = ('cold_' if self.IsRunCold(page.url) else 125 'warm_') 126 127 results.Add('page_load_time', 'ms', 128 int(float(tab.EvaluateJavaScript('__pc_load_time'))), 129 chart_name=chart_name_prefix+'times') 130 131 self._has_loaded_page[page.url] += 1 132 133 self._power_metric.Stop(page, tab) 134 self._memory_metric.Stop(page, tab) 135 self._memory_metric.AddResults(tab, results) 136 self._power_metric.AddResults(tab, results) 137 138 self._cpu_metric.Stop(page, tab) 139 self._cpu_metric.AddResults(tab, results) 140 if self._record_v8_object_stats: 141 self._v8_object_stats_metric.Stop(page, tab) 142 self._v8_object_stats_metric.AddResults(tab, results) 143 144 if self._report_speed_index: 145 def SpeedIndexIsFinished(): 146 return self._speedindex_metric.IsFinished(tab) 147 util.WaitFor(SpeedIndexIsFinished, 60) 148 self._speedindex_metric.Stop(page, tab) 149 self._speedindex_metric.AddResults( 150 tab, results, chart_name=chart_name_prefix+'speed_index') 151 152 def DidRunTest(self, browser, results): 153 iometric.IOMetric().AddSummaryResults(browser, results) 154 155 def IsRunCold(self, url): 156 return (self.ShouldRunCold(url) or 157 self._has_loaded_page[url] == 0) 158 159 def ShouldRunCold(self, url): 160 # We do the warm runs first for two reasons. The first is so we can 161 # preserve any initial profile cache for as long as possible. 162 # The second is that, if we did cold runs first, we'd have a transition 163 # page set during which we wanted the run for each URL to both 164 # contribute to the cold data and warm the catch for the following 165 # warm run, and clearing the cache before the load of the following 166 # URL would eliminate the intended warmup for the previous URL. 167 return (self._has_loaded_page[url] >= self._cold_run_start_index) 168 169 def results_are_the_same_on_every_page(self): 170 return False 171