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
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
16 from __future__ import absolute_import, print_function
23 from argparse import ArgumentParser
24 from collections import defaultdict
28 from moztest.resolve import (
34 here = os.path.abspath(os.path.dirname(__file__))
35 ACTIVE_DATA_URL = "https://activedata.allizom.org/query"
37 # Suites that exceed 10,000 ActiveData result limit will be defined here.
39 'web-platform-tests-reftest
',
45 def construct_query(suite, platform):
46 if platform in ('windows
', 'android
'):
47 platform_clause = '{"find":{"run.machine.platform": "%s"}}' % platform
49 # Bundle macosx and linux results together - they are not too different.
54 {"find":{"run.machine.platform": "windows"}},
55 {"find":{"run.machine.platform": "android"}}
61 # Only use this if the suite being queried exceeeds 10,000 results.
62 output_clause = '"destination": "url",\n"format": "list",' if suite in EXCEED_LIMIT else ''
68 "groupby":["result.test"],
69 "select":{"value":"result.duration","aggregate":"median"},
72 {"eq":{"repo.branch.name": "mozilla-central"}},
73 {"in":{"result.status": ["OK", "PASS", "FAIL"]}},
74 {"gt":{"run.timestamp": {"date": "today-week"}}},
75 {"eq":{"run.suite.fullname":"%s"}},
79 """ % (output_clause, suite, platform_clause)
84 def query_activedata(suite, platform):
85 query = construct_query(suite, platform)
86 print("Querying ActiveData for '{}' tests on '{}' platforms.. "
87 .format(suite, platform), end='')
89 response = requests.post(ACTIVE_DATA_URL,
92 response.raise_for_status()
94 # Presence of destination clause in the query requires additional processing
95 # to produce the dataset that can be used.
96 if suite in EXCEED_LIMIT:
97 # The output_url is where result of the query will be stored.
98 output_url = response.json()["url"]
101 while tried < MAX_RETRIES:
102 # Use the requests.Session object to manage requests, since the output_url
103 # can often return 403 Forbidden.
104 session = requests.Session()
105 response = session.get(output_url)
106 if response.status_code == 200:
108 # A non-200 status code means we should retry after some wait.
109 time.sleep(RETRY_INTERVAL)
112 # Data returned from destination is in format of:
113 # {data: [result: {test: test_name, duration: duration}]}
114 # Normalize it to the format expected by compute_manifest_runtimes.
115 raw_data = response.json()["data"]
116 data = dict([[item['result
']['test'], item['result
']['duration
']] for item in raw_data])
118 data = dict(response.json()["data"])
120 print("{} found".format(len(data)))
124 def write_runtimes(manifest_runtimes, platform, suite, outdir=here):
125 if not os.path.exists(outdir):
128 outfilename = os.path.join(outdir, "manifest-runtimes-{}.json".format(platform))
129 # If file is not present, initialize a file with empty JSON object.
130 if not os.path.exists(outfilename):
131 with open(outfilename, 'w
+') as f:
134 # Load the entire file.
135 with open(outfilename, 'r
') as f:
138 # Update the specific suite with the new runtime information and write to file.
139 data[suite] = manifest_runtimes
140 with open(outfilename, 'w
') as f:
141 json.dump(data, f, indent=2, sort_keys=True)
144 def compute_manifest_runtimes(suite, platform):
145 resolver = TestResolver.from_environment(cwd=here, loader_cls=TestManifestLoader)
147 crashtest_prefixes = {
149 'chrome
': '/reftest
/content
/',
150 'file': '/reftest
/tests
/',
152 manifest_runtimes = defaultdict(float)
153 data = query_activedata(suite, platform)
155 if "web-platform-tests" in suite:
156 wpt_groups = {t["name"]: t["manifest"]
157 for t in resolver.resolve_tests(flavor="web-platform-tests")}
159 for path, duration in data.items():
160 # Returned data did not contain a test path, so go to next result.
164 if suite in ('reftest
', 'crashtest
') and ' ' in path:
165 path = path.split()[0]
167 if suite == 'crashtest
' and '://' in path:
168 # Crashtest paths are URLs with various schemes and prefixes.
169 # Normalize it to become relative to mozilla-central.
170 scheme = path[:path.index('://')]
172 scheme = scheme.split(':')[-1]
173 prefix = crashtest_prefixes[scheme]
174 path = path.split(prefix, 1)[-1]
175 elif suite == 'xpcshell
' and ':' in path:
176 path = path.split(':', 1)[-1]
178 if "web-platform-tests" in suite:
179 if path in wpt_groups:
180 manifest_runtimes[wpt_groups[path]] += duration
183 if path not in resolver.tests_by_path:
186 for test in resolver.tests_by_path[path]:
187 manifest = test.get('ancestor_manifest
') or test['manifest_relpath
']
188 manifest_runtimes[manifest] += duration
190 manifest_runtimes = {k: round(v, 2) for k, v in manifest_runtimes.items()}
191 return manifest_runtimes
194 def cli(args=sys.argv[1:]):
195 default_suites = [suite for suite, obj in TEST_SUITES.items() if 'build_flavor
' in obj]
196 default_platforms = ['android
', 'windows
', 'unix
']
198 parser = ArgumentParser()
199 parser.add_argument('-o', '--output-directory', dest='outdir
', default=here,
200 help="Directory to save runtime data.")
201 parser.add_argument('-s', '--suite', dest='suites
', action='append
',
202 default=None, choices=default_suites,
203 help="Suite(s) to include in the data set (default: all)")
204 parser.add_argument('-p', '--platform', dest='platforms
', action='append
',
205 default=None, choices=default_platforms,
206 help="Platform(s) to gather runtime information on "
208 args = parser.parse_args(args)
210 # If a suite was specified, use that. Otherwise, use the full default set.
211 suites = args.suites or default_suites
212 # Same as above, but for the platform clause.
213 platforms = args.platforms or default_platforms
215 for platform in platforms:
217 runtimes = compute_manifest_runtimes(suite, platform)
219 print("Not writing runtime data for '{}' for '{}' as no data was found".format(suite, platform))
222 write_runtimes(runtimes, platform, suite, outdir=args.outdir)
225 if __name__ == "__main__":