Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,10 @@ jobs:
env:
HOSTED_COMPILE_SECONDS: ${{ steps.hosted-compile.outputs.seconds }}
HOSTED_COMPILE_OUTCOME: ${{ steps.hosted-compile.outcome }}
SEED_DISTANCE: ${{ steps.seed-derived-data.outputs.seed_distance || steps.prefer-seed.outputs.seed_distance }}
# Report only the seed that adopt actually installed. prefer-seed's
# distance describes a candidate and can remain set when adoption
# fails or is skipped.
SEED_DISTANCE: ${{ steps.seed-derived-data.outputs.seed_distance }}
SEED_SECONDS: ${{ steps.seed-derived-data.outputs.seconds }}
run: |
set -euo pipefail
Expand Down
20 changes: 20 additions & 0 deletions scripts/ci/build_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
r"^\s*(.+?)(?:\s+\(\d+\s+tasks?\)\s+\|)?\s+"
r"([0-9]+(?:\.[0-9]+)?) seconds\s*$"
)
CACHE_PLUGIN_SETTING_RE = re.compile(
r"\bCOMPILATION_CACHE_ENABLE_PLUGIN\s*=\s*YES\b",
re.IGNORECASE,
)
CACHE_REMOTE_SETTING_RE = re.compile(
r"\bCOMPILATION_CACHE_REMOTE_SERVICE_PATH\s*=\s*\S+",
re.IGNORECASE,
)
CACHE_VALUES = {"Cache hit": "hit", "Cache miss": "miss"}


Expand Down Expand Up @@ -140,6 +148,15 @@ def parse_log(path: Path) -> dict[str, object]:
"swift_compile_events": sum(swift_compile_by_target.values()),
"swift_emit_module_events": sum(swift_emit_by_target.values()),
"cacheable_tasks": cache["hit"] + cache["miss"],
# The command's build-settings echo is the only portable evidence of
# whether Xcode was given the fleet plugin/socket. Keep the value
# categorical; never copy the socket path into telemetry.
"cache_backend": (
"fleet"
if CACHE_PLUGIN_SETTING_RE.search("\n".join(lines))
and CACHE_REMOTE_SETTING_RE.search("\n".join(lines))
else "local"
),
"timing_summary_seconds": timing_summary(lines),
"targets": targets,
}
Expand All @@ -162,6 +179,7 @@ def aggregate(schemes: list[dict[str, object]]) -> dict[str, object]:

return {
**dict(totals),
"cache_backend": "fleet" if any(scheme.get("cache_backend") == "fleet" for scheme in schemes) else "local",
"timing_summary_seconds": dict(sorted(timing.items())),
"targets": {
target: dict(values)
Expand Down Expand Up @@ -233,8 +251,10 @@ def build_receipt(
"hit_rate": round(hits / cacheable, 6) if cacheable else None,
"seed_distance": seed_distance,
"compile_seconds": compile_phase,
"compile_wall_seconds": compile_seconds,
"fetch_seconds": fetch_seconds,
"link_seconds": link_phase,
"cache_backend": aggregate_values.get("cache_backend", "local"),
}
return {
"schema_version": 1,
Expand Down
62 changes: 62 additions & 0 deletions tests/test_build_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import importlib.util
import json
from pathlib import Path
import os
import subprocess
import sys
import tempfile
import unittest

Expand Down Expand Up @@ -56,6 +59,14 @@
** BUILD SUCCEEDED **
"""

FLEET_CACHE_LOG = """COMPILATION_CACHE_ENABLE_PLUGIN = YES
COMPILATION_CACHE_REMOTE_SERVICE_PATH = /Users/Shared/cmux-build-fleet/xcode/fleet-cas.sock
Build Timing Summary
CompileSwiftSources (2 tasks) | 3.000 seconds
Ld (1 task) | 0.500 seconds
** BUILD SUCCEEDED **
"""


class BuildMetricsTests(unittest.TestCase):
def test_parse_log_attributes_cache_and_swift_work(self):
Expand Down Expand Up @@ -156,6 +167,11 @@ def test_ci_wires_advisory_receipt_and_timing_summary(self):
self.assertIn("steps.hosted-compile.outcome != 'skipped'", workflow)
self.assertIn("--compile-outcome \"$HOSTED_COMPILE_OUTCOME\"", workflow)
self.assertIn("--host-telemetry \"$RUNNER_TEMP/glaeda-compile-telemetry.json\"", workflow)
self.assertIn("SEED_DISTANCE: ${{ steps.seed-derived-data.outputs.seed_distance }}", workflow)
self.assertNotIn(
"SEED_DISTANCE: ${{ steps.seed-derived-data.outputs.seed_distance || steps.prefer-seed.outputs.seed_distance }}",
workflow,
)
self.assertIn("steps.build-metrics.outcome == 'success'", workflow)
self.assertIn("continue-on-error: true", workflow)

Expand All @@ -179,10 +195,56 @@ def test_receipt_discovers_logs_and_activity_metadata(self):
self.assertEqual(receipt["compiler_cache"]["cacheable_tasks"], 3)
self.assertEqual(receipt["compiler_cache"]["compile_seconds"], 12.5)
self.assertEqual(receipt["compiler_cache"]["link_seconds"], 2.25)
self.assertEqual(receipt["compiler_cache"]["compile_wall_seconds"], 42.5)
self.assertEqual(receipt["compiler_cache"]["cache_backend"], "local")
self.assertEqual(receipt["derived_data_log_count"], 1)
self.assertEqual(receipt["activity_logs"], [{"name": "one.xcactivitylog", "bytes": 3}])
json.dumps(receipt)

def test_receipt_identifies_fleet_cache_backend_without_emitting_paths(self):
with tempfile.TemporaryDirectory() as directory:
derived = Path(directory)
(derived / "cmux-build.log").write_text(FLEET_CACHE_LOG)

receipt = build_metrics.build_receipt(derived, 9.25, compile_outcome="success")

self.assertEqual(receipt["compiler_cache"]["cache_backend"], "fleet")
self.assertEqual(receipt["compiler_cache"]["compile_wall_seconds"], 9.25)
self.assertNotIn("fleet-cas.sock", json.dumps(receipt["compiler_cache"]))

def test_host_sidecar_carries_wall_time_and_backend(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
derived = root / "derived"
derived.mkdir()
(derived / "cmux-build.log").write_text(FLEET_CACHE_LOG)
output = root / "receipt.json"
host = root / "host.json"
env = {**os.environ, "GITHUB_RUN_ID": "123", "GITHUB_RUN_ATTEMPT": "2"}
subprocess.run(
[
sys.executable,
str(SCRIPT),
str(derived),
"--output",
str(output),
"--compile-seconds",
"9.25",
"--compile-outcome",
"success",
"--host-telemetry",
str(host),
],
check=True,
env=env,
)
sidecar = json.loads(host.read_text())

self.assertEqual(sidecar["compile_wall_seconds"], 9.25)
self.assertEqual(sidecar["cache_backend"], "fleet")
self.assertEqual(sidecar["run_id"], "123")
self.assertEqual(sidecar["run_attempt"], "2")


if __name__ == "__main__":
unittest.main()
Loading