memscope-rs
๐ฌ Research Project | A Rust memory analyzer that tries its best. Sometimes succeeds.
The Honest Truth
After pouring countless hours into this project, I've come to a humbling realization:
You can't track what Rust doesn't let you track.
I started with dreams of building the "perfect memory analyzer" โ one that would capture every borrow, every move, every drop. Rust's ownership system would be laid bare before my eyes.
Reality had other plans.
Rust's runtime provides exactly zero hooks for &T/&mut T creation. No callbacks for Rc::clone. No way to observe ownership transfers. The compiler knows everything; the runtime knows nothing.
So here we are: a project that does what it can, admits what it can't, and tries to be useful anyway.
What We Actually Capture (The Real Stuff)
These are 100% real, no guessing:
| Data | Source |
|---|---|
| Pointer address | GlobalAlloc hook |
| Allocation size | GlobalAlloc hook |
| Thread ID | Runtime |
| Timestamps | Runtime |
| Alloc/Free events | GlobalAlloc hook |
This is the ground truth. Everything else? Well...
What We Infer (The "Trust at Your Own Risk" Stuff)
For data we can't capture, we use an Inference Engine:
- Borrow counts
- Smart pointer relationships
- Ownership patterns
- Async task migrations
Important: All inferred data is clearly marked:
Is it accurate? Sometimes. Is it better than nothing? That's for you to decide.
Known Limitations
Let's be upfront about what this tool cannot do:
-
No Borrow Tracking โ Rust doesn't expose
&T/&mut Tcreation. We guess based on heuristics. -
No True Ownership Model โ We can't observe moves. The ownership graph is inferred, not captured.
-
Async is Hard โ Task IDs are unstable. Cross-thread migrations are fuzzy at best.
-
Arc/Rc Sharing โ We can't tell who "really" owns shared data. Nobody can, really.
-
Address Reuse โ Pointers get recycled. We use generation counters, but it's a heuristic.
Why This Project Still Matters
Despite the limitations, this project serves a purpose:
1. Explores the Boundaries
What can a runtime memory tracker actually do in Rust? Now we know.
2. Validates Architecture
Event โ State โ Analysis works. The design is sound.
3. Performance Experiments
Lock-free structures, O(1) aggregation, high-throughput event systems โ all battle-tested.
4. Paves the Way
This project directly informs my next-generation tools based on LLVM/compile-time analysis.
Quick Start
use ;
Performance
Tested on Apple M3 Max, macOS Sonoma, Rust 1.85+.
Backend Performance
| Backend | Allocation | Deallocation | Reallocation | Move |
|---|---|---|---|---|
| Core | 21 ns | 21 ns | 21 ns | 21 ns |
| Async | 21 ns | 21 ns | 21 ns | 21 ns |
| Lockfree | 40 ns | 40 ns | 40 ns | 40 ns |
| Unified | 40 ns | 40 ns | 40 ns | 40 ns |
Tracking Overhead
| Operation | Latency | Throughput |
|---|---|---|
| Single Track (64B) | 528 ns | 115.55 MiB/s |
| Single Track (1KB) | 544 ns | 1.75 GiB/s |
| Single Track (1MB) | 4.72 ยตs | 206.74 GiB/s |
| Batch Track (1000) | 541 ยตs | 1.85 Melem/s |
Analysis Performance
| Analysis Type | Scale | Latency |
|---|---|---|
| Stats Query | Any | 250 ns |
| Small Analysis | 1,000 allocs | 536 ยตs |
| Medium Analysis | 10,000 allocs | 5.85 ms |
| Large Analysis | 50,000 allocs | 35.7 ms |
Concurrency Performance
| Threads | Latency | Efficiency |
|---|---|---|
| 1 | 19.3 ยตs | 100% |
| 4 | 55.7 ยตs | 139% โก |
| 8 | 138 ยตs | 112% |
| 16 | 475 ยตs | 65% |
Optimal Concurrency: 4-8 threads
Architecture
graph TB
subgraph "User Code"
A[track_var! macro]
B[track_scope! macro]
end
subgraph "Facade Layer"
C[Unified Tracker API]
end
subgraph "Engines"
D[Capture Engine]
E[Analysis Engine]
F[Event Store Engine]
G[Render Engine]
H[Snapshot Engine]
I[Timeline Engine]
J[Query Engine]
K[Metadata Engine]
end
subgraph "Backends"
L[CoreTracker]
M[LockfreeTracker]
N[AsyncTracker]
O[GlobalTracker]
end
A --> C
B --> C
C --> D
D --> L
D --> M
D --> N
D --> O
D --> F
E --> F
E --> G
G --> J
H --> F
I --> F
J --> K
Comparison with Other Tools
| Feature | memscope-rs | Valgrind | AddressSanitizer | Heaptrack |
|---|---|---|---|---|
| Language | Rust native | C/C++ | C/C++/Rust | C/C++ |
| Runtime | In-process | External | In-process | External |
| Overhead | Low (<5%) | High (10-50x) | Medium (2x) | Medium |
| Variable Names | โ | โ | โ | โ |
| Source Location | โ | โ | โ | โ |
| Leak Detection | โ | โ | โ | โ |
| UAF Detection | โ | โ | โ | โ ๏ธ |
| Buffer Overflow | โ ๏ธ | โ | โ | โ |
| Thread Analysis | โ | โ | โ | โ |
| Async Support | โ | โ | โ | โ |
| FFI Tracking | โ | โ ๏ธ | โ ๏ธ | โ ๏ธ |
| HTML Dashboard | โ | โ | โ | โ ๏ธ |
| Data Accuracy | โ ๏ธ Mixed | โ High | โ High | โ High |
โ ๏ธ memscope-rs has mixed accuracy: real data for alloc/free, inferred data for borrows/ownership.
When to Use This
Good fit:
- You want variable-level tracking in Rust
- You're debugging memory patterns
- You accept that some data is inferred
Consider alternatives:
- Valgrind โ When you need 100% accuracy
- AddressSanitizer โ For production-grade UAF detection
- Heaptrack โ For C/C++ projects
The Bottom Line
This is a research project. It's honest about its limitations. It tries to be useful despite Rust's runtime constraints.
If you need perfect accuracy, look elsewhere. If you want to explore what's possible, welcome aboard.
Documentation
- LIMITATIONS.md โ The full list of what we can't do
- Architecture โ How it works (the parts that do)
- API Guide โ How to use it
License
MIT OR Apache-2.0. Use at your own risk.
Acknowledgments
Built with โค๏ธ (and a fair amount of frustration) for the Rust community.
Special thanks to Rust for teaching me the difference between "impossible" and "really, genuinely impossible."