release_utils.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #!/usr/bin/env python
  2. import click
  3. import json
  4. import logging
  5. import requests
  6. from typing import Dict, Optional
  7. logger = logging.getLogger(__file__)
  8. logging.basicConfig(level=logging.INFO)
  9. @click.group()
  10. def cli():
  11. pass
  12. def load_metrics(file_path: str) -> Dict[str, float]:
  13. with open(file_path) as f:
  14. report = json.load(f)
  15. if "perf_metrics" in report:
  16. # Input file is the TEST_OUTPUT_JSON (e.g. /tmp/release_test_out.json)
  17. # that is written to directly from running `python workloads/abc.py`
  18. results = report
  19. elif "results" in report:
  20. # Input file is either downloaded from the buildkite job artifacts
  21. # or from file uploaded to AWS
  22. results = report["results"]
  23. else:
  24. raise RuntimeError(f"Invalid results file {file_path}")
  25. return {
  26. perf_metric["perf_metric_name"]: perf_metric["perf_metric_value"]
  27. for perf_metric in results["perf_metrics"]
  28. }
  29. @cli.command(
  30. help=(
  31. "Print a table that compares the performance metric results from two runs of "
  32. "the same release test."
  33. )
  34. )
  35. @click.argument("results_file1")
  36. @click.argument("results_file2")
  37. def compare_perf(results_file1: str, results_file2: str):
  38. metrics1 = load_metrics(results_file1)
  39. metrics2 = load_metrics(results_file2)
  40. print("|metric |results 1 |results 2 |%change |")
  41. print("|------------------------------|---------------|---------------|----------|")
  42. for key in metrics1:
  43. if key in metrics2:
  44. change = metrics2[key] / metrics1[key] - 1
  45. percent_change = str(round(100 * change, 2))
  46. metric1 = str(round(metrics1[key], 2))
  47. metric2 = str(round(metrics2[key], 2))
  48. print(f"|{key.ljust(30)}", end="")
  49. print(f"|{metric1.ljust(15)}", end="")
  50. print(f"|{metric2.ljust(15)}", end="")
  51. print(f"|{percent_change.ljust(10)}|")
  52. @cli.command(
  53. help=(
  54. "Fetch the results from the most recent nightly run of the specified release "
  55. "test within the past 30 days."
  56. )
  57. )
  58. @click.argument("test_name")
  59. @click.option(
  60. "--output-path",
  61. "-o",
  62. type=str,
  63. default=None,
  64. help="Output file to write results to",
  65. )
  66. def fetch_nightly(test_name: str, output_path: Optional[str]):
  67. auth_header = {
  68. "Authorization": "Bearer bkua_6474b2bfb20dd78d44b83f197d12cb7921583ed7"
  69. }
  70. # Get job ID of the most recent run that passed on master
  71. # Builds returned from buildkite rest api are sorted from newest to
  72. # oldest already
  73. builds = requests.get(
  74. (
  75. "https://api.buildkite.com/v2/organizations/ray-project/pipelines/release/"
  76. "builds"
  77. ),
  78. headers=auth_header,
  79. params={"branch": "master"},
  80. ).json()
  81. build_number = None
  82. job_id = None
  83. for build in builds:
  84. for job in build["jobs"]:
  85. job_name = job.get("name")
  86. if (
  87. job_name
  88. and job_name.startswith(test_name)
  89. and job.get("state") == "passed"
  90. ):
  91. build_number = build["number"]
  92. job_id = job["id"]
  93. if job_id:
  94. logger.info(
  95. f"Found latest release run on master with build #{build_number} and "
  96. f"job ID {job_id}."
  97. )
  98. break
  99. if job_id is None:
  100. raise RuntimeError(
  101. f"Did not find any successful runs of the release test {test_name} on "
  102. "master in the past 30 days."
  103. )
  104. # Get results file from Job ID
  105. artifacts = requests.get(
  106. (
  107. "https://api.buildkite.com/v2/organizations/ray-project/pipelines/release/"
  108. f"builds/{build_number}/jobs/{job_id}/artifacts"
  109. ),
  110. headers=auth_header,
  111. ).json()
  112. results = None
  113. for artifact in artifacts:
  114. if artifact.get("filename") == "result.json":
  115. results = requests.get(artifact["download_url"], headers=auth_header).json()
  116. results = results["results"]
  117. if results is None:
  118. raise RuntimeError(f"Did not find results file for buildkite job {job_id}")
  119. if output_path:
  120. with open(output_path, "w+") as f:
  121. json.dump(results, f)
  122. else:
  123. print(json.dumps(results, indent=4))
  124. if __name__ == "__main__":
  125. cli()