From 5fa1c61ee0babe9e4c3b2fa0069912e6d42ee2dc Mon Sep 17 00:00:00 2001 From: nikolaos Date: Wed, 11 May 2016 05:23:19 -0700 Subject: [PATCH] Add script injection when replaying with callstats.py This allows benchmarking without --single-process and correctly gathering --runtime-call-stats numbers. Add optional parameter to %GetAndResetRuntimeCallStats - Without any parameter, it returns a string with the runtime call statistics (as before). - With one string parameter, it appends the statistics to the file with that file name. - With one integer parameter (which must be 1=stdout or 2=stderr), it prints the statistics to the output with of that file descriptor. The injected script is automatically generated. Also, callstats.py does not have a hardwired DEFAULT_SITES anymore. R=cbruni@chromium.org BUG= LOG=N Review-Url: https://codereview.chromium.org/1966193002 Cr-Commit-Position: refs/heads/master@{#36172} --- src/runtime/runtime-internal.cc | 42 +++++++++-- src/runtime/runtime.h | 2 +- tools/callstats.py | 130 ++++++++++++++++++-------------- 3 files changed, 108 insertions(+), 66 deletions(-) mode change 100644 => 100755 tools/callstats.py diff --git a/src/runtime/runtime-internal.cc b/src/runtime/runtime-internal.cc index ffaea8d1a1..d42c45bc4f 100644 --- a/src/runtime/runtime-internal.cc +++ b/src/runtime/runtime-internal.cc @@ -497,13 +497,41 @@ RUNTIME_FUNCTION(Runtime_GetOrdinaryHasInstance) { RUNTIME_FUNCTION(Runtime_GetAndResetRuntimeCallStats) { HandleScope scope(isolate); - DCHECK_EQ(0, args.length()); - std::stringstream stats_stream; - isolate->counters()->runtime_call_stats()->Print(stats_stream); - Handle result = - isolate->factory()->NewStringFromAsciiChecked(stats_stream.str().c_str()); - isolate->counters()->runtime_call_stats()->Reset(); - return *result; + if (args.length() == 0) { + // Without arguments, the result is returned as a string. + DCHECK_EQ(0, args.length()); + std::stringstream stats_stream; + isolate->counters()->runtime_call_stats()->Print(stats_stream); + Handle result = isolate->factory()->NewStringFromAsciiChecked( + stats_stream.str().c_str()); + isolate->counters()->runtime_call_stats()->Reset(); + return *result; + } else { + DCHECK_EQ(1, args.length()); + std::FILE* f; + if (args[0]->IsString()) { + // With a string argument, the results are appended to that file. + CONVERT_ARG_HANDLE_CHECKED(String, arg0, 0); + String::FlatContent flat = arg0->GetFlatContent(); + const char* filename = + reinterpret_cast(&(flat.ToOneByteVector()[0])); + f = std::fopen(filename, "a"); + DCHECK_NOT_NULL(f); + } else { + // With an integer argument, the results are written to stdout/stderr. + CONVERT_SMI_ARG_CHECKED(fd, 0); + DCHECK(fd == 1 || fd == 2); + f = fd == 1 ? stdout : stderr; + } + OFStream stats_stream(f); + isolate->counters()->runtime_call_stats()->Print(stats_stream); + isolate->counters()->runtime_call_stats()->Reset(); + if (args[0]->IsString()) + std::fclose(f); + else + std::fflush(f); + return isolate->heap()->undefined_value(); + } } RUNTIME_FUNCTION(Runtime_EnqueueMicrotask) { diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index d807477d7a..ece2cfc06d 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -315,7 +315,7 @@ namespace internal { F(CreateListFromArrayLike, 1, 1) \ F(IncrementUseCounter, 1, 1) \ F(GetOrdinaryHasInstance, 0, 1) \ - F(GetAndResetRuntimeCallStats, 0, 1) \ + F(GetAndResetRuntimeCallStats, -1 /* <= 1 */, 1) \ F(EnqueueMicrotask, 1, 1) \ F(RunMicrotasks, 0, 1) \ F(WasmGetFunctionName, 2, 1) diff --git a/tools/callstats.py b/tools/callstats.py old mode 100644 new mode 100755 index 88b07088e3..13ab33b5da --- a/tools/callstats.py +++ b/tools/callstats.py @@ -34,46 +34,6 @@ from math import sqrt # Run benchmarks. -DEFAULT_SITES = [ - # top websites (http://alexa.com/topsites): -------------------- - "https://www.google.de/search?q=v8", - "https://www.youtube.com", - "https://www.facebook.com/shakira", - "http://www.baidu.com/s?wd=v8", - "http://www.yahoo.co.jp", - "http://www.amazon.com/s/?field-keywords=v8", - "http://hi.wikipedia.org/wiki/" \ - "%E0%A4%AE%E0%A5%81%E0%A4%96%E0%A4%AA%E0%A5%83%E0%A4%B7%E0%A5%8D%E0%A4%A0", - "http://www.qq.com", - "http://www.twitter.com/taylorswift13", - "http://www.reddit.com", - "http://www.ebay.fr/sch/i.html?_nkw=v8", - "http://edition.cnn.com", - "http://world.taobao.com", - "http://www.instagram.com/archdigest", - "https://www.linkedin.com/pub/dir/?first=john&last=doe&search=search", - "http://www.msn.com/ar-ae", - "http://www.bing.com/search?q=v8+engine", - "http://www.pinterest.com/categories/popular", - "http://www.sina.com.cn", - "http://weibo.com", - "http://yandex.ru/search/?text=v8", - # framework driven decisions: ----------------------------------- - # wikipedia content + angularjs - "http://www.wikiwand.com/en/hill", - # ember website - "http://meta.discourse.org", - # backbone js - "http://reddit.musicplayer.io", - # gwt application - "http://inbox.google.com", - # webgl / algorithmic case - "http://maps.google.co.jp/maps/search/restaurant+tokyo", - # whatever framework adwords uses - "https://adwords.google.com", -] - - def print_command(cmd_args): def fix_for_printing(arg): m = re.match(r'^--([^=]+)=(.*)$', arg) @@ -85,7 +45,11 @@ def print_command(cmd_args): print " ".join(map(fix_for_printing, cmd_args)) -def start_replay_server(args): +def start_replay_server(args, sites): + with tempfile.NamedTemporaryFile(prefix='callstats-inject-', suffix='.js', + mode='wt', delete=False) as f: + injection = f.name + generate_injection(f, sites) cmd_args = [ args.replay_bin, "--port=4080", @@ -93,6 +57,7 @@ def start_replay_server(args): "--no-dns_forwarding", "--use_closest_match", "--no-diff_unknown_requests", + "--inject_scripts=deterministic.js,{}".format(injection), args.replay_wpr, ] print "=" * 80 @@ -101,12 +66,51 @@ def start_replay_server(args): server = subprocess.Popen(cmd_args, stdout=null, stderr=null) print "RUNNING REPLAY SERVER: %s with PID=%s" % (args.replay_bin, server.pid) print "=" * 80 - return server + return {'process': server, 'injection': injection} def stop_replay_server(server): - print("SHUTTING DOWN REPLAY SERVER %s" % server.pid) - server.terminate() + print("SHUTTING DOWN REPLAY SERVER %s" % server['process'].pid) + server['process'].terminate() + os.remove(server['injection']) + + +def generate_injection(f, sites): + print >> f, """\ +(function() { + function match(url, item) { + if ('regexp' in item) return url.match(item.regexp) !== null; + let url_wanted = item.url; + // Allow automatic redirections from http to https. + if (url_wanted.startsWith("http://") && url.startsWith("https://")) { + url_wanted = "https://" + url_wanted.substr(7); + } + return url.startsWith(url_wanted); + }; + + function onLoad(e) { + let url = e.target.URL; + for (let item of sites) { + if (!match(url, item)) continue; + let timeout = 'timeline' in item ? 2500 * item.timeline + 3000 + : 'timeout' in item ? 1000 * (item.timeout - 3) + : 10000; + console.log("Setting time out of " + timeout + " for: " + url); + window.setTimeout(function () { + console.log("Time is out for: " + url); + %GetAndResetRuntimeCallStats(1); + }, timeout); + return; + } + console.log("Ignoring: " + url); + }; + + let sites = + """, json.dumps(sites), """; + + console.log("Event listenner added for: " + window.location.href); + window.addEventListener("load", onLoad); +})();""" def run_site(site, domain, args, timeout=None): @@ -115,6 +119,8 @@ def run_site(site, domain, args, timeout=None): print "="*80 result_template = "{domain}#{count}.txt" if args.repeat else "{domain}.txt" count = 0 + if timeout is None: timeout = args.timeout + if args.replay_wpr: timeout += 1 while count == 0 or args.repeat is not None and count < args.repeat: count += 1 result = result_template.format(domain=domain, count=count) @@ -122,15 +128,17 @@ def run_site(site, domain, args, timeout=None): while args.retries is None or retries < args.retries: retries += 1 try: - temp_user_data_dir = args.user_data_dir is None - if temp_user_data_dir: + if args.user_data_dir: + user_data_dir = args.user_data_dir + else: user_data_dir = tempfile.mkdtemp(prefix="chr_") js_flags = "--runtime-call-stats" + if args.replay_wpr: js_flags += " --allow-natives-syntax" if args.js_flags: js_flags += " " + args.js_flags chrome_flags = [ "--no-default-browser-check", "--disable-translate", - "--single-process", + "--disable-seccomp-sandbox", "--no-sandbox", "--js-flags={}".format(js_flags), "--no-first-run", @@ -146,9 +154,12 @@ def run_site(site, domain, args, timeout=None): "--reduce-security-for-testing", "--allow-insecure-localhost", ] + else: + chrome_flags += [ + "--single-process", + ] if args.chrome_flags: chrome_flags += args.chrome_flags.split() - if timeout is None: timeout = args.timeout cmd_args = [ "timeout", str(timeout), args.with_chrome @@ -171,9 +182,10 @@ def run_site(site, domain, args, timeout=None): print >> f print >> f, "URL: {}".format(site) break + if retries <= 5: timeout += 1 print("EMPTY RESULT, REPEATING RUN"); finally: - if temp_user_data_dir: + if not args.user_data_dir: shutil.rmtree(user_data_dir) @@ -204,10 +216,8 @@ def do_run(args): # Determine the websites to benchmark. if args.sites_file: sites = read_sites_file(args) - elif args.sites: - sites = [{'url': site, 'timeout': args.timeout} for site in args.sites] else: - sites = [{'url': site, 'timeout': args.timeout} for site in DEFAULT_SITES] + sites = [{'url': site, 'timeout': args.timeout} for site in args.sites] # Disambiguate domains, if needed. L = [] domains = {} @@ -228,8 +238,7 @@ def do_run(args): domains[domain] += 1 entry[2] = domains[domain] L.append(entry) - if args.replay_wpr: - replay_server = start_replay_server(args); + replay_server = start_replay_server(args, sites) if args.replay_wpr else None try: # Run them. for site, domain, count, timeout in L: @@ -427,6 +436,10 @@ def do_help(parser, subparsers, args): # Main program, parse command line and execute. +def coexist(*l): + given = sum(1 for x in l if x) + return given == 0 or given == len(l) + def main(): parser = argparse.ArgumentParser() subparser_adder = parser.add_subparsers(title="commands", dest="command", @@ -511,11 +524,12 @@ def main(): help="command for which to display help") # Execute the command. args = parser.parse_args() - if args.command == "run" and args.sites_file and args.sites: - args.error("if --sites-file is used, no site URLS must be given") + setattr(args, 'script_path', os.path.dirname(sys.argv[0])) + if args.command == "run" and coexist(args.sites_file, args.sites): + args.error("use either option --sites-file or site URLs") sys.exit(1) - elif args.command == "run" and args.replay_wpr and not args.replay_bin: - args.error("if --replay-wpr is used, --replay-bin must be given") + elif args.command == "run" and not coexist(args.replay_wpr, args.replay_bin): + args.error("options --replay-wpr and --replay-bin must be used together") sys.exit(1) else: args.func(args)