Files
wehub-resource-sync 461bf6fd40
CI / lint (3.11) (push) Blocked by required conditions
CI / lint (3.12) (push) Blocked by required conditions
CI / lint (3.13) (push) Blocked by required conditions
CI / shellcheck (push) Waiting to run
CI / shfmt (push) Waiting to run
CI / setup (3.11) (push) Waiting to run
CI / setup (3.12) (push) Waiting to run
CI / setup (3.13) (push) Waiting to run
CI / check-licenses (3.12) (push) Blocked by required conditions
CI / test_unit (3.11) (push) Blocked by required conditions
CI / test_unit (3.12) (push) Blocked by required conditions
CI / test_unit (3.13) (push) Blocked by required conditions
CI / test_unit_no_extras (3.11) (push) Blocked by required conditions
CI / test_unit_no_extras (3.12) (push) Blocked by required conditions
CI / test_json_to_html (3.12) (push) Blocked by required conditions
CI / test_unit_no_extras (3.13) (push) Blocked by required conditions
CI / test_unit_dependency_extras (csv, 3.11, --extra csv) (push) Blocked by required conditions
CI / test_unit_dependency_extras (csv, 3.12, --extra csv) (push) Blocked by required conditions
CI / test_unit_dependency_extras (csv, 3.13, --extra csv) (push) Blocked by required conditions
CI / test_unit_dependency_extras (docx, 3.11, --extra docx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (docx, 3.12, --extra docx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (docx, 3.13, --extra docx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (markdown, 3.11, --extra md) (push) Blocked by required conditions
CI / test_unit_dependency_extras (markdown, 3.12, --extra md) (push) Blocked by required conditions
CI / test_unit_dependency_extras (markdown, 3.13, --extra md) (push) Blocked by required conditions
CI / test_unit_dependency_extras (odt, 3.11, --extra odt) (push) Blocked by required conditions
CI / test_unit_dependency_extras (odt, 3.12, --extra odt) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pdf-image, 3.12, --extra pdf --extra image --extra paddleocr) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pdf-image, 3.13, --extra pdf --extra image --extra paddleocr) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pptx, 3.13, --extra pptx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (odt, 3.13, --extra odt) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pdf-image, 3.11, --extra pdf --extra image --extra paddleocr) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pptx, 3.11, --extra pptx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pptx, 3.12, --extra pptx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pypandoc, 3.11, --extra epub --extra org --extra rtf --extra rst) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pypandoc, 3.12, --extra epub --extra org --extra rtf --extra rst) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pypandoc, 3.13, --extra epub --extra org --extra rtf --extra rst) (push) Blocked by required conditions
CI / test_unit_dependency_extras (xlsx, 3.11, --extra xlsx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (xlsx, 3.12, --extra xlsx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (xlsx, 3.13, --extra xlsx) (push) Blocked by required conditions
CI / test_ingest_src (3.12) (push) Blocked by required conditions
CI / test_json_to_markdown (3.12) (push) Blocked by required conditions
CI / changelog (push) Waiting to run
CI / test_dockerfile (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Build And Push Docker Image / set-short-sha (push) Waiting to run
Build And Push Docker Image / build-images (linux/amd64, opensource-linux-8core) (push) Blocked by required conditions
Build And Push Docker Image / build-images (linux/arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
Build And Push Docker Image / publish-images (push) Blocked by required conditions
Partition Benchmark / setup (push) Waiting to run
Partition Benchmark / Measure and compare partition() runtime (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:33:56 +08:00

267 lines
9.1 KiB
Python

"""Quick local benchmark for PDF partition cold/warm timing.
Examples:
uv run --active --frozen --no-sync scripts/performance/quick_partition_bench.py \
--pdf example-docs/pdf/DA-1p.pdf --strategy fast --repeats 4 --warmups 1 --mode both
uv run --active --frozen --no-sync scripts/performance/quick_partition_bench.py \
--pdf example-docs/pdf/DA-1p.pdf --pdf example-docs/pdf/chevron-page.pdf \
--strategy hi_res --repeats 3 --warmups 1 --mode both
"""
import argparse
import io
import json
import statistics
import subprocess
import sys
import time
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
def _partition_once(pdf: str, strategy: str) -> dict[str, object]:
sink_out = io.StringIO()
sink_err = io.StringIO()
start = time.perf_counter()
try:
from unstructured.partition.auto import partition
with redirect_stdout(sink_out), redirect_stderr(sink_err):
elements = partition(filename=pdf, strategy=strategy)
return {
"ok": True,
"elapsed_s": time.perf_counter() - start,
"elements": len(elements),
}
except Exception as exc: # noqa: BLE001
return {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
def _summary(values: list[float]) -> dict[str, float]:
return {
"mean_s": statistics.mean(values),
"median_s": statistics.median(values),
"min_s": min(values),
"max_s": max(values),
"stdev_s": statistics.stdev(values) if len(values) > 1 else 0.0,
}
def _run_cold(pdf: str, strategy: str, repeats: int) -> tuple[list[float], int, list[str]]:
times: list[float] = []
elements = -1
errors: list[str] = []
for _ in range(repeats):
proc = subprocess.run(
[
sys.executable,
__file__,
"--_child",
"--pdf",
pdf,
"--strategy",
strategy,
],
capture_output=True,
text=True,
check=False,
)
lines = [line.strip() for line in (proc.stdout or "").splitlines() if line.strip()]
json_line = next((line for line in reversed(lines) if line.startswith("{")), "")
if not json_line:
stderr_tail = (proc.stderr or "").strip().splitlines()
detail = stderr_tail[-1] if stderr_tail else "no json output"
errors.append(f"child failed rc={proc.returncode} ({detail})")
continue
row = json.loads(json_line)
if bool(row.get("ok")):
times.append(float(row["elapsed_s"]))
elements = int(row["elements"])
else:
errors.append(str(row.get("error", "unknown error")))
return times, elements, errors
def _run_warm(
pdf: str,
strategy: str,
repeats: int,
warmups: int,
) -> tuple[list[float], int, list[str]]:
errors: list[str] = []
for _ in range(warmups):
row = _partition_once(pdf=pdf, strategy=strategy)
if not bool(row.get("ok")):
errors.append(str(row.get("error", "unknown error")))
return [], -1, errors
times: list[float] = []
elements = -1
for _ in range(repeats):
row = _partition_once(pdf=pdf, strategy=strategy)
if bool(row.get("ok")):
times.append(float(row["elapsed_s"]))
elements = int(row["elements"])
else:
errors.append(str(row.get("error", "unknown error")))
return times, elements, errors
def _collect_pdfs(pdf_args: list[str], pdf_dir_args: list[str]) -> list[str]:
paths = [str(Path(p)) for p in pdf_args]
for pdf_dir in pdf_dir_args:
root = Path(pdf_dir)
if not root.is_dir():
raise FileNotFoundError(f"pdf-dir does not exist: {root}")
paths.extend(str(p) for p in sorted(root.rglob("*.pdf")))
if not paths:
raise ValueError("Provide at least one --pdf or --pdf-dir")
deduped = [Path(p) for p in dict.fromkeys(paths)]
missing = [str(p) for p in deduped if not p.exists()]
if missing:
raise FileNotFoundError(f"Missing files: {', '.join(missing)}")
return [str(p) for p in deduped]
def _print_mode(label: str, values: list[float], elements: int, errors: list[str]) -> None:
if not values:
first = errors[0] if errors else "unknown error"
print(f" {label} FAILED ({first})", flush=True)
return
s = _summary(values)
print(
f" {label} mean={s['mean_s']:.4f}s median={s['median_s']:.4f}s "
f"min={s['min_s']:.4f}s max={s['max_s']:.4f}s n={len(values)} elements={elements}",
flush=True,
)
if errors:
print(f" {label} partial_failures={len(errors)} first_error={errors[0]}", flush=True)
def main() -> None:
parser = argparse.ArgumentParser(description="Quick local PDF partition benchmark")
parser.add_argument("--pdf", action="append", default=[], help="PDF path (repeatable)")
parser.add_argument("--pdf-dir", action="append", default=[], help="Directory of PDFs")
parser.add_argument("--strategy", default="fast", choices=["fast", "hi_res", "auto"])
parser.add_argument("--repeats", type=int, default=3)
parser.add_argument("--warmups", type=int, default=1)
parser.add_argument("--mode", default="both", choices=["cold", "warm", "both"])
parser.add_argument("--json-out", default="", help="Optional JSON output path")
parser.add_argument("--_child", action="store_true", help=argparse.SUPPRESS)
args = parser.parse_args()
if args._child:
print(json.dumps(_partition_once(args.pdf[0], args.strategy)), flush=True)
return
pdfs = _collect_pdfs(args.pdf, args.pdf_dir)
print(
f"strategy={args.strategy} mode={args.mode} repeats={args.repeats} "
f"warmups={args.warmups} pdf_count={len(pdfs)}",
flush=True,
)
by_mode_times: dict[str, list[float]] = {"cold": [], "warm": []}
by_mode_file_means: dict[str, list[float]] = {"cold": [], "warm": []}
by_mode_failed_files: dict[str, int] = {"cold": 0, "warm": 0}
results: list[dict[str, object]] = []
for pdf in pdfs:
row: dict[str, object] = {"pdf": pdf}
print(f"FILE {pdf}", flush=True)
if args.mode in ("cold", "both"):
times, elements, errors = _run_cold(pdf, args.strategy, args.repeats)
_print_mode("cold", times, elements, errors)
if times:
by_mode_times["cold"].extend(times)
by_mode_file_means["cold"].append(statistics.mean(times))
row["cold"] = {"ok": True, "times_s": times, "elements": elements, "errors": errors}
else:
by_mode_failed_files["cold"] += 1
row["cold"] = {"ok": False, "errors": errors}
if args.mode in ("warm", "both"):
times, elements, errors = _run_warm(pdf, args.strategy, args.repeats, args.warmups)
_print_mode("warm", times, elements, errors)
if times:
by_mode_times["warm"].extend(times)
by_mode_file_means["warm"].append(statistics.mean(times))
row["warm"] = {
"ok": True,
"times_s": times,
"elements": elements,
"errors": errors,
"warmups": args.warmups,
}
else:
by_mode_failed_files["warm"] += 1
row["warm"] = {"ok": False, "errors": errors, "warmups": args.warmups}
results.append(row)
aggregate: dict[str, object] = {}
print("AGGREGATE", flush=True)
for mode in ("cold", "warm"):
times = by_mode_times[mode]
if not times:
continue
s = _summary(times)
file_mean = statistics.mean(by_mode_file_means[mode])
succeeded = len(by_mode_file_means[mode])
failed = by_mode_failed_files[mode]
aggregate[mode] = {
"summary": s,
"file_mean_s": file_mean,
"samples": len(times),
"succeeded_files": succeeded,
"failed_files": failed,
}
print(
f" {mode} succeeded_files={succeeded} failed_files={failed} "
f"file_mean={file_mean:.4f}s mean={s['mean_s']:.4f}s median={s['median_s']:.4f}s "
f"min={s['min_s']:.4f}s max={s['max_s']:.4f}s "
f"stdev={s['stdev_s']:.4f}s samples={len(times)}",
flush=True,
)
if args.json_out:
out = Path(args.json_out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(
json.dumps(
{
"strategy": args.strategy,
"mode": args.mode,
"repeats": args.repeats,
"warmups": args.warmups,
"pdf_count": len(pdfs),
"per_file": results,
"aggregate": aggregate,
},
indent=2,
)
)
print(f"json_out={out}", flush=True)
if __name__ == "__main__":
main()