Source code

Revision control

Other Tools

1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5
import os
6
import sys
7
import yaml
8
9
import mozinfo
10
11
from marionette_driver.errors import JavascriptException, ScriptTimeoutException
12
from mozproxy import get_playback
13
14
AWSY_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
15
if AWSY_PATH not in sys.path:
16
sys.path.append(AWSY_PATH)
17
18
from awsy import process_perf_data, webservers
19
from awsy.awsy_test_case import AwsyTestCase
20
21
22
class TestMemoryUsage(AwsyTestCase):
23
"""Provides a test that collects memory usage at various checkpoints:
24
- "Start" - Just after startup
25
- "StartSettled" - After an additional wait time
26
- "TabsOpen" - After opening all provided URLs
27
- "TabsOpenSettled" - After an additional wait time
28
- "TabsOpenForceGC" - After forcibly invoking garbage collection
29
- "TabsClosed" - After closing all tabs
30
- "TabsClosedSettled" - After an additional wait time
31
- "TabsClosedForceGC" - After forcibly invoking garbage collection
32
"""
33
34
def urls(self):
35
return self._urls
36
37
def perf_suites(self):
38
return process_perf_data.PERF_SUITES
39
40
def perf_checkpoints(self):
41
return process_perf_data.CHECKPOINTS
42
43
def perf_extra_opts(self):
44
return self._extra_opts
45
46
def setupTp5(self):
47
urls = None
48
default_tp5n_manifest = os.path.join(self._webroot_dir, 'page_load_test', 'tp5n',
49
'tp5n.manifest')
50
tp5n_manifest = self.testvars.get("pageManifest", default_tp5n_manifest)
51
with open(tp5n_manifest) as fp:
52
urls = fp.readlines()
53
urls = map(lambda x: x.replace('localhost', 'localhost:{}'), urls)
54
55
# We haven't set self._urls yet, so this value might be zero if
56
# 'entities' wasn't specified.
57
to_load = self.pages_to_load()
58
if not to_load:
59
to_load = len(urls)
60
self._webservers = webservers.WebServers("localhost",
61
8001,
62
self._webroot_dir,
63
to_load)
64
self._webservers.start()
65
for url, server in zip(urls, self._webservers.servers):
66
self._urls.append(url.strip().format(server.port))
67
68
def setupTp6(self):
69
# tp5n stores its manifest in the zip file that gets extracted, tp6
70
# doesn't so we just keep one in our project dir for now.
71
default_tp6_pages_manifest = os.path.join(AWSY_PATH, 'conf', 'tp6-pages.yml')
72
tp6_pages_manifest = self.testvars.get("pageManifest", default_tp6_pages_manifest)
73
urls = []
74
recordings = set()
75
with open(tp6_pages_manifest) as f:
76
d = yaml.safe_load(f)
77
for r in d:
78
recordings.add(r['rec'])
79
url = r['url']
80
if isinstance(url, list):
81
urls.extend(url)
82
else:
83
urls.append(url)
84
85
self._urls = urls
86
87
# Indicate that we're using tp6 in the perf data.
88
self._extra_opts = ["tp6"]
89
90
# Now we setup the mitm proxy with our tp6 pageset.
91
tp6_pageset_manifest = os.path.join(AWSY_PATH, 'tp6-pageset.manifest')
92
config = {
93
'playback_tool': 'mitmproxy',
94
'playback_binary_manifest': 'mitmproxy-rel-bin-{platform}.manifest',
95
'playback_pageset_manifest': tp6_pageset_manifest,
96
'platform': mozinfo.os,
97
'obj_path': self._webroot_dir,
98
'binary': self._binary,
99
'run_local': self._run_local,
100
'app': 'firefox',
101
'host': 'localhost',
102
'ignore_mitmdump_exit_failure': True,
103
}
104
105
self._playback = get_playback(config)
106
107
script = os.path.join(AWSY_PATH, "awsy", "alternate-server-replay.py")
108
recording_arg = []
109
for recording in recordings:
110
recording_arg.append(os.path.join(self._playback.mozproxy_dir, recording))
111
112
script = '""%s %s""' % (script, " ".join(recording_arg))
113
114
if mozinfo.os == "win":
115
script = script.replace("\\", "\\\\\\")
116
117
# --no-upstream-cert prevents mitmproxy from needing network access to
118
# the upstream servers
119
self._playback.config['playback_tool_args'] = [
120
"--no-upstream-cert",
121
"-s", script]
122
123
self.logger.info("Using script %s" % script)
124
125
self._playback.start()
126
127
# We need to reload after the mitmproxy cert is installed
128
self.marionette.restart(clean=False)
129
130
# Setup WebDriver capabilities that we need
131
self.marionette.delete_session()
132
caps = {
133
"unhandledPromptBehavior": "dismiss", # Ignore page navigation warnings
134
}
135
self.marionette.start_session(caps)
136
self.marionette.set_context('chrome')
137
138
def setUp(self):
139
AwsyTestCase.setUp(self)
140
self.logger.info("setting up")
141
self._webroot_dir = self.testvars["webRootDir"]
142
self._urls = []
143
self._extra_opts = None
144
145
if self.testvars.get("tp6", False):
146
self.setupTp6()
147
else:
148
self.setupTp5()
149
150
self.logger.info("areweslimyet run by %d pages, %d iterations,"
151
" %d perTabPause, %d settleWaitTime"
152
% (self._pages_to_load, self._iterations,
153
self._perTabPause, self._settleWaitTime))
154
self.logger.info("done setting up!")
155
156
def tearDown(self):
157
self.logger.info("tearing down!")
158
159
self.logger.info("tearing down webservers!")
160
161
if self.testvars.get("tp6", False):
162
self._playback.stop()
163
else:
164
self._webservers.stop()
165
166
AwsyTestCase.tearDown(self)
167
168
self.logger.info("done tearing down!")
169
170
def clear_preloaded_browser(self):
171
"""
172
Clears out the preloaded browser.
173
174
Note: Does nothing on older builds that don't have a
175
`gBrowser.removePreloadedBrowser` method.
176
"""
177
self.logger.info("closing preloaded browser")
178
script = """
179
if (window.NewTabPagePreloading) {
180
return NewTabPagePreloading.removePreloadedBrowser(window);
181
}
182
if ("removePreloadedBrowser" in gBrowser) {
183
return gBrowser.removePreloadedBrowser();
184
}
185
return "gBrowser.removePreloadedBrowser not available";
186
"""
187
try:
188
result = self.marionette.execute_script(script,
189
script_timeout=180000)
190
except JavascriptException, e:
191
self.logger.error("removePreloadedBrowser() JavaScript error: %s" % e)
192
except ScriptTimeoutException:
193
self.logger.error("removePreloadedBrowser() timed out")
194
except Exception:
195
self.logger.error(
196
"removePreloadedBrowser() Unexpected error: %s" % sys.exc_info()[0])
197
else:
198
if result:
199
self.logger.info(result)
200
201
def test_open_tabs(self):
202
"""Marionette test entry that returns an array of checkoint arrays.
203
204
This will generate a set of checkpoints for each iteration requested.
205
Upon succesful completion the results will be stored in
206
|self.testvars["results"]| and accessible to the test runner via the
207
|testvars| object it passed in.
208
"""
209
# setup the results array
210
results = [[] for _ in range(self.iterations())]
211
212
def create_checkpoint(name, iteration):
213
checkpoint = self.do_memory_report(name, iteration)
214
self.assertIsNotNone(checkpoint, "Checkpoint was recorded")
215
results[iteration].append(checkpoint)
216
217
# The first iteration gets Start and StartSettled entries before
218
# opening tabs
219
create_checkpoint("Start", 0)
220
self.settle()
221
create_checkpoint("StartSettled", 0)
222
223
for itr in range(self.iterations()):
224
self.open_pages()
225
226
create_checkpoint("TabsOpen", itr)
227
self.settle()
228
create_checkpoint("TabsOpenSettled", itr)
229
self.assertTrue(self.do_full_gc())
230
create_checkpoint("TabsOpenForceGC", itr)
231
232
# Close all tabs
233
self.reset_state()
234
235
with self.marionette.using_context('content'):
236
self.logger.info("navigating to about:blank")
237
self.marionette.navigate("about:blank")
238
self.logger.info("navigated to about:blank")
239
self.signal_user_active()
240
241
# Create checkpoint that may contain retained processes that will
242
# be reused.
243
create_checkpoint("TabsClosedExtraProcesses", itr)
244
245
# Clear out the retained processes and measure again.
246
self.clear_preloaded_browser()
247
248
create_checkpoint("TabsClosed", itr)
249
self.settle()
250
create_checkpoint("TabsClosedSettled", itr)
251
self.assertTrue(self.do_full_gc(), "GC ran")
252
create_checkpoint("TabsClosedForceGC", itr)
253
254
# TODO(ER): Temporary hack until bug 1121139 lands
255
self.logger.info("setting results")
256
self.testvars["results"] = results