Add domain-specific XML documentation across src server components to satisfy CommentChecker, and update dotTrace parsing outputs used for diagnostics.
122 lines
4.1 KiB
Python
122 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
HELPER_PROJECT = ROOT / "tools" / "DtpSnapshotExtractor" / "DtpSnapshotExtractor.csproj"
|
|
HELPER_DLL = ROOT / "tools" / "DtpSnapshotExtractor" / "bin" / "Debug" / "net10.0" / "DtpSnapshotExtractor.dll"
|
|
DEFAULT_DOTTRACE_DIR = Path.home() / "Applications" / "dotTrace.app" / "Contents" / "DotFiles"
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Parse a raw dotTrace .dtp snapshot family into JSON call-tree data."
|
|
)
|
|
parser.add_argument("snapshot", help="Path to the .dtp snapshot index file.")
|
|
parser.add_argument("--out", help="Write JSON to this file.")
|
|
parser.add_argument("--stdout", action="store_true", help="Write JSON to stdout.")
|
|
parser.add_argument("--top", type=int, default=200, help="Maximum hotspot and path entries to emit.")
|
|
parser.add_argument("--filter", dest="name_filter", help="Case-insensitive substring filter for node names.")
|
|
parser.add_argument(
|
|
"--flat",
|
|
"--paths",
|
|
dest="flat_paths",
|
|
action="store_true",
|
|
help="Include the top heaviest call paths as flat strings.",
|
|
)
|
|
idle_group = parser.add_mutually_exclusive_group()
|
|
idle_group.add_argument(
|
|
"--exclude-idle",
|
|
dest="exclude_idle",
|
|
action="store_true",
|
|
default=True,
|
|
help="Exclude idle and wait methods from hotspot and path rankings.",
|
|
)
|
|
idle_group.add_argument(
|
|
"--include-idle",
|
|
dest="exclude_idle",
|
|
action="store_false",
|
|
help="Keep idle and wait methods in hotspot and path rankings.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def find_dottrace_dir() -> Path:
|
|
value = os.environ.get("DOTTRACE_APP_DIR")
|
|
if value:
|
|
candidate = Path(value).expanduser()
|
|
if candidate.is_dir():
|
|
return candidate
|
|
if DEFAULT_DOTTRACE_DIR.is_dir():
|
|
return DEFAULT_DOTTRACE_DIR
|
|
raise FileNotFoundError(
|
|
f"dotTrace assemblies not found. Set DOTTRACE_APP_DIR or install dotTrace under {DEFAULT_DOTTRACE_DIR}."
|
|
)
|
|
|
|
|
|
def build_helper(dottrace_dir: Path) -> None:
|
|
command = [
|
|
"dotnet",
|
|
"build",
|
|
str(HELPER_PROJECT),
|
|
"-nologo",
|
|
"-clp:ErrorsOnly",
|
|
f"-p:DotTraceAppDir={dottrace_dir}",
|
|
]
|
|
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(result.stderr.strip() or result.stdout.strip() or "dotnet build failed")
|
|
|
|
|
|
def run_helper(snapshot: Path, dottrace_dir: Path, args: argparse.Namespace) -> dict:
|
|
command = ["dotnet", str(HELPER_DLL), str(snapshot), "--top", str(args.top)]
|
|
if args.name_filter:
|
|
command.extend(["--filter", args.name_filter])
|
|
if args.flat_paths:
|
|
command.append("--flat")
|
|
if not args.exclude_idle:
|
|
command.append("--include-idle")
|
|
env = os.environ.copy()
|
|
env["DOTTRACE_APP_DIR"] = str(dottrace_dir)
|
|
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False, env=env)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(result.stderr.strip() or result.stdout.strip() or "extractor failed")
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
snapshot = Path(args.snapshot).expanduser().resolve()
|
|
if not snapshot.is_file():
|
|
print(f"Snapshot not found: {snapshot}", file=sys.stderr)
|
|
return 2
|
|
|
|
if not args.stdout and not args.out:
|
|
args.stdout = True
|
|
|
|
try:
|
|
dottrace_dir = find_dottrace_dir()
|
|
build_helper(dottrace_dir)
|
|
payload = run_helper(snapshot, dottrace_dir, args)
|
|
except Exception as exc: # noqa: BLE001
|
|
print(str(exc), file=sys.stderr)
|
|
return 1
|
|
|
|
text = json.dumps(payload, indent=2)
|
|
if args.out:
|
|
out_path = Path(args.out).expanduser().resolve()
|
|
out_path.write_text(text + "\n", encoding="utf-8")
|
|
if args.stdout:
|
|
sys.stdout.write(text + "\n")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|