ci: Print URL to image diff when a trace replay fails
[mesa.git] / .gitlab-ci / tracie / tracie.py
1 import argparse
2 import base64
3 import datetime
4 import enum
5 import glob
6 import hashlib
7 import hmac
8 import json
9 import os
10 import requests
11 import sys
12 import tempfile
13 import time
14 import yaml
15 import shutil
16
17 from email.utils import formatdate
18 from pathlib import Path
19 from PIL import Image
20 from urllib import parse
21
22 import dump_trace_images
23
24 TRACES_DB_PATH = "./traces-db/"
25 RESULTS_PATH = "./results/"
26 MINIO_HOST = "minio-packet.freedesktop.org"
27 DASHBOARD_URL = "https://tracie.freedesktop.org/dashboard"
28
29 def replay(trace_path, device_name):
30 success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
31
32 if not success:
33 print("[check_image] Trace %s couldn't be replayed. See above logs for more information." % (str(trace_path)))
34 return None, None, None
35 else:
36 base_path = trace_path.parent
37 file_name = trace_path.name
38 files = glob.glob(str(base_path / "test" / device_name / (file_name + "-*" + ".png")))
39 assert(files)
40 image_file = files[0]
41 files = glob.glob(str(base_path / "test" / device_name / (file_name + ".log")))
42 assert(files)
43 log_file = files[0]
44 return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
45
46 def gitlab_ensure_trace(project_url, trace):
47 trace_path = TRACES_DB_PATH + trace['path']
48 if project_url is None:
49 if not os.path.exists(trace_path):
50 print("{} missing".format(trace_path))
51 sys.exit(1)
52 return
53
54 os.makedirs(os.path.dirname(trace_path), exist_ok=True)
55
56 if os.path.exists(trace_path):
57 return
58
59 print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
60 download_time = time.time()
61 r = requests.get(project_url + trace['path'])
62 open(trace_path, "wb").write(r.content)
63 print("took %ds." % (time.time() - download_time), flush=True)
64
65 def sign_with_hmac(key, message):
66 key = key.encode("UTF-8")
67 message = message.encode("UTF-8")
68
69 signature = hmac.new(key, message, hashlib.sha1).digest()
70
71 return base64.encodebytes(signature).strip().decode()
72
73 def upload_to_minio(file_name, resource, content_type):
74 with open('.minio_credentials', 'r') as f:
75 credentials = json.load(f)[MINIO_HOST]
76 minio_key = credentials["AccessKeyId"]
77 minio_secret = credentials["SecretAccessKey"]
78 minio_token = credentials["SessionToken"]
79
80 date = formatdate(timeval=None, localtime=False, usegmt=True)
81 url = 'https://%s%s' % (MINIO_HOST, resource)
82 to_sign = "PUT\n\n%s\n%s\nx-amz-security-token:%s\n%s" % (content_type, date, minio_token, resource)
83 signature = sign_with_hmac(minio_secret, to_sign)
84
85 with open(file_name, 'rb') as data:
86 headers = {'Host': MINIO_HOST,
87 'Date': date,
88 'Content-Type': content_type,
89 'Authorization': 'AWS %s:%s' % (minio_key, signature),
90 'x-amz-security-token': minio_token}
91 print("Uploading artifact to %s" % url);
92 r = requests.put(url, headers=headers, data=data)
93 if r.status_code >= 400:
94 print(r.text)
95 r.raise_for_status()
96
97 def upload_artifact(file_name, key, content_type):
98 resource = '/artifacts/%s/%s/%s/%s' % (os.environ['CI_PROJECT_PATH'],
99 os.environ['CI_PIPELINE_ID'],
100 os.environ['CI_JOB_ID'],
101 key)
102 upload_to_minio(file_name, resource, content_type)
103
104 def ensure_reference_image(file_name, checksum):
105 resource = '/mesa-tracie-results/%s/%s.png' % (os.environ['CI_PROJECT_PATH'], checksum)
106 url = 'https://%s%s' % (MINIO_HOST, resource)
107 r = requests.head(url, allow_redirects=True)
108 if r.status_code == 200:
109 return
110 upload_to_minio(file_name, resource, 'image/png')
111
112 def gitlab_check_trace(project_url, device_name, trace, expectation):
113 gitlab_ensure_trace(project_url, trace)
114
115 result = {}
116 result[trace['path']] = {}
117 result[trace['path']]['expected'] = expectation['checksum']
118
119 trace_path = Path(TRACES_DB_PATH + trace['path'])
120 checksum, image_file, log_file = replay(trace_path, device_name)
121 if checksum is None:
122 result[trace['path']]['actual'] = 'error'
123 return False, result
124 elif checksum == expectation['checksum']:
125 print("[check_image] Images match for %s" % (trace['path']))
126 ok = True
127 else:
128 print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
129 (trace['path'], expectation['checksum'], checksum))
130 print("[check_image] For more information see "
131 "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
132 image_diff_url = "%s/imagediff/%s/%s/%s/%s/%s" % (DASHBOARD_URL,
133 os.environ['CI_PROJECT_PATH'],
134 os.environ['CI_PIPELINE_ID'],
135 os.environ['CI_JOB_ID'],
136 expectation['checksum'],
137 checksum)
138 print("[check_image] %s" % image_diff_url)
139 ok = False
140
141 trace_dir = os.path.split(trace['path'])[0]
142 dir_in_results = os.path.join(trace_dir, "test", device_name)
143 results_path = os.path.join(RESULTS_PATH, dir_in_results)
144 os.makedirs(results_path, exist_ok=True)
145 shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
146 if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
147 if ok:
148 if os.environ['CI_PROJECT_PATH'] == 'mesa/mesa':
149 ensure_reference_image(image_file, checksum)
150 else:
151 upload_artifact(image_file, 'traces/%s.png' % checksum, 'image/png')
152 if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
153 image_name = os.path.split(image_file)[1]
154 shutil.move(image_file, os.path.join(results_path, image_name))
155 result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
156
157 result[trace['path']]['actual'] = checksum
158
159 return ok, result
160
161 def run(filename, device_name):
162
163 with open(filename, 'r') as f:
164 y = yaml.safe_load(f)
165
166 if "traces-db" in y:
167 project_url = y["traces-db"]["download-url"]
168 else:
169 project_url = None
170
171 traces = y['traces'] or []
172 all_ok = True
173 results = {}
174 for trace in traces:
175 for expectation in trace['expectations']:
176 if expectation['device'] == device_name:
177 ok, result = gitlab_check_trace(project_url,
178 device_name, trace,
179 expectation)
180 all_ok = all_ok and ok
181 results.update(result)
182
183 os.makedirs(RESULTS_PATH, exist_ok=True)
184 with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
185 yaml.safe_dump(results, f, default_flow_style=False)
186 if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
187 upload_artifact(os.path.join(RESULTS_PATH, 'results.yml'), 'traces/results.yml', 'text/yaml')
188
189 return all_ok
190
191 def main(args):
192 parser = argparse.ArgumentParser()
193 parser.add_argument('--file', required=True,
194 help='the name of the traces.yml file listing traces and their checksums for each device')
195 parser.add_argument('--device-name', required=True,
196 help="the name of the graphics device used to replay traces")
197
198 args = parser.parse_args(args)
199 return run(args.file, args.device_name)
200
201 if __name__ == "__main__":
202 all_ok = main(sys.argv[1:])
203 sys.exit(0 if all_ok else 1)