No bug - tagging b4d3227540c9ebc43d64aac6168fdca7019c22d8 with FIREFOX_BETA_126_BASE...
[gecko.git] / testing / runtimes / writeruntimes
blob1f21347fed6fa54cd6daba4b67cde4644fa3a3bb
1 #!/bin/sh
2 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
3 # vim: set filetype=python:
5 # This Source Code Form is subject to the terms of the Mozilla Public
6 # License, v. 2.0. If a copy of the MPL was not distributed with this
7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 # The beginning of this script is both valid shell and valid python,
10 # such that the script starts with the shell and is reexecuted python
11 ''':'
12 which mach > /dev/null 2>&1 && exec mach python "$0" "$@" ||
13 echo "mach not found, either add it to your \$PATH or run this script via ./mach python testing/runtimes/writeruntimes"; exit # noqa
14 '''
16 import datetime
17 import json
18 import os
19 import sys
20 import time
21 from argparse import ArgumentParser
22 from collections import defaultdict
24 import requests
26 from moztest.resolve import (
27 TestManifestLoader,
28 TestResolver,
29 TEST_SUITES,
32 here = os.path.abspath(os.path.dirname(__file__))
33 ACTIVE_DATA_URL = "https://activedata.allizom.org/query"
34 EXCEED_LIMIT = [
35 # Suites that exceed 10,000 ActiveData result limit will be defined here.
36 'web-platform-tests',
37 'web-platform-tests-reftest',
39 MAX_RETRIES = 10
40 RETRY_INTERVAL = 10
43 def construct_query(suite, platform):
44 if platform in ('windows', 'android'):
45 platform_clause = '{"find":{"run.machine.platform": "%s"}}' % platform
46 else:
47 # Bundle macosx and linux results together - they are not too different.
48 platform_clause = '''
50 "not": {
51 "or": [
52 {"find":{"run.machine.platform": "windows"}},
53 {"find":{"run.machine.platform": "android"}}
57 '''
59 # Only use this if the suite being queried exceeeds 10,000 results.
60 output_clause = '"destination": "url",\n"format": "list",' if suite in EXCEED_LIMIT else ''
62 query = """
64 "from":"unittest",
65 "limit":200000,
66 "groupby":["result.test"],
67 "select":{"value":"result.duration","aggregate":"median"},
69 "where":{"and":[
70 {"eq":{"repo.branch.name": "mozilla-central"}},
71 {"in":{"result.status": ["OK", "PASS", "FAIL"]}},
72 {"gt":{"run.timestamp": {"date": "today-week"}}},
73 {"eq":{"run.suite.fullname":"%s"}},
77 """ % (output_clause, suite, platform_clause)
79 return query
82 def query_activedata(suite, platform):
83 query = construct_query(suite, platform)
84 print("Querying ActiveData for '{}' tests on '{}' platforms.. "
85 .format(suite, platform), end='')
86 sys.stdout.flush()
87 response = requests.post(ACTIVE_DATA_URL,
88 data=query,
89 stream=True)
90 response.raise_for_status()
92 # Presence of destination clause in the query requires additional processing
93 # to produce the dataset that can be used.
94 if suite in EXCEED_LIMIT:
95 # The output_url is where result of the query will be stored.
96 output_url = response.json()["url"]
98 tried = 0
99 while tried < MAX_RETRIES:
100 # Use the requests.Session object to manage requests, since the output_url
101 # can often return 403 Forbidden.
102 session = requests.Session()
103 response = session.get(output_url)
104 if response.status_code == 200:
105 break
106 # A non-200 status code means we should retry after some wait.
107 time.sleep(RETRY_INTERVAL)
108 tried += 1
110 # Data returned from destination is in format of:
111 # {data: [result: {test: test_name, duration: duration}]}
112 # Normalize it to the format expected by compute_manifest_runtimes.
113 raw_data = response.json()["data"]
114 data = dict([[item['result']['test'], item['result']['duration']] for item in raw_data])
115 else:
116 data = dict(response.json()["data"])
118 print("{} found".format(len(data)))
119 return data
122 def write_runtimes(manifest_runtimes, platform, suite, outdir=here):
123 if not os.path.exists(outdir):
124 os.makedirs(outdir)
126 outfilename = os.path.join(outdir, "manifest-runtimes-{}.json".format(platform))
127 # If file is not present, initialize a file with empty JSON object.
128 if not os.path.exists(outfilename):
129 with open(outfilename, 'w+') as f:
130 json.dump({}, f)
132 # Load the entire file.
133 with open(outfilename, 'r') as f:
134 data = json.load(f)
136 # Update the specific suite with the new runtime information and write to file.
137 data[suite] = manifest_runtimes
138 with open(outfilename, 'w') as f:
139 json.dump(data, f, indent=2, sort_keys=True)
142 def compute_manifest_runtimes(suite, platform):
143 resolver = TestResolver.from_environment(cwd=here, loader_cls=TestManifestLoader)
145 crashtest_prefixes = {
146 'http': '/tests/',
147 'chrome': '/reftest/content/',
148 'file': '/reftest/tests/',
150 manifest_runtimes = defaultdict(float)
151 data = query_activedata(suite, platform)
153 if "web-platform-tests" in suite:
154 wpt_groups = {t["name"]: t["manifest"]
155 for t in resolver.resolve_tests(flavor="web-platform-tests")}
157 for path, duration in data.items():
158 # Returned data did not contain a test path, so go to next result.
159 if not path:
160 continue
162 if suite in ('reftest', 'crashtest') and ' ' in path:
163 path = path.split()[0]
165 if suite == 'crashtest' and '://' in path:
166 # Crashtest paths are URLs with various schemes and prefixes.
167 # Normalize it to become relative to mozilla-central.
168 scheme = path[:path.index('://')]
169 if ':' in scheme:
170 scheme = scheme.split(':')[-1]
171 prefix = crashtest_prefixes[scheme]
172 path = path.split(prefix, 1)[-1]
173 elif suite == 'xpcshell' and ':' in path:
174 path = path.split(':', 1)[-1]
176 if "web-platform-tests" in suite:
177 if path in wpt_groups:
178 manifest_runtimes[wpt_groups[path]] += duration
179 continue
181 if path not in resolver.tests_by_path:
182 continue
184 for test in resolver.tests_by_path[path]:
185 manifest = test.get('ancestor_manifest') or test['manifest_relpath']
186 manifest_runtimes[manifest] += duration
188 manifest_runtimes = {k: round(v, 2) for k, v in manifest_runtimes.items()}
189 return manifest_runtimes
192 def cli(args=sys.argv[1:]):
193 default_suites = [suite for suite, obj in TEST_SUITES.items() if 'build_flavor' in obj]
194 default_platforms = ['android', 'windows', 'unix']
196 parser = ArgumentParser()
197 parser.add_argument('-o', '--output-directory', dest='outdir', default=here,
198 help="Directory to save runtime data.")
199 parser.add_argument('-s', '--suite', dest='suites', action='append',
200 default=None, choices=default_suites,
201 help="Suite(s) to include in the data set (default: all)")
202 parser.add_argument('-p', '--platform', dest='platforms', action='append',
203 default=None, choices=default_platforms,
204 help="Platform(s) to gather runtime information on "
205 "(default: all).")
206 args = parser.parse_args(args)
208 # If a suite was specified, use that. Otherwise, use the full default set.
209 suites = args.suites or default_suites
210 # Same as above, but for the platform clause.
211 platforms = args.platforms or default_platforms
213 for platform in platforms:
214 for suite in suites:
215 runtimes = compute_manifest_runtimes(suite, platform)
216 if not runtimes:
217 print("Not writing runtime data for '{}' for '{}' as no data was found".format(suite, platform))
218 continue
220 write_runtimes(runtimes, platform, suite, outdir=args.outdir)
223 if __name__ == "__main__":
224 sys.exit(cli())