diff --git a/tools/op_complexity_report.py b/tools/op_complexity_report.py new file mode 100644 index 0000000..e3196c6 --- /dev/null +++ b/tools/op_complexity_report.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Summarize CUDA/MACA operator source complexity for validation planning.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +SOURCE_SUFFIXES = {".cu", ".cuh", ".cpp", ".h"} + + +def analyze_file(path: Path, root: Path) -> dict[str, object]: + text = path.read_text(encoding="utf-8", errors="replace") + return { + "path": path.relative_to(root).as_posix(), + "lines": len(text.splitlines()), + "kernel_launches": text.count("<<<"), + "templates": text.count("template"), + "torch_bindings": text.count("PYBIND11_MODULE"), + } + + +def build_report(root: Path) -> dict[str, object]: + op_dir = root / "op" + if not op_dir.is_dir(): + return {"file_count": 0, "total_lines": 0, "top_by_lines": []} + + files = [ + analyze_file(path, root) + for path in sorted(op_dir.rglob("*")) + if path.is_file() and path.suffix in SOURCE_SUFFIXES + ] + return { + "file_count": len(files), + "total_lines": sum(item["lines"] for item in files), + "top_by_lines": sorted(files, key=lambda item: item["lines"], reverse=True)[:20], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path.cwd(), help="repository root") + parser.add_argument("--output", type=Path, help="write JSON report to this path") + args = parser.parse_args() + + text = json.dumps(build_report(args.root), indent=2, ensure_ascii=False) + if args.output: + args.output.write_text(text + "\n", encoding="utf-8") + else: + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/unit_test/test_op_complexity_report.py b/unit_test/test_op_complexity_report.py new file mode 100644 index 0000000..a66fffb --- /dev/null +++ b/unit_test/test_op_complexity_report.py @@ -0,0 +1,29 @@ +import tempfile +import unittest +from pathlib import Path + +from tools.op_complexity_report import build_report + + +class OpComplexityReportTest(unittest.TestCase): + def test_counts_operator_sources(self): + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + op = root / "op" + op.mkdir() + (op / "kernel.cu").write_text("template \nvoid f(){ k<<<1,1>>>(); }\n", encoding="utf-8") + + report = build_report(root) + + self.assertEqual(report["file_count"], 1) + self.assertEqual(report["top_by_lines"][0]["kernel_launches"], 1) + + def test_returns_empty_report_when_op_directory_is_missing(self): + with tempfile.TemporaryDirectory() as tmpdir: + report = build_report(Path(tmpdir)) + + self.assertEqual(report, {"file_count": 0, "total_lines": 0, "top_by_lines": []}) + + +if __name__ == "__main__": + unittest.main()