OLD | NEW |
---|---|
1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 import time | 4 import time |
5 | 5 |
6 from telemetry.core import browser_finder | 6 from telemetry.core import browser_finder |
7 from telemetry.core import browser_finder_exceptions | 7 from telemetry.core import browser_finder_exceptions |
8 from telemetry.core import exceptions | 8 from telemetry.core import exceptions |
9 from telemetry.core import platform | 9 from telemetry.core import platform |
10 from telemetry.core import util | 10 from telemetry.core import util |
11 from telemetry.core.backends.chrome_inspector import devtools_http | |
11 | 12 |
12 | 13 |
13 class FastNavigationProfileExtender(object): | 14 class FastNavigationProfileExtender(object): |
14 """Extends a Chrome profile. | 15 """Extends a Chrome profile. |
15 | 16 |
16 This class creates or extends an existing profile by performing a set of tab | 17 This class creates or extends an existing profile by performing a set of tab |
17 navigations in large batches. This is accomplished by opening a large number | 18 navigations in large batches. This is accomplished by opening a large number |
18 of tabs, simultaneously navigating all the tabs, and then waiting for all the | 19 of tabs, simultaneously navigating all the tabs, and then waiting for all the |
19 tabs to load. This provides two benefits: | 20 tabs to load. This provides two benefits: |
20 - Takes advantage of the high number of logical cores on modern CPUs. | 21 - Takes advantage of the high number of logical cores on modern CPUs. |
(...skipping 12 matching lines...) Expand all Loading... | |
33 | 34 |
34 # The path of the profile that the browser will use while it's running. | 35 # The path of the profile that the browser will use while it's running. |
35 # This member is initialized during SetUp(). | 36 # This member is initialized during SetUp(). |
36 self._profile_path = None | 37 self._profile_path = None |
37 | 38 |
38 # A reference to the browser that will be performing all of the tab | 39 # A reference to the browser that will be performing all of the tab |
39 # navigations. | 40 # navigations. |
40 # This member is initialized during SetUp(). | 41 # This member is initialized during SetUp(). |
41 self._browser = None | 42 self._browser = None |
42 | 43 |
44 # The list of tabs to use for url navigations. This list may not contain | |
45 # all tabs in the browser. | |
46 self._navigation_tabs = [] | |
47 | |
43 # The number of tabs to use. | 48 # The number of tabs to use. |
44 self._NUM_TABS = maximum_batch_size | 49 self._NUM_TABS = maximum_batch_size |
45 | 50 |
46 # The amount of time to wait for a batch of pages to finish loading. | 51 # The amount of time to wait for a batch of pages to finish loading. |
47 self._BATCH_PAGE_LOAD_TIMEOUT_IN_SECONDS = 10 | 52 self._BATCH_PAGE_LOAD_TIMEOUT_IN_SECONDS = 10 |
48 | 53 |
49 # The default amount of time to wait for the retrieval of the URL of a tab. | 54 # The default amount of time to wait for the retrieval of the URL of a tab. |
50 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS = 1 | 55 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS = 1 |
51 | 56 |
52 def Run(self, finder_options): | 57 def Run(self, finder_options): |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
88 implementation. | 93 implementation. |
89 """ | 94 """ |
90 self._profile_path = finder_options.output_profile_path | 95 self._profile_path = finder_options.output_profile_path |
91 possible_browser = self._GetPossibleBrowser(finder_options) | 96 possible_browser = self._GetPossibleBrowser(finder_options) |
92 | 97 |
93 assert possible_browser.supports_tab_control | 98 assert possible_browser.supports_tab_control |
94 assert (platform.GetHostPlatform().GetOSName() in | 99 assert (platform.GetHostPlatform().GetOSName() in |
95 ["win", "mac", "linux"]) | 100 ["win", "mac", "linux"]) |
96 self._browser = possible_browser.Create(finder_options) | 101 self._browser = possible_browser.Create(finder_options) |
97 | 102 |
98 while(len(self._browser.tabs) < self._NUM_TABS): | |
99 self._browser.tabs.New() | |
100 | |
101 def TearDown(self): | 103 def TearDown(self): |
102 """Teardown that is guaranteed to be executed before the instance is | 104 """Teardown that is guaranteed to be executed before the instance is |
103 destroyed. | 105 destroyed. |
104 | 106 |
105 Can be overridden by subclasses. Subclasses must call the super class | 107 Can be overridden by subclasses. Subclasses must call the super class |
106 implementation. | 108 implementation. |
107 """ | 109 """ |
108 if self._browser: | 110 if self._browser: |
109 self._browser.Close() | 111 self._browser.Close() |
110 self._browser = None | 112 self._browser = None |
111 | 113 |
112 def CleanUpAfterBatchNavigation(self): | 114 def CleanUpAfterBatchNavigation(self): |
113 """A hook for subclasses to perform cleanup after each batch of | 115 """A hook for subclasses to perform cleanup after each batch of |
114 navigations. | 116 navigations. |
115 | 117 |
116 Can be overridden by subclasses. | 118 Can be overridden by subclasses. |
117 """ | 119 """ |
118 pass | 120 pass |
119 | 121 |
120 @property | 122 @property |
121 def profile_path(self): | 123 def profile_path(self): |
122 return self._profile_path | 124 return self._profile_path |
123 | 125 |
126 def _RefreshNavigationTabs(self): | |
127 """Updates the member self._navigation_tabs to contain self._NUM_TABS | |
128 elements, each of which is not crashed. The crashed tabs are intentionally | |
129 leaked, since Telemetry doesn't have a good way of killing crashed tabs.""" | |
130 live_tabs = [] | |
131 for tab in self._navigation_tabs: | |
132 try: | |
133 live_tab = self._browser.tabs.GetTabById(tab.id) | |
nednguyen
2015/02/19 20:05:04
If you have to do this just to get the "live tab",
erikchen
2015/02/19 21:55:12
I've added comments - this method is doing more th
| |
134 live_tabs.append(live_tab) | |
135 except KeyError: | |
136 pass | |
137 | |
138 self._navigation_tabs = live_tabs | |
139 | |
140 while len(self._navigation_tabs) < self._NUM_TABS: | |
141 self._navigation_tabs.append(self._browser.tabs.New()) | |
142 | |
143 def _RemoveNavigationTab(self, tab): | |
144 self._navigation_tabs.remove(tab) | |
145 | |
124 def _GetPossibleBrowser(self, finder_options): | 146 def _GetPossibleBrowser(self, finder_options): |
125 """Return a possible_browser with the given options.""" | 147 """Return a possible_browser with the given options.""" |
126 possible_browser = browser_finder.FindBrowser(finder_options) | 148 possible_browser = browser_finder.FindBrowser(finder_options) |
127 if not possible_browser: | 149 if not possible_browser: |
128 raise browser_finder_exceptions.BrowserFinderException( | 150 raise browser_finder_exceptions.BrowserFinderException( |
129 'No browser found.\n\nAvailable browsers:\n%s\n' % | 151 'No browser found.\n\nAvailable browsers:\n%s\n' % |
130 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) | 152 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) |
131 finder_options.browser_options.browser_type = ( | 153 finder_options.browser_options.browser_type = ( |
132 possible_browser.browser_type) | 154 possible_browser.browser_type) |
133 | 155 |
134 return possible_browser | 156 return possible_browser |
135 | 157 |
136 def _RetrieveTabUrl(self, tab, timeout): | 158 def _RetrieveTabUrl(self, tab, timeout): |
137 """Retrives the URL of the tab.""" | 159 """Retrives the URL of the tab.""" |
138 try: | 160 try: |
139 return tab.EvaluateJavaScript('document.URL', timeout) | 161 return tab.EvaluateJavaScript('document.URL', timeout) |
140 except exceptions.DevtoolsTargetCrashException: | 162 except (exceptions.DevtoolsTargetCrashException, |
163 devtools_http.DevToolsClientConnectionError, | |
164 devtools_http.DevToolsClientUrlError): | |
141 return None | 165 return None |
142 | 166 |
143 def _WaitForUrlToChange(self, tab, initial_url, timeout): | 167 def _WaitForUrlToChange(self, tab, initial_url, timeout): |
144 """Waits for the tab to navigate away from its initial url.""" | 168 """Waits for the tab to navigate away from its initial url.""" |
145 end_time = time.time() + timeout | 169 end_time = time.time() + timeout |
146 while True: | 170 while True: |
147 seconds_to_wait = end_time - time.time() | 171 seconds_to_wait = end_time - time.time() |
148 seconds_to_wait = max(0, seconds_to_wait) | 172 seconds_to_wait = max(0, seconds_to_wait) |
149 | 173 |
150 if seconds_to_wait == 0: | 174 if seconds_to_wait == 0: |
(...skipping 20 matching lines...) Expand all Loading... | |
171 """ | 195 """ |
172 timeout_in_seconds = 0 | 196 timeout_in_seconds = 0 |
173 | 197 |
174 queued_tabs = [] | 198 queued_tabs = [] |
175 for tab, url in batch: | 199 for tab, url in batch: |
176 initial_url = self._RetrieveTabUrl(tab, | 200 initial_url = self._RetrieveTabUrl(tab, |
177 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS) | 201 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS) |
178 | 202 |
179 try: | 203 try: |
180 tab.Navigate(url, None, timeout_in_seconds) | 204 tab.Navigate(url, None, timeout_in_seconds) |
181 except exceptions.DevtoolsTargetCrashException: | 205 except (exceptions.DevtoolsTargetCrashException, |
182 # We expect a time out, and don't mind if the webpage crashes. Ignore | 206 devtools_http.DevToolsClientConnectionError, |
183 # both exceptions. | 207 devtools_http.DevToolsClientUrlError): |
208 # We expect a time out. It's possible for other problems to arise, but | |
209 # this method is not responsible for dealing with them. Ignore all | |
210 # exceptions. | |
184 pass | 211 pass |
185 | 212 |
186 queued_tabs.append((tab, initial_url)) | 213 queued_tabs.append((tab, initial_url)) |
187 return queued_tabs | 214 return queued_tabs |
188 | 215 |
189 def _WaitForQueuedTabsToLoad(self, queued_tabs): | 216 def _WaitForQueuedTabsToLoad(self, queued_tabs): |
190 """Waits for all the batch navigated tabs to finish loading. | 217 """Waits for all the batch navigated tabs to finish loading. |
191 | 218 |
192 Args: | 219 Args: |
193 queued_tabs: A list of tuples (tab, initial_url). Each tab is guaranteed | 220 queued_tabs: A list of tuples (tab, initial_url). Each tab is guaranteed |
194 to have already been sent a navigation command. | 221 to have already been sent a navigation command. |
195 """ | 222 """ |
196 end_time = time.time() + self._BATCH_PAGE_LOAD_TIMEOUT_IN_SECONDS | 223 end_time = time.time() + self._BATCH_PAGE_LOAD_TIMEOUT_IN_SECONDS |
197 for tab, initial_url in queued_tabs: | 224 for tab, initial_url in queued_tabs: |
198 seconds_to_wait = end_time - time.time() | 225 seconds_to_wait = end_time - time.time() |
199 seconds_to_wait = max(0, seconds_to_wait) | 226 seconds_to_wait = max(0, seconds_to_wait) |
200 | 227 |
201 if seconds_to_wait == 0: | 228 if seconds_to_wait == 0: |
202 break | 229 break |
203 | 230 |
204 # Since we don't wait any time for the tab url navigation to commit, it's | 231 # Since we don't wait any time for the tab url navigation to commit, it's |
205 # possible that the tab hasn't started navigating yet. | 232 # possible that the tab hasn't started navigating yet. |
206 self._WaitForUrlToChange(tab, initial_url, seconds_to_wait) | 233 self._WaitForUrlToChange(tab, initial_url, seconds_to_wait) |
207 | 234 |
208 seconds_to_wait = end_time - time.time() | 235 seconds_to_wait = end_time - time.time() |
209 seconds_to_wait = max(0, seconds_to_wait) | 236 seconds_to_wait = max(0, seconds_to_wait) |
210 | 237 |
211 try: | 238 try: |
212 tab.WaitForDocumentReadyStateToBeComplete(seconds_to_wait) | 239 tab.WaitForDocumentReadyStateToBeComplete(seconds_to_wait) |
213 except (util.TimeoutException, exceptions.DevtoolsTargetCrashException): | 240 except util.TimeoutException: |
214 # Ignore time outs and web page crashes. | 241 # Ignore time outs. |
215 pass | 242 pass |
243 except (exceptions.DevtoolsTargetCrashException, | |
244 devtools_http.DevToolsClientConnectionError, | |
245 devtools_http.DevToolsClientUrlError): | |
246 # If any error occurs, remove the tab. it's probably in an | |
247 # unrecoverable state. | |
248 self._RemoveNavigationTab(tab) | |
216 | 249 |
217 def _GetUrlsToNavigate(self, url_iterator): | 250 def _GetUrlsToNavigate(self, url_iterator): |
218 """Returns an array of urls to navigate to, given a url_iterator.""" | 251 """Returns an array of urls to navigate to, given a url_iterator.""" |
219 urls = [] | 252 urls = [] |
220 for _ in xrange(self._NUM_TABS): | 253 for _ in xrange(self._NUM_TABS): |
221 try: | 254 try: |
222 urls.append(url_iterator.next()) | 255 urls.append(url_iterator.next()) |
223 except StopIteration: | 256 except StopIteration: |
224 break | 257 break |
225 return urls | 258 return urls |
226 | 259 |
227 def _PerformNavigations(self): | 260 def _PerformNavigations(self): |
228 """Repeatedly fetches a batch of urls, and navigates to those urls. This | 261 """Repeatedly fetches a batch of urls, and navigates to those urls. This |
229 will run until an empty batch is returned, or | 262 will run until an empty batch is returned, or |
230 ShouldExitAfterBatchNavigation() returns True. | 263 ShouldExitAfterBatchNavigation() returns True. |
231 """ | 264 """ |
232 url_iterator = self.GetUrlIterator() | 265 url_iterator = self.GetUrlIterator() |
233 while True: | 266 while True: |
267 self._RefreshNavigationTabs() | |
234 urls = self._GetUrlsToNavigate(url_iterator) | 268 urls = self._GetUrlsToNavigate(url_iterator) |
235 | 269 |
236 if len(urls) == 0: | 270 if len(urls) == 0: |
237 break | 271 break |
238 | 272 |
239 batch = [] | 273 batch = [] |
240 for i in range(len(urls)): | 274 for i in range(len(urls)): |
241 url = urls[i] | 275 url = urls[i] |
242 tab = self._browser.tabs[i] | 276 tab = self._navigation_tabs[i] |
243 batch.append((tab, url)) | 277 batch.append((tab, url)) |
244 | 278 |
245 queued_tabs = self._BatchNavigateTabs(batch) | 279 queued_tabs = self._BatchNavigateTabs(batch) |
246 self._WaitForQueuedTabsToLoad(queued_tabs) | 280 self._WaitForQueuedTabsToLoad(queued_tabs) |
247 | 281 |
248 self.CleanUpAfterBatchNavigation() | 282 self.CleanUpAfterBatchNavigation() |
249 | 283 |
250 if self.ShouldExitAfterBatchNavigation(): | 284 if self.ShouldExitAfterBatchNavigation(): |
251 break | 285 break |
OLD | NEW |