emit_partial.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/env python3
  2. import json
  3. import os
  4. import pathlib
  5. import re
  6. import subprocess
  7. import sys
  8. from typing import Tuple, Optional, List
  9. def run(cmd: List[str]) -> str:
  10. """Run a command and return stdout as text; return empty string on any error."""
  11. try:
  12. return subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT)
  13. except Exception:
  14. return ""
  15. def detect(platform: str) -> Tuple[str, List[str], str]:
  16. """Detect cache tool, command to print stats, and a family label from platform."""
  17. if platform.startswith("msvc"):
  18. return ("sccache", ["sccache", "--show-stats"], "windows-msvc")
  19. if platform.startswith("mingw_"):
  20. return ("ccache", ["ccache", "-s"], "windows-mingw")
  21. if platform.startswith("mac"):
  22. return ("ccache", ["ccache", "-s"], "macos")
  23. if platform == "ios":
  24. return ("ccache", ["ccache", "-s"], "ios")
  25. if platform.startswith("android"):
  26. return ("ccache", ["ccache", "-s"], "android")
  27. return ("ccache", ["ccache", "-s"], "other")
  28. def parse_ccache(text: str) -> Tuple[int, int]:
  29. """
  30. Parse ccache stats. Supports:
  31. - Legacy lines: "Hits: 123" / "Misses: 45"
  32. - Modern lines: "cache hit (direct) 10"
  33. "cache hit (preprocessed) 5"
  34. "cache hit (remote) 2" (optional)
  35. "cache miss 12"
  36. Returns (hits, misses).
  37. """
  38. # Legacy format
  39. m_hits = re.search(r"^\s*Hits:\s*(\d+)\b", text, re.M)
  40. m_miss = re.search(r"^\s*Misses:\s*(\d+)\b", text, re.M)
  41. if m_hits and m_miss:
  42. return int(m_hits.group(1)), int(m_miss.group(1))
  43. # Modern format: sum all hit buckets
  44. def pick(pattern: str) -> int:
  45. m = re.search(pattern, text, re.M | re.I)
  46. return int(m.group(1)) if m else 0
  47. hits_direct = pick(r"^cache hit\s*\(direct\)\s+(\d+)\b")
  48. hits_pre = pick(r"^cache hit\s*\(preprocessed\)\s+(\d+)\b")
  49. hits_remote = pick(r"^cache hit\s*\(remote\)\s+(\d+)\b") # may be absent
  50. misses = pick(r"^cache miss\s+(\d+)\b")
  51. hits_total = hits_direct + hits_pre + hits_remote
  52. return hits_total, misses
  53. def parse_sccache(text: str) -> Tuple[int, int]:
  54. """
  55. Parse sccache --show-stats lines:
  56. "Cache hits 123"
  57. "Cache misses 45"
  58. Returns (hits, misses).
  59. """
  60. def pick(label: str) -> int:
  61. m = re.search(rf"^{re.escape(label)}\s+(\d+)\b", text, re.M | re.I)
  62. return int(m.group(1)) if m else 0
  63. hits = pick("Cache hits")
  64. misses = pick("Cache misses")
  65. return hits, misses
  66. def arch_label(platform: str) -> str:
  67. """Produce a nice arch label."""
  68. mapping = {
  69. "mac-intel": "Intel",
  70. "mac-arm": "Apple Silicon",
  71. "ios": "ARM64",
  72. "msvc-x64": "x64",
  73. "msvc-x86": "x86",
  74. "msvc-arm64": "ARM64",
  75. "mingw_x86": "x86",
  76. "mingw_x86_64": "x64",
  77. "android-32": "ARMv7",
  78. "android-64": "ARM64",
  79. "android-64-intel": "x86_64",
  80. }
  81. return mapping.get(platform, platform)
  82. def main() -> int:
  83. # Prefer our explicit PLATFORM env; fall back to VS's "Platform" on Windows if needed.
  84. platform = os.getenv("PLATFORM") or os.getenv("Platform") or "unknown"
  85. arch = arch_label(platform)
  86. tool, cmd, family = detect(platform)
  87. stats_raw = run(cmd)
  88. if tool == "sccache":
  89. hits, misses = parse_sccache(stats_raw)
  90. else:
  91. hits, misses = parse_ccache(stats_raw)
  92. total = hits + misses
  93. rate = f"{(100.0 * hits / total):.2f}%" if total else "n/a"
  94. payload = {
  95. "platform": platform,
  96. "family": family,
  97. "arch": arch,
  98. "tool": tool,
  99. "hits": hits,
  100. "misses": misses,
  101. "total": total,
  102. "rate": rate,
  103. "artifact_url": os.getenv("ARTIFACT_URL", ""),
  104. "debug_symbols_url": os.getenv("DEBUG_SYMBOLS_URL", ""),
  105. "aab_url": os.getenv("AAB_URL", ""),
  106. "stats_cmd": " ".join(cmd),
  107. "stats_raw": stats_raw,
  108. }
  109. outdir = pathlib.Path(".summary")
  110. outdir.mkdir(parents=True, exist_ok=True)
  111. outpath = outdir / f"{platform}.json"
  112. outpath.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
  113. print(f"Wrote {outpath}")
  114. return 0
  115. if __name__ == "__main__":
  116. sys.exit(main())