Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 import time | |
| 5 | |
| 6 from telemetry.core import browser_finder | |
| 7 from telemetry.core import browser_finder_exceptions | |
| 8 from telemetry.core import exceptions | |
| 9 from telemetry.core import util | |
| 10 | |
| 11 | |
| 12 class FastNavigationProfileExtender(object): | |
| 13 """ | |
| 14 This class creates or extends an existing profile by performing a set of tab | |
| 15 navigations in large batches. This is accomplished by opening a large number | |
| 16 of tabs, simultaneously navigating all the tabs, and then waiting for all the | |
| 17 tabs to load. This provides two benefits: | |
| 18 - Takes advantage of the high number of logical cores on modern CPUs. | |
| 19 - The total time spent waiting for navigations to time out scales linearly | |
| 20 with the number of batches, but does not scale with the size of the | |
| 21 batch. | |
| 22 """ | |
| 23 def Run(self, finder_options): | |
| 24 """ | |
| 25 Superclass override. | |
| 26 | |
| 27 |finder_options| contains the directory of the input profile, the directory | |
| 28 to place the output profile, and sufficient information to choose a specific | |
| 29 browser binary. | |
| 30 """ | |
| 31 profile_extender = _FastNavigationUserStory(finder_options, | |
| 32 self.NavigationUrls()) | |
| 33 try: | |
| 34 profile_extender.WillRunUserStory() | |
| 35 profile_extender.RunUserStory() | |
|
nednguyen
2015/02/09 20:57:18
You will only need these two hooks if you think th
erikchen
2015/02/09 21:17:35
Renamed the hooks to BrowserSetup() and PerformNav
| |
| 36 except: | |
| 37 raise | |
| 38 finally: | |
| 39 profile_extender.TearDownState() | |
|
nednguyen
2015/02/09 20:57:18
Also this one.
erikchen
2015/02/09 21:17:35
Renamed to BrowserTeardown()
| |
| 40 | |
| 41 def NavigationUrls(self): | |
| 42 """ | |
| 43 Intended for subclass override. Returns a list of urls to be navigated to. | |
| 44 """ | |
| 45 raise NotImplementedError() | |
| 46 | |
| 47 | |
| 48 class _FastNavigationUserStory(object): | |
| 49 """ | |
| 50 This class contains the bulk of the logic related to | |
| 51 FastNavigationProfileExtender. Once UserStory has been refactored, this class | |
| 52 should become a subclass of UserStory. For more details, see | |
| 53 http://code.google.com/p/chromium/issues/detail?id=417812 | |
| 54 | |
| 55 This class intentionally mimics the format of SharedUserStoryState to make | |
| 56 the future refactor easier. | |
| 57 """ | |
| 58 def __init__(self, finder_options, navigation_urls): | |
| 59 super(_FastNavigationUserStory, self).__init__() | |
| 60 self.browser = None | |
| 61 self._finder_options = finder_options | |
| 62 self._navigation_urls = navigation_urls | |
| 63 | |
| 64 # The number of tabs to use. | |
| 65 self._NUM_TABS = 15 | |
| 66 | |
| 67 # The number of pages to load in parallel. | |
| 68 self._NUM_PARALLEL_PAGES = 15 | |
| 69 | |
| 70 # The amount of time to wait for pages to finish loading. | |
| 71 self._PAGE_LOAD_TIMEOUT_IN_SECONDS = 10 | |
| 72 | |
| 73 # The amount of time to wait for the retrieval of the URL of a tab. | |
| 74 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS = 1 | |
| 75 | |
| 76 # The amount of time to wait for a navigation to be committed. | |
| 77 self._NAVIGATION_COMMIT_WAIT_IN_SECONDS = 0.1 | |
| 78 | |
| 79 # A list of tuples (tab, initial_url). A navigation command has been sent | |
| 80 # to |tab|. |tab| had a URL of |initial_url| before the command was sent. | |
| 81 self._queued_tabs = [] | |
| 82 | |
| 83 # The index of the first url that has not yet been navigated to. | |
| 84 self._navigation_url_index = 0 | |
| 85 | |
| 86 def _GetPossibleBrowser(self, finder_options): | |
| 87 """Return a possible_browser with the given options.""" | |
| 88 possible_browser = browser_finder.FindBrowser(finder_options) | |
| 89 if not possible_browser: | |
| 90 raise browser_finder_exceptions.BrowserFinderException( | |
| 91 'No browser found.\n\nAvailable browsers:\n%s\n' % | |
| 92 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) | |
| 93 finder_options.browser_options.browser_type = ( | |
| 94 possible_browser.browser_type) | |
| 95 | |
| 96 return possible_browser | |
| 97 | |
| 98 def _RetrieveTabUrl(self, tab): | |
| 99 """Retrives the URL of the tab.""" | |
| 100 try: | |
| 101 return tab.EvaluateJavaScript('document.URL', | |
| 102 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS) | |
| 103 except exceptions.DevtoolsTargetCrashException: | |
| 104 return None | |
| 105 | |
| 106 def _BatchNavigateTabs(self): | |
| 107 """Performs a batch of tab navigations with minimal delay.""" | |
| 108 max_index = min(self._navigation_url_index + self._NUM_PARALLEL_PAGES, | |
| 109 len(self._navigation_urls)) | |
| 110 timeout_in_seconds = 0 | |
| 111 | |
| 112 for i in range(self._navigation_url_index, max_index): | |
| 113 url = self._navigation_urls[i] | |
| 114 tab = self.browser.tabs[i % self._NUM_TABS] | |
| 115 initial_url = self._RetrieveTabUrl(tab) | |
| 116 | |
| 117 try: | |
| 118 tab.Navigate(url, None, timeout_in_seconds) | |
| 119 except exceptions.DevtoolsTargetCrashException: | |
| 120 # We expect a time out, and don't mind if the webpage crashes. Ignore | |
| 121 # both exceptions. | |
| 122 pass | |
| 123 | |
| 124 self._queued_tabs.append((tab, initial_url)) | |
| 125 self._navigation_url_index = max_index | |
| 126 | |
| 127 def _WaitForQueuedTabsToLoad(self): | |
| 128 """Waits for all the batch navigated tabs to finish loading.""" | |
| 129 end_time = time.time() + self._PAGE_LOAD_TIMEOUT_IN_SECONDS | |
| 130 for tab, initial_url in self._queued_tabs: | |
| 131 seconds_to_wait = end_time - time.time() | |
| 132 seconds_to_wait = max(0, seconds_to_wait) | |
| 133 | |
| 134 if seconds_to_wait == 0: | |
| 135 break | |
| 136 | |
| 137 # Since we don't wait any time for the tab url navigation to commit, it's | |
| 138 # possible that the tab hasn't started navigating yet. | |
| 139 current_url = self._RetrieveTabUrl(tab) | |
| 140 | |
| 141 if current_url == initial_url: | |
| 142 # If the navigation hasn't been committed yet, wait a small amount of | |
| 143 # time. Don't bother rechecking the condition, since it's also possible | |
| 144 # that the web page isn't processing javascript. | |
| 145 time.sleep(self._NAVIGATION_COMMIT_WAIT_IN_SECONDS) | |
| 146 | |
| 147 try: | |
| 148 tab.WaitForDocumentReadyStateToBeComplete(seconds_to_wait) | |
| 149 except (util.TimeoutException, exceptions.DevtoolsTargetCrashException): | |
| 150 # Ignore time outs and web page crashes. | |
| 151 pass | |
| 152 self._queued_tabs = [] | |
| 153 | |
| 154 def WillRunUserStory(self): | |
| 155 """ | |
| 156 Finds the browser, starts the browser, and opens the requisite number of | |
| 157 tabs. | |
| 158 """ | |
| 159 possible_browser = self._GetPossibleBrowser(self._finder_options) | |
| 160 self.browser = possible_browser.Create(self._finder_options) | |
| 161 | |
| 162 for _ in range(self._NUM_TABS): | |
| 163 self.browser.tabs.New() | |
| 164 | |
| 165 def RunUserStory(self): | |
| 166 """ | |
| 167 Performs the navigations specified by |_navigation_urls| in large batches. | |
| 168 """ | |
| 169 while True: | |
| 170 self._BatchNavigateTabs() | |
| 171 self._WaitForQueuedTabsToLoad() | |
| 172 | |
| 173 if self._navigation_url_index == len(self._navigation_urls): | |
| 174 break | |
| 175 | |
| 176 def TearDownState(self): | |
| 177 """ | |
| 178 Teardown that is guaranteed to be executed before the instance is destroyed. | |
| 179 """ | |
| 180 if self.browser: | |
| 181 self.browser.Close() | |
| 182 self.browser = None | |
| OLD | NEW |