mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			195 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| from argparse import ArgumentParser, Namespace
 | |
| from enum import Enum, unique
 | |
| from pathlib import Path
 | |
| from typing import Mapping, Optional
 | |
| import sys
 | |
| 
 | |
| import requests
 | |
| 
 | |
| # Our scripts/ is not a proper Python package so we need to modify PYTHONPATH to import from it
 | |
| # pragma pylint: disable=import-error,wrong-import-position
 | |
| SCRIPTS_DIR = Path(__file__).parent.parent
 | |
| sys.path.insert(0, str(SCRIPTS_DIR))
 | |
| 
 | |
| from common.git_helpers import git_current_branch, git_commit_hash
 | |
| from common.rest_api_helpers import APIHelperError, JobNotSuccessful, CircleCI, Github, download_file
 | |
| # pragma pylint: enable=import-error,wrong-import-position
 | |
| 
 | |
| 
 | |
| @unique
 | |
| class Status(Enum):
 | |
|     OK = 0            # Benchmarks downloaded successfully
 | |
|     ERROR = 1         # Error in the script, bad API response, unexpected data, etc.
 | |
|     NO_BENCHMARK = 2  # Benchmark collector job did not finish successfully and/or benchmark artifacts are missing.
 | |
|     PENDING = 3       # Benchmark collector job has not finished yet.
 | |
| 
 | |
| 
 | |
| def process_commandline() -> Namespace:
 | |
|     script_description = (
 | |
|         "Downloads benchmark results attached as artifacts to the c_ext_benchmarks job on CircleCI. "
 | |
|         "If no options are specified, downloads results for the currently checked out git branch."
 | |
|     )
 | |
| 
 | |
|     parser = ArgumentParser(description=script_description)
 | |
| 
 | |
|     target_definition = parser.add_mutually_exclusive_group()
 | |
|     target_definition.add_argument(
 | |
|         '--branch',
 | |
|         dest='branch',
 | |
|         help="Git branch that the job ran on.",
 | |
|     )
 | |
|     target_definition.add_argument(
 | |
|         '--pr',
 | |
|         dest='pull_request_id',
 | |
|         type=int,
 | |
|         help="Github PR ID that the job ran on.",
 | |
|     )
 | |
|     target_definition.add_argument(
 | |
|         '--base-of-pr',
 | |
|         dest='base_of_pr',
 | |
|         type=int,
 | |
|         help="ID of a Github PR that's based on top of the branch we're interested in."
 | |
|     )
 | |
| 
 | |
|     parser.add_argument(
 | |
|         '--any-commit',
 | |
|         dest='ignore_commit_hash',
 | |
|         default=False,
 | |
|         action='store_true',
 | |
|         help="Include pipelines that ran on a different commit as long as branch/PR matches."
 | |
|     )
 | |
|     parser.add_argument(
 | |
|         '--overwrite',
 | |
|         dest='overwrite',
 | |
|         default=False,
 | |
|         action='store_true',
 | |
|         help="If artifacts already exist on disk, overwrite them.",
 | |
|     )
 | |
|     parser.add_argument(
 | |
|         '--debug-requests',
 | |
|         dest='debug_requests',
 | |
|         default=False,
 | |
|         action='store_true',
 | |
|         help="Print detailed info about performed API requests and received responses.",
 | |
|     )
 | |
| 
 | |
|     return parser.parse_args()
 | |
| 
 | |
| 
 | |
| def download_benchmark_artifact(
 | |
|     artifacts: Mapping[str, dict],
 | |
|     benchmark_name: str,
 | |
|     branch: str,
 | |
|     commit_hash: str,
 | |
|     overwrite: bool,
 | |
|     silent: bool = False
 | |
| ) -> bool:
 | |
|     if not silent:
 | |
|         print(f"Downloading artifact: {benchmark_name}-{branch}-{commit_hash[:8]}.json.")
 | |
| 
 | |
|     artifact_path = f'reports/externalTests/{benchmark_name}.json'
 | |
| 
 | |
|     if artifact_path not in artifacts:
 | |
|         if not silent:
 | |
|             print(f"Missing artifact: {artifact_path}.")
 | |
|         return False
 | |
| 
 | |
|     download_file(
 | |
|         artifacts[artifact_path]['url'],
 | |
|         Path(f'{benchmark_name}-{branch}-{commit_hash[:8]}.json'),
 | |
|         overwrite,
 | |
|     )
 | |
| 
 | |
|     return True
 | |
| 
 | |
| 
 | |
| def download_benchmarks(
 | |
|     branch: Optional[str],
 | |
|     pull_request_id: Optional[int],
 | |
|     base_of_pr: Optional[int],
 | |
|     ignore_commit_hash: bool = False,
 | |
|     overwrite: bool = False,
 | |
|     debug_requests: bool = False,
 | |
|     silent: bool = False,
 | |
| ) -> Status:
 | |
|     github = Github('ethereum/solidity', debug_requests)
 | |
|     circleci = CircleCI('ethereum/solidity', debug_requests)
 | |
| 
 | |
|     expected_commit_hash = None
 | |
|     if branch is None and pull_request_id is None and base_of_pr is None:
 | |
|         branch = git_current_branch()
 | |
|         expected_commit_hash = git_commit_hash()
 | |
|     elif branch is not None:
 | |
|         expected_commit_hash = git_commit_hash(branch)
 | |
|     elif pull_request_id is not None:
 | |
|         pr_info = github.pull_request(pull_request_id)
 | |
|         branch = pr_info['head']['ref']
 | |
|         expected_commit_hash = pr_info['head']['sha']
 | |
|     elif base_of_pr is not None:
 | |
|         pr_info = github.pull_request(base_of_pr)
 | |
|         branch = pr_info['base']['ref']
 | |
|         expected_commit_hash = pr_info['base']['sha']
 | |
| 
 | |
|     if not silent:
 | |
|         print(
 | |
|             f"Looking for pipelines that ran on branch {branch}" +
 | |
|             (f", commit {expected_commit_hash}." if not ignore_commit_hash else " (any commit).")
 | |
|         )
 | |
| 
 | |
|     pipeline = circleci.latest_item(circleci.pipelines(
 | |
|         branch,
 | |
|         expected_commit_hash if not ignore_commit_hash else None,
 | |
|         # Skip nightly workflows. They don't have the c_ext_benchmarks job and even if they did,
 | |
|         # they would likely be running a different set of external tests.
 | |
|         excluded_trigger_types=['schedule'],
 | |
|     ))
 | |
|     if pipeline is None:
 | |
|         raise RuntimeError("No matching pipelines found.")
 | |
| 
 | |
|     actual_commit_hash = pipeline['vcs']['revision']
 | |
|     workflow_id = circleci.latest_item(circleci.workflows(pipeline['id']))['id']
 | |
|     benchmark_collector_job = circleci.job(workflow_id, 'c_ext_benchmarks', require_success=True)
 | |
| 
 | |
|     artifacts = circleci.artifacts(int(benchmark_collector_job['job_number']))
 | |
| 
 | |
|     got_summary = download_benchmark_artifact(artifacts, 'summarized-benchmarks', branch, actual_commit_hash, overwrite, silent)
 | |
|     got_full = download_benchmark_artifact(artifacts, 'all-benchmarks', branch, actual_commit_hash, overwrite, silent)
 | |
| 
 | |
|     return Status.OK if got_summary and got_full else Status.NO_BENCHMARK
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     try:
 | |
|         options = process_commandline()
 | |
|         return download_benchmarks(
 | |
|             options.branch,
 | |
|             options.pull_request_id,
 | |
|             options.base_of_pr,
 | |
|             options.ignore_commit_hash,
 | |
|             options.overwrite,
 | |
|             options.debug_requests,
 | |
|         ).value
 | |
|     except JobNotSuccessful as exception:
 | |
|         print(f"[ERROR] {exception}", file=sys.stderr)
 | |
|         if not exception.job_finished:
 | |
|             print("Please wait for the workflow to finish and try again.", file=sys.stderr)
 | |
|             return Status.PENDING.value
 | |
|         else:
 | |
|             print("Benchmarks from this run of the pipeline are not available.", file=sys.stderr)
 | |
|             return Status.NO_BENCHMARK.value
 | |
|     except APIHelperError as exception:
 | |
|         print(f"[ERROR] {exception}", file=sys.stderr)
 | |
|         return Status.ERROR.value
 | |
|     except requests.exceptions.HTTPError as exception:
 | |
|         print(f"[ERROR] {exception}", file=sys.stderr)
 | |
|         return Status.ERROR.value
 | |
|     except RuntimeError as exception:
 | |
|         print(f"[ERROR] {exception}", file=sys.stderr)
 | |
|         return Status.ERROR.value
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     sys.exit(main())
 |