#!/usr/bin/python import os import sys import json import hashlib import httplib import logging import mimetypes import xml.etree.ElementTree as ET from urlparse import urljoin, urlsplit from StringIO import StringIO logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) httplib.HTTPConnection.debuglevel = 0 RESULT_ENDPOINT = "/api/result/" BENCHMARK_MANIFEST_PROJECT_LIST = [ 'linaro-art/platform/bionic', 'linaro-art/platform/build', 'linaro-art/platform/external/vixl', 'linaro-art/platform/art' ] def transcode_results_dict(results_dict, parsed_dict, prefix_key=None): for key, value in parsed_dict.items(): if prefix_key is not None: key = "%s/%s" % (prefix_key, key) if isinstance(value, dict): transcode_results_dict(results_dict, value, key) else: results_dict.update({key: value}) def encode_multipart_formdata(fields, files): LIMIT = '----------lImIt_of_THE_fIle_eW_$' CRLF = '\r\n' L = [] for key, value in fields.iteritems(): L.append('--' + LIMIT) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) for key, values in files.iteritems(): if type(values) is not list: values = [values] for value in values: # value should be a 2-tuple with first item as filename and second item file descriptor L.append('--' + LIMIT) L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, value[0])) L.append('Content-Type: %s' % get_content_type(key)) L.append('') value[1].seek(0) # in case the pointer wasn't placed in the beginning of the file L.append(value[1].read()) L.append('--' + LIMIT + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % LIMIT return content_type, body def get_content_type(filename): return mimetypes.guess_type(filename)[0] or 'application/octet-stream' def _push_object(auth_pw, backend_url, endpoint, params, files): usplit = urlsplit(backend_url) url = urljoin(backend_url, endpoint) logger.info("Submitting to URL: %s" % url) conn = None if usplit.scheme.lower() == "http": conn = httplib.HTTP(usplit.netloc) if usplit.scheme.lower() == "https": conn = httplib.HTTPS(usplit.netloc) if conn is None: logger.info("Unknown scheme: %s" % usplit.scheme) sys.exit(1) content_type, body = encode_multipart_formdata(params, files) conn.putrequest('POST', endpoint) conn.putheader("Authorization", "Token %s" % auth_pw) conn.putheader("Auth-Token", auth_pw) conn.putheader('Content-Type', content_type) conn.putheader('Content-Length', str(len(body))) conn.putheader('Host', usplit.netloc) conn.endheaders() conn.set_debuglevel(-1) conn.send(body) errcode, errmsg, headers = conn.getreply() logger.info("return code: %s" % errcode) logger.info(errmsg) logger.info(headers) if errcode < 300: return conn.file.read() else: logger.warn(errcode) logger.warn(errmsg) return [] def _get_manifest(workspace_path): manifest_path = os.path.join(workspace_path, "pinned-manifest.xml") logger.info("Searching for: %s" % manifest_path) if os.path.exists(manifest_path): with open(manifest_path, "r") as manifest_file: return manifest_file.read() logger.warning("Manifest not found") return None def _get_files(workspace_path): files_dict = {} for dirpath, dirnames, filenames in os.walk(workspace_path): for result_file_name in filenames: if result_file_name.endswith(".json"): statinfo = os.stat(os.path.join(dirpath, result_file_name)) logger.info("adding %s [%s]" % (result_file_name, statinfo.st_size)) files_dict.update({result_file_name: (result_file_name, open(os.path.join(dirpath, result_file_name), 'rb'))}) return files_dict def _results(workspace_path): benchmarks = { "Boot.oat size": ['boot_oat_size_ARM_32_Quick.txt', 'boot_oat_size_ARM_64_Quick.txt', 'boot_oat_size_x86_32_Quick.txt', 'boot_oat_size_x86_64_Quick.txt', 'boot_oat_size_x86_64_Optimizing.txt', 'boot_oat_size_x86_32_Optimizing.txt', 'boot_oat_size_ARM_64_Optimizing.txt', 'boot_oat_size_ARM_32_Optimizing.txt', 'boot_oat_size_mips_64_Optimizing.txt', 'boot_oat_size_mips_32_Quick.txt'], "Oat Execution Time": ['avg_oat_time_ARM_32_Quick.txt', 'avg_oat_time_ARM_64_Quick.txt', 'avg_oat_time_x86_64_Quick.txt', 'avg_oat_time_x86_32_Quick.txt', 'avg_oat_time_x86_64_Optimizing.txt', 'avg_oat_time_x86_32_Optimizing.txt', 'avg_oat_time_ARM_32_Optimizing.txt', 'avg_oat_time_ARM_64_Optimizing.txt', 'avg_oat_time_mips_64_Optimizing.txt', 'avg_oat_time_mips_32_Quick.txt'] } val = [] for benchmark, subscores in benchmarks.items(): for subscore in subscores: path = os.path.join(workspace_path, subscore) logger.debug(path, os.path.exists(path)) if os.path.exists(path): raw = open(path, 'r').read().strip() measurement = float(raw.replace("YVALUE=", "")) name = (subscore .replace("avg_oat_time_", "") .replace("boot_oat_size_", "") .replace(".txt", "") .replace("_", " ")) val.append({ "benchmark": benchmark, "name": name, "measurement": measurement }) return val def main(): jenkins_project_name = os.environ.get("SOURCE_PROJECT_NAME") jenkins_build_number = os.environ.get("SOURCE_BUILD_NUMBER") jenkins_build_id = os.environ.get("SOURCE_BUILD_ID", jenkins_build_number) jenkins_build_url = os.environ.get("SOURCE_BUILD_URL") branch_name = os.environ.get("SOURCE_BRANCH_NAME", "") # QA reports submission qa_reports_url = os.environ.get("QA_REPORTS_URL") qa_reports_token = os.environ.get("QA_REPORTS_TOKEN") manifest = _get_manifest("./artifacts") test_jobs = os.environ.get("LAVA_JOB_IDS", None) results = _results("./artifacts") if jenkins_build_number is None: logger.error("Build number not set. Exiting!") sys.exit(1) if jenkins_project_name is None: logger.error("Project name not set. Exiting!") sys.exit(1) if jenkins_build_url is None: logger.error("Build URL not set. Exiting!") sys.exit(1) if not manifest: logger.error("Manifest missing. Exiting!") sys.exit(1) logger.info("Registered test jobs: %s" % test_jobs) params = { 'name': jenkins_project_name, 'build_id': jenkins_build_id, 'build_url': jenkins_build_url, 'build_number': jenkins_build_number, 'test_jobs': test_jobs, 'manifest': manifest, 'branch_name': branch_name, "gerrit_change_number": os.environ.get("SOURCE_GERRIT_CHANGE_NUMBER", ""), "gerrit_patchset_number":os.environ.get("SOURCE_GERRIT_PATCHSET_NUMBER", ""), "gerrit_change_url": os.environ.get("SOURCE_GERRIT_CHANGE_URL", ""), "gerrit_change_id": os.environ.get("SOURCE_GERRIT_CHANGE_ID", ""), "results": results } #jenkins_created_at = os.environ.get("CREATED_AT", None) #if jenkins_created_at is not None: # params.update({'created_at': jenkins_created_at}) if not results: params.pop("results") files = {} if test_jobs is None: params.pop('test_jobs') files = _get_files("./artifacts") params.pop('manifest') logger.debug(params) # submit to QA reports if qa_reports_url is None: return if qa_reports_token is None: logger.error("Token for QA reports missing. Exiting") return # produce reduced manifest hash doc = ET.fromstring(manifest) commit_id_hash = hashlib.sha1() for project in BENCHMARK_MANIFEST_PROJECT_LIST: project_element_list = doc.findall('.//project[@name="%s"]' % project) if project_element_list: for project_element in project_element_list: if project_element.tag == "project": commit_id = project_element.get('revision') commit_id_hash.update(commit_id) reduced_manifest = commit_id_hash.hexdigest()[:12] # endpoint comes in format team/project/build/environment # there should 'baseline' and 'patch' projects for each branch project_name = "%s" % (branch_name) if os.environ.get("SOURCE_GERRIT_CHANGE_NUMBER", ""): project_name = project_name + "-patch" else: project_name = project_name + "-baseline" for file_path, file_tuple in files.items(): file_name, file_contents = file_tuple if file_path.endswith(".json"): job_id = file_path.replace(".json","") qa_reports_endpoint = "/api/submit/art/%s/%s/%s" % (project_name, reduced_manifest, job_id) squad_params = StringIO() params.update({"job_id": job_id}) json.dump(params, squad_params) # repack the result file to SQUAD friendly format metrics = StringIO() file_contents.seek(0) metrics_data_raw = json.load(file_contents) metrics_data = metrics_data_raw['benchmarks'] transcode_results_dict(metrics_data, metrics_data_raw.get('compilation statistics', {})) json.dump(metrics_data, metrics) result_files = { "metadata": ("metadata.json", squad_params), "attachment": [("pinned-manifest.xml", StringIO(manifest)), (file_path, file_contents)], "metrics": ("results.json", metrics) } _push_object(qa_reports_token, qa_reports_url, qa_reports_endpoint, {}, result_files) if __name__ == '__main__': main()