fetch_release_logs.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. """
  2. This script will automatically fetch the latest release logs from the
  3. OSS release testing pipeline on buildkite.
  4. Specifically, this will loop through all release test pipeline builds for the
  5. specified Ray version and fetch the latest available results from the respective
  6. tests. It will then write these to the directory in `ray/release/release_logs`.
  7. To use this script, either set the BUILDKITE_TOKEN environment variable to a
  8. valid Buildkite API token with read access, or authenticate in AWS with the
  9. OSS CI account.
  10. Usage:
  11. python fetch_release_logs.py <version>
  12. Example:
  13. python fetch_release_logs 1.13.0rc0
  14. Results in:
  15. Fetched microbenchmark.json for commit 025e4b01822214e03907db0b09f3af17203a6671
  16. ...
  17. Writing 1.13.0rc0/microbenchmark.json
  18. ...
  19. """
  20. import json
  21. import os
  22. import re
  23. from dataclasses import dataclass
  24. from pathlib import Path
  25. from typing import Any, Dict, List, Optional
  26. import boto3
  27. import click
  28. from pybuildkite.buildkite import Buildkite
  29. BUILDKITE_ORGANIZATION = "ray-project"
  30. BUILDKITE_PIPELINE = "release"
  31. # Format: job name regex --> filename to save results to
  32. RESULTS_TO_FETCH = {
  33. r"^microbenchmark.aws \(.+\)$": "microbenchmark.json",
  34. r"^many_actors.aws \(.+\)$": "benchmarks/many_actors.json",
  35. r"^many_nodes.aws \(.+\)$": "benchmarks/many_nodes.json",
  36. r"^many_pgs.aws \(.+\)$": "benchmarks/many_pgs.json",
  37. r"^many_tasks.aws \(.+\)$": "benchmarks/many_tasks.json",
  38. r"^object_store.aws \(.+\)$": "scalability/object_store.json",
  39. r"^single_node.aws \(.+\)$": "scalability/single_node.json",
  40. r"^stress_test_dead_actors.aws \(.+\)$": (
  41. "stress_tests/stress_test_dead_actors.json"
  42. ),
  43. r"^stress_test_many_tasks.aws \(.+\)$": "stress_tests/stress_test_many_tasks.json",
  44. r"^stress_test_placement_group.aws \(.+\)$": (
  45. "stress_tests/stress_test_placement_group.json"
  46. ),
  47. }
  48. @dataclass
  49. class Build:
  50. id: str
  51. number: int
  52. commit: str
  53. job_dict_list: List[Dict]
  54. pipeline: str = BUILDKITE_PIPELINE
  55. organization: str = BUILDKITE_ORGANIZATION
  56. @dataclass
  57. class Job:
  58. build: Build
  59. id: str
  60. name: Optional[str]
  61. @dataclass
  62. class Artifact:
  63. job: Job
  64. id: str
  65. def get_buildkite_api() -> Buildkite:
  66. bk = Buildkite()
  67. buildkite_token = maybe_fetch_buildkite_token()
  68. bk.set_access_token(buildkite_token)
  69. return bk
  70. def maybe_fetch_buildkite_token() -> str:
  71. buildkite_token = os.environ.get("BUILDKITE_TOKEN", None)
  72. if buildkite_token:
  73. return buildkite_token
  74. print("Missing BUILDKITE_TOKEN, retrieving from AWS secrets store")
  75. buildkite_token = boto3.client(
  76. "secretsmanager", region_name="us-west-2"
  77. ).get_secret_value(
  78. SecretId="arn:aws:secretsmanager:us-west-2:029272617770:secret:"
  79. "buildkite/ro-token"
  80. )[
  81. "SecretString"
  82. ]
  83. os.environ["BUILDKITE_TOKEN"] = buildkite_token
  84. return buildkite_token
  85. def get_results_from_build_collection(
  86. bk: Buildkite, build_dict_list: List[Dict]
  87. ) -> Dict[str, Dict]:
  88. results_to_fetch = RESULTS_TO_FETCH.copy()
  89. fetched_results = {}
  90. for build_dict in sorted(build_dict_list, key=lambda bd: -bd["number"]):
  91. if not results_to_fetch:
  92. break
  93. build = Build(
  94. id=build_dict["id"],
  95. number=build_dict["number"],
  96. commit=build_dict["commit"],
  97. job_dict_list=build_dict["jobs"],
  98. )
  99. build_results = get_results_from_build(bk, build, results_to_fetch)
  100. fetched_results.update(build_results)
  101. return fetched_results
  102. def get_results_from_build(bk: Buildkite, build: Build, results_to_fetch: Dict) -> Dict:
  103. fetched_results = {}
  104. for job_dict in build.job_dict_list:
  105. if not results_to_fetch:
  106. break
  107. job = Job(build=build, id=job_dict["id"], name=job_dict.get("name", None))
  108. if not job.name:
  109. continue
  110. for job_regex, filename in list(results_to_fetch.items()):
  111. if re.match(job_regex, job.name):
  112. result = get_results_artifact_for_job(bk, job=job)
  113. if not result:
  114. continue
  115. fetched_results[filename] = result
  116. results_to_fetch.pop(job_regex)
  117. print(f"Fetched {filename} for commit {job.build.commit}")
  118. return fetched_results
  119. def get_results_artifact_for_job(bk: Buildkite, job: Job) -> Optional[Dict]:
  120. artifacts = bk.artifacts().list_artifacts_for_job(
  121. organization=job.build.organization,
  122. pipeline=job.build.pipeline,
  123. build=job.build.number,
  124. job=job.id,
  125. )
  126. for artifact in artifacts:
  127. if "result.json" in artifact["filename"]:
  128. artifact = Artifact(job=job, id=artifact["id"])
  129. return download_results_artifact(bk=bk, artifact=artifact)
  130. return None
  131. def download_results_artifact(bk: Buildkite, artifact: Artifact) -> Dict:
  132. blob = bk.artifacts().download_artifact(
  133. organization=artifact.job.build.organization,
  134. pipeline=artifact.job.build.pipeline,
  135. build=artifact.job.build.number,
  136. job=artifact.job.id,
  137. artifact=artifact.id,
  138. )
  139. data_dict = json.loads(blob)
  140. return data_dict.get("results", {})
  141. def write_results(log_dir: Path, fetched_results: Dict[str, Any]) -> None:
  142. log_dir.mkdir(parents=True, exist_ok=True)
  143. for filepath, content in fetched_results.items():
  144. path = log_dir.joinpath(filepath)
  145. path.parent.mkdir(parents=True, exist_ok=True)
  146. print(f"Writing {path}")
  147. with open(path, "w") as fp:
  148. json.dump(content, fp, sort_keys=True, indent=4)
  149. fp.write("\n")
  150. @click.command()
  151. @click.argument("version", required=True)
  152. @click.argument("commit", required=True)
  153. @click.argument("branch", required=True)
  154. def main(version: str, commit: str, branch: str):
  155. log_dir = Path(__file__).parent.joinpath(version)
  156. bk = get_buildkite_api()
  157. build_dict_list = bk.builds().list_all_for_pipeline(
  158. organization=BUILDKITE_ORGANIZATION,
  159. pipeline=BUILDKITE_PIPELINE,
  160. branch=branch,
  161. commit=commit,
  162. )
  163. fetched_results = get_results_from_build_collection(bk, build_dict_list)
  164. write_results(log_dir, fetched_results)
  165. if __name__ == "__main__":
  166. main()