grafana / grafana/pyroscope-python

py-spy's short_filenames cache is never evicted (unbounded growth for apps that compile code with fresh filenames)

Đang mở
#148 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
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

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.