2026-04-02 13:22:27 -07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import importlib
|
|
|
|
|
import json
|
|
|
|
|
import time
|
|
|
|
|
from pathlib import Path
|
2026-04-02 15:47:41 -07:00
|
|
|
from typing import Any
|
2026-04-02 13:22:27 -07:00
|
|
|
|
|
|
|
|
from masque import LibraryError
|
|
|
|
|
|
|
|
|
|
|
2026-04-02 15:47:41 -07:00
|
|
|
READERS: dict[str, tuple[str, tuple[str, ...]]] = {
|
|
|
|
|
'gdsii': ('masque.file.gdsii', ('readfile',)),
|
|
|
|
|
'gdsii_arrow': ('masque.file.gdsii_arrow', ('readfile', 'arrow_import', 'arrow_convert')),
|
2026-04-02 13:22:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-02 15:47:41 -07:00
|
|
|
def _summarize_library(path: Path, elapsed_s: float, info: dict[str, object], lib: object) -> dict[str, object]:
|
2026-04-02 13:22:27 -07:00
|
|
|
assert hasattr(lib, '__len__')
|
|
|
|
|
assert hasattr(lib, 'tops')
|
|
|
|
|
tops = lib.tops() # type: ignore[no-any-return, attr-defined]
|
|
|
|
|
try:
|
|
|
|
|
unique_top = lib.top() # type: ignore[no-any-return, attr-defined]
|
|
|
|
|
except LibraryError:
|
|
|
|
|
unique_top = None
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'path': str(path),
|
|
|
|
|
'elapsed_s': elapsed_s,
|
|
|
|
|
'library_name': info['name'],
|
|
|
|
|
'cell_count': len(lib), # type: ignore[arg-type]
|
|
|
|
|
'topcells': tops,
|
|
|
|
|
'topcell': unique_top,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-02 15:47:41 -07:00
|
|
|
def _summarize_arrow_import(path: Path, elapsed_s: float, arrow_arr: Any) -> dict[str, object]:
|
|
|
|
|
libarr = arrow_arr[0]
|
|
|
|
|
return {
|
|
|
|
|
'path': str(path),
|
|
|
|
|
'elapsed_s': elapsed_s,
|
|
|
|
|
'arrow_rows': len(arrow_arr),
|
|
|
|
|
'library_name': libarr['lib_name'].as_py(),
|
|
|
|
|
'cell_count': len(libarr['cells']),
|
|
|
|
|
'layer_count': len(libarr['layers']),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _profile_stage(module: Any, stage: str, path: Path) -> dict[str, object]:
|
|
|
|
|
start = time.perf_counter()
|
|
|
|
|
|
|
|
|
|
if stage == 'readfile':
|
|
|
|
|
lib, info = module.readfile(path)
|
|
|
|
|
elapsed_s = time.perf_counter() - start
|
|
|
|
|
return _summarize_library(path, elapsed_s, info, lib)
|
|
|
|
|
|
|
|
|
|
if stage == 'arrow_import':
|
|
|
|
|
arrow_arr = module._read_to_arrow(path)
|
|
|
|
|
elapsed_s = time.perf_counter() - start
|
|
|
|
|
return _summarize_arrow_import(path, elapsed_s, arrow_arr)
|
|
|
|
|
|
|
|
|
|
if stage == 'arrow_convert':
|
|
|
|
|
arrow_arr = module._read_to_arrow(path)
|
|
|
|
|
libarr = arrow_arr[0]
|
|
|
|
|
start = time.perf_counter()
|
|
|
|
|
lib, info = module.read_arrow(libarr)
|
|
|
|
|
elapsed_s = time.perf_counter() - start
|
|
|
|
|
return _summarize_library(path, elapsed_s, info, lib)
|
|
|
|
|
|
|
|
|
|
raise ValueError(f'Unsupported stage {stage!r}')
|
|
|
|
|
|
|
|
|
|
|
2026-04-02 13:22:27 -07:00
|
|
|
def build_arg_parser() -> argparse.ArgumentParser:
|
|
|
|
|
parser = argparse.ArgumentParser(description='Profile GDS readers with a stable end-to-end workload.')
|
|
|
|
|
parser.add_argument('--reader', choices=sorted(READERS), required=True)
|
2026-04-02 15:47:41 -07:00
|
|
|
parser.add_argument('--stage', default='readfile')
|
2026-04-02 13:22:27 -07:00
|
|
|
parser.add_argument('--path', type=Path, required=True)
|
|
|
|
|
parser.add_argument('--warmup', type=int, default=1)
|
|
|
|
|
parser.add_argument('--repeat', type=int, default=1)
|
2026-04-02 15:47:41 -07:00
|
|
|
parser.add_argument('--output-json', type=Path)
|
2026-04-02 13:22:27 -07:00
|
|
|
return parser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
|
|
|
parser = build_arg_parser()
|
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
|
|
2026-04-02 15:47:41 -07:00
|
|
|
module_name, stages = READERS[args.reader]
|
|
|
|
|
if args.stage not in stages:
|
|
|
|
|
parser.error(f'reader {args.reader!r} only supports stages: {", ".join(stages)}')
|
|
|
|
|
|
|
|
|
|
module = importlib.import_module(module_name)
|
2026-04-02 13:22:27 -07:00
|
|
|
path = args.path.expanduser().resolve()
|
|
|
|
|
|
|
|
|
|
for _ in range(args.warmup):
|
2026-04-02 15:47:41 -07:00
|
|
|
_profile_stage(module, args.stage, path)
|
2026-04-02 13:22:27 -07:00
|
|
|
|
|
|
|
|
runs = []
|
|
|
|
|
for _ in range(args.repeat):
|
2026-04-02 15:47:41 -07:00
|
|
|
runs.append(_profile_stage(module, args.stage, path))
|
2026-04-02 13:22:27 -07:00
|
|
|
|
2026-04-02 15:47:41 -07:00
|
|
|
payload = {
|
2026-04-02 13:22:27 -07:00
|
|
|
'reader': args.reader,
|
2026-04-02 15:47:41 -07:00
|
|
|
'stage': args.stage,
|
2026-04-02 13:22:27 -07:00
|
|
|
'warmup': args.warmup,
|
|
|
|
|
'repeat': args.repeat,
|
|
|
|
|
'runs': runs,
|
2026-04-02 15:47:41 -07:00
|
|
|
}
|
|
|
|
|
rendered = json.dumps(payload, indent=2, sort_keys=True)
|
|
|
|
|
if args.output_json is not None:
|
|
|
|
|
args.output_json.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
args.output_json.write_text(rendered + '\n')
|
|
|
|
|
print(rendered)
|
2026-04-02 13:22:27 -07:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
raise SystemExit(main())
|