Index: tools/callstats.py |
diff --git a/tools/callstats.py b/tools/callstats.py |
index 9044618421a6276748f9d56ce9856e70f4d2d0f5..66ef87b7c2435a3aa2baf5d86faf16f60bfec9ad 100755 |
--- a/tools/callstats.py |
+++ b/tools/callstats.py |
@@ -25,6 +25,7 @@ import shutil |
import subprocess |
import sys |
import tempfile |
+import operator |
import numpy |
import scipy |
@@ -50,10 +51,12 @@ def start_replay_server(args, sites): |
mode='wt', delete=False) as f: |
injection = f.name |
generate_injection(f, sites, args.refresh) |
+ http_port = 4080 + args.port_offset |
+ https_port = 4443 + args.port_offset |
cmd_args = [ |
args.replay_bin, |
- "--port=4080", |
- "--ssl_port=4443", |
+ "--port=%s" % http_port, |
+ "--ssl_port=%s" % https_port, |
"--no-dns_forwarding", |
"--use_closest_match", |
"--no-diff_unknown_requests", |
@@ -78,38 +81,36 @@ def stop_replay_server(server): |
def generate_injection(f, sites, refreshes=0): |
print >> f, """\ |
(function() { |
- let s = window.sessionStorage.getItem("refreshCounter"); |
- let refreshTotal = """, refreshes, """; |
- let refreshCounter = s ? parseInt(s) : refreshTotal; |
- let refreshId = refreshTotal - refreshCounter; |
+ var s = window.sessionStorage.getItem("refreshCounter"); |
+ var refreshTotal = """, refreshes, """; |
+ var refreshCounter = s ? parseInt(s) : refreshTotal; |
+ var refreshId = refreshTotal - refreshCounter; |
if (refreshCounter > 0) { |
window.sessionStorage.setItem("refreshCounter", refreshCounter-1); |
} |
- |
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 ('regexp' in item) { return url.match(item.regexp) !== null }; |
+ var 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) { |
+ function onLoad(url) { |
+ for (var item of sites) { |
if (!match(url, item)) continue; |
- let timeout = 'timeline' in item ? 2500 * item.timeline |
+ var timeout = 'timeline' in item ? 2000 * item.timeline |
: '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); |
- let msg = "STATS: (" + refreshId + ") " + url; |
+ var msg = "STATS: (" + refreshId + ") " + url; |
%GetAndResetRuntimeCallStats(1, msg); |
if (refreshCounter > 0) { |
- console.log("Refresh counter is " + refreshCounter + ", refreshing: " + url); |
+ console.log( |
+ "Refresh counter is " + refreshCounter + ", refreshing: " + url); |
window.location.reload(); |
} |
}, timeout); |
@@ -117,12 +118,9 @@ def generate_injection(f, sites, refreshes=0): |
} |
console.log("Ignoring: " + url); |
}; |
- |
- let sites = |
+ var sites = |
""", json.dumps(sites), """; |
- |
- console.log("Event listenner added for: " + window.location.href); |
- window.addEventListener("load", onLoad); |
+ onLoad(window.location.href); |
})();""" |
@@ -136,6 +134,7 @@ def run_site(site, domain, args, timeout=None): |
if args.replay_wpr: |
timeout *= 1 + args.refresh |
timeout += 1 |
+ retries_since_good_run = 0 |
while count == 0 or args.repeat is not None and count < args.repeat: |
count += 1 |
result = result_template.format(domain=domain, count=count) |
@@ -152,16 +151,20 @@ def run_site(site, domain, args, timeout=None): |
if args.js_flags: js_flags += " " + args.js_flags |
chrome_flags = [ |
"--no-default-browser-check", |
+ "--no-sandbox", |
"--disable-translate", |
"--js-flags={}".format(js_flags), |
"--no-first-run", |
"--user-data-dir={}".format(user_data_dir), |
] |
if args.replay_wpr: |
+ http_port = 4080 + args.port_offset |
+ https_port = 4443 + args.port_offset |
chrome_flags += [ |
- "--host-resolver-rules=MAP *:80 localhost:4080, " \ |
- "MAP *:443 localhost:4443, " \ |
- "EXCLUDE localhost", |
+ "--host-resolver-rules=MAP *:80 localhost:%s, " \ |
+ "MAP *:443 localhost:%s, " \ |
+ "EXCLUDE localhost" % ( |
+ http_port, https_port), |
"--ignore-certificate-errors", |
"--disable-seccomp-sandbox", |
"--disable-web-security", |
@@ -182,7 +185,8 @@ def run_site(site, domain, args, timeout=None): |
print_command(cmd_args) |
print "- " * 40 |
with open(result, "wt") as f: |
- status = subprocess.call(cmd_args, stdout=f) |
+ with open(args.log_stderr or os.devnull, 'at') as err: |
+ status = subprocess.call(cmd_args, stdout=f, stderr=err) |
# 124 means timeout killed chrome, 0 means the user was bored first! |
# If none of these two happened, then chrome apparently crashed, so |
# it must be called again. |
@@ -195,9 +199,13 @@ def run_site(site, domain, args, timeout=None): |
with open(result, "at") as f: |
print >> f |
print >> f, "URL: {}".format(site) |
+ retries_since_good_run = 0 |
break |
- if retries <= 6: timeout += 2 ** (retries-1) |
- print("EMPTY RESULT, REPEATING RUN"); |
+ if retries_since_good_run < 6: |
+ timeout += 2 ** retries_since_good_run |
+ retries_since_good_run += 1 |
+ print("EMPTY RESULT, REPEATING RUN ({})".format( |
+ retries_since_good_run)); |
finally: |
if not args.user_data_dir: |
shutil.rmtree(user_data_dir) |
@@ -211,7 +219,7 @@ def read_sites_file(args): |
for item in json.load(f): |
if 'timeout' not in item: |
# This is more-or-less arbitrary. |
- item['timeout'] = int(2.5 * item['timeline'] + 3) |
+ item['timeout'] = int(1.5 * item['timeline'] + 7) |
if item['timeout'] > args.timeout: item['timeout'] = args.timeout |
sites.append(item) |
except ValueError: |
@@ -237,11 +245,17 @@ def do_run(args): |
domains = {} |
for item in sites: |
site = item['url'] |
- m = re.match(r'^(https?://)?([^/]+)(/.*)?$', site) |
- if not m: |
- args.error("Invalid URL {}.".format(site)) |
- continue |
- domain = m.group(2) |
+ domain = None |
+ if args.domain: |
+ domain = args.domain |
+ elif 'domain' in item: |
+ domain = item['domain'] |
+ else: |
+ m = re.match(r'^(https?://)?([^/]+)(/.*)?$', site) |
+ if not m: |
+ args.error("Invalid URL {}.".format(site)) |
+ continue |
+ domain = m.group(2) |
entry = [site, domain, None, item['timeout']] |
if domain not in domains: |
domains[domain] = entry |
@@ -296,10 +310,25 @@ def statistics(data): |
'stddev': stddev, 'min': low, 'max': high, 'ci': ci } |
-def read_stats(path, S): |
+def read_stats(path, domain, args): |
+ groups = []; |
+ if args.aggregate: |
+ groups = [ |
+ ('Group-IC', re.compile(".*IC.*")), |
+ ('Group-Optimize', |
+ re.compile("StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*")), |
+ ('Group-Compile', re.compile("Compile.*")), |
+ ('Group-Parse', re.compile("Parse.*")), |
+ ('Group-Callback', re.compile("Callback$")), |
+ ('Group-API', re.compile("API.*")), |
+ ('Group-GC', re.compile("GC|AllocateInTargetSpace")), |
+ ('Group-JavaScript', re.compile("JS_Execution")), |
+ ('Group-Runtime', re.compile(".*"))] |
with open(path, "rt") as f: |
# Process the whole file and sum repeating entries. |
- D = { 'Sum': {'time': 0, 'count': 0} } |
+ entries = { 'Sum': {'time': 0, 'count': 0} } |
+ for group_name, regexp in groups: |
+ entries[group_name] = { 'time': 0, 'count': 0 } |
for line in f: |
line = line.strip() |
# Discard headers and footers. |
@@ -314,18 +343,23 @@ def read_stats(path, S): |
key = fields[0] |
time = float(fields[1].replace("ms", "")) |
count = int(fields[3]) |
- if key not in D: D[key] = { 'time': 0, 'count': 0 } |
- D[key]['time'] += time |
- D[key]['count'] += count |
+ if key not in entries: entries[key] = { 'time': 0, 'count': 0 } |
+ entries[key]['time'] += time |
+ entries[key]['count'] += count |
# We calculate the sum, if it's not the "total" line. |
if key != "Total": |
- D['Sum']['time'] += time |
- D['Sum']['count'] += count |
- # Append the sums as single entries to S. |
- for key in D: |
- if key not in S: S[key] = { 'time_list': [], 'count_list': [] } |
- S[key]['time_list'].append(D[key]['time']) |
- S[key]['count_list'].append(D[key]['count']) |
+ entries['Sum']['time'] += time |
+ entries['Sum']['count'] += count |
+ for group_name, regexp in groups: |
+ if not regexp.match(key): continue |
+ entries[group_name]['time'] += time |
+ entries[group_name]['count'] += count |
+ break |
+ # Append the sums as single entries to domain. |
+ for key in entries : |
+ if key not in domain: domain[key] = { 'time_list': [], 'count_list': [] } |
+ domain[key]['time_list'].append(entries[key]['time']) |
+ domain[key]['count_list'].append(entries[key]['count']) |
def print_stats(S, args): |
@@ -364,7 +398,7 @@ def print_stats(S, args): |
# Print and calculate partial sums, if necessary. |
for i in range(low, high): |
print_entry(*L[i]) |
- if args.totals and args.limit != 0: |
+ if args.totals and args.limit != 0 and not args.aggregate: |
if i == low: |
partial = { 'time_list': [0] * len(L[i][1]['time_list']), |
'count_list': [0] * len(L[i][1]['count_list']) } |
@@ -377,7 +411,7 @@ def print_stats(S, args): |
# Print totals, if necessary. |
if args.totals: |
print '-' * 80 |
- if args.limit != 0: |
+ if args.limit != 0 and not args.aggregate: |
partial['time_stat'] = statistics(partial['time_list']) |
partial['count_stat'] = statistics(partial['count_list']) |
print_entry("Partial", partial) |
@@ -386,55 +420,86 @@ def print_stats(S, args): |
def do_stats(args): |
- T = {} |
+ domains = {} |
for path in args.logfiles: |
filename = os.path.basename(path) |
m = re.match(r'^([^#]+)(#.*)?$', filename) |
domain = m.group(1) |
- if domain not in T: T[domain] = {} |
- read_stats(path, T[domain]) |
- for i, domain in enumerate(sorted(T)): |
- if len(T) > 1: |
+ if domain not in domains: domains[domain] = {} |
+ read_stats(path, domains[domain], args) |
+ if args.aggregate: |
+ create_total_page_stats(domains, args) |
+ for i, domain in enumerate(sorted(domains)): |
+ if len(domains) > 1: |
if i > 0: print |
print "{}:".format(domain) |
print '=' * 80 |
- S = T[domain] |
- for key in S: |
- S[key]['time_stat'] = statistics(S[key]['time_list']) |
- S[key]['count_stat'] = statistics(S[key]['count_list']) |
- print_stats(S, args) |
+ domain_stats = domains[domain] |
+ for key in domain_stats: |
+ domain_stats[key]['time_stat'] = \ |
+ statistics(domain_stats[key]['time_list']) |
+ domain_stats[key]['count_stat'] = \ |
+ statistics(domain_stats[key]['count_list']) |
+ print_stats(domain_stats, args) |
+ |
+ |
+# Create a Total page with all entries summed up. |
+def create_total_page_stats(domains, args): |
+ total = {} |
+ def sum_up(parent, key, other): |
+ sums = parent[key] |
+ for i, item in enumerate(other[key]): |
+ if i >= len(sums): |
+ sums.extend([0] * (i - len(sums) + 1)) |
+ if item is not None: |
+ sums[i] += item |
+ # Sum up all the entries/metrics from all domains |
+ for domain, entries in domains.items(): |
+ for key, domain_stats in entries.items(): |
+ if key not in total: |
+ total[key] = {} |
+ total[key]['time_list'] = list(domain_stats['time_list']) |
+ total[key]['count_list'] = list(domain_stats['count_list']) |
+ else: |
+ sum_up(total[key], 'time_list', domain_stats) |
+ sum_up(total[key], 'count_list', domain_stats) |
+ # Add a new "Total" page containing the summed up metrics. |
+ domains['Total'] = total |
# Generate JSON file. |
def do_json(args): |
- J = {} |
+ versions = {} |
for path in args.logdirs: |
if os.path.isdir(path): |
for root, dirs, files in os.walk(path): |
version = os.path.basename(root) |
- if version not in J: J[version] = {} |
+ if version not in versions: versions[version] = {} |
for filename in files: |
if filename.endswith(".txt"): |
m = re.match(r'^([^#]+)(#.*)?\.txt$', filename) |
domain = m.group(1) |
- if domain not in J[version]: J[version][domain] = {} |
- read_stats(os.path.join(root, filename), J[version][domain]) |
- for version, T in J.items(): |
- for domain, S in T.items(): |
- A = [] |
- for name, value in S.items(): |
+ if domain not in versions[version]: versions[version][domain] = {} |
+ read_stats(os.path.join(root, filename), |
+ versions[version][domain], args) |
+ for version, domains in versions.items(): |
+ if args.aggregate: |
+ create_total_page_stats(domains, args) |
+ for domain, entries in domains.items(): |
+ stats = [] |
+ for name, value in entries.items(): |
# We don't want the calculated sum in the JSON file. |
if name == "Sum": continue |
entry = [name] |
for x in ['time_list', 'count_list']: |
- s = statistics(S[name][x]) |
+ s = statistics(entries[name][x]) |
entry.append(round(s['average'], 1)) |
entry.append(round(s['ci']['abs'], 1)) |
entry.append(round(s['ci']['perc'], 2)) |
- A.append(entry) |
- T[domain] = A |
- print json.dumps(J, separators=(',', ':')) |
+ stats.append(entry) |
+ domains[domain] = stats |
+ print json.dumps(versions, separators=(',', ':')) |
# Help. |
@@ -472,6 +537,9 @@ def main(): |
"--js-flags", type=str, default="", |
help="specify additional V8 flags") |
subparsers["run"].add_argument( |
+ "--domain", type=str, default="", |
+ help="specify the output file domain name") |
+ subparsers["run"].add_argument( |
"--no-url", dest="print_url", action="store_false", default=True, |
help="do not include url in statistics file") |
subparsers["run"].add_argument( |
@@ -497,6 +565,9 @@ def main(): |
"-t", "--timeout", type=int, metavar="<seconds>", default=60, |
help="specify seconds before chrome is killed") |
subparsers["run"].add_argument( |
+ "-p", "--port-offset", type=int, metavar="<offset>", default=0, |
+ help="specify the offset for the replay server's default ports") |
+ subparsers["run"].add_argument( |
"-u", "--user-data-dir", type=str, metavar="<path>", |
help="specify user data dir (default is temporary)") |
subparsers["run"].add_argument( |
@@ -504,6 +575,9 @@ def main(): |
default="/usr/bin/google-chrome", |
help="specify chrome executable to use") |
subparsers["run"].add_argument( |
+ "-l", "--log-stderr", type=str, metavar="<path>", |
+ help="specify where chrome's stderr should go (default: /dev/null)") |
+ subparsers["run"].add_argument( |
"sites", type=str, metavar="<URL>", nargs="*", |
help="specify benchmark website") |
# Command: stats. |
@@ -523,6 +597,10 @@ def main(): |
subparsers["stats"].add_argument( |
"logfiles", type=str, metavar="<logfile>", nargs="*", |
help="specify log files to parse") |
+ subparsers["stats"].add_argument( |
+ "--aggregate", dest="aggregate", action="store_true", default=False, |
+ help="Create aggregated entries. Adds Group-* entries at the toplevel. " + |
+ "Additionally creates a Total page with all entries.") |
# Command: json. |
subparsers["json"] = subparser_adder.add_parser( |
"json", help="json --help") |
@@ -531,6 +609,10 @@ def main(): |
subparsers["json"].add_argument( |
"logdirs", type=str, metavar="<logdir>", nargs="*", |
help="specify directories with log files to parse") |
+ subparsers["json"].add_argument( |
+ "--aggregate", dest="aggregate", action="store_true", default=False, |
+ help="Create aggregated entries. Adds Group-* entries at the toplevel. " + |
+ "Additionally creates a Total page with all entries.") |
# Command: help. |
subparsers["help"] = subparser_adder.add_parser( |
"help", help="help information") |