xref: /aosp_15_r20/external/mesa3d/bin/ci/nightly_compare.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1*61046927SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*61046927SAndroid Build Coastguard Worker# Copyright © 2020 - 2024 Collabora Ltd.
3*61046927SAndroid Build Coastguard Worker# Authors:
4*61046927SAndroid Build Coastguard Worker#   David Heidelberg <[email protected]>
5*61046927SAndroid Build Coastguard Worker#   Sergi Blanch Torne <[email protected]>
6*61046927SAndroid Build Coastguard Worker# SPDX-License-Identifier: MIT
7*61046927SAndroid Build Coastguard Worker
8*61046927SAndroid Build Coastguard Worker"""
9*61046927SAndroid Build Coastguard WorkerCompare the two latest scheduled pipelines and provide information
10*61046927SAndroid Build Coastguard Workerabout the jobs you're interested in.
11*61046927SAndroid Build Coastguard Worker"""
12*61046927SAndroid Build Coastguard Worker
13*61046927SAndroid Build Coastguard Workerimport argparse
14*61046927SAndroid Build Coastguard Workerimport csv
15*61046927SAndroid Build Coastguard Workerimport re
16*61046927SAndroid Build Coastguard Workerimport requests
17*61046927SAndroid Build Coastguard Workerimport io
18*61046927SAndroid Build Coastguard Workerfrom tabulate import tabulate
19*61046927SAndroid Build Coastguard Worker
20*61046927SAndroid Build Coastguard Workerimport gitlab
21*61046927SAndroid Build Coastguard Workerfrom colorama import Fore, Style
22*61046927SAndroid Build Coastguard Workerfrom gitlab_common import read_token
23*61046927SAndroid Build Coastguard Worker
24*61046927SAndroid Build Coastguard Worker
25*61046927SAndroid Build Coastguard WorkerMARGE_BOT_USER_ID = 9716
26*61046927SAndroid Build Coastguard Worker
27*61046927SAndroid Build Coastguard Workerdef print_failures_csv(id):
28*61046927SAndroid Build Coastguard Worker    url = 'https://gitlab.freedesktop.org/mesa/mesa/-/jobs/' + str(id) + '/artifacts/raw/results/failures.csv'
29*61046927SAndroid Build Coastguard Worker    missing: int = 0
30*61046927SAndroid Build Coastguard Worker    MAX_MISS: int = 20
31*61046927SAndroid Build Coastguard Worker    try:
32*61046927SAndroid Build Coastguard Worker        response = requests.get(url)
33*61046927SAndroid Build Coastguard Worker        response.raise_for_status()
34*61046927SAndroid Build Coastguard Worker        csv_content = io.StringIO(response.text)
35*61046927SAndroid Build Coastguard Worker        csv_reader = csv.reader(csv_content)
36*61046927SAndroid Build Coastguard Worker        data = list(csv_reader)
37*61046927SAndroid Build Coastguard Worker
38*61046927SAndroid Build Coastguard Worker        for line in data[:]:
39*61046927SAndroid Build Coastguard Worker            if line[1] == "UnexpectedImprovement(Pass)":
40*61046927SAndroid Build Coastguard Worker                line[1] = Fore.GREEN + line[1] + Style.RESET_ALL
41*61046927SAndroid Build Coastguard Worker            elif line[1] == "UnexpectedImprovement(Fail)":
42*61046927SAndroid Build Coastguard Worker                line[1] = Fore.YELLOW + line[1] + Style.RESET_ALL
43*61046927SAndroid Build Coastguard Worker            elif line[1] == "Crash" or line[1] == "Fail":
44*61046927SAndroid Build Coastguard Worker                line[1] = Fore.RED + line[1] + Style.RESET_ALL
45*61046927SAndroid Build Coastguard Worker            elif line[1] == "Missing":
46*61046927SAndroid Build Coastguard Worker                if missing > MAX_MISS:
47*61046927SAndroid Build Coastguard Worker                    data.remove(line)
48*61046927SAndroid Build Coastguard Worker                    continue
49*61046927SAndroid Build Coastguard Worker                missing += 1
50*61046927SAndroid Build Coastguard Worker                line[1] = Fore.YELLOW + line[1] + Style.RESET_ALL
51*61046927SAndroid Build Coastguard Worker            elif line[1] == "Fail":
52*61046927SAndroid Build Coastguard Worker                line[1] = Fore.RED + line[1] + Style.RESET_ALL
53*61046927SAndroid Build Coastguard Worker            else:
54*61046927SAndroid Build Coastguard Worker                line[1] = Fore.WHITE + line[1] + Style.RESET_ALL
55*61046927SAndroid Build Coastguard Worker
56*61046927SAndroid Build Coastguard Worker        if missing > MAX_MISS:
57*61046927SAndroid Build Coastguard Worker            data.append([Fore.RED + f"... more than {MAX_MISS} missing tests, something crashed?", "Missing" + Style.RESET_ALL])
58*61046927SAndroid Build Coastguard Worker        headers = ["Test                                                                           ", "Result"]
59*61046927SAndroid Build Coastguard Worker        print(tabulate(data, headers, tablefmt="plain"))
60*61046927SAndroid Build Coastguard Worker    except Exception:
61*61046927SAndroid Build Coastguard Worker        pass
62*61046927SAndroid Build Coastguard Worker
63*61046927SAndroid Build Coastguard Worker
64*61046927SAndroid Build Coastguard Workerdef job_failed_before(old_jobs, job):
65*61046927SAndroid Build Coastguard Worker    for old_job in old_jobs:
66*61046927SAndroid Build Coastguard Worker        if job.name == old_job.name:
67*61046927SAndroid Build Coastguard Worker            return old_job
68*61046927SAndroid Build Coastguard Worker
69*61046927SAndroid Build Coastguard Worker
70*61046927SAndroid Build Coastguard Workerdef parse_args() -> None:
71*61046927SAndroid Build Coastguard Worker    """Parse args"""
72*61046927SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
73*61046927SAndroid Build Coastguard Worker        description="Tool to show merge requests assigned to the marge-bot",
74*61046927SAndroid Build Coastguard Worker    )
75*61046927SAndroid Build Coastguard Worker    parser.add_argument(
76*61046927SAndroid Build Coastguard Worker        "--target",
77*61046927SAndroid Build Coastguard Worker        metavar="target-job",
78*61046927SAndroid Build Coastguard Worker        help="Target job regex. For multiple targets, pass multiple values, "
79*61046927SAndroid Build Coastguard Worker        "eg. `--target foo bar`.",
80*61046927SAndroid Build Coastguard Worker        required=False,
81*61046927SAndroid Build Coastguard Worker        nargs=argparse.ONE_OR_MORE,
82*61046927SAndroid Build Coastguard Worker    )
83*61046927SAndroid Build Coastguard Worker    parser.add_argument(
84*61046927SAndroid Build Coastguard Worker        "--token",
85*61046927SAndroid Build Coastguard Worker        metavar="token",
86*61046927SAndroid Build Coastguard Worker        help="force GitLab token, otherwise it's read from ~/.config/gitlab-token",
87*61046927SAndroid Build Coastguard Worker    )
88*61046927SAndroid Build Coastguard Worker    return parser.parse_args()
89*61046927SAndroid Build Coastguard Worker
90*61046927SAndroid Build Coastguard Worker
91*61046927SAndroid Build Coastguard Workerif __name__ == "__main__":
92*61046927SAndroid Build Coastguard Worker    args = parse_args()
93*61046927SAndroid Build Coastguard Worker    token = read_token(args.token)
94*61046927SAndroid Build Coastguard Worker    gl = gitlab.Gitlab(url="https://gitlab.freedesktop.org", private_token=token)
95*61046927SAndroid Build Coastguard Worker
96*61046927SAndroid Build Coastguard Worker    project = gl.projects.get("mesa/mesa")
97*61046927SAndroid Build Coastguard Worker
98*61046927SAndroid Build Coastguard Worker    print(
99*61046927SAndroid Build Coastguard Worker        "\u001b]8;;https://gitlab.freedesktop.org/mesa/mesa/-/pipelines?page=1&scope=all&source=schedule\u001b\\Scheduled pipelines overview\u001b]8;;\u001b\\"
100*61046927SAndroid Build Coastguard Worker    )
101*61046927SAndroid Build Coastguard Worker    pipelines = project.pipelines.list(
102*61046927SAndroid Build Coastguard Worker        source="schedule", ordered_by="created_at", sort="desc", page=1, per_page=2
103*61046927SAndroid Build Coastguard Worker    )
104*61046927SAndroid Build Coastguard Worker    print(
105*61046927SAndroid Build Coastguard Worker        f"Old pipeline: {pipelines[1].created_at}\t\u001b]8;;{pipelines[1].web_url}\u001b\\{pipelines[1].status}\u001b]8;;\u001b\\\t{pipelines[1].sha}"
106*61046927SAndroid Build Coastguard Worker    )
107*61046927SAndroid Build Coastguard Worker    print(
108*61046927SAndroid Build Coastguard Worker        f"New pipeline: {pipelines[0].created_at}\t\u001b]8;;{pipelines[0].web_url}\u001b\\{pipelines[0].status}\u001b]8;;\u001b\\\t{pipelines[0].sha}"
109*61046927SAndroid Build Coastguard Worker    )
110*61046927SAndroid Build Coastguard Worker    print(
111*61046927SAndroid Build Coastguard Worker        f"\nWebUI visual compare: https://gitlab.freedesktop.org/mesa/mesa/-/compare/{pipelines[1].sha}...{pipelines[0].sha}\n"
112*61046927SAndroid Build Coastguard Worker    )
113*61046927SAndroid Build Coastguard Worker
114*61046927SAndroid Build Coastguard Worker    # regex part
115*61046927SAndroid Build Coastguard Worker    if args.target:
116*61046927SAndroid Build Coastguard Worker        target = "|".join(args.target)
117*61046927SAndroid Build Coastguard Worker        target = target.strip()
118*61046927SAndroid Build Coastguard Worker        print("�� jobs: " + Fore.BLUE + target + Style.RESET_ALL)
119*61046927SAndroid Build Coastguard Worker
120*61046927SAndroid Build Coastguard Worker        target = f"({target})" + r"( \d+/\d+)?"
121*61046927SAndroid Build Coastguard Worker    else:
122*61046927SAndroid Build Coastguard Worker        target = ".*"
123*61046927SAndroid Build Coastguard Worker
124*61046927SAndroid Build Coastguard Worker    target_jobs_regex: re.Pattern = re.compile(target)
125*61046927SAndroid Build Coastguard Worker
126*61046927SAndroid Build Coastguard Worker    old_failed_jobs = []
127*61046927SAndroid Build Coastguard Worker    for job in pipelines[1].jobs.list(all=True):
128*61046927SAndroid Build Coastguard Worker        if (
129*61046927SAndroid Build Coastguard Worker            job.status != "failed"
130*61046927SAndroid Build Coastguard Worker            or target_jobs_regex
131*61046927SAndroid Build Coastguard Worker            and not target_jobs_regex.fullmatch(job.name)
132*61046927SAndroid Build Coastguard Worker        ):
133*61046927SAndroid Build Coastguard Worker            continue
134*61046927SAndroid Build Coastguard Worker        old_failed_jobs.append(job)
135*61046927SAndroid Build Coastguard Worker
136*61046927SAndroid Build Coastguard Worker    job_failed = False
137*61046927SAndroid Build Coastguard Worker    for job in pipelines[0].jobs.list(all=True):
138*61046927SAndroid Build Coastguard Worker        if (
139*61046927SAndroid Build Coastguard Worker            job.status != "failed"
140*61046927SAndroid Build Coastguard Worker            or target_jobs_regex
141*61046927SAndroid Build Coastguard Worker            and not target_jobs_regex.fullmatch(job.name)
142*61046927SAndroid Build Coastguard Worker        ):
143*61046927SAndroid Build Coastguard Worker            continue
144*61046927SAndroid Build Coastguard Worker
145*61046927SAndroid Build Coastguard Worker        job_failed = True
146*61046927SAndroid Build Coastguard Worker
147*61046927SAndroid Build Coastguard Worker        previously_failed_job = job_failed_before(old_failed_jobs, job)
148*61046927SAndroid Build Coastguard Worker        if previously_failed_job:
149*61046927SAndroid Build Coastguard Worker            print(
150*61046927SAndroid Build Coastguard Worker                Fore.YELLOW
151*61046927SAndroid Build Coastguard Worker                + f":: \u001b]8;;{job.web_url}\u001b\\{job.name}\u001b]8;;\u001b\\"
152*61046927SAndroid Build Coastguard Worker                + Fore.MAGENTA
153*61046927SAndroid Build Coastguard Worker                + f" \u001b]8;;{previously_failed_job.web_url}\u001b\\(previous run)\u001b]8;;\u001b\\"
154*61046927SAndroid Build Coastguard Worker                + Style.RESET_ALL
155*61046927SAndroid Build Coastguard Worker            )
156*61046927SAndroid Build Coastguard Worker        else:
157*61046927SAndroid Build Coastguard Worker            print(
158*61046927SAndroid Build Coastguard Worker                Fore.RED
159*61046927SAndroid Build Coastguard Worker                + f":: \u001b]8;;{job.web_url}\u001b\\{job.name}\u001b]8;;\u001b\\"
160*61046927SAndroid Build Coastguard Worker                + Style.RESET_ALL
161*61046927SAndroid Build Coastguard Worker            )
162*61046927SAndroid Build Coastguard Worker        print_failures_csv(job.id)
163*61046927SAndroid Build Coastguard Worker
164*61046927SAndroid Build Coastguard Worker    if not job_failed:
165*61046927SAndroid Build Coastguard Worker        exit(0)
166*61046927SAndroid Build Coastguard Worker
167*61046927SAndroid Build Coastguard Worker    print("Commits between nightly pipelines:")
168*61046927SAndroid Build Coastguard Worker    commit = project.commits.get(pipelines[0].sha)
169*61046927SAndroid Build Coastguard Worker    while True:
170*61046927SAndroid Build Coastguard Worker        print(
171*61046927SAndroid Build Coastguard Worker            f"{commit.id}  \u001b]8;;{commit.web_url}\u001b\\{commit.title}\u001b]8;;\u001b\\"
172*61046927SAndroid Build Coastguard Worker        )
173*61046927SAndroid Build Coastguard Worker        if commit.id == pipelines[1].sha:
174*61046927SAndroid Build Coastguard Worker            break
175*61046927SAndroid Build Coastguard Worker        commit = project.commits.get(commit.parent_ids[0])
176