OLD | NEW |
1 #! /usr/bin/python | 1 #! /usr/bin/python |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import argparse | 6 import argparse |
7 import cgi | 7 import cgi |
8 import json | 8 import json |
9 import logging | 9 import logging |
10 import os | 10 import os |
11 import subprocess | 11 import subprocess |
12 import sys | 12 import sys |
13 import tempfile | 13 import tempfile |
14 import time | 14 import time |
15 | 15 |
16 _SRC_DIR = os.path.abspath(os.path.join( | 16 _SRC_DIR = os.path.abspath(os.path.join( |
17 os.path.dirname(__file__), '..', '..', '..')) | 17 os.path.dirname(__file__), '..', '..', '..')) |
18 | 18 |
19 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) | 19 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) |
20 from devil.android import device_utils | 20 from devil.android import device_utils |
21 from devil.android.sdk import intent | 21 from devil.android.sdk import intent |
22 | 22 |
23 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) | 23 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) |
24 import devil_chromium | 24 import devil_chromium |
25 from pylib import constants | 25 from pylib import constants |
26 | 26 |
27 import log_parser | 27 import device_setup |
28 import log_requests | |
29 import loading_model | 28 import loading_model |
| 29 import loading_trace |
| 30 import trace_recorder |
30 | 31 |
31 | 32 |
32 # TODO(mattcary): logging.info isn't that useful; we need something finer | 33 # TODO(mattcary): logging.info isn't that useful, as the whole (tools) world |
33 # grained. For now we just do logging.warning. | 34 # uses logging info; we need to introduce logging modules to get finer-grained |
| 35 # output. For now we just do logging.warning. |
34 | 36 |
35 | 37 |
36 # TODO(mattcary): probably we want this piped in through a flag. | 38 # TODO(mattcary): probably we want this piped in through a flag. |
37 CHROME = constants.PACKAGE_INFO['chrome'] | 39 CHROME = constants.PACKAGE_INFO['chrome'] |
38 | 40 |
39 | 41 |
40 def _SetupAndGetDevice(): | |
41 """Gets an android device, set up the way we like it. | |
42 | |
43 Returns: | |
44 An instance of DeviceUtils for the first device found. | |
45 """ | |
46 device = device_utils.DeviceUtils.HealthyDevices()[0] | |
47 device.EnableRoot() | |
48 device.KillAll(CHROME.package, quiet=True) | |
49 return device | |
50 | |
51 | |
52 def _LoadPage(device, url): | 42 def _LoadPage(device, url): |
53 """Load a page on chrome on our device. | 43 """Load a page on chrome on our device. |
54 | 44 |
55 Args: | 45 Args: |
56 device: an AdbWrapper for the device on which to load the page. | 46 device: an AdbWrapper for the device on which to load the page. |
57 url: url as a string to load. | 47 url: url as a string to load. |
58 """ | 48 """ |
59 load_intent = intent.Intent( | 49 load_intent = intent.Intent( |
60 package=CHROME.package, activity=CHROME.activity, data=url) | 50 package=CHROME.package, activity=CHROME.activity, data=url) |
61 logging.warning('Loading ' + url) | 51 logging.warning('Loading ' + url) |
(...skipping 30 matching lines...) Expand all Loading... |
92 <html> | 82 <html> |
93 <head> | 83 <head> |
94 <title>%s</title> | 84 <title>%s</title> |
95 """ % title) | 85 """ % title) |
96 for info in graph.ResourceInfo(): | 86 for info in graph.ResourceInfo(): |
97 output.append('<link rel="prefetch" href="%s">\n' % info.Url()) | 87 output.append('<link rel="prefetch" href="%s">\n' % info.Url()) |
98 output.append("""</head> | 88 output.append("""</head> |
99 <body>%s</body> | 89 <body>%s</body> |
100 </html> | 90 </html> |
101 """ % title) | 91 """ % title) |
102 | |
103 return '\n'.join(output) | 92 return '\n'.join(output) |
104 | 93 |
105 | 94 |
106 def _LogRequests(url, clear_cache=True, local=False): | 95 def _LogRequests(url, clear_cache=True, local=False): |
107 """Log requests for a web page. | 96 """Log requests for a web page. |
108 | 97 |
109 TODO(mattcary): loading.log_requests probably needs to be refactored as we're | |
110 using private methods, also there's ugliness like _ResponseDataToJson return a | |
111 json.dumps that we immediately json.loads. | |
112 | |
113 Args: | 98 Args: |
114 url: url to log as string. | 99 url: url to log as string. |
115 clear_cache: optional flag to clear the cache. | 100 clear_cache: optional flag to clear the cache. |
116 local: log from local (desktop) chrome session. | 101 local: log from local (desktop) chrome session. |
117 | 102 |
118 Returns: | 103 Returns: |
119 JSON of logged information (ie, a dict that describes JSON). | 104 JSON dict of logged information (ie, a dict that describes JSON). |
120 """ | 105 """ |
121 device = _SetupAndGetDevice() if not local else None | 106 device = device_setup.GetFirstDevice() if not local else None |
122 request_logger = log_requests.AndroidRequestsLogger(device) | 107 with device_setup.DeviceConnection(device) as connection: |
123 logging.warning('Logging %scached %s' % ('un' if clear_cache else '', url)) | 108 trace = trace_recorder.MonitorUrl(connection, url, clear_cache=clear_cache) |
124 response_data = request_logger.LogPageLoad( | 109 return trace.ToJsonDict() |
125 url, clear_cache, 'chrome') | |
126 return json.loads(log_requests._ResponseDataToJson(response_data)) | |
127 | 110 |
128 | 111 |
129 def _FullFetch(url, json_output, prefetch, local, prefetch_delay_seconds): | 112 def _FullFetch(url, json_output, prefetch, local, prefetch_delay_seconds): |
130 """Do a full fetch with optional prefetching.""" | 113 """Do a full fetch with optional prefetching.""" |
131 if not url.startswith('http'): | 114 if not url.startswith('http'): |
132 url = 'http://' + url | 115 url = 'http://' + url |
133 logging.warning('Cold fetch') | 116 logging.warning('Cold fetch') |
134 cold_data = _LogRequests(url, local=local) | 117 cold_data = _LogRequests(url, local=local) |
135 assert cold_data, 'Cold fetch failed to produce data. Check your phone.' | 118 assert cold_data, 'Cold fetch failed to produce data. Check your phone.' |
136 if prefetch: | 119 if prefetch: |
137 assert not local | 120 assert not local |
138 logging.warning('Generating prefetch') | 121 logging.warning('Generating prefetch') |
139 prefetch_html = _GetPrefetchHtml(_ProcessJson(cold_data), name=url) | 122 prefetch_html = _GetPrefetchHtml( |
| 123 loading_model.ResourceGraph(cold_data), name=url) |
140 tmp = tempfile.NamedTemporaryFile() | 124 tmp = tempfile.NamedTemporaryFile() |
141 tmp.write(prefetch_html) | 125 tmp.write(prefetch_html) |
142 tmp.flush() | 126 tmp.flush() |
143 # We hope that the tmpfile name is unique enough for the device. | 127 # We hope that the tmpfile name is unique enough for the device. |
144 target = os.path.join('/sdcard/Download', os.path.basename(tmp.name)) | 128 target = os.path.join('/sdcard/Download', os.path.basename(tmp.name)) |
145 device = _SetupAndGetDevice() | 129 device = device_setup.GetFirstDevice() |
146 device.adb.Push(tmp.name, target) | 130 device.adb.Push(tmp.name, target) |
147 logging.warning('Pushed prefetch %s to device at %s' % (tmp.name, target)) | 131 logging.warning('Pushed prefetch %s to device at %s' % (tmp.name, target)) |
148 _LoadPage(device, 'file://' + target) | 132 _LoadPage(device, 'file://' + target) |
149 time.sleep(prefetch_delay_seconds) | 133 time.sleep(prefetch_delay_seconds) |
150 logging.warning('Warm fetch') | 134 logging.warning('Warm fetch') |
151 warm_data = _LogRequests(url, clear_cache=False) | 135 warm_data = _LogRequests(url, clear_cache=False) |
152 with open(json_output, 'w') as f: | 136 with open(json_output, 'w') as f: |
153 _WriteJson(f, warm_data) | 137 _WriteJson(f, warm_data) |
154 logging.warning('Wrote ' + json_output) | 138 logging.warning('Wrote ' + json_output) |
155 with open(json_output + '.cold', 'w') as f: | 139 with open(json_output + '.cold', 'w') as f: |
156 _WriteJson(f, cold_data) | 140 _WriteJson(f, cold_data) |
157 logging.warning('Wrote ' + json_output + '.cold') | 141 logging.warning('Wrote ' + json_output + '.cold') |
158 else: | 142 else: |
159 with open(json_output, 'w') as f: | 143 with open(json_output, 'w') as f: |
160 _WriteJson(f, cold_data) | 144 _WriteJson(f, cold_data) |
161 logging.warning('Wrote ' + json_output) | 145 logging.warning('Wrote ' + json_output) |
162 | 146 |
163 | 147 |
164 # TODO(mattcary): it would be nice to refactor so the --noads flag gets dealt | 148 # TODO(mattcary): it would be nice to refactor so the --noads flag gets dealt |
165 # with here. | 149 # with here. |
166 def _ProcessRequests(filename): | 150 def _ProcessRequests(filename): |
167 requests = log_parser.FilterRequests(log_parser.ParseJsonFile(filename)) | 151 with open(filename) as f: |
168 return loading_model.ResourceGraph(requests) | 152 return loading_model.ResourceGraph( |
169 | 153 loading_trace.LoadingTrace.FromJsonDict(json.load(f))) |
170 | |
171 def _ProcessJson(json_data): | |
172 assert json_data | |
173 return loading_model.ResourceGraph(log_parser.FilterRequests( | |
174 [log_parser.RequestData.FromDict(r) for r in json_data])) | |
175 | 154 |
176 | 155 |
177 def InvalidCommand(cmd): | 156 def InvalidCommand(cmd): |
178 sys.exit('Invalid command "%s"\nChoices are: %s' % | 157 sys.exit('Invalid command "%s"\nChoices are: %s' % |
179 (cmd, ' '.join(COMMAND_MAP.keys()))) | 158 (cmd, ' '.join(COMMAND_MAP.keys()))) |
180 | 159 |
181 | 160 |
182 def DoCost(arg_str): | 161 def DoCost(arg_str): |
183 parser = argparse.ArgumentParser(usage='cost [--parameter ...] REQUEST_JSON') | 162 parser = argparse.ArgumentParser(usage='cost [--parameter ...] REQUEST_JSON') |
184 parser.add_argument('request_json') | 163 parser.add_argument('request_json') |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
248 usage='prefetch_setup [--upload] REQUEST_JSON TARGET_HTML') | 227 usage='prefetch_setup [--upload] REQUEST_JSON TARGET_HTML') |
249 parser.add_argument('request_json') | 228 parser.add_argument('request_json') |
250 parser.add_argument('target_html') | 229 parser.add_argument('target_html') |
251 parser.add_argument('--upload', action='store_true') | 230 parser.add_argument('--upload', action='store_true') |
252 args = parser.parse_args(arg_str) | 231 args = parser.parse_args(arg_str) |
253 graph = _ProcessRequests(args.request_json) | 232 graph = _ProcessRequests(args.request_json) |
254 with open(args.target_html, 'w') as html: | 233 with open(args.target_html, 'w') as html: |
255 html.write(_GetPrefetchHtml( | 234 html.write(_GetPrefetchHtml( |
256 graph, name=os.path.basename(args.request_json))) | 235 graph, name=os.path.basename(args.request_json))) |
257 if args.upload: | 236 if args.upload: |
258 device = _SetupAndGetDevice() | 237 device = device_setup.GetFirstDevice() |
259 destination = os.path.join('/sdcard/Download', | 238 destination = os.path.join('/sdcard/Download', |
260 os.path.basename(args.target_html)) | 239 os.path.basename(args.target_html)) |
261 device.adb.Push(args.target_html, destination) | 240 device.adb.Push(args.target_html, destination) |
262 | 241 |
263 logging.warning( | 242 logging.warning( |
264 'Pushed %s to device at %s' % (args.target_html, destination)) | 243 'Pushed %s to device at %s' % (args.target_html, destination)) |
265 | 244 |
266 | 245 |
267 def DoLogRequests(arg_str): | 246 def DoLogRequests(arg_str): |
268 parser = argparse.ArgumentParser( | 247 parser = argparse.ArgumentParser( |
(...skipping 26 matching lines...) Expand all Loading... |
295 args = parser.parse_args(arg_str) | 274 args = parser.parse_args(arg_str) |
296 if not os.path.exists(args.dir): | 275 if not os.path.exists(args.dir): |
297 os.makedirs(args.dir) | 276 os.makedirs(args.dir) |
298 _FullFetch(url=args.site, | 277 _FullFetch(url=args.site, |
299 json_output=os.path.join(args.dir, args.site + '.json'), | 278 json_output=os.path.join(args.dir, args.site + '.json'), |
300 prefetch=True, | 279 prefetch=True, |
301 prefetch_delay_seconds=args.prefetch_delay_seconds, | 280 prefetch_delay_seconds=args.prefetch_delay_seconds, |
302 local=False) | 281 local=False) |
303 | 282 |
304 | 283 |
305 def DoTracing(arg_str): | |
306 parser = argparse.ArgumentParser( | |
307 usage='tracing URL JSON_OUTPUT') | |
308 parser.add_argument('url') | |
309 parser.add_argument('json_output') | |
310 args = parser.parse_args(arg_str) | |
311 device = _SetupAndGetDevice() | |
312 request_logger = log_requests.AndroidRequestsLogger(device) | |
313 tracing = request_logger.LogTracing(args.url) | |
314 with open(args.json_output, 'w') as f: | |
315 _WriteJson(f, tracing) | |
316 logging.warning('Wrote ' + args.json_output) | |
317 | |
318 | |
319 def DoLongPole(arg_str): | 284 def DoLongPole(arg_str): |
320 parser = argparse.ArgumentParser(usage='longpole [--noads] REQUEST_JSON') | 285 parser = argparse.ArgumentParser(usage='longpole [--noads] REQUEST_JSON') |
321 parser.add_argument('request_json') | 286 parser.add_argument('request_json') |
322 parser.add_argument('--noads', action='store_true') | 287 parser.add_argument('--noads', action='store_true') |
323 args = parser.parse_args(arg_str) | 288 args = parser.parse_args(arg_str) |
324 graph = _ProcessRequests(args.request_json) | 289 graph = _ProcessRequests(args.request_json) |
325 if args.noads: | 290 if args.noads: |
326 graph.Set(node_filter=graph.FilterAds) | 291 graph.Set(node_filter=graph.FilterAds) |
327 path_list = [] | 292 path_list = [] |
328 cost = graph.Cost(path_list=path_list) | 293 cost = graph.Cost(path_list=path_list) |
(...skipping 10 matching lines...) Expand all Loading... |
339 graph.Set(node_filter=graph.FilterAds) | 304 graph.Set(node_filter=graph.FilterAds) |
340 print sum((n.NodeCost() for n in graph.Nodes())) | 305 print sum((n.NodeCost() for n in graph.Nodes())) |
341 | 306 |
342 | 307 |
343 COMMAND_MAP = { | 308 COMMAND_MAP = { |
344 'cost': DoCost, | 309 'cost': DoCost, |
345 'png': DoPng, | 310 'png': DoPng, |
346 'compare': DoCompare, | 311 'compare': DoCompare, |
347 'prefetch_setup': DoPrefetchSetup, | 312 'prefetch_setup': DoPrefetchSetup, |
348 'log_requests': DoLogRequests, | 313 'log_requests': DoLogRequests, |
349 'tracing': DoTracing, | |
350 'longpole': DoLongPole, | 314 'longpole': DoLongPole, |
351 'nodecost': DoNodeCost, | 315 'nodecost': DoNodeCost, |
352 'fetch': DoFetch, | 316 'fetch': DoFetch, |
353 } | 317 } |
354 | 318 |
355 def main(): | 319 def main(): |
356 logging.basicConfig(level=logging.WARNING) | 320 logging.basicConfig(level=logging.WARNING) |
357 parser = argparse.ArgumentParser(usage=' '.join(COMMAND_MAP.keys())) | 321 parser = argparse.ArgumentParser(usage=' '.join(COMMAND_MAP.keys())) |
358 parser.add_argument('command') | 322 parser.add_argument('command') |
359 parser.add_argument('rest', nargs=argparse.REMAINDER) | 323 parser.add_argument('rest', nargs=argparse.REMAINDER) |
360 args = parser.parse_args() | 324 args = parser.parse_args() |
361 devil_chromium.Initialize() | 325 devil_chromium.Initialize() |
362 COMMAND_MAP.get(args.command, | 326 COMMAND_MAP.get(args.command, |
363 lambda _: InvalidCommand(args.command))(args.rest) | 327 lambda _: InvalidCommand(args.command))(args.rest) |
364 | 328 |
365 | 329 |
366 if __name__ == '__main__': | 330 if __name__ == '__main__': |
367 main() | 331 main() |
OLD | NEW |