cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
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
|