File: iris-bot

package info (click to toggle)
coq-iris 4.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,116 kB
  • sloc: python: 130; makefile: 61; sh: 28; sed: 2
file content (189 lines) | stat: -rwxr-xr-x 9,499 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/python3
import sys, os, subprocess
import requests, argparse
from datetime import datetime, timezone
from collections import namedtuple

################################################################################
# This script lets you autoamtically trigger some operations on the Iris CI to
# do further test/analysis on a branch (usually an MR).
# Set the GITLAB_TOKEN environment variable to a GitLab access token.
# You can generate such a token at
# <https://gitlab.mpi-sws.org/-/user_settings/personal_access_tokens>.
# Select only the "api" scope.
#
# Set at least one of IRIS_REV or STDPP_REV to control which branches of these
# projects to build against (defaults to default git branch). IRIS_REPO and
# STDPP_REPO can be used to take branches from forks. Setting IRIS to
# "user:branch" will use the given branch on that user's fork of Iris, and
# similar for STDPP.
#
# Supported commands:
# - `./iris-bot build [$filter]`: Builds all reverse dependencies against the
#   given branches. The optional `filter` argument only builds projects whose
#   names contains that string.
# - `./iris-bot time $project`: Measure the impact of this branch on the build
#   time of the given reverse dependency. Only Iris branches are supported for
#   now.
################################################################################

PROJECTS = {
    'lambda-rust': { 'name': 'lambda-rust', 'branch': 'master', 'timing': True },
    'lambda-rust-weak': { 'name': 'lambda-rust', 'branch': 'masters/weak_mem' }, # covers GPFSL and ORC11
    'examples': { 'name': 'examples', 'branch': 'master', 'timing': True },
    'gpfsl': { 'name': 'gpfsl', 'branch': 'master', 'timing': True }, # need separate entry for timing
    'iron': { 'name': 'iron', 'branch': 'master', 'timing': True },
    'reloc': { 'name': 'reloc', 'branch': 'master' },
    'actris': { 'name': 'actris', 'branch': 'master' },
    'simuliris': { 'name': 'simuliris', 'branch': 'master' },
    'tutorial-popl20': { 'name': 'tutorial-popl20', 'branch': 'master' },
    'tutorial-popl21': { 'name': 'tutorial-popl21', 'branch': 'master' },
}

if not "GITLAB_TOKEN" in os.environ:
    print("You need to set the GITLAB_TOKEN environment variable to a GitLab access token.")
    print("You can create such tokens at <https://gitlab.mpi-sws.org/profile/personal_access_tokens>.")
    print("Make sure you grant access to the 'api' scope.")
    sys.exit(1)
GITLAB_TOKEN = os.environ["GITLAB_TOKEN"]

# Pre-processing for branch variables of dependency projects: you can set
# 'PROJECT' to 'user:branch', or set 'PROJECT_REPO' and 'PROJECT_REV'
# automatically.
BUILD_BRANCHES = {}
for project in ['stdpp', 'iris', 'orc11', 'gpfsl']:
    var = project.upper()
    if var in os.environ:
        (repo, rev) = os.environ[var].split(':')
        repo = repo + "/" + project
    else:
        repo = os.environ.get(var+"_REPO", "iris/"+project)
        rev = os.environ.get(var+"_REV")
    if rev is not None:
        BUILD_BRANCHES[project] = (repo, rev)

if not "iris" in BUILD_BRANCHES:
    print("Please set IRIS_REV, STDPP_REV, ORC11_REV and GPFSL_REV environment variables to the branch/tag/commit of the respective project that you want to use.")
    print("Only IRIS_REV is mandatory, the rest defaults to the default git branch.")
    sys.exit(1)

# Useful helpers
def trigger_build(project, branch, vars):
    id = "iris%2F{}".format(project)
    url = "https://gitlab.mpi-sws.org/api/v4/projects/{}/pipeline".format(id)
    json = {
        'ref': branch,
        'variables': [{ 'key': key, 'value': val } for (key, val) in vars.items()],
    }
    r = requests.post(url, headers={'PRIVATE-TOKEN': GITLAB_TOKEN}, json=json)
    r.raise_for_status()
    return r.json()

# The commands
def build(args):
    # Convert BUILD_BRANCHES into suitable dictionary
    vars = {}
    for project in BUILD_BRANCHES.keys():
        (repo, rev) = BUILD_BRANCHES[project]
        var = project.upper()
        vars[var+"_REPO"] = repo
        vars[var+"_REV"] = rev
    if args.coq:
        vars["NIGHTLY_COQ"] = args.coq
    # Loop over all projects, and trigger build.
    for (name, project) in PROJECTS.items():
        if args.filter in name:
            print("Triggering build for {}...".format(name))
            pipeline_url = trigger_build(project['name'], project['branch'], vars)['web_url']
            print("    Pipeline running at {}".format(pipeline_url))

TimeJob = namedtuple("TimeJob", "id base_commit base_pipeline test_commit test_pipeline compare")

def time_project(project, iris_repo, iris_rev, test_rev):
    # Obtain a unique ID for this experiment
    id = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
    # Determine the branch commit to build
    subprocess.run(["git", "fetch", "-q", "https://gitlab.mpi-sws.org/{}".format(iris_repo), iris_rev], check=True)
    test_commit = subprocess.run(["git", "rev-parse", "FETCH_HEAD"], check=True, stdout=subprocess.PIPE).stdout.decode().strip()
    # Determine the base commit in master
    subprocess.run(["git", "fetch", "-q", "https://gitlab.mpi-sws.org/iris/iris.git", "master"], check=True)
    base_commit = subprocess.run(["git", "merge-base", test_commit, "FETCH_HEAD"], check=True, stdout=subprocess.PIPE).stdout.decode().strip()
    # Trigger the builds
    vars = {
        'IRIS_REPO': iris_repo,
        'IRIS_REV': base_commit,
        'TIMING_AD_HOC_ID': id+"-base",
    }
    base_pipeline = trigger_build(project['name'], project['branch'], vars)
    vars = {
        'IRIS_REPO': iris_repo,
        'IRIS_REV': test_commit,
        'TIMING_AD_HOC_ID': id+"-test",
    }
    if test_rev is None:
        # We hope that this repository did not change since the job we created just above...
        test_pipeline = trigger_build(project['name'], project['branch'], vars)
    else:
        test_pipeline = trigger_build(project['name'], args.test_rev, vars)
    compare = "https://coq-speed.mpi-sws.org/d/1QE_dqjiz/coq-compare?orgId=1&var-project={}&var-branch1=@hoc&var-commit1={}&var-config1={}&var-branch2=@hoc&var-commit2={}&var-config2={}".format(project['name'], base_pipeline['sha'], id+"-base", test_pipeline['sha'], id+"-test")
    return TimeJob(id, base_commit, base_pipeline['web_url'], test_commit, test_pipeline['web_url'], compare)

def time(args):
    # Make sure only 'iris' variables are set.
    # One could imagine generalizing to "either Iris or std++", but then if the
    # ad-hoc timing jobs honor STDPP_REV, how do we make it so that particular
    # deterministic std++ versions are used for Iris timing? This does not
    # currently seem worth the effort / hacks.
    for project in BUILD_BRANCHES.keys():
        if project != 'iris':
            print("'time' command only supports Iris branches")
            sys.exit(1)
    (iris_repo, iris_rev) = BUILD_BRANCHES['iris']
    # Special mode: time everything
    if args.project == 'all':
        if args.test_rev is not None:
            print("'time all' does not support '--test-rev'")
            sys.exit(1)
        for (name, project) in PROJECTS.items():
            if not project.get('timing'):
                continue
            job = time_project(project, iris_repo, iris_rev, None)
            print("- [{}]({})".format(name, job.compare))
        return
    # Get project to test and ensure it supports timing
    project_name = args.project
    if project_name not in PROJECTS:
        print("ERROR: no such project: {}".format(project_name))
        sys.exit(1)
    project = PROJECTS[project_name]
    if not project.get('timing'):
        print("ERROR: {} does not support timing".format(project_name))
        sys.exit(1)
    # Run it!
    job = time_project(project, iris_repo, iris_rev, args.test_rev)
    print("Triggering timing builds for {} with Iris base commit {} and test commit {} using ad-hoc ID {}...".format(project_name, job.base_commit[:8], job.test_commit[:8], job.id))
    print("    Base pipeline running at {}".format(job.base_pipeline))
    if args.test_rev is None:
        print("    Test pipeline running at {}".format(job.test_pipeline))
    else:
        print("    Test pipeline (on non-standard branch {}) running at {}".format(args.test_rev, job.test_pipeline))
    print("    Once done, timing comparison will be available at {}".format(job.compare))

# Dispatch
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Iris CI utility')
    subparsers = parser.add_subparsers(required=True, title='iris-bot command to execute', description='see "$command -h" for help', metavar="$command")

    parser_build = subparsers.add_parser('build', help='Build many reverse dependencies against an Iris branch')
    parser_build.set_defaults(func=build)
    parser_build.add_argument('--coq', help='the (opam) Coq version to use for these tests')
    parser_build.add_argument('filter', nargs='?', default='', help='(optional) restrict build to projects matching the filter')

    parser_time = subparsers.add_parser('time', help='Time one reverse dependency against an Iris branch')
    parser_time.add_argument("project", help="the project to measure the time of, or 'all' to measure all of them")
    parser_time.add_argument("--test-rev", help="use different revision on project for the test build (in case the project requires changes to still build)")
    parser_time.set_defaults(func=time)

    # Parse, and dispatch to sub-command
    args = parser.parse_args()
    args.func(args)