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 __init__(self): | |
24 super(FastNavigationProfileExtender, self).__init__() | |
25 | |
26 # A reference to the browser that will be performing all of the tab | |
27 # navigations. | |
28 self._browser = None | |
29 | |
30 # A static copy of the urls that this class is going to navigate to. | |
31 self._navigation_urls = self.NavigationUrls() | |
nednguyen
2015/02/09 21:35:02
def GetUrlsToNavigate()
erikchen
2015/02/10 00:45:02
Done.
| |
32 | |
33 # The number of tabs to use. | |
34 self._NUM_TABS = 15 | |
35 | |
36 # The number of pages to load in parallel. | |
37 self._NUM_PARALLEL_PAGES = 15 | |
38 | |
39 # The amount of time to wait for pages to finish loading. | |
40 self._PAGE_LOAD_TIMEOUT_IN_SECONDS = 10 | |
41 | |
42 # The amount of time to wait for the retrieval of the URL of a tab. | |
43 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS = 1 | |
44 | |
45 # The amount of time to wait for a navigation to be committed. | |
46 self._NAVIGATION_COMMIT_WAIT_IN_SECONDS = 0.1 | |
47 | |
48 # A list of tuples (tab, initial_url). A navigation command has been sent | |
49 # to |tab|. |tab| had a URL of |initial_url| before the command was sent. | |
50 self._queued_tabs = [] | |
nednguyen
2015/02/09 21:35:02
What about making this a map from tabs to intial_u
erikchen
2015/02/10 00:45:02
That loses the ordering, which matters for perform
nednguyen
2015/02/10 18:48:43
Can you elaborate on why the ordering matters for
erikchen
2015/02/10 21:30:11
Sending commands through the telemetry/Chromium bi
nednguyen
2015/02/10 22:11:37
I want to see real benchmark to justify whether th
erikchen
2015/02/11 02:45:50
There is no optimization here. I'm using a list of
nednguyen
2015/02/12 02:10:53
Hmh, there was context, but the context has change
erikchen
2015/02/12 03:02:58
Your statement "You was using _navigation_url_inde
| |
51 | |
52 # The index of the first url that has not yet been navigated to. | |
53 self._navigation_url_index = 0 | |
nednguyen
2015/02/09 21:35:02
I don't like having this counter as a class's memb
erikchen
2015/02/10 00:45:02
I've removed this member.
| |
54 | |
55 def Run(self, finder_options): | |
56 """ | |
57 |finder_options| contains the directory of the input profile, the directory | |
58 to place the output profile, and sufficient information to choose a specific | |
59 browser binary. | |
60 """ | |
61 try: | |
62 self._BrowserSetup(finder_options) | |
nednguyen
2015/02/09 21:35:02
s/_BrowserSetup/_SetupBrowser
erikchen
2015/02/10 00:45:02
I've renamed _BrowserSetup to _SetUpBrowser.
| |
63 self._PerformNavigations() | |
64 except: | |
nednguyen
2015/02/09 21:35:02
you don't need the "except:\nraise:"
erikchen
2015/02/10 00:45:02
Done.
| |
65 raise | |
66 finally: | |
67 self._BrowserTeardown() | |
nednguyen
2015/02/09 21:35:02
s/_BrowserTeardown/_TeardownBrowser()
erikchen
2015/02/10 00:45:02
I've renamed _BrowserTeardown to _TearDownBrowser.
| |
68 | |
69 def NavigationUrls(self): | |
70 """ | |
71 Intended for subclass override. Returns a list of urls to be navigated to. | |
72 """ | |
73 raise NotImplementedError() | |
74 | |
75 | |
76 def _GetPossibleBrowser(self, finder_options): | |
77 """Return a possible_browser with the given options.""" | |
78 possible_browser = browser_finder.FindBrowser(finder_options) | |
79 if not possible_browser: | |
80 raise browser_finder_exceptions.BrowserFinderException( | |
81 'No browser found.\n\nAvailable browsers:\n%s\n' % | |
82 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options))) | |
83 finder_options.browser_options.browser_type = ( | |
84 possible_browser.browser_type) | |
85 | |
86 return possible_browser | |
87 | |
88 def _RetrieveTabUrl(self, tab): | |
89 """Retrives the URL of the tab.""" | |
90 try: | |
91 return tab.EvaluateJavaScript('document.URL', | |
92 self._TAB_URL_RETRIEVAL_TIMEOUT_IN_SECONDS) | |
93 except exceptions.DevtoolsTargetCrashException: | |
94 return None | |
95 | |
96 def _BatchNavigateTabs(self): | |
97 """Performs a batch of tab navigations with minimal delay.""" | |
98 max_index = min(self._navigation_url_index + self._NUM_PARALLEL_PAGES, | |
99 len(self._navigation_urls)) | |
100 timeout_in_seconds = 0 | |
101 | |
102 for i in range(self._navigation_url_index, max_index): | |
103 url = self._navigation_urls[i] | |
104 tab = self._browser.tabs[i % self._NUM_TABS] | |
105 initial_url = self._RetrieveTabUrl(tab) | |
106 | |
107 try: | |
108 tab.Navigate(url, None, timeout_in_seconds) | |
109 except exceptions.DevtoolsTargetCrashException: | |
110 # We expect a time out, and don't mind if the webpage crashes. Ignore | |
111 # both exceptions. | |
112 pass | |
113 | |
114 self._queued_tabs.append((tab, initial_url)) | |
115 self._navigation_url_index = max_index | |
116 | |
117 def _WaitForQueuedTabsToLoad(self): | |
118 """Waits for all the batch navigated tabs to finish loading.""" | |
119 end_time = time.time() + self._PAGE_LOAD_TIMEOUT_IN_SECONDS | |
120 for tab, initial_url in self._queued_tabs: | |
121 seconds_to_wait = end_time - time.time() | |
122 seconds_to_wait = max(0, seconds_to_wait) | |
123 | |
124 if seconds_to_wait == 0: | |
125 break | |
126 | |
127 # Since we don't wait any time for the tab url navigation to commit, it's | |
128 # possible that the tab hasn't started navigating yet. | |
129 current_url = self._RetrieveTabUrl(tab) | |
130 | |
131 if current_url == initial_url: | |
132 # If the navigation hasn't been committed yet, wait a small amount of | |
133 # time. Don't bother rechecking the condition, since it's also possible | |
134 # that the web page isn't processing javascript. | |
135 time.sleep(self._NAVIGATION_COMMIT_WAIT_IN_SECONDS) | |
136 | |
137 try: | |
138 tab.WaitForDocumentReadyStateToBeComplete(seconds_to_wait) | |
139 except (util.TimeoutException, exceptions.DevtoolsTargetCrashException): | |
140 # Ignore time outs and web page crashes. | |
141 pass | |
142 self._queued_tabs = [] | |
143 | |
144 def _BrowserSetup(self, finder_options): | |
145 """ | |
146 Finds the browser, starts the browser, and opens the requisite number of | |
147 tabs. | |
148 """ | |
149 possible_browser = self._GetPossibleBrowser(finder_options) | |
150 self._browser = possible_browser.Create(finder_options) | |
151 | |
152 for _ in range(self._NUM_TABS): | |
153 self._browser.tabs.New() | |
154 | |
155 def _PerformNavigations(self): | |
156 """ | |
157 Performs the navigations specified by |_navigation_urls| in large batches. | |
158 """ | |
159 while True: | |
160 self._BatchNavigateTabs() | |
161 self._WaitForQueuedTabsToLoad() | |
162 | |
163 if self._navigation_url_index == len(self._navigation_urls): | |
164 break | |
165 | |
166 def _BrowserTeardown(self): | |
167 """ | |
168 Teardown that is guaranteed to be executed before the instance is destroyed. | |
169 """ | |
170 if self._browser: | |
171 self._browser.Close() | |
172 self._browser = None | |
OLD | NEW |