| 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 |