OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2016 the V8 project authors. All rights reserved. | 2 # Copyright 2016 the V8 project 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 Usage: runtime-call-stats.py [-h] <command> ... | 6 Usage: runtime-call-stats.py [-h] <command> ... |
7 | 7 |
8 Optional arguments: | 8 Optional arguments: |
9 -h, --help show this help message and exit | 9 -h, --help show this help message and exit |
10 | 10 |
(...skipping 16 matching lines...) Expand all Loading... |
27 import tempfile | 27 import tempfile |
28 | 28 |
29 import numpy | 29 import numpy |
30 import scipy | 30 import scipy |
31 import scipy.stats | 31 import scipy.stats |
32 from math import sqrt | 32 from math import sqrt |
33 | 33 |
34 | 34 |
35 # Run benchmarks. | 35 # Run benchmarks. |
36 | 36 |
37 DEFAULT_SITES = [ | |
38 # top websites (http://alexa.com/topsites): -------------------- | |
39 "https://www.google.de/search?q=v8", | |
40 "https://www.youtube.com", | |
41 "https://www.facebook.com/shakira", | |
42 "http://www.baidu.com/s?wd=v8", | |
43 "http://www.yahoo.co.jp", | |
44 "http://www.amazon.com/s/?field-keywords=v8", | |
45 "http://hi.wikipedia.org/wiki/" \ | |
46 "%E0%A4%AE%E0%A5%81%E0%A4%96%E0%A4%AA%E0%A5%83%E0%A4%B7%E0%A5%8D%E0%A4%A0", | |
47 "http://www.qq.com", | |
48 "http://www.twitter.com/taylorswift13", | |
49 "http://www.reddit.com", | |
50 "http://www.ebay.fr/sch/i.html?_nkw=v8", | |
51 "http://edition.cnn.com", | |
52 "http://world.taobao.com", | |
53 "http://www.instagram.com/archdigest", | |
54 "https://www.linkedin.com/pub/dir/?first=john&last=doe&search=search", | |
55 "http://www.msn.com/ar-ae", | |
56 "http://www.bing.com/search?q=v8+engine", | |
57 "http://www.pinterest.com/categories/popular", | |
58 "http://www.sina.com.cn", | |
59 "http://weibo.com", | |
60 "http://yandex.ru/search/?text=v8", | |
61 # framework driven decisions: ----------------------------------- | |
62 # wikipedia content + angularjs | |
63 "http://www.wikiwand.com/en/hill", | |
64 # ember website | |
65 "http://meta.discourse.org", | |
66 # backbone js | |
67 "http://reddit.musicplayer.io", | |
68 # gwt application | |
69 "http://inbox.google.com", | |
70 # webgl / algorithmic case | |
71 "http://maps.google.co.jp/maps/search/restaurant+tokyo", | |
72 # whatever framework adwords uses | |
73 "https://adwords.google.com", | |
74 ] | |
75 | |
76 | |
77 def print_command(cmd_args): | 37 def print_command(cmd_args): |
78 def fix_for_printing(arg): | 38 def fix_for_printing(arg): |
79 m = re.match(r'^--([^=]+)=(.*)$', arg) | 39 m = re.match(r'^--([^=]+)=(.*)$', arg) |
80 if m and (' ' in m.group(2) or m.group(2).startswith('-')): | 40 if m and (' ' in m.group(2) or m.group(2).startswith('-')): |
81 arg = "--{}='{}'".format(m.group(1), m.group(2)) | 41 arg = "--{}='{}'".format(m.group(1), m.group(2)) |
82 elif ' ' in arg: | 42 elif ' ' in arg: |
83 arg = "'{}'".format(arg) | 43 arg = "'{}'".format(arg) |
84 return arg | 44 return arg |
85 print " ".join(map(fix_for_printing, cmd_args)) | 45 print " ".join(map(fix_for_printing, cmd_args)) |
86 | 46 |
87 | 47 |
88 def start_replay_server(args): | 48 def start_replay_server(args, sites): |
| 49 with tempfile.NamedTemporaryFile(prefix='callstats-inject-', suffix='.js', |
| 50 mode='wt', delete=False) as f: |
| 51 injection = f.name |
| 52 generate_injection(f, sites) |
89 cmd_args = [ | 53 cmd_args = [ |
90 args.replay_bin, | 54 args.replay_bin, |
91 "--port=4080", | 55 "--port=4080", |
92 "--ssl_port=4443", | 56 "--ssl_port=4443", |
93 "--no-dns_forwarding", | 57 "--no-dns_forwarding", |
94 "--use_closest_match", | 58 "--use_closest_match", |
95 "--no-diff_unknown_requests", | 59 "--no-diff_unknown_requests", |
| 60 "--inject_scripts=deterministic.js,{}".format(injection), |
96 args.replay_wpr, | 61 args.replay_wpr, |
97 ] | 62 ] |
98 print "=" * 80 | 63 print "=" * 80 |
99 print_command(cmd_args) | 64 print_command(cmd_args) |
100 with open(os.devnull, 'w') as null: | 65 with open(os.devnull, 'w') as null: |
101 server = subprocess.Popen(cmd_args, stdout=null, stderr=null) | 66 server = subprocess.Popen(cmd_args, stdout=null, stderr=null) |
102 print "RUNNING REPLAY SERVER: %s with PID=%s" % (args.replay_bin, server.pid) | 67 print "RUNNING REPLAY SERVER: %s with PID=%s" % (args.replay_bin, server.pid) |
103 print "=" * 80 | 68 print "=" * 80 |
104 return server | 69 return {'process': server, 'injection': injection} |
105 | 70 |
106 | 71 |
107 def stop_replay_server(server): | 72 def stop_replay_server(server): |
108 print("SHUTTING DOWN REPLAY SERVER %s" % server.pid) | 73 print("SHUTTING DOWN REPLAY SERVER %s" % server['process'].pid) |
109 server.terminate() | 74 server['process'].terminate() |
| 75 os.remove(server['injection']) |
| 76 |
| 77 |
| 78 def generate_injection(f, sites): |
| 79 print >> f, """\ |
| 80 (function() { |
| 81 function match(url, item) { |
| 82 if ('regexp' in item) return url.match(item.regexp) !== null; |
| 83 let url_wanted = item.url; |
| 84 // Allow automatic redirections from http to https. |
| 85 if (url_wanted.startsWith("http://") && url.startsWith("https://")) { |
| 86 url_wanted = "https://" + url_wanted.substr(7); |
| 87 } |
| 88 return url.startsWith(url_wanted); |
| 89 }; |
| 90 |
| 91 function onLoad(e) { |
| 92 let url = e.target.URL; |
| 93 for (let item of sites) { |
| 94 if (!match(url, item)) continue; |
| 95 let timeout = 'timeline' in item ? 2500 * item.timeline + 3000 |
| 96 : 'timeout' in item ? 1000 * (item.timeout - 3) |
| 97 : 10000; |
| 98 console.log("Setting time out of " + timeout + " for: " + url); |
| 99 window.setTimeout(function () { |
| 100 console.log("Time is out for: " + url); |
| 101 %GetAndResetRuntimeCallStats(1); |
| 102 }, timeout); |
| 103 return; |
| 104 } |
| 105 console.log("Ignoring: " + url); |
| 106 }; |
| 107 |
| 108 let sites = |
| 109 """, json.dumps(sites), """; |
| 110 |
| 111 console.log("Event listenner added for: " + window.location.href); |
| 112 window.addEventListener("load", onLoad); |
| 113 })();""" |
110 | 114 |
111 | 115 |
112 def run_site(site, domain, args, timeout=None): | 116 def run_site(site, domain, args, timeout=None): |
113 print "="*80 | 117 print "="*80 |
114 print "RUNNING DOMAIN %s" % domain | 118 print "RUNNING DOMAIN %s" % domain |
115 print "="*80 | 119 print "="*80 |
116 result_template = "{domain}#{count}.txt" if args.repeat else "{domain}.txt" | 120 result_template = "{domain}#{count}.txt" if args.repeat else "{domain}.txt" |
117 count = 0 | 121 count = 0 |
| 122 if timeout is None: timeout = args.timeout |
| 123 if args.replay_wpr: timeout += 1 |
118 while count == 0 or args.repeat is not None and count < args.repeat: | 124 while count == 0 or args.repeat is not None and count < args.repeat: |
119 count += 1 | 125 count += 1 |
120 result = result_template.format(domain=domain, count=count) | 126 result = result_template.format(domain=domain, count=count) |
121 retries = 0 | 127 retries = 0 |
122 while args.retries is None or retries < args.retries: | 128 while args.retries is None or retries < args.retries: |
123 retries += 1 | 129 retries += 1 |
124 try: | 130 try: |
125 temp_user_data_dir = args.user_data_dir is None | 131 if args.user_data_dir: |
126 if temp_user_data_dir: | 132 user_data_dir = args.user_data_dir |
| 133 else: |
127 user_data_dir = tempfile.mkdtemp(prefix="chr_") | 134 user_data_dir = tempfile.mkdtemp(prefix="chr_") |
128 js_flags = "--runtime-call-stats" | 135 js_flags = "--runtime-call-stats" |
| 136 if args.replay_wpr: js_flags += " --allow-natives-syntax" |
129 if args.js_flags: js_flags += " " + args.js_flags | 137 if args.js_flags: js_flags += " " + args.js_flags |
130 chrome_flags = [ | 138 chrome_flags = [ |
131 "--no-default-browser-check", | 139 "--no-default-browser-check", |
132 "--disable-translate", | 140 "--disable-translate", |
133 "--single-process", | 141 "--disable-seccomp-sandbox", |
134 "--no-sandbox", | 142 "--no-sandbox", |
135 "--js-flags={}".format(js_flags), | 143 "--js-flags={}".format(js_flags), |
136 "--no-first-run", | 144 "--no-first-run", |
137 "--user-data-dir={}".format(user_data_dir), | 145 "--user-data-dir={}".format(user_data_dir), |
138 ] | 146 ] |
139 if args.replay_wpr: | 147 if args.replay_wpr: |
140 chrome_flags += [ | 148 chrome_flags += [ |
141 "--host-resolver-rules=MAP *:80 localhost:4080, " \ | 149 "--host-resolver-rules=MAP *:80 localhost:4080, " \ |
142 "MAP *:443 localhost:4443, " \ | 150 "MAP *:443 localhost:4443, " \ |
143 "EXCLUDE localhost", | 151 "EXCLUDE localhost", |
144 "--ignore-certificate-errors", | 152 "--ignore-certificate-errors", |
145 "--disable-web-security", | 153 "--disable-web-security", |
146 "--reduce-security-for-testing", | 154 "--reduce-security-for-testing", |
147 "--allow-insecure-localhost", | 155 "--allow-insecure-localhost", |
148 ] | 156 ] |
| 157 else: |
| 158 chrome_flags += [ |
| 159 "--single-process", |
| 160 ] |
149 if args.chrome_flags: | 161 if args.chrome_flags: |
150 chrome_flags += args.chrome_flags.split() | 162 chrome_flags += args.chrome_flags.split() |
151 if timeout is None: timeout = args.timeout | |
152 cmd_args = [ | 163 cmd_args = [ |
153 "timeout", str(timeout), | 164 "timeout", str(timeout), |
154 args.with_chrome | 165 args.with_chrome |
155 ] + chrome_flags + [ site ] | 166 ] + chrome_flags + [ site ] |
156 print "- " * 40 | 167 print "- " * 40 |
157 print_command(cmd_args) | 168 print_command(cmd_args) |
158 print "- " * 40 | 169 print "- " * 40 |
159 with open(result, "wt") as f: | 170 with open(result, "wt") as f: |
160 status = subprocess.call(cmd_args, stdout=f) | 171 status = subprocess.call(cmd_args, stdout=f) |
161 # 124 means timeout killed chrome, 0 means the user was bored first! | 172 # 124 means timeout killed chrome, 0 means the user was bored first! |
162 # If none of these two happened, then chrome apparently crashed, so | 173 # If none of these two happened, then chrome apparently crashed, so |
163 # it must be called again. | 174 # it must be called again. |
164 if status != 124 and status != 0: | 175 if status != 124 and status != 0: |
165 print("CHROME CRASHED, REPEATING RUN"); | 176 print("CHROME CRASHED, REPEATING RUN"); |
166 continue | 177 continue |
167 # If the stats file is empty, chrome must be called again. | 178 # If the stats file is empty, chrome must be called again. |
168 if os.path.isfile(result) and os.path.getsize(result) > 0: | 179 if os.path.isfile(result) and os.path.getsize(result) > 0: |
169 if args.print_url: | 180 if args.print_url: |
170 with open(result, "at") as f: | 181 with open(result, "at") as f: |
171 print >> f | 182 print >> f |
172 print >> f, "URL: {}".format(site) | 183 print >> f, "URL: {}".format(site) |
173 break | 184 break |
| 185 if retries <= 5: timeout += 1 |
174 print("EMPTY RESULT, REPEATING RUN"); | 186 print("EMPTY RESULT, REPEATING RUN"); |
175 finally: | 187 finally: |
176 if temp_user_data_dir: | 188 if not args.user_data_dir: |
177 shutil.rmtree(user_data_dir) | 189 shutil.rmtree(user_data_dir) |
178 | 190 |
179 | 191 |
180 def read_sites_file(args): | 192 def read_sites_file(args): |
181 try: | 193 try: |
182 sites = [] | 194 sites = [] |
183 try: | 195 try: |
184 with open(args.sites_file, "rt") as f: | 196 with open(args.sites_file, "rt") as f: |
185 for item in json.load(f): | 197 for item in json.load(f): |
186 if 'timeout' not in item: | 198 if 'timeout' not in item: |
(...skipping 10 matching lines...) Expand all Loading... |
197 return sites | 209 return sites |
198 except IOError as e: | 210 except IOError as e: |
199 args.error("Cannot read from {}. {}.".format(args.sites_file, e.strerror)) | 211 args.error("Cannot read from {}. {}.".format(args.sites_file, e.strerror)) |
200 sys.exit(1) | 212 sys.exit(1) |
201 | 213 |
202 | 214 |
203 def do_run(args): | 215 def do_run(args): |
204 # Determine the websites to benchmark. | 216 # Determine the websites to benchmark. |
205 if args.sites_file: | 217 if args.sites_file: |
206 sites = read_sites_file(args) | 218 sites = read_sites_file(args) |
207 elif args.sites: | 219 else: |
208 sites = [{'url': site, 'timeout': args.timeout} for site in args.sites] | 220 sites = [{'url': site, 'timeout': args.timeout} for site in args.sites] |
209 else: | |
210 sites = [{'url': site, 'timeout': args.timeout} for site in DEFAULT_SITES] | |
211 # Disambiguate domains, if needed. | 221 # Disambiguate domains, if needed. |
212 L = [] | 222 L = [] |
213 domains = {} | 223 domains = {} |
214 for item in sites: | 224 for item in sites: |
215 site = item['url'] | 225 site = item['url'] |
216 m = re.match(r'^(https?://)?([^/]+)(/.*)?$', site) | 226 m = re.match(r'^(https?://)?([^/]+)(/.*)?$', site) |
217 if not m: | 227 if not m: |
218 args.error("Invalid URL {}.".format(site)) | 228 args.error("Invalid URL {}.".format(site)) |
219 continue | 229 continue |
220 domain = m.group(2) | 230 domain = m.group(2) |
221 entry = [site, domain, None, item['timeout']] | 231 entry = [site, domain, None, item['timeout']] |
222 if domain not in domains: | 232 if domain not in domains: |
223 domains[domain] = entry | 233 domains[domain] = entry |
224 else: | 234 else: |
225 if not isinstance(domains[domain], int): | 235 if not isinstance(domains[domain], int): |
226 domains[domain][2] = 1 | 236 domains[domain][2] = 1 |
227 domains[domain] = 1 | 237 domains[domain] = 1 |
228 domains[domain] += 1 | 238 domains[domain] += 1 |
229 entry[2] = domains[domain] | 239 entry[2] = domains[domain] |
230 L.append(entry) | 240 L.append(entry) |
231 if args.replay_wpr: | 241 replay_server = start_replay_server(args, sites) if args.replay_wpr else None |
232 replay_server = start_replay_server(args); | |
233 try: | 242 try: |
234 # Run them. | 243 # Run them. |
235 for site, domain, count, timeout in L: | 244 for site, domain, count, timeout in L: |
236 if count is not None: domain = "{}%{}".format(domain, count) | 245 if count is not None: domain = "{}%{}".format(domain, count) |
237 print site, domain, timeout | 246 print site, domain, timeout |
238 run_site(site, domain, args, timeout) | 247 run_site(site, domain, args, timeout) |
239 finally: | 248 finally: |
240 if replay_server: | 249 if replay_server: |
241 stop_replay_server(replay_server) | 250 stop_replay_server(replay_server) |
242 | 251 |
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
420 if args.help_cmd in subparsers: | 429 if args.help_cmd in subparsers: |
421 subparsers[args.help_cmd].print_help() | 430 subparsers[args.help_cmd].print_help() |
422 else: | 431 else: |
423 args.error("Unknown command '{}'".format(args.help_cmd)) | 432 args.error("Unknown command '{}'".format(args.help_cmd)) |
424 else: | 433 else: |
425 parser.print_help() | 434 parser.print_help() |
426 | 435 |
427 | 436 |
428 # Main program, parse command line and execute. | 437 # Main program, parse command line and execute. |
429 | 438 |
| 439 def coexist(*l): |
| 440 given = sum(1 for x in l if x) |
| 441 return given == 0 or given == len(l) |
| 442 |
430 def main(): | 443 def main(): |
431 parser = argparse.ArgumentParser() | 444 parser = argparse.ArgumentParser() |
432 subparser_adder = parser.add_subparsers(title="commands", dest="command", | 445 subparser_adder = parser.add_subparsers(title="commands", dest="command", |
433 metavar="<command>") | 446 metavar="<command>") |
434 subparsers = {} | 447 subparsers = {} |
435 # Command: run. | 448 # Command: run. |
436 subparsers["run"] = subparser_adder.add_parser( | 449 subparsers["run"] = subparser_adder.add_parser( |
437 "run", help="run --help") | 450 "run", help="run --help") |
438 subparsers["run"].set_defaults( | 451 subparsers["run"].set_defaults( |
439 func=do_run, error=subparsers["run"].error) | 452 func=do_run, error=subparsers["run"].error) |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
504 subparsers["help"] = subparser_adder.add_parser( | 517 subparsers["help"] = subparser_adder.add_parser( |
505 "help", help="help information") | 518 "help", help="help information") |
506 subparsers["help"].set_defaults( | 519 subparsers["help"].set_defaults( |
507 func=lambda args: do_help(parser, subparsers, args), | 520 func=lambda args: do_help(parser, subparsers, args), |
508 error=subparsers["help"].error) | 521 error=subparsers["help"].error) |
509 subparsers["help"].add_argument( | 522 subparsers["help"].add_argument( |
510 "help_cmd", type=str, metavar="<command>", nargs="?", | 523 "help_cmd", type=str, metavar="<command>", nargs="?", |
511 help="command for which to display help") | 524 help="command for which to display help") |
512 # Execute the command. | 525 # Execute the command. |
513 args = parser.parse_args() | 526 args = parser.parse_args() |
514 if args.command == "run" and args.sites_file and args.sites: | 527 setattr(args, 'script_path', os.path.dirname(sys.argv[0])) |
515 args.error("if --sites-file is used, no site URLS must be given") | 528 if args.command == "run" and coexist(args.sites_file, args.sites): |
| 529 args.error("use either option --sites-file or site URLs") |
516 sys.exit(1) | 530 sys.exit(1) |
517 elif args.command == "run" and args.replay_wpr and not args.replay_bin: | 531 elif args.command == "run" and not coexist(args.replay_wpr, args.replay_bin): |
518 args.error("if --replay-wpr is used, --replay-bin must be given") | 532 args.error("options --replay-wpr and --replay-bin must be used together") |
519 sys.exit(1) | 533 sys.exit(1) |
520 else: | 534 else: |
521 args.func(args) | 535 args.func(args) |
522 | 536 |
523 if __name__ == "__main__": | 537 if __name__ == "__main__": |
524 sys.exit(main()) | 538 sys.exit(main()) |
OLD | NEW |