forked from mirrors/gecko-dev
This adds two new mach commands: `mach wpt-fetch-logs` which can download log files from mozilla CI or taskcluster runs on GitHub. `mach wpt-interop-score` which can download runs and compute the interop score. By default this filters tasks to those most like upstream CI i.e. linux64 opt builds. The actual implementation is mostly in external packages; the mach commands are mostly frontends to these. Differential Revision: https://phabricator.services.mozilla.com/D180731
188 lines
5.4 KiB
Python
188 lines
5.4 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
import argparse
|
|
import math
|
|
import os
|
|
import re
|
|
from typing import Callable, Iterable, List, Mapping, Optional, Tuple
|
|
|
|
repos = ["autoland", "mozilla-central", "try", "mozilla-central", "mozilla-beta", "wpt"]
|
|
|
|
default_fetch_task_filters = ["-web-platform-tests-|-spidermonkey-"]
|
|
default_interop_task_filters = [
|
|
"web-platform-tests",
|
|
"linux.*-64",
|
|
"/opt",
|
|
"!-nofis|-headless|-asan|-tsan|-ccov",
|
|
]
|
|
|
|
|
|
def get_parser_fetch_logs() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--log-dir", action="store", help="Directory into which to download logs"
|
|
)
|
|
parser.add_argument(
|
|
"--task-filter",
|
|
dest="task_filters",
|
|
action="append",
|
|
help="Regex filter applied to task names. Filters starting ! must not match. Filters starting ^ (after any !) match the entire task name, otherwise any substring can match. Multiple filters must all match",
|
|
)
|
|
parser.add_argument(
|
|
"--check-complete",
|
|
action="store_true",
|
|
help="Only download logs if the task is complete",
|
|
)
|
|
parser.add_argument(
|
|
"commits",
|
|
nargs="+",
|
|
help="repo:commit e.g. mozilla-central:fae24810aef1 for the runs to include",
|
|
)
|
|
return parser
|
|
|
|
|
|
def get_parser_interop_score() -> argparse.Namespace:
|
|
parser = get_parser_fetch_logs()
|
|
parser.add_argument(
|
|
"--year",
|
|
action="store",
|
|
default=2023,
|
|
type=int,
|
|
help="Interop year to score against",
|
|
)
|
|
parser.add_argument(
|
|
"--category-filter",
|
|
action="append",
|
|
dest="category_filters",
|
|
help="Regex filter applied to category names. Filters starting ! must not match. Filters starting ^ (after any !) match the entire task name, otherwise any substring can match. Multiple filters must all match",
|
|
)
|
|
return parser
|
|
|
|
|
|
def print_scores(
|
|
runs: Iterable[Tuple[str, str]],
|
|
results_by_category: Mapping[str, int],
|
|
include_total: bool,
|
|
):
|
|
tab = "\t" # For f-string
|
|
header = "\t".join(f"{repo}:{commit}" for repo, commit in runs)
|
|
print(f"\t{header}")
|
|
totals = [0] * len(runs)
|
|
for category, category_results in results_by_category.items():
|
|
for i, result in enumerate(category_results):
|
|
totals[i] += result
|
|
print(f"{category}\t{tab.join(str(item / 10) for item in category_results)}")
|
|
if include_total:
|
|
totals = [math.floor(float(item) / len(results_by_category)) for item in totals]
|
|
print(f"Total\t{tab.join(str(item / 10) for item in totals)}")
|
|
|
|
|
|
def get_wptreports(
|
|
repo: str, commit: str, task_filters: List[str], log_dir: str, check_complete: bool
|
|
) -> List[str]:
|
|
import tcfetch
|
|
|
|
return tcfetch.download_artifacts(
|
|
repo,
|
|
commit,
|
|
task_filters=task_filters,
|
|
check_complete=check_complete,
|
|
out_dir=log_dir,
|
|
)
|
|
|
|
|
|
def get_runs(commits: List[str]) -> List[Tuple[str, str]]:
|
|
runs = []
|
|
for item in commits:
|
|
if ":" not in item:
|
|
raise ValueError(f"Expected commits of the form repo:commit, got {item}")
|
|
repo, commit = item.split(":", 1)
|
|
if repo not in repos:
|
|
raise ValueError(f"Unsupported repo {repo}")
|
|
runs.append((repo, commit))
|
|
return runs
|
|
|
|
|
|
def get_category_filter(
|
|
category_filters: Optional[List[str]],
|
|
) -> Optional[Callable[[str], bool]]:
|
|
if category_filters is None:
|
|
return None
|
|
|
|
filters = []
|
|
for item in category_filters:
|
|
if not item:
|
|
continue
|
|
invert = item[0] == "!"
|
|
if invert:
|
|
item = item[1:]
|
|
if item[0] == "^":
|
|
regex = re.compile(item)
|
|
else:
|
|
regex = re.compile(f"^(.*)(?:{item})")
|
|
filters.append((regex, invert))
|
|
|
|
def match_filters(category):
|
|
for regex, invert in filters:
|
|
matches = regex.match(category) is not None
|
|
if invert:
|
|
matches = not matches
|
|
if not matches:
|
|
return False
|
|
return True
|
|
|
|
return match_filters
|
|
|
|
|
|
def fetch_logs(
|
|
commits: List[str],
|
|
task_filters: List[str],
|
|
log_dir: Optional[str],
|
|
check_complete: bool,
|
|
**kwargs,
|
|
):
|
|
runs = get_runs(commits)
|
|
|
|
if not task_filters:
|
|
task_filters = default_fetch_task_filters
|
|
|
|
if log_dir is None:
|
|
log_dir = os.path.abspath(os.curdir)
|
|
|
|
for repo, commit in runs:
|
|
get_wptreports(repo, commit, task_filters, log_dir, check_complete)
|
|
|
|
|
|
def score_runs(
|
|
commits: List[str],
|
|
task_filters: List[str],
|
|
log_dir: Optional[str],
|
|
year: int,
|
|
check_complete: bool,
|
|
category_filters: Optional[List[str]],
|
|
**kwargs,
|
|
):
|
|
from wpt_interop import score
|
|
|
|
runs = get_runs(commits)
|
|
|
|
if not task_filters:
|
|
task_filters = default_interop_task_filters
|
|
|
|
run_logs = []
|
|
for repo, commit in runs:
|
|
log_paths = get_wptreports(repo, commit, task_filters, log_dir, check_complete)
|
|
run_logs.append(log_paths)
|
|
|
|
include_total = category_filters is None
|
|
if category_filters is None:
|
|
category_filters = [f"-{year}-"]
|
|
|
|
category_filter = get_category_filter(category_filters)
|
|
|
|
scores = score.score_wptreports(
|
|
run_logs, year=year, category_filter=category_filter
|
|
)
|
|
print_scores(runs, scores, include_total)
|