xref: /aosp_15_r20/external/pytorch/tools/stats/test_dashboard.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1*da0073e9SAndroid Build Coastguard Workerfrom __future__ import annotations
2*da0073e9SAndroid Build Coastguard Worker
3*da0073e9SAndroid Build Coastguard Workerimport json
4*da0073e9SAndroid Build Coastguard Workerimport os
5*da0073e9SAndroid Build Coastguard Workerimport re
6*da0073e9SAndroid Build Coastguard Workerimport time
7*da0073e9SAndroid Build Coastguard Workerfrom collections import defaultdict
8*da0073e9SAndroid Build Coastguard Workerfrom functools import lru_cache
9*da0073e9SAndroid Build Coastguard Workerfrom pathlib import Path
10*da0073e9SAndroid Build Coastguard Workerfrom tempfile import TemporaryDirectory
11*da0073e9SAndroid Build Coastguard Workerfrom typing import Any, cast
12*da0073e9SAndroid Build Coastguard Worker
13*da0073e9SAndroid Build Coastguard Workerimport requests
14*da0073e9SAndroid Build Coastguard Worker
15*da0073e9SAndroid Build Coastguard Workerfrom tools.stats.upload_stats_lib import (
16*da0073e9SAndroid Build Coastguard Worker    _get_request_headers,
17*da0073e9SAndroid Build Coastguard Worker    download_s3_artifacts,
18*da0073e9SAndroid Build Coastguard Worker    get_job_id,
19*da0073e9SAndroid Build Coastguard Worker    unzip,
20*da0073e9SAndroid Build Coastguard Worker    upload_workflow_stats_to_s3,
21*da0073e9SAndroid Build Coastguard Worker)
22*da0073e9SAndroid Build Coastguard Worker
23*da0073e9SAndroid Build Coastguard Worker
24*da0073e9SAndroid Build Coastguard WorkerREGEX_JOB_INFO = r"(.*) \/ .*test \(([^,]*), .*\)"
25*da0073e9SAndroid Build Coastguard Worker
26*da0073e9SAndroid Build Coastguard Worker
27*da0073e9SAndroid Build Coastguard Worker@lru_cache(maxsize=1000)
28*da0073e9SAndroid Build Coastguard Workerdef get_job_name(job_id: int) -> str:
29*da0073e9SAndroid Build Coastguard Worker    try:
30*da0073e9SAndroid Build Coastguard Worker        return cast(
31*da0073e9SAndroid Build Coastguard Worker            str,
32*da0073e9SAndroid Build Coastguard Worker            requests.get(
33*da0073e9SAndroid Build Coastguard Worker                f"https://api.github.com/repos/pytorch/pytorch/actions/jobs/{job_id}",
34*da0073e9SAndroid Build Coastguard Worker                headers=_get_request_headers(),
35*da0073e9SAndroid Build Coastguard Worker            ).json()["name"],
36*da0073e9SAndroid Build Coastguard Worker        )
37*da0073e9SAndroid Build Coastguard Worker    except Exception as e:
38*da0073e9SAndroid Build Coastguard Worker        print(f"Failed to get job name for job id {job_id}: {e}")
39*da0073e9SAndroid Build Coastguard Worker        return "NoJobName"
40*da0073e9SAndroid Build Coastguard Worker
41*da0073e9SAndroid Build Coastguard Worker
42*da0073e9SAndroid Build Coastguard Worker@lru_cache(maxsize=1000)
43*da0073e9SAndroid Build Coastguard Workerdef get_build_name(job_name: str) -> str:
44*da0073e9SAndroid Build Coastguard Worker    try:
45*da0073e9SAndroid Build Coastguard Worker        return re.match(REGEX_JOB_INFO, job_name).group(1)  # type: ignore[union-attr]
46*da0073e9SAndroid Build Coastguard Worker    except AttributeError:
47*da0073e9SAndroid Build Coastguard Worker        print(f"Failed to match job name: {job_name}")
48*da0073e9SAndroid Build Coastguard Worker        return "NoBuildEnv"
49*da0073e9SAndroid Build Coastguard Worker
50*da0073e9SAndroid Build Coastguard Worker
51*da0073e9SAndroid Build Coastguard Worker@lru_cache(maxsize=1000)
52*da0073e9SAndroid Build Coastguard Workerdef get_test_config(job_name: str) -> str:
53*da0073e9SAndroid Build Coastguard Worker    try:
54*da0073e9SAndroid Build Coastguard Worker        return re.match(REGEX_JOB_INFO, job_name).group(2)  # type: ignore[union-attr]
55*da0073e9SAndroid Build Coastguard Worker    except AttributeError:
56*da0073e9SAndroid Build Coastguard Worker        print(f"Failed to match job name: {job_name}")
57*da0073e9SAndroid Build Coastguard Worker        return "NoTestConfig"
58*da0073e9SAndroid Build Coastguard Worker
59*da0073e9SAndroid Build Coastguard Worker
60*da0073e9SAndroid Build Coastguard Workerdef get_td_exclusions(
61*da0073e9SAndroid Build Coastguard Worker    workflow_run_id: int, workflow_run_attempt: int
62*da0073e9SAndroid Build Coastguard Worker) -> dict[str, Any]:
63*da0073e9SAndroid Build Coastguard Worker    with TemporaryDirectory() as temp_dir:
64*da0073e9SAndroid Build Coastguard Worker        print("Using temporary directory:", temp_dir)
65*da0073e9SAndroid Build Coastguard Worker        os.chdir(temp_dir)
66*da0073e9SAndroid Build Coastguard Worker
67*da0073e9SAndroid Build Coastguard Worker        # Download and extract all the reports (both GHA and S3)
68*da0073e9SAndroid Build Coastguard Worker        s3_paths = download_s3_artifacts(
69*da0073e9SAndroid Build Coastguard Worker            "test-jsons", workflow_run_id, workflow_run_attempt
70*da0073e9SAndroid Build Coastguard Worker        )
71*da0073e9SAndroid Build Coastguard Worker        for path in s3_paths:
72*da0073e9SAndroid Build Coastguard Worker            unzip(path)
73*da0073e9SAndroid Build Coastguard Worker
74*da0073e9SAndroid Build Coastguard Worker        grouped_tests: dict[str, Any] = defaultdict(lambda: defaultdict(set))
75*da0073e9SAndroid Build Coastguard Worker        for td_exclusions in Path(".").glob("**/td_exclusions*.json"):
76*da0073e9SAndroid Build Coastguard Worker            with open(td_exclusions) as f:
77*da0073e9SAndroid Build Coastguard Worker                exclusions = json.load(f)
78*da0073e9SAndroid Build Coastguard Worker                for exclusion in exclusions["excluded"]:
79*da0073e9SAndroid Build Coastguard Worker                    job_id = get_job_id(td_exclusions)
80*da0073e9SAndroid Build Coastguard Worker                    job_name = get_job_name(job_id)
81*da0073e9SAndroid Build Coastguard Worker                    build_name = get_build_name(job_name)
82*da0073e9SAndroid Build Coastguard Worker                    test_config = get_test_config(job_name)
83*da0073e9SAndroid Build Coastguard Worker                    grouped_tests[build_name][test_config].add(exclusion["test_file"])
84*da0073e9SAndroid Build Coastguard Worker
85*da0073e9SAndroid Build Coastguard Worker        for build_name, build in grouped_tests.items():
86*da0073e9SAndroid Build Coastguard Worker            for test_config, test_files in build.items():
87*da0073e9SAndroid Build Coastguard Worker                grouped_tests[build_name][test_config] = sorted(test_files)
88*da0073e9SAndroid Build Coastguard Worker        return grouped_tests
89*da0073e9SAndroid Build Coastguard Worker
90*da0073e9SAndroid Build Coastguard Worker
91*da0073e9SAndroid Build Coastguard Workerdef group_test_cases(test_cases: list[dict[str, Any]]) -> dict[str, Any]:
92*da0073e9SAndroid Build Coastguard Worker    start = time.time()
93*da0073e9SAndroid Build Coastguard Worker    grouped_tests: dict[str, Any] = defaultdict(
94*da0073e9SAndroid Build Coastguard Worker        lambda: defaultdict(
95*da0073e9SAndroid Build Coastguard Worker            lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
96*da0073e9SAndroid Build Coastguard Worker        )
97*da0073e9SAndroid Build Coastguard Worker    )
98*da0073e9SAndroid Build Coastguard Worker    for test_case in test_cases:
99*da0073e9SAndroid Build Coastguard Worker        job_name = get_job_name(test_case["job_id"])
100*da0073e9SAndroid Build Coastguard Worker        build_name = get_build_name(job_name)
101*da0073e9SAndroid Build Coastguard Worker        if "bazel" in build_name:
102*da0073e9SAndroid Build Coastguard Worker            continue
103*da0073e9SAndroid Build Coastguard Worker        test_config = get_test_config(job_name)
104*da0073e9SAndroid Build Coastguard Worker        class_name = test_case.pop("classname", "NoClass")
105*da0073e9SAndroid Build Coastguard Worker        name = test_case.pop("name", "NoName")
106*da0073e9SAndroid Build Coastguard Worker        invoking_file = test_case.pop("invoking_file", "NoFile")
107*da0073e9SAndroid Build Coastguard Worker        invoking_file = invoking_file.replace(".", "/")
108*da0073e9SAndroid Build Coastguard Worker        test_case.pop("workflow_id")
109*da0073e9SAndroid Build Coastguard Worker        test_case.pop("workflow_run_attempt")
110*da0073e9SAndroid Build Coastguard Worker        grouped_tests[build_name][test_config][invoking_file][class_name][name].append(
111*da0073e9SAndroid Build Coastguard Worker            test_case
112*da0073e9SAndroid Build Coastguard Worker        )
113*da0073e9SAndroid Build Coastguard Worker
114*da0073e9SAndroid Build Coastguard Worker    print(f"Time taken to group tests: {time.time() - start}")
115*da0073e9SAndroid Build Coastguard Worker    return grouped_tests
116*da0073e9SAndroid Build Coastguard Worker
117*da0073e9SAndroid Build Coastguard Worker
118*da0073e9SAndroid Build Coastguard Workerdef get_reruns(grouped_tests: dict[str, Any]) -> dict[str, Any]:
119*da0073e9SAndroid Build Coastguard Worker    reruns: dict[str, Any] = defaultdict(
120*da0073e9SAndroid Build Coastguard Worker        lambda: defaultdict(
121*da0073e9SAndroid Build Coastguard Worker            lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
122*da0073e9SAndroid Build Coastguard Worker        )
123*da0073e9SAndroid Build Coastguard Worker    )
124*da0073e9SAndroid Build Coastguard Worker    for build_name, build in grouped_tests.items():
125*da0073e9SAndroid Build Coastguard Worker        for test_config, test_config_data in build.items():
126*da0073e9SAndroid Build Coastguard Worker            for invoking_file, invoking_file_data in test_config_data.items():
127*da0073e9SAndroid Build Coastguard Worker                for class_name, class_data in invoking_file_data.items():
128*da0073e9SAndroid Build Coastguard Worker                    for test_name, test_data in class_data.items():
129*da0073e9SAndroid Build Coastguard Worker                        if len(test_data) > 1:
130*da0073e9SAndroid Build Coastguard Worker                            if invoking_file in (
131*da0073e9SAndroid Build Coastguard Worker                                "distributed/test_distributed_spawn",
132*da0073e9SAndroid Build Coastguard Worker                                "onnx/test_fx_to_onnx_with_onnxruntime",
133*da0073e9SAndroid Build Coastguard Worker                                "distributed/algorithms/quantization/test_quantization",
134*da0073e9SAndroid Build Coastguard Worker                            ):
135*da0073e9SAndroid Build Coastguard Worker                                continue
136*da0073e9SAndroid Build Coastguard Worker                            reruns[build_name][test_config][invoking_file][class_name][
137*da0073e9SAndroid Build Coastguard Worker                                test_name
138*da0073e9SAndroid Build Coastguard Worker                            ] = test_data
139*da0073e9SAndroid Build Coastguard Worker    return reruns
140*da0073e9SAndroid Build Coastguard Worker
141*da0073e9SAndroid Build Coastguard Worker
142*da0073e9SAndroid Build Coastguard Workerdef get_invoking_file_summary(grouped_tests: dict[str, Any]) -> dict[str, Any]:
143*da0073e9SAndroid Build Coastguard Worker    invoking_file_summary: dict[str, Any] = defaultdict(
144*da0073e9SAndroid Build Coastguard Worker        lambda: defaultdict(lambda: defaultdict(lambda: {"count": 0, "time": 0.0}))
145*da0073e9SAndroid Build Coastguard Worker    )
146*da0073e9SAndroid Build Coastguard Worker    for build_name, build in grouped_tests.items():
147*da0073e9SAndroid Build Coastguard Worker        for test_config, test_config_data in build.items():
148*da0073e9SAndroid Build Coastguard Worker            for invoking_file, invoking_file_data in test_config_data.items():
149*da0073e9SAndroid Build Coastguard Worker                for class_data in invoking_file_data.values():
150*da0073e9SAndroid Build Coastguard Worker                    for test_data in class_data.values():
151*da0073e9SAndroid Build Coastguard Worker                        invoking_file_summary[build_name][test_config][invoking_file][
152*da0073e9SAndroid Build Coastguard Worker                            "count"
153*da0073e9SAndroid Build Coastguard Worker                        ] += 1
154*da0073e9SAndroid Build Coastguard Worker                        for i in test_data:
155*da0073e9SAndroid Build Coastguard Worker                            invoking_file_summary[build_name][test_config][
156*da0073e9SAndroid Build Coastguard Worker                                invoking_file
157*da0073e9SAndroid Build Coastguard Worker                            ]["time"] += i["time"]
158*da0073e9SAndroid Build Coastguard Worker
159*da0073e9SAndroid Build Coastguard Worker    return invoking_file_summary
160*da0073e9SAndroid Build Coastguard Worker
161*da0073e9SAndroid Build Coastguard Worker
162*da0073e9SAndroid Build Coastguard Workerdef upload_additional_info(
163*da0073e9SAndroid Build Coastguard Worker    workflow_run_id: int, workflow_run_attempt: int, test_cases: list[dict[str, Any]]
164*da0073e9SAndroid Build Coastguard Worker) -> None:
165*da0073e9SAndroid Build Coastguard Worker    grouped_tests = group_test_cases(test_cases)
166*da0073e9SAndroid Build Coastguard Worker    reruns = get_reruns(grouped_tests)
167*da0073e9SAndroid Build Coastguard Worker    exclusions = get_td_exclusions(workflow_run_id, workflow_run_attempt)
168*da0073e9SAndroid Build Coastguard Worker    invoking_file_summary = get_invoking_file_summary(grouped_tests)
169*da0073e9SAndroid Build Coastguard Worker
170*da0073e9SAndroid Build Coastguard Worker    upload_workflow_stats_to_s3(
171*da0073e9SAndroid Build Coastguard Worker        workflow_run_id,
172*da0073e9SAndroid Build Coastguard Worker        workflow_run_attempt,
173*da0073e9SAndroid Build Coastguard Worker        "additional_info/reruns",
174*da0073e9SAndroid Build Coastguard Worker        [reruns],
175*da0073e9SAndroid Build Coastguard Worker    )
176*da0073e9SAndroid Build Coastguard Worker    upload_workflow_stats_to_s3(
177*da0073e9SAndroid Build Coastguard Worker        workflow_run_id,
178*da0073e9SAndroid Build Coastguard Worker        workflow_run_attempt,
179*da0073e9SAndroid Build Coastguard Worker        "additional_info/td_exclusions",
180*da0073e9SAndroid Build Coastguard Worker        [exclusions],
181*da0073e9SAndroid Build Coastguard Worker    )
182*da0073e9SAndroid Build Coastguard Worker    upload_workflow_stats_to_s3(
183*da0073e9SAndroid Build Coastguard Worker        workflow_run_id,
184*da0073e9SAndroid Build Coastguard Worker        workflow_run_attempt,
185*da0073e9SAndroid Build Coastguard Worker        "additional_info/invoking_file_summary",
186*da0073e9SAndroid Build Coastguard Worker        [invoking_file_summary],
187*da0073e9SAndroid Build Coastguard Worker    )
188