From 3198d99f0dacace5b71f97fa61b0533b864c8026 Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Mon, 23 Mar 2026 13:51:44 +0100 Subject: [PATCH 1/3] wip add pixi tasks --- pyproject.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index df3888db..7bb74844 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -208,3 +208,16 @@ convention = "numpy" # pyupgrade typing rewrite TODO: remove at some point from per-file ignore # "UP006", "UP007" + +[tool.pixi.workspace] +channels = ["conda-forge"] +platforms = ["osx-arm64", "linux-64"] + +[tool.pixi.dependencies] +proj = "*" + +[tool.pixi.tasks] +test = "pytest" +test-parallel = "pytest -n auto --dist worksteal" +pre-commit = "pre-commit run" +pre-commit-all = "pre-commit run --all-files" From 126c672ae74af95b8d185204060e481cbf735028 Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Mon, 23 Mar 2026 13:51:53 +0100 Subject: [PATCH 2/3] wip add pixi tasks --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 25836a3b..105ac7e5 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ node_modules/ .mypy_cache .ruff_cache uv.lock +pixi.lock From 38e67749ff43b4146f497aa188397deedd55062e Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Mon, 11 May 2026 12:44:49 +0200 Subject: [PATCH 3/3] Add profiling skills and pixi profiling environment tasks Co-Authored-By: Claude Sonnet 4.6 --- .claude/skills/memray/SKILL.md | 37 +++++++++++++++++++++++++++ .claude/skills/profimp/SKILL.md | 30 ++++++++++++++++++++++ .claude/skills/pyspy/SKILL.md | 45 +++++++++++++++++++++++++++++++++ .gitignore | 10 ++++++-- pyproject.toml | 21 +++++++++++++++ 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 .claude/skills/memray/SKILL.md create mode 100644 .claude/skills/profimp/SKILL.md create mode 100644 .claude/skills/pyspy/SKILL.md diff --git a/.claude/skills/memray/SKILL.md b/.claude/skills/memray/SKILL.md new file mode 100644 index 00000000..94f2feae --- /dev/null +++ b/.claude/skills/memray/SKILL.md @@ -0,0 +1,37 @@ +--- +name: memray +description: Profile the memory usage of a Python script using memray and visualize a temporal flamegraph in the browser. Use when the user wants to investigate memory consumption, find leaks, or understand allocation patterns. +compatibility: Requires the pixi profiling environment (pixi run -e profiling). Supports Linux and macOS. +allowed-tools: Bash(pixi run -e profiling memray-run:*) Bash(pixi run -e profiling memray-flame:*) Bash(open:*) Bash(xdg-open:*) Bash(python -m webbrowser:*) +--- + +## Steps + +1. Ask the user which script to profile (full or relative path). + +2. Run the script under memray: + + ```bash + pixi run -e profiling memray-run script.py + ``` + + This produces a binary file named `memray-script.py..bin` in the current directory. + +3. Generate the flamegraph HTML report from the `.bin` file: + + ```bash + pixi run -e profiling memray-flame memray-script.py..bin + ``` + + Replace `` with the actual PID shown in the filename. This writes `memray-flamegraph-script.py..html`. + +4. Open the report in the browser: + - macOS: `open memray-flamegraph-script.py..html` + - Linux: `xdg-open memray-flamegraph-script.py..html` + - Either: `python -m webbrowser memray-flamegraph-script.py..html` + +## Notes + +- The `--temporal` flag (included in `memray-flame`) shows memory over time, not just peak — use this to spot leaks and allocation bursts. +- To find the `.bin` file if unsure of the name: `ls memray-*.bin` +- To compare runs, save the previous report: `cp memray-flamegraph-script.py..html memray-flamegraph-before.html` diff --git a/.claude/skills/profimp/SKILL.md b/.claude/skills/profimp/SKILL.md new file mode 100644 index 00000000..2da0903a --- /dev/null +++ b/.claude/skills/profimp/SKILL.md @@ -0,0 +1,30 @@ +--- +name: profimp +description: Profile Python import time using profimp and open a waterfall HTML report. Use when investigating slow startup or wanting to identify which imports are most expensive. +compatibility: Requires profimp (available as a pixi dependency). macOS or Linux. +allowed-tools: Bash(profimp:*) Bash(open:*) Bash(xdg-open:*) Bash(python -m webbrowser:*) +--- + +## Steps + +1. Ask what to profile. Suggest common patterns for this repo: + - `import spatialdata` + - `from spatialdata import SpatialData` + - `from spatialdata_io import xenium` + +2. Run: + +```bash +profimp --html "" > /tmp/profimp.html +``` + +3. Open the report: + - macOS: `open /tmp/profimp.html` + - Linux: `xdg-open /tmp/profimp.html` + - Either: `python -m webbrowser /tmp/profimp.html` + +## Notes + +- The report is a waterfall chart showing every sub-import and its timing. +- To compare before/after: `cp /tmp/profimp.html /tmp/profimp-before.html` before re-running. +- `pixi run python -m profimp` also works if `profimp` is not on PATH. diff --git a/.claude/skills/pyspy/SKILL.md b/.claude/skills/pyspy/SKILL.md new file mode 100644 index 00000000..00c6408c --- /dev/null +++ b/.claude/skills/pyspy/SKILL.md @@ -0,0 +1,45 @@ +--- +name: pyspy +description: Profile the execution time of a Python script using py-spy and visualize the result with speedscope. Use when the user wants to benchmark performance, find slow code paths, or profile CPU time. +compatibility: Requires the pixi profiling environment (pixi run -e profiling). Speedscope must be installed separately (npm install -g speedscope). sudo is required on macOS. +allowed-tools: Bash(pixi run -e profiling pyspy:*) Bash(pixi run -e profiling speedscope:*) Bash(sudo pixi run -e profiling pyspy:*) +--- + +## Steps + +1. Ask the user which script to profile (full or relative path). + +2. Run py-spy to record the profile. The output is always written to `profile.speedscope.json` in the current directory. + + **Linux** (no sudo needed): + + ```bash + pixi run -e profiling pyspy script.py + ``` + + **macOS** (sudo required — py-spy needs to attach to the process): + + ```bash + sudo pixi run -e profiling pyspy script.py + ``` + + If sudo fails to find the pixi environment, use absolute paths: + + ```bash + sudo /path/to/.pixi/envs/profiling/bin/py-spy record --gil \ + -o profile.speedscope.json --format speedscope \ + -- /path/to/.pixi/envs/profiling/bin/python script.py + ``` + +3. Open the result in speedscope: + ```bash + pixi run -e profiling speedscope + ``` + This opens `profile.speedscope.json` in the browser via the local speedscope CLI. + +## Notes + +- If the speedscope view is blank, switch threads using the thread selector in the top-right corner. +- To save a profile before overwriting: `cp profile.speedscope.json profile-before.speedscope.json` +- `--gil` records only time when the GIL is held (Python-level CPU time). Drop it to include C extension time. +- speedscope must be installed globally: `npm install -g speedscope` diff --git a/.gitignore b/.gitignore index 105ac7e5..23fa4b3d 100644 --- a/.gitignore +++ b/.gitignore @@ -46,12 +46,18 @@ spatialdata-sandbox # version file _version.py -# other -node_modules/ +# agents configurations +.claude/settings.local.json +# benchmarking and profiling .asv/ +profile.speedscope.json + +# other +node_modules/ .mypy_cache .ruff_cache uv.lock pixi.lock + diff --git a/pyproject.toml b/pyproject.toml index 10dfa2c0..91aa0d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -223,3 +223,24 @@ test = "pytest" test-parallel = "pytest -n auto --dist worksteal" pre-commit = "pre-commit run" pre-commit-all = "pre-commit run --all-files" + +[tool.pixi.feature.profiling.dependencies] +py-spy = "*" + +[tool.pixi.feature.profiling.pypi-dependencies] +spatialdata = { path = ".", editable = true } +profimp = "*" +memray = "*" + +[tool.pixi.feature.profiling.tasks] +# Usage: pixi run -e profiling pyspy script.py (prefix with sudo on macOS) +pyspy = "py-spy record --gil -o profile.speedscope.json --format speedscope -- python" +# Usage: pixi run -e profiling speedscope +speedscope = "npx --yes speedscope profile.speedscope.json" +# Usage: pixi run -e profiling memray-run script.py +memray-run = "memray run" +# Usage: pixi run -e profiling memray-flame memray-script.py..bin +memray-flame = "memray flamegraph --temporal" + +[tool.pixi.environments] +profiling = { features = ["profiling"], solve-group = "default" }