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 |