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 util | 10 from telemetry.core import util |
10 | 11 |
11 | 12 |
12 class FastNavigationProfileExtender(object): | 13 class FastNavigationProfileExtender(object): |
13 """Extends a Chrome profile. | 14 """Extends a Chrome profile. |
14 | 15 |
15 This class creates or extends an existing profile by performing a set of tab | 16 This class creates or extends an existing profile by performing a set of tab |
16 navigations in large batches. This is accomplished by opening a large number | 17 navigations in large batches. This is accomplished by opening a large number |
17 of tabs, simultaneously navigating all the tabs, and then waiting for all the | 18 of tabs, simultaneously navigating all the tabs, and then waiting for all the |
18 tabs to load. This provides two benefits: | 19 tabs to load. This provides two benefits: |
19 - Takes advantage of the high number of logical cores on modern CPUs. | 20 - Takes advantage of the high number of logical cores on modern CPUs. |
20 - The total time spent waiting for navigations to time out scales linearly | 21 - The total time spent waiting for navigations to time out scales linearly |
21 with the number of batches, but does not scale with the size of the | 22 with the number of batches, but does not scale with the size of the |
22 batch. | 23 batch. |
23 """ | 24 """ |
24 def __init__(self): | 25 def __init__(self, maximum_batch_size): |
| 26 """Initializer. |
| 27 |
| 28 Args: |
| 29 maximum_batch_size: A positive integer indicating the number of tabs to |
| 30 simultaneously perform navigations. |
| 31 """ |
25 super(FastNavigationProfileExtender, self).__init__() | 32 super(FastNavigationProfileExtender, self).__init__() |
26 | 33 |
| 34 # The path of the profile that the browser will use while it's running. |
| 35 # This member is initialized during SetUp(). |
| 36 self._profile_path = None |
| 37 |
27 # A reference to the browser that will be performing all of the tab | 38 # A reference to the browser that will be performing all of the tab |
28 # navigations. | 39 # navigations. |
| 40 # This member is initialized during SetUp(). |
29 self._browser = None | 41 self._browser = None |
30 | 42 |
31 # A static copy of the urls that this class is going to navigate to. | |
32 self._navigation_urls = None | |
33 | |
34 # The number of tabs to use. | 43 # The number of tabs to use. |
35 self._NUM_TABS = 15 | 44 self._NUM_TABS = maximum_batch_size |
36 | |
37 # The number of pages to load in parallel. | |
38 self._NUM_PARALLEL_PAGES = 15 | |
39 | |
40 assert self._NUM_PARALLEL_PAGES <= self._NUM_TABS, (' the batch size can\'t' | |
41 ' be larger than the number of available tabs') | |
42 | 45 |
43 # The amount of time to wait for a batch of pages to finish loading. | 46 # The amount of time to wait for a batch of pages to finish loading. |
44 self._BATCH_PAGE_LOAD_TIMEOUT_IN_SECONDS = 10 | 47 self._BATCH_PAGE_LOAD_TIMEOUT_IN_SECONDS = 10 |
45 | 48 |
46 # The default amount of time to wait for the retrieval of the URL of a tab. | 49 # The default amount of time to wait for the retrieval of the URL of a tab. |
47 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS = 1 | 50 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS = 1 |
48 | 51 |
49 def Run(self, finder_options): | 52 def Run(self, finder_options): |
50 """Extends the profile. | 53 """Extends the profile. |
51 | 54 |
52 Args: | 55 Args: |
53 finder_options: An instance of BrowserFinderOptions that contains the | 56 finder_options: An instance of BrowserFinderOptions that contains the |
54 directory of the input profile, the directory to place the output | 57 directory of the input profile, the directory to place the output |
55 profile, and sufficient information to choose a specific browser binary. | 58 profile, and sufficient information to choose a specific browser binary. |
56 """ | 59 """ |
57 try: | 60 try: |
58 self._navigation_urls = self.GetUrlsToNavigate() | 61 self.SetUp(finder_options) |
59 self._SetUpBrowser(finder_options) | |
60 self._PerformNavigations() | 62 self._PerformNavigations() |
61 finally: | 63 finally: |
62 self._TearDownBrowser() | 64 self.TearDown() |
63 | 65 |
64 def GetUrlsToNavigate(self): | 66 def GetUrlIterator(self): |
65 """Returns a list of urls to be navigated to. | 67 """Gets URLs for the browser to navigate to. |
| 68 |
| 69 Intended for subclass override. |
| 70 |
| 71 Returns: |
| 72 An iterator whose elements are urls to be navigated to. |
| 73 """ |
| 74 raise NotImplementedError() |
| 75 |
| 76 def ShouldExitAfterBatchNavigation(self): |
| 77 """Returns a boolean indicating whether profile extension is finished. |
66 | 78 |
67 Intended for subclass override. | 79 Intended for subclass override. |
68 """ | 80 """ |
69 raise NotImplementedError() | 81 raise NotImplementedError() |
70 | 82 |
| 83 def SetUp(self, finder_options): |
| 84 """Finds the browser, starts the browser, and opens the requisite number of |
| 85 tabs. |
| 86 |
| 87 Can be overridden by subclasses. Subclasses must call the super class |
| 88 implementation. |
| 89 """ |
| 90 self._profile_path = finder_options.output_profile_path |
| 91 possible_browser = self._GetPossibleBrowser(finder_options) |
| 92 |
| 93 assert possible_browser.supports_tab_control |
| 94 assert (platform.GetHostPlatform().GetOSName() in |
| 95 ["win", "mac", "linux"]) |
| 96 self._browser = possible_browser.Create(finder_options) |
| 97 |
| 98 while(len(self._browser.tabs) < self._NUM_TABS): |
| 99 self._browser.tabs.New() |
| 100 |
| 101 def TearDown(self): |
| 102 """Teardown that is guaranteed to be executed before the instance is |
| 103 destroyed. |
| 104 |
| 105 Can be overridden by subclasses. Subclasses must call the super class |
| 106 implementation. |
| 107 """ |
| 108 if self._browser: |
| 109 self._browser.Close() |
| 110 self._browser = None |
| 111 |
| 112 def CleanUpAfterBatchNavigation(self): |
| 113 """A hook for subclasses to perform cleanup after each batch of |
| 114 navigations. |
| 115 |
| 116 Can be overridden by subclasses. |
| 117 """ |
| 118 pass |
| 119 |
| 120 @property |
| 121 def profile_path(self): |
| 122 return self._profile_path |
71 | 123 |
72 def _GetPossibleBrowser(self, finder_options): | 124 def _GetPossibleBrowser(self, finder_options): |
73 """Return a possible_browser with the given options.""" | 125 """Return a possible_browser with the given options.""" |
74 possible_browser = browser_finder.FindBrowser(finder_options) | 126 possible_browser = browser_finder.FindBrowser(finder_options) |
75 if not possible_browser: | 127 if not possible_browser: |
76 raise browser_finder_exceptions.BrowserFinderException( | 128 raise browser_finder_exceptions.BrowserFinderException( |
77 'No browser found.\n\nAvailable browsers:\n%s\n' % | 129 'No browser found.\n\nAvailable browsers:\n%s\n' % |
78 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) | 130 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) |
79 finder_options.browser_options.browser_type = ( | 131 finder_options.browser_options.browser_type = ( |
80 possible_browser.browser_type) | 132 possible_browser.browser_type) |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 | 207 |
156 seconds_to_wait = end_time - time.time() | 208 seconds_to_wait = end_time - time.time() |
157 seconds_to_wait = max(0, seconds_to_wait) | 209 seconds_to_wait = max(0, seconds_to_wait) |
158 | 210 |
159 try: | 211 try: |
160 tab.WaitForDocumentReadyStateToBeComplete(seconds_to_wait) | 212 tab.WaitForDocumentReadyStateToBeComplete(seconds_to_wait) |
161 except (util.TimeoutException, exceptions.DevtoolsTargetCrashException): | 213 except (util.TimeoutException, exceptions.DevtoolsTargetCrashException): |
162 # Ignore time outs and web page crashes. | 214 # Ignore time outs and web page crashes. |
163 pass | 215 pass |
164 | 216 |
165 def _SetUpBrowser(self, finder_options): | 217 def _GetUrlsToNavigate(self, url_iterator): |
166 """Finds the browser, starts the browser, and opens the requisite number of | 218 """Returns an array of urls to navigate to, given a url_iterator.""" |
167 tabs.""" | 219 urls = [] |
168 possible_browser = self._GetPossibleBrowser(finder_options) | 220 for _ in xrange(self._NUM_TABS): |
169 self._browser = possible_browser.Create(finder_options) | 221 try: |
170 | 222 urls.append(url_iterator.next()) |
171 for _ in range(self._NUM_TABS): | 223 except StopIteration: |
172 self._browser.tabs.New() | 224 break |
| 225 return urls |
173 | 226 |
174 def _PerformNavigations(self): | 227 def _PerformNavigations(self): |
175 """Performs the navigations specified by |_navigation_urls| in large | 228 """Repeatedly fetches a batch of urls, and navigates to those urls. This |
176 batches.""" | 229 will run until an empty batch is returned, or |
177 # The index of the first url that has not yet been navigated to. | 230 ShouldExitAfterBatchNavigation() returns True. |
178 navigation_url_index = 0 | 231 """ |
| 232 url_iterator = self.GetUrlIterator() |
179 while True: | 233 while True: |
180 # Generate the next batch of navigations. | 234 urls = self._GetUrlsToNavigate(url_iterator) |
| 235 |
| 236 if len(urls) == 0: |
| 237 break |
| 238 |
181 batch = [] | 239 batch = [] |
182 max_index = min(navigation_url_index + self._NUM_PARALLEL_PAGES, | 240 for i in range(len(urls)): |
183 len(self._navigation_urls)) | 241 url = urls[i] |
184 for i in range(navigation_url_index, max_index): | 242 tab = self._browser.tabs[i] |
185 url = self._navigation_urls[i] | |
186 tab = self._browser.tabs[i % self._NUM_TABS] | |
187 batch.append((tab, url)) | 243 batch.append((tab, url)) |
188 navigation_url_index = max_index | |
189 | 244 |
190 queued_tabs = self._BatchNavigateTabs(batch) | 245 queued_tabs = self._BatchNavigateTabs(batch) |
191 self._WaitForQueuedTabsToLoad(queued_tabs) | 246 self._WaitForQueuedTabsToLoad(queued_tabs) |
192 | 247 |
193 if navigation_url_index == len(self._navigation_urls): | 248 self.CleanUpAfterBatchNavigation() |
| 249 |
| 250 if self.ShouldExitAfterBatchNavigation(): |
194 break | 251 break |
195 | |
196 def _TearDownBrowser(self): | |
197 """Teardown that is guaranteed to be executed before the instance is | |
198 destroyed.""" | |
199 if self._browser: | |
200 self._browser.Close() | |
201 self._browser = None | |
OLD | NEW |