grafana / grafana/pyroscope-python
py-spy's short_filenames cache is never evicted (unbounded growth for apps that compile code with fresh filenames)
- Ngôn ngữ chính
- Rust
- Star
- 4
- Fork
- 2
- Merge trung bình
- 2 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 5
Mô tả
> 🤖 **This issue was written by an AI agent** (Claude Code), while investigating
> #37. The code references and measurements below were verified against the
> actual sources and a running container, but a human has not reviewed the
> writeup.
## What
`PythonSpy::shorten_filename` memoizes every filename it has ever seen and
never evicts (`py-spy/src/python_spy.rs`):
```rust
pub short_filenames: HashMap>, // :37, initialized empty at :96
fn shorten_filename(&mut self, filename: &str) -> Option {
if self.config.full_filenames {
return Some(filename.to_string());
}
if let Some(short) = self.short_filenames.get(filename) { // :561
return short.clone();
}
...
self.short_filenames.insert(filename.to_owned(), shortened.clone()); // :596
shortened
}
```
There is no cap and no eviction. By contrast the sibling caches in the same
struct *are* invalidated when a thread exits:
```rust
self.python_thread_ids.clear();
self.python_thread_names.clear(); // :276-277 — but not short_filenames
```
This path is active for pyroscope: `full_filenames` defaults to `false`
(`py-spy/src/config.rs:134`) and the extension builds its config with
`..py_spy::Config::default()`.
## Impact, measured honestly
Growth is bounded by the number of **distinct filenames that appear in sampled
frames**, not by how many code objects the app creates. For a normal app that
is the size of the codebase — a few thousand entries, and effectively bounded.
It becomes unbounded for apps that compile code with fresh filenames *and*
spend CPU inside it: templating engines, notebooks, ORM/dataclass-style
codegen, `eval`/`exec`-heavy code.
A/B in a container (Python 3.11, `pyroscope-io==1.2.1`, `sample_rate=997`,
identical interpreter-side work in both runs, only the filename differs):
```
UNIQUE=1 90s modules= 71616 rss=69 MiB (+50) # fresh filename per module
UNIQUE=0 90s modules= 75132 rss=57 MiB (+38) # one reused filename
```
~12 MiB of the growth is attributable to the ~71k distinct filenames the
sampler observed, i.e. **~175 bytes per distinct filename**, and it is
monotonic. (Most of the shared +38 MiB is interpreter churn and buffered
profile data, not this cache.)
A first attempt at this A/B showed *no* difference (+6 vs +5 MiB) even with 5M
unique filenames, because the generated functions were too short-lived to be
sampled — worth knowing if anyone re-measures.
Secondary cost on the same code path: every cache *miss* does filesystem work
on the sampler thread — `Path::exists()` for `__init__.py` while walking
parents, plus an extra `stat` of `/proc//root` when the process
is detected as dockerized. So a high rate of unique filenames also means a
`stat` storm at sampling frequency.
## Suggested fix
Bound it: an LRU (or a plain cap with a clear-on-overflow, matching how
`python_thread_*` are already handled), or drop the memoization and pay the
`strip_prefix` cost per sample — it is cheap next to the syscalls it currently
guards.
Upstream: this code lives in `benfred/py-spy`, so a fix there plus a pin bump
here.
## Context
Found while investigating #37 (root cause and reproducer in
grafana/pyroscope-python#146). Unrelated to that crash.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Hướng nghiên cứu
Bắt đầu trong py-spy/src/python_spy.rs, nơi short_filenames được định nghĩa, đọc và nạp dữ liệu, rồi so sánh vòng đời của nó với các cache python_thread_ids và python_thread_names. Kiểm tra giá trị mặc định của full_filenames trong py-spy/src/config.rs, sau đó phối hợp thay đổi upstream của py-spy và cập nhật dependency pin tại đây; hoàn tất khi mức tăng trưởng của filename-cache được giới hạn hoặc loại bỏ.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- rust
- Lĩnh vực
- performance
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 45/100