bencher.render ============== .. py:module:: bencher.render .. autoapi-nested-parse:: Out-of-process rendering for benchmark results. This module is the *render* half of bencher's collect/render split. The collect half (:meth:`bencher.Bench.collect`, i.e. ``plot_sweep(auto_plot=False)``) runs a sweep and computes regression detection **without** building any holoviews/panel/bokeh objects, returning a fully-populated :class:`BenchResult`. Why split: holoviews/panel/bokeh build large object graphs backed by C-extension wrappers (param, pandas, bokeh). When CPython's cyclic garbage collector traverses those wrappers while *foreign* live C-extension state exists in the same interpreter (e.g. ROS 2 ``rclpy``/DDS), the process can segfault. Rendering from a persisted result in a separate, clean process (one that never imported the foreign extension) makes that class of crash impossible by construction. Typical usage:: # Process 1 — holds rclpy/DDS; never builds plots: res = bench.collect(...) # auto_plot=False under the hood bencher.save_result(res, "result.pkl") # Process 2 — clean (only imports bencher), e.g. spawned via: # python -m bencher.render result.pkl output_dir bencher.render_report("result.pkl", "output_dir") ``render_report`` is also importable for in-process use when isolation is not required. Attributes ---------- .. autoapisummary:: bencher.render.logger Functions --------- .. autoapisummary:: bencher.render.save_result bencher.render.load_result bencher.render.render_report bencher.render.main Module Contents --------------- .. py:data:: logger .. py:function:: save_result(bench_res: bencher.results.bench_result.BenchResult, path: str | pathlib.Path) -> pathlib.Path Persist a collected :class:`BenchResult` to *path* via pickle. Mirrors how bencher already caches results internally: the (potentially non-pickleable) ``object_index`` is stripped before writing and restored afterwards, so the live object is unchanged. :param bench_res: A result from :meth:`Bench.collect` / ``plot_sweep``. :param path: Destination file path. :returns: The path written. .. py:function:: load_result(path: str | pathlib.Path) -> bencher.results.bench_result.BenchResult Load a :class:`BenchResult` previously written by :func:`save_result`. .. py:function:: render_report(result_or_path: bencher.results.bench_result.BenchResult | str | pathlib.Path, output_dir: str | pathlib.Path, *, report: bencher.bench_report.BenchReport | None = None, filename: str | None = None, in_html_folder: bool = False, portable: bool = False) -> pathlib.Path Render a collected result to an HTML report. Reconstructs the holoviews/panel report from a result produced by :meth:`Bench.collect` (or a path to one saved with :func:`save_result`) and writes it under *output_dir*. This is the only step that constructs plotting objects, and it is designed to run in a process free of foreign C-extension state. The result already carries its ``regression_report`` (computed during collection), so no sweep re-execution happens here. :param result_or_path: A :class:`BenchResult`, or a path to a saved one. :param output_dir: Directory to write the report into. :param report: An existing :class:`BenchReport` to append to. A new one is created (named after the benchmark) when omitted. :param filename: Output HTML filename. Defaults to ``.html``. :param in_html_folder: Forwarded to :meth:`BenchReport.save`. :param portable: Forwarded to :meth:`BenchReport.save` (base64-inline assets). :returns: The path to the saved report. .. py:function:: main(argv: list[str] | None = None) -> int CLI entrypoint: ``python -m bencher.render ``.