| import cProfile |
| from logging import Logger |
| from pathlib import Path |
| from typing import Optional |
|
|
|
|
| class Profiler: |
| """ |
| Simple wrapper around cProfile. |
| |
| Usage |
| ``` |
| # Create a profiler |
| profiler = Profiler(logger, output_dir, "sql_query_perf") |
| # Start a new profile |
| profiler.start("my_profile") |
| # Do stuff |
| profiler.stop() |
| ``` |
| |
| Visualize a profile as a flamegraph with [snakeviz](https://jiffyclub.github.io/snakeviz/) |
| ```sh |
| snakeviz my_profile.prof |
| ``` |
| |
| Visualize a profile as directed graph with [graphviz](https://graphviz.org/download/) & [gprof2dot](https://github.com/jrfonseca/gprof2dot) |
| ```sh |
| gprof2dot -f pstats my_profile.prof | dot -Tpng -o my_profile.png |
| # SVG or PDF may be nicer - you can search for function names |
| gprof2dot -f pstats my_profile.prof | dot -Tsvg -o my_profile.svg |
| gprof2dot -f pstats my_profile.prof | dot -Tpdf -o my_profile.pdf |
| ``` |
| """ |
|
|
| def __init__(self, logger: Logger, output_dir: Path, prefix: Optional[str] = None) -> None: |
| self._logger = logger.getChild(f"profiler.{prefix}" if prefix else "profiler") |
| self._output_dir = output_dir |
| self._output_dir.mkdir(parents=True, exist_ok=True) |
| self._profiler: Optional[cProfile.Profile] = None |
| self._prefix = prefix |
|
|
| self.profile_id: Optional[str] = None |
|
|
| def start(self, profile_id: str) -> None: |
| if self._profiler: |
| self.stop() |
|
|
| self.profile_id = profile_id |
|
|
| self._profiler = cProfile.Profile() |
| self._profiler.enable() |
| self._logger.info(f"Started profiling {self.profile_id}.") |
|
|
| def stop(self) -> Path: |
| if not self._profiler: |
| raise RuntimeError("Profiler not initialized. Call start() first.") |
| self._profiler.disable() |
|
|
| filename = f"{self._prefix}_{self.profile_id}.prof" if self._prefix else f"{self.profile_id}.prof" |
| path = Path(self._output_dir, filename) |
|
|
| self._profiler.dump_stats(path) |
| self._logger.info(f"Stopped profiling, profile dumped to {path}.") |
| self._profiler = None |
| self.profile_id = None |
|
|
| return path |
|
|