* support weekly benchmarks * updated for perftest * rem clipper * other tests * parse cmake output + use ember-benchmark-cases.json * add only required STl files for ember tests * remove ember tests+fix corrupted stl file * shell script to py + minor changes * mac-os * more info of machine with linux fallback * storage for weekly benchmarks * rebase * binary stl * clean * . * fix ember phase capture and drop removed phases - merge stderr into stdout in run_ember_case so man_bench diagnostic output is not silently dropped - remove Assembly/Triangulation/Simplification/Sorting from INDEPENDENT_PHASES; their Timer::Print calls were removed in PR #1745 and they always produced 0.0; only Intersect12 and Winding03 phases are still reported - update build_ember_summary table to match the 4 remaining phases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * revert runner * add frontend + trigger on publish + san extended test * fix formatting * TEMP: allow workflow_dispatch to exercise data-publish steps for testing Not for merge - revert before this PR lands. Only relaxing the if: gate so a manual run can validate the folded run: > block and sanitizer arg passing reviewers asked about, without waiting for a scheduled/release run. * Revert "TEMP: allow workflow_dispatch to exercise data-publish steps for testing" This reverts commit e00c5f5784eefa27255681dce4105bd45be3daa0. * Move weekly sanitizer lane to Linux, fix artifact path, derive compiler - Move build_sanitizer/test_sanitizer in weekly_benchmark.yml from macos-15 to ubuntu-24.04, matching the existing PR-triggered sanitizer lane in manifold.yml, since nothing about ASan/UBSan requires macOS here. Drops the compiler overrides and macOS-only setup steps. - Download sanitizer build artifacts into build/ instead of the workspace root, so manifold_test's baked-in RPATH to libsamples can actually resolve. It was crashing on load with "Library not loaded: @rpath/libsamples.3.dylib" because the artifact download flattened the build/ directory away. - Capture the sanitizer build's own cmake configure log and derive SANITIZER_SUMMARY_COMPILER from it at runtime instead of hardcoding the string, reusing system_metadata.py's existing helpers. - Trim set -euo pipefail to set -u in the data-publish steps, since GitHub Actions' default bash invocation already applies -e/pipefail. - Remove the duplicate weeklybenchmark-test job from manifold.yml in favor of testing weekly_benchmark.yml directly via workflow_dispatch. * Merge dashboard pages + remove dead JS fallbacks + gtest JSON output + simplify perf summary formatting + clean up .gitignore * Stop forcing clang-18 for sanitizer builds, use runner default (gcc) * small changes * -Wno-maybe-uninitialized * review changes * dead code * fix cmke read * change flags * remove redundant flags and schema version * /dashboard route + remove upload route + change dead and retention changes --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
4.9 KiB
Python
152 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SCHEMA_VERSION = 1
|
|
|
|
# Metadata fields copied into each index entry. The index is a lightweight
|
|
# listing, so it deliberately carries a subset - the full metadata (cmake
|
|
# details, per-core CPU counts, ...) stays in the run's result.json.
|
|
INDEX_METADATA_FIELDS = (
|
|
"commit_sha",
|
|
"workflow",
|
|
"runner",
|
|
"os",
|
|
"compiler",
|
|
"cpu_model",
|
|
"cpu_count",
|
|
)
|
|
|
|
|
|
def utc_now() -> datetime.datetime:
|
|
return datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
|
|
|
|
|
|
def parse_timestamp(raw: str) -> datetime.datetime:
|
|
return datetime.datetime.fromisoformat(raw.replace("Z", "+00:00")).astimezone(
|
|
datetime.timezone.utc
|
|
)
|
|
|
|
|
|
def json_dump(path: Path, payload: dict) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
|
)
|
|
|
|
|
|
def github_run_url(run_id: str) -> str | None:
|
|
server = os.getenv("GITHUB_SERVER_URL")
|
|
repo = os.getenv("GITHUB_REPOSITORY")
|
|
if not server or not repo or not os.getenv("GITHUB_RUN_ID"):
|
|
return None
|
|
return f"{server}/{repo}/actions/runs/{run_id}"
|
|
|
|
|
|
def load_index(index_path: Path) -> dict:
|
|
if not index_path.exists():
|
|
return {"schema_version": SCHEMA_VERSION, "runs": []}
|
|
return json.loads(index_path.read_text(encoding="utf-8-sig"))
|
|
|
|
|
|
def optional_field(payload: dict, key: str, value: str | int | None) -> None:
|
|
if value not in (None, ""):
|
|
payload[key] = value
|
|
|
|
|
|
def sanitizer_summary(args: argparse.Namespace) -> dict | None:
|
|
summary = {}
|
|
optional_field(summary, "subset", args.sanitizer_subset)
|
|
optional_field(summary, "build_result", args.sanitizer_build_result)
|
|
optional_field(summary, "test_result", args.sanitizer_test_result)
|
|
optional_field(summary, "runner", args.sanitizer_runner)
|
|
optional_field(summary, "os", args.sanitizer_os)
|
|
return summary or None
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Publish weekly benchmark result files.")
|
|
parser.add_argument("suite_dir", type=Path)
|
|
parser.add_argument("data_dir", type=Path)
|
|
parser.add_argument("--release-tag")
|
|
parser.add_argument("--sanitizer-subset")
|
|
parser.add_argument("--sanitizer-build-result")
|
|
parser.add_argument("--sanitizer-test-result")
|
|
parser.add_argument("--sanitizer-runner")
|
|
parser.add_argument("--sanitizer-os")
|
|
args = parser.parse_args()
|
|
|
|
result_path = args.suite_dir / "result.json"
|
|
summary_path = args.suite_dir / "summary.md"
|
|
if not result_path.exists():
|
|
raise FileNotFoundError(f"Missing weekly benchmark result: {result_path}")
|
|
if not summary_path.exists():
|
|
raise FileNotFoundError(f"Missing weekly benchmark summary: {summary_path}")
|
|
|
|
result = json.loads(result_path.read_text(encoding="utf-8-sig"))
|
|
metadata = result["metadata"]
|
|
timestamp = parse_timestamp(metadata["timestamp"])
|
|
timestamp_iso = timestamp.isoformat().replace("+00:00", "Z")
|
|
run_id = os.getenv("GITHUB_RUN_ID") or timestamp.strftime("%Y%m%dT%H%M%SZ")
|
|
|
|
dated_rel = (
|
|
Path("weekly")
|
|
/ timestamp.strftime("%Y")
|
|
/ timestamp.strftime("%m")
|
|
/ timestamp.strftime("%d")
|
|
/ run_id
|
|
)
|
|
dated_dir = args.data_dir / dated_rel
|
|
dated_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
entry = {
|
|
"run_id": run_id,
|
|
"timestamp": timestamp_iso,
|
|
"date": timestamp.strftime("%Y-%m-%d"),
|
|
**{name: metadata[name] for name in INDEX_METADATA_FIELDS},
|
|
"result_path": str(dated_rel / "result.json").replace("\\", "/"),
|
|
"summary_path": str(dated_rel / "summary.md").replace("\\", "/"),
|
|
"github_run_url": github_run_url(run_id),
|
|
}
|
|
if args.release_tag:
|
|
entry["release_tag"] = args.release_tag
|
|
sanitizer = sanitizer_summary(args)
|
|
if sanitizer:
|
|
entry["sanitizer"] = sanitizer
|
|
entry["trigger"] = "release" if args.release_tag else "weekly"
|
|
|
|
result_dest = dated_dir / "result.json"
|
|
summary_dest = dated_dir / "summary.md"
|
|
stored_result = dict(result)
|
|
stored_result["storage"] = entry
|
|
json_dump(result_dest, stored_result)
|
|
shutil.copy2(summary_path, summary_dest)
|
|
|
|
json_dump(args.data_dir / "weekly" / "latest.json", stored_result)
|
|
|
|
index_path = args.data_dir / "weekly" / "index.json"
|
|
index = load_index(index_path)
|
|
runs = [run for run in index.get("runs", []) if str(run.get("run_id")) != run_id]
|
|
runs.append(entry)
|
|
runs.sort(key=lambda run: run.get("timestamp", ""))
|
|
index = {
|
|
"schema_version": SCHEMA_VERSION,
|
|
"generated_at": utc_now().isoformat().replace("+00:00", "Z"),
|
|
"latest_run_id": run_id,
|
|
"runs": runs,
|
|
}
|
|
json_dump(index_path, index)
|
|
|
|
print(f"Published weekly benchmark result to {result_dest}")
|
|
print(f"Updated {index_path}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|