import argparse
+import base64
+import datetime
import enum
import glob
import hashlib
+import hmac
+import json
import os
import requests
import sys
import time
import yaml
import shutil
+import xml.etree.ElementTree as ET
+from email.utils import formatdate
from pathlib import Path
from PIL import Image
from urllib import parse
import dump_trace_images
-TRACES_DB_PATH = os.getcwd() + "/traces-db/"
-RESULTS_PATH = os.getcwd() + "/results/"
+TRACES_DB_PATH = "./traces-db/"
+RESULTS_PATH = "./results/"
+MINIO_HOST = "minio-packet.freedesktop.org"
+DASHBOARD_URL = "https://tracie.freedesktop.org/dashboard"
+
+minio_credentials = None
def replay(trace_path, device_name):
success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
log_file = files[0]
return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
-def download_metadata(repo_url, repo_commit, trace_path):
- # The GitLab API doesn't want the .git postfix
- url = repo_url
- if url.endswith(".git"):
- url = url[:-4]
- url = parse.urlparse(url)
-
- url_path = url.path
- if url_path.startswith("/"):
- url_path = url_path[1:]
-
- gitlab_api_url = url.scheme + "://" + url.netloc + "/api/v4/projects/" + parse.quote_plus(url_path)
-
- r = requests.get(gitlab_api_url + "/repository/files/%s/raw?ref=%s" % (parse.quote_plus(trace_path), repo_commit))
- metadata_raw = r.text.strip().split('\n')
- metadata = dict(line.split(' ', 1) for line in metadata_raw[1:])
- oid = metadata["oid"][7:] if metadata["oid"].startswith('sha256:') else metadata["oid"]
- size = int(metadata['size'])
-
- return oid, size
-
-def download_trace(repo_url, repo_commit, trace_path, oid, size):
- headers = {
- "Accept": "application/vnd.git-lfs+json",
- "Content-Type": "application/vnd.git-lfs+json"
- }
- json = {
- "operation": "download",
- "transfers": [ "basic" ],
- "ref": { "name": "refs/heads/%s" % repo_commit },
- "objects": [
- {
- "oid": oid,
- "size": size
- }
- ]
- }
-
- # The LFS API really wants the .git postfix...
- if not repo_url.endswith(".git"):
- repo_url += ".git"
-
- r = requests.post(repo_url + "/info/lfs/objects/batch", headers=headers, json=json)
- url = r.json()["objects"][0]["actions"]["download"]["href"]
- open(TRACES_DB_PATH + trace_path, "wb").write(requests.get(url).content)
-
-def checksum(filename, hash_factory=hashlib.sha256, chunk_num_blocks=128):
- h = hash_factory()
- with open(filename,'rb') as f:
- for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
- h.update(chunk)
- return h.hexdigest()
-
-def ensure_trace(repo_url, repo_commit, trace):
+def gitlab_ensure_trace(project_url, trace):
trace_path = TRACES_DB_PATH + trace['path']
- if repo_url is None:
- assert(repo_commit is None)
- assert(os.path.exists(trace_path))
+ if project_url is None:
+ if not os.path.exists(trace_path):
+ print("{} missing".format(trace_path))
+ sys.exit(1)
return
os.makedirs(os.path.dirname(trace_path), exist_ok=True)
if os.path.exists(trace_path):
- local_oid = checksum(trace_path)
+ return
- remote_oid, size = download_metadata(repo_url, repo_commit, trace['path'])
+ print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
+ download_time = time.time()
+ r = requests.get(project_url + trace['path'])
+ open(trace_path, "wb").write(r.content)
+ print("took %ds." % (time.time() - download_time), flush=True)
+
+def sign_with_hmac(key, message):
+ key = key.encode("UTF-8")
+ message = message.encode("UTF-8")
+
+ signature = hmac.new(key, message, hashlib.sha1).digest()
+
+ return base64.encodebytes(signature).strip().decode()
+
+def ensure_minio_credentials():
+ global minio_credentials
+
+ if minio_credentials is None:
+ minio_credentials = {}
+
+ params = {'Action': 'AssumeRoleWithWebIdentity',
+ 'Version': '2011-06-15',
+ 'RoleArn': 'arn:aws:iam::123456789012:role/FederatedWebIdentityRole',
+ 'RoleSessionName': '%s:%s' % (os.environ['CI_PROJECT_PATH'], os.environ['CI_JOB_ID']),
+ 'DurationSeconds': 900,
+ 'WebIdentityToken': os.environ['CI_JOB_JWT']}
+ r = requests.post('https://%s' % (MINIO_HOST), params=params)
+ if r.status_code >= 400:
+ print(r.text)
+ r.raise_for_status()
+
+ root = ET.fromstring(r.text)
+ for attr in root.iter():
+ if attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}AccessKeyId':
+ minio_credentials['AccessKeyId'] = attr.text
+ elif attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}SecretAccessKey':
+ minio_credentials['SecretAccessKey'] = attr.text
+ elif attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}SessionToken':
+ minio_credentials['SessionToken'] = attr.text
+
+def upload_to_minio(file_name, resource, content_type):
+ ensure_minio_credentials()
+
+ minio_key = minio_credentials['AccessKeyId']
+ minio_secret = minio_credentials['SecretAccessKey']
+ minio_token = minio_credentials['SessionToken']
+
+ date = formatdate(timeval=None, localtime=False, usegmt=True)
+ url = 'https://%s%s' % (MINIO_HOST, resource)
+ to_sign = "PUT\n\n%s\n%s\nx-amz-security-token:%s\n%s" % (content_type, date, minio_token, resource)
+ signature = sign_with_hmac(minio_secret, to_sign)
+
+ with open(file_name, 'rb') as data:
+ headers = {'Host': MINIO_HOST,
+ 'Date': date,
+ 'Content-Type': content_type,
+ 'Authorization': 'AWS %s:%s' % (minio_key, signature),
+ 'x-amz-security-token': minio_token}
+ print("Uploading artifact to %s" % url);
+ r = requests.put(url, headers=headers, data=data)
+ if r.status_code >= 400:
+ print(r.text)
+ r.raise_for_status()
+
+def upload_artifact(file_name, key, content_type):
+ resource = '/artifacts/%s/%s/%s/%s' % (os.environ['CI_PROJECT_PATH'],
+ os.environ['CI_PIPELINE_ID'],
+ os.environ['CI_JOB_ID'],
+ key)
+ upload_to_minio(file_name, resource, content_type)
+
+def ensure_reference_image(file_name, checksum):
+ resource = '/mesa-tracie-results/%s/%s.png' % (os.environ['CI_PROJECT_PATH'], checksum)
+ url = 'https://%s%s' % (MINIO_HOST, resource)
+ r = requests.head(url, allow_redirects=True)
+ if r.status_code == 200:
+ return
+ upload_to_minio(file_name, resource, 'image/png')
- if not os.path.exists(trace_path) or local_oid != remote_oid:
- print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
- download_time = time.time()
- download_trace(repo_url, repo_commit, trace['path'], remote_oid, size)
- print("took %ds." % (time.time() - download_time), flush=True)
+def gitlab_check_trace(project_url, device_name, trace, expectation):
+ gitlab_ensure_trace(project_url, trace)
-def check_trace(repo_url, repo_commit, device_name, trace, expectation):
- ensure_trace(repo_url, repo_commit, trace)
+ result = {}
+ result[trace['path']] = {}
+ result[trace['path']]['expected'] = expectation['checksum']
trace_path = Path(TRACES_DB_PATH + trace['path'])
checksum, image_file, log_file = replay(trace_path, device_name)
if checksum is None:
- return False
+ result[trace['path']]['actual'] = 'error'
+ return False, result
elif checksum == expectation['checksum']:
- print("[check_image] Images match for %s" % (trace['path']))
- ok = True
+ print("[check_image] Images match for %s" % (trace['path']))
+ ok = True
else:
- print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
- (trace['path'], expectation['checksum'], checksum))
- print("[check_image] For more information see "
- "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
- ok = False
+ print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
+ (trace['path'], expectation['checksum'], checksum))
+ print("[check_image] For more information see "
+ "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
+ image_diff_url = "%s/imagediff/%s/%s/%s/%s/%s" % (DASHBOARD_URL,
+ os.environ['CI_PROJECT_PATH'],
+ os.environ['CI_PIPELINE_ID'],
+ os.environ['CI_JOB_ID'],
+ expectation['checksum'],
+ checksum)
+ print("[check_image] %s" % image_diff_url)
+ ok = False
trace_dir = os.path.split(trace['path'])[0]
- results_path = os.path.join(RESULTS_PATH, trace_dir, "test", device_name)
+ dir_in_results = os.path.join(trace_dir, "test", device_name)
+ results_path = os.path.join(RESULTS_PATH, dir_in_results)
os.makedirs(results_path, exist_ok=True)
shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
+ if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
+ if ok:
+ if os.environ['CI_PROJECT_PATH'] == 'mesa/mesa':
+ ensure_reference_image(image_file, checksum)
+ else:
+ upload_artifact(image_file, 'traces/%s.png' % checksum, 'image/png')
if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
- shutil.move(image_file, os.path.join(results_path, os.path.split(image_file)[1]))
+ image_name = os.path.split(image_file)[1]
+ shutil.move(image_file, os.path.join(results_path, image_name))
+ result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
- return ok
+ result[trace['path']]['actual'] = checksum
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--file', required=True,
- help='the name of the traces.yml file listing traces and their checksums for each device')
- parser.add_argument('--device-name', required=True,
- help="the name of the graphics device used to replay traces")
+ return ok, result
- args = parser.parse_args()
+def run(filename, device_name):
- with open(args.file, 'r') as f:
+ with open(filename, 'r') as f:
y = yaml.safe_load(f)
if "traces-db" in y:
- repo = y["traces-db"]["repo"]
- commit_id = y["traces-db"]["commit"]
+ project_url = y["traces-db"]["download-url"]
else:
- repo = None
- commit_id = None
+ project_url = None
- traces = y['traces']
+ traces = y['traces'] or []
all_ok = True
+ results = {}
for trace in traces:
for expectation in trace['expectations']:
- if expectation['device'] == args.device_name:
- ok = check_trace(repo, commit_id, args.device_name, trace, expectation)
- all_ok = all_ok and ok
+ if expectation['device'] == device_name:
+ ok, result = gitlab_check_trace(project_url,
+ device_name, trace,
+ expectation)
+ all_ok = all_ok and ok
+ results.update(result)
+
+ os.makedirs(RESULTS_PATH, exist_ok=True)
+ with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
+ yaml.safe_dump(results, f, default_flow_style=False)
+ if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
+ upload_artifact(os.path.join(RESULTS_PATH, 'results.yml'), 'traces/results.yml', 'text/yaml')
+
+ return all_ok
+
+def main(args):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--file', required=True,
+ help='the name of the traces.yml file listing traces and their checksums for each device')
+ parser.add_argument('--device-name', required=True,
+ help="the name of the graphics device used to replay traces")
- sys.exit(0 if all_ok else 1)
+ args = parser.parse_args(args)
+ return run(args.file, args.device_name)
if __name__ == "__main__":
- main()
+ all_ok = main(sys.argv[1:])
+ sys.exit(0 if all_ok else 1)