gitlab-ci: Fix traces caching in tracie
[mesa.git] / .gitlab-ci / tracie / tracie.py
1 import argparse
2 import enum
3 import glob
4 import hashlib
5 import os
6 import requests
7 import sys
8 import tempfile
9 import time
10 import yaml
11
12 from pathlib import Path
13 from PIL import Image
14 from urllib import parse
15
16 import dump_trace_images
17
18 TRACES_DB_PATH = os.getcwd() + "/traces-db/"
19 RESULTS_PATH = os.getcwd() + "/results/"
20
21 def replay(trace_path, device_name):
22 success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
23
24 if not success:
25 print("[check_image] Trace %s couldn't be replayed. See above logs for more information." % (str(trace_path)))
26 return None, None, None
27 else:
28 base_path = trace_path.parent
29 file_name = trace_path.name
30 files = glob.glob(str(base_path / "test" / device_name / (file_name + "-*" + ".png")))
31 assert(files)
32 image_file = files[0]
33 files = glob.glob(str(base_path / "test" / device_name / (file_name + ".log")))
34 assert(files)
35 log_file = files[0]
36 return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
37
38 def download_metadata(repo_url, repo_commit, trace_path):
39 # The GitLab API doesn't want the .git postfix
40 url = repo_url
41 if url.endswith(".git"):
42 url = url[:-4]
43 url = parse.urlparse(url)
44
45 url_path = url.path
46 if url_path.startswith("/"):
47 url_path = url_path[1:]
48
49 gitlab_api_url = url.scheme + "://" + url.netloc + "/api/v4/projects/" + parse.quote_plus(url_path)
50
51 r = requests.get(gitlab_api_url + "/repository/files/%s/raw?ref=%s" % (parse.quote_plus(trace_path), repo_commit))
52 metadata_raw = r.text.strip().split('\n')
53 metadata = dict(line.split(' ', 1) for line in metadata_raw[1:])
54 oid = metadata["oid"][7:] if metadata["oid"].startswith('sha256:') else metadata["oid"]
55 size = int(metadata['size'])
56
57 return oid, size
58
59 def download_trace(repo_url, repo_commit, trace_path, oid, size):
60 headers = {
61 "Accept": "application/vnd.git-lfs+json",
62 "Content-Type": "application/vnd.git-lfs+json"
63 }
64 json = {
65 "operation": "download",
66 "transfers": [ "basic" ],
67 "ref": { "name": "refs/heads/%s" % repo_commit },
68 "objects": [
69 {
70 "oid": oid,
71 "size": size
72 }
73 ]
74 }
75
76 # The LFS API really wants the .git postfix...
77 if not repo_url.endswith(".git"):
78 repo_url += ".git"
79
80 r = requests.post(repo_url + "/info/lfs/objects/batch", headers=headers, json=json)
81 url = r.json()["objects"][0]["actions"]["download"]["href"]
82 open(TRACES_DB_PATH + trace_path, "wb").write(requests.get(url).content)
83
84 def checksum(filename, hash_factory=hashlib.sha256, chunk_num_blocks=128):
85 h = hash_factory()
86 with open(filename,'rb') as f:
87 for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
88 h.update(chunk)
89 return h.hexdigest()
90
91 def ensure_trace(repo_url, repo_commit, trace):
92 trace_path = TRACES_DB_PATH + trace['path']
93 if repo_url is None:
94 assert(repo_commit is None)
95 assert(os.path.exists(trace_path))
96 return
97
98 os.makedirs(os.path.dirname(trace_path), exist_ok=True)
99
100 if os.path.exists(trace_path):
101 local_oid = checksum(trace_path)
102
103 remote_oid, size = download_metadata(repo_url, repo_commit, trace['path'])
104
105 if not os.path.exists(trace_path) or local_oid != remote_oid:
106 print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
107 download_time = time.time()
108 download_trace(repo_url, repo_commit, trace['path'], remote_oid, size)
109 print("took %ds." % (time.time() - download_time), flush=True)
110
111 def check_trace(repo_url, repo_commit, device_name, trace, expectation):
112 ensure_trace(repo_url, repo_commit, trace)
113
114 trace_path = Path(TRACES_DB_PATH + trace['path'])
115 checksum, image_file, log_file = replay(trace_path, device_name)
116 if checksum is None:
117 return False
118 elif checksum == expectation['checksum']:
119 print("[check_image] Images match for %s" % (trace['path']))
120 ok = True
121 else:
122 print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
123 (trace['path'], expectation['checksum'], checksum))
124 print("[check_image] For more information see "
125 "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
126 ok = False
127
128 trace_dir = os.path.split(trace['path'])[0]
129 results_path = os.path.join(RESULTS_PATH, trace_dir, "test", device_name)
130 os.makedirs(results_path, exist_ok=True)
131 os.rename(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
132 if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
133 os.rename(image_file, os.path.join(results_path, os.path.split(image_file)[1]))
134
135 return ok
136
137 def main():
138 parser = argparse.ArgumentParser()
139 parser.add_argument('--file', required=True,
140 help='the name of the traces.yml file listing traces and their checksums for each device')
141 parser.add_argument('--device-name', required=True,
142 help="the name of the graphics device used to replay traces")
143
144 args = parser.parse_args()
145
146 with open(args.file, 'r') as f:
147 y = yaml.safe_load(f)
148
149 if "traces-db" in y:
150 repo = y["traces-db"]["repo"]
151 commit_id = y["traces-db"]["commit"]
152 else:
153 repo = None
154 commit_id = None
155
156 traces = y['traces']
157 all_ok = True
158 for trace in traces:
159 for expectation in trace['expectations']:
160 if expectation['device'] == args.device_name:
161 ok = check_trace(repo, commit_id, args.device_name, trace, expectation)
162 all_ok = all_ok and ok
163
164 sys.exit(0 if all_ok else 1)
165
166 if __name__ == "__main__":
167 main()