1 # Copyright 2014 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 import logging 6 import os 7 import sys 8 9 from telemetry.core import exceptions 10 from telemetry.core import util 11 from telemetry import decorators 12 from telemetry.internal.browser import browser_finder 13 from telemetry.internal.browser import browser_finder_exceptions 14 from telemetry.internal.browser import browser_info as browser_info_module 15 from telemetry.internal.platform.profiler import profiler_finder 16 from telemetry.internal.util import exception_formatter 17 from telemetry.internal.util import file_handle 18 from telemetry.page import cache_temperature 19 from telemetry.page import traffic_setting 20 from telemetry.page import legacy_page_test 21 from telemetry import story 22 from telemetry.util import screenshot 23 from telemetry.util import wpr_modes 24 from telemetry.web_perf import timeline_based_measurement 25 26 27 def _PrepareFinderOptions(finder_options, test, device_type): 28 browser_options = finder_options.browser_options 29 # Set up user agent. 30 browser_options.browser_user_agent_type = device_type 31 32 test.CustomizeBrowserOptions(finder_options.browser_options) 33 if finder_options.profiler: 34 profiler_class = profiler_finder.FindProfiler(finder_options.profiler) 35 profiler_class.CustomizeBrowserOptions(browser_options.browser_type, 36 finder_options) 37 38 39 class SharedPageState(story.SharedState): 40 """ 41 This class contains all specific logic necessary to run a Chrome browser 42 benchmark. 43 """ 44 45 _device_type = None 46 47 def __init__(self, test, finder_options, story_set): 48 super(SharedPageState, self).__init__(test, finder_options, story_set) 49 if isinstance(test, timeline_based_measurement.TimelineBasedMeasurement): 50 if finder_options.profiler: 51 assert not 'trace' in finder_options.profiler, ( 52 'This is a Timeline Based Measurement benchmark. You cannot run it ' 53 'with trace profiler enabled. If you need trace data, tracing is ' 54 'always enabled in Timeline Based Measurement benchmarks and you ' 55 'can get the trace data by adding --output-format=json.') 56 # This is to avoid the cyclic-import caused by timeline_based_page_test. 57 from telemetry.web_perf import timeline_based_page_test 58 self._test = timeline_based_page_test.TimelineBasedPageTest(test) 59 else: 60 self._test = test 61 _PrepareFinderOptions(finder_options, self._test, self._device_type) 62 self._browser = None 63 self._finder_options = finder_options 64 self._possible_browser = self._GetPossibleBrowser( 65 self._test, finder_options) 66 67 self._first_browser = True 68 self._did_login_for_current_page = False 69 self._previous_page = None 70 self._current_page = None 71 self._current_tab = None 72 73 self._test.SetOptions(self._finder_options) 74 75 # TODO(crbug/404771): Move network controller options out of 76 # browser_options and into finder_options. 77 browser_options = self._finder_options.browser_options 78 if self._finder_options.use_live_sites: 79 wpr_mode = wpr_modes.WPR_OFF 80 elif browser_options.wpr_mode == wpr_modes.WPR_RECORD: 81 wpr_mode = wpr_modes.WPR_RECORD 82 else: 83 wpr_mode = wpr_modes.WPR_REPLAY 84 85 use_live_traffic = wpr_mode == wpr_modes.WPR_OFF 86 87 if self.platform.network_controller.is_open: 88 self.platform.network_controller.Close() 89 self.platform.network_controller.InitializeIfNeeded( 90 use_live_traffic=use_live_traffic) 91 self.platform.network_controller.Open(wpr_mode, 92 browser_options.extra_wpr_args) 93 self.platform.Initialize() 94 95 @property 96 def possible_browser(self): 97 return self._possible_browser 98 99 @property 100 def browser(self): 101 return self._browser 102 103 def _FindBrowser(self, finder_options): 104 possible_browser = browser_finder.FindBrowser(finder_options) 105 if not possible_browser: 106 raise browser_finder_exceptions.BrowserFinderException( 107 'No browser found.\n\nAvailable browsers:\n%s\n' % 108 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) 109 return possible_browser 110 111 def _GetPossibleBrowser(self, test, finder_options): 112 """Return a possible_browser with the given options for |test|. """ 113 possible_browser = self._FindBrowser(finder_options) 114 finder_options.browser_options.browser_type = ( 115 possible_browser.browser_type) 116 117 enabled, msg = decorators.IsEnabled(test, possible_browser) 118 if not enabled and not finder_options.run_disabled_tests: 119 logging.warning(msg) 120 logging.warning('You are trying to run a disabled test.') 121 122 if possible_browser.IsRemote(): 123 possible_browser.RunRemote() 124 sys.exit(0) 125 return possible_browser 126 127 def DumpStateUponFailure(self, page, results): 128 # Dump browser standard output and log. 129 if self._browser: 130 self._browser.DumpStateUponFailure() 131 else: 132 logging.warning('Cannot dump browser state: No browser.') 133 134 # Capture a screenshot 135 if self._finder_options.browser_options.take_screenshot_for_failed_page: 136 fh = screenshot.TryCaptureScreenShot(self.platform, self._current_tab) 137 if fh is not None: 138 results.AddProfilingFile(page, fh) 139 else: 140 logging.warning('Taking screenshots upon failures disabled.') 141 142 def DidRunStory(self, results): 143 if self._finder_options.profiler: 144 self._StopProfiling(results) 145 # We might hang while trying to close the connection, and need to guarantee 146 # the page will get cleaned up to avoid future tests failing in weird ways. 147 try: 148 if self._current_tab and self._current_tab.IsAlive(): 149 self._current_tab.CloseConnections() 150 self._previous_page = self._current_page 151 except Exception: 152 if self._current_tab: 153 self._current_tab.Close() 154 finally: 155 if self._current_page.credentials and self._did_login_for_current_page: 156 self.browser.credentials.LoginNoLongerNeeded( 157 self._current_tab, self._current_page.credentials) 158 if self._test.StopBrowserAfterPage(self.browser, self._current_page): 159 self._StopBrowser() 160 self._current_page = None 161 self._current_tab = None 162 163 @property 164 def platform(self): 165 return self._possible_browser.platform 166 167 def _StartBrowser(self, page): 168 assert self._browser is None 169 self._possible_browser.SetCredentialsPath(page.credentials_path) 170 171 self._test.WillStartBrowser(self.platform) 172 if page.startup_url: 173 self._finder_options.browser_options.startup_url = page.startup_url 174 self._browser = self._possible_browser.Create(self._finder_options) 175 self._test.DidStartBrowser(self.browser) 176 177 if self._first_browser: 178 self._first_browser = False 179 self.browser.credentials.WarnIfMissingCredentials(page) 180 181 def WillRunStory(self, page): 182 if not self.platform.tracing_controller.is_tracing_running: 183 # For TimelineBasedMeasurement benchmarks, tracing has already started. 184 # For PageTest benchmarks, tracing has not yet started. We need to make 185 # sure no tracing state is left before starting the browser for PageTest 186 # benchmarks. 187 self.platform.tracing_controller.ClearStateIfNeeded() 188 189 page_set = page.page_set 190 self._current_page = page 191 if self._browser and (self._test.RestartBrowserBeforeEachPage() 192 or page.startup_url): 193 assert not self.platform.tracing_controller.is_tracing_running, ( 194 'Should not restart browser when tracing is already running. For ' 195 'TimelineBasedMeasurement (TBM) benchmarks, you should not use ' 196 'startup_url. Use benchmark.ShouldTearDownStateAfterEachStoryRun ' 197 'instead.') 198 self._StopBrowser() 199 started_browser = not self.browser 200 201 archive_path = page_set.WprFilePathForStory(page, self.platform.GetOSName()) 202 # TODO(nednguyen, perezju): Ideally we should just let the network 203 # controller raise an exception when the archive_path is not found. 204 if archive_path is not None and not os.path.isfile(archive_path): 205 logging.warning('WPR archive missing: %s', archive_path) 206 archive_path = None 207 self.platform.network_controller.StartReplay( 208 archive_path, page.make_javascript_deterministic) 209 210 if self.browser: 211 # Set new credential path for browser. 212 self.browser.credentials.credentials_path = page.credentials_path 213 else: 214 self._StartBrowser(page) 215 if self.browser.supports_tab_control and self._test.close_tabs_before_run: 216 # Create a tab if there's none. 217 if len(self.browser.tabs) == 0: 218 self.browser.tabs.New() 219 220 # Ensure only one tab is open, unless the test is a multi-tab test. 221 if not self._test.is_multi_tab_test: 222 while len(self.browser.tabs) > 1: 223 self.browser.tabs[-1].Close() 224 225 # Must wait for tab to commit otherwise it can commit after the next 226 # navigation has begun and RenderFrameHostManager::DidNavigateMainFrame() 227 # will cancel the next navigation because it's pending. This manifests as 228 # the first navigation in a PageSet freezing indefinitely because the 229 # navigation was silently canceled when |self.browser.tabs[0]| was 230 # committed. Only do this when we just started the browser, otherwise 231 # there are cases where previous pages in a PageSet never complete 232 # loading so we'll wait forever. 233 if started_browser: 234 self.browser.tabs[0].WaitForDocumentReadyStateToBeComplete() 235 236 # Reset traffic shaping to speed up cache temperature setup. 237 self.platform.network_controller.UpdateTrafficSettings(0, 0, 0) 238 cache_temperature.EnsurePageCacheTemperature( 239 self._current_page, self.browser, self._previous_page) 240 if self._current_page.traffic_setting != traffic_setting.NONE: 241 s = traffic_setting.NETWORK_CONFIGS[self._current_page.traffic_setting] 242 self.platform.network_controller.UpdateTrafficSettings( 243 round_trip_latency_ms=s.round_trip_latency_ms, 244 download_bandwidth_kbps=s.download_bandwidth_kbps, 245 upload_bandwidth_kbps=s.upload_bandwidth_kbps) 246 247 # Start profiling if needed. 248 if self._finder_options.profiler: 249 self._StartProfiling(self._current_page) 250 251 def CanRunStory(self, page): 252 return self.CanRunOnBrowser(browser_info_module.BrowserInfo(self.browser), 253 page) 254 255 def CanRunOnBrowser(self, browser_info, 256 page): # pylint: disable=unused-argument 257 """Override this to return whether the browser brought up by this state 258 instance is suitable for running the given page. 259 260 Args: 261 browser_info: an instance of telemetry.core.browser_info.BrowserInfo 262 page: an instance of telemetry.page.Page 263 """ 264 del browser_info, page # unused 265 return True 266 267 def _PreparePage(self): 268 self._current_tab = self._test.TabForPage(self._current_page, self.browser) 269 if self._current_page.is_file: 270 self.platform.SetHTTPServerDirectories( 271 self._current_page.page_set.serving_dirs | 272 set([self._current_page.serving_dir])) 273 274 if self._current_page.credentials: 275 if not self.browser.credentials.LoginNeeded( 276 self._current_tab, self._current_page.credentials): 277 raise legacy_page_test.Failure( 278 'Login as ' + self._current_page.credentials + ' failed') 279 self._did_login_for_current_page = True 280 281 if self._test.clear_cache_before_each_run: 282 self._current_tab.ClearCache(force=True) 283 284 @property 285 def current_page(self): 286 return self._current_page 287 288 @property 289 def current_tab(self): 290 return self._current_tab 291 292 @property 293 def page_test(self): 294 return self._test 295 296 def RunStory(self, results): 297 try: 298 self._PreparePage() 299 self._current_page.Run(self) 300 self._test.ValidateAndMeasurePage( 301 self._current_page, self._current_tab, results) 302 except exceptions.Error: 303 if self._test.is_multi_tab_test: 304 # Avoid trying to recover from an unknown multi-tab state. 305 exception_formatter.PrintFormattedException( 306 msg='Telemetry Error during multi tab test:') 307 raise legacy_page_test.MultiTabTestAppCrashError 308 raise 309 310 def TearDownState(self): 311 self._StopBrowser() 312 self.platform.StopAllLocalServers() 313 self.platform.network_controller.Close() 314 315 def _StopBrowser(self): 316 if self._browser: 317 self._browser.Close() 318 self._browser = None 319 320 def _StartProfiling(self, page): 321 output_file = os.path.join(self._finder_options.output_dir, 322 page.file_safe_name) 323 if self._finder_options.pageset_repeat != 1: 324 output_file = util.GetSequentialFileName(output_file) 325 self.browser.profiling_controller.Start( 326 self._finder_options.profiler, output_file) 327 328 def _StopProfiling(self, results): 329 if self.browser: 330 profiler_files = self.browser.profiling_controller.Stop() 331 for f in profiler_files: 332 if os.path.isfile(f): 333 results.AddProfilingFile(self._current_page, 334 file_handle.FromFilePath(f)) 335 336 337 class SharedMobilePageState(SharedPageState): 338 _device_type = 'mobile' 339 340 341 class SharedDesktopPageState(SharedPageState): 342 _device_type = 'desktop' 343 344 345 class SharedTabletPageState(SharedPageState): 346 _device_type = 'tablet' 347 348 349 class Shared10InchTabletPageState(SharedPageState): 350 _device_type = 'tablet_10_inch' 351