Ruby object retention, heap growth, and memory leak diagnostics
Not just “how much memory” — why the process is keeping it.
RubyGems · Website · Docs hub · Quick start · CLI · Privacy · GitHub Sponsors · thanks.dev · Changelog · Release notes
What HeapScope is
HeapScope is a local, evidence-driven Ruby gem for diagnosing object retention, abnormal heap growth, allocation hot spots, long-lived objects, and leak-shaped patterns in long-running processes.
It is designed for Rails, Puma, Sidekiq, background jobs, CLIs, and CI — distinguishing allocation pressure from retention, and intentional caches from suspicious growth.
No SaaS. No uploads. No telemetry. Everything runs in-process on your machine.
| | |
|--|--|
| Install (gem) | rubygems.org/gems/heapscope · gem install heapscope |
| Site / docs | theworker02.github.io/heapscope |
| Source | github.com/theworker02/heapscope |
| Changelog | CHANGELOG.md · narrative 0.7.0 |
| Release notes | GitHub v0.7.0 |
| Sponsor | GitHub Sponsors · thanks.dev |
Why HeapScope
Traditional tools often answer: “How much memory is the process using?”
HeapScope answers: “Why is this Ruby process retaining more memory than expected?”
Every finding separates observed facts, derived behavior, hypothesis, and
suspected cause — and never claims "Memory leak confirmed" without strong evidence.
Installation
Install from RubyGems (MRI Ruby ≥ 3.1 recommended):
gem install heapscope
Or add to your Gemfile:
# Gemfile
gem "heapscope", "~> 0.7"
bundle add heapscope
Confirm the install:
heapscope about
heapscope doctor
From source (development / contributing):
git clone https://github.com/theworker02/heapscope.git
cd heapscope
bundle install
bundle exec rake test
Release notes and source tags: GitHub Releases · narrative: docs/changelogs/0.7.0.md
Ruby compatibility
| Engine | Status |
|---|---|
| MRI Ruby ≥ 3.1 | Primary target — full ObjectSpace / allocation tracing / memsize |
| JRuby | Adapter present; capabilities degrade safely |
| TruffleRuby | Adapter present; capabilities degrade safely |
puts HeapScope.capabilities
Quick start
require "heapscope"
report = HeapScope.measure(force_gc: true) { perform_work }
puts report
report.save("report.json")
report.save_html("report.html")
# Ranked follow-ups
puts HeapScope.next_steps(report)
# CI gate
budget = HeapScope.budget_preset(:ci_strict)
HeapScope.check(budget: budget) { perform_work }
heapscope doctor --fix # write starter heapscope.yml
heapscope snapshot --slim -o before.json
heapscope diff before.json after.json --html report.html --fail-on-medium
heapscope suggest report.json # next steps + ignore hints
heapscope watch --duration 120 -o watch.json
heapscope about
Full CLI: docs/cli.md
Product surface
| Capability | How | ||
|---|---|---|---|
| Snapshots (lightweight / standard / deep / slim JSON) | HeapScope.snapshot / heapscope snapshot |
||
| Diff & compare | HeapScope.compare / heapscope diff |
||
| Block measure & multi-cycle retention | measure / retention_test |
||
| Ranked findings + next steps | analyzer + Suggest |
||
| Budget presets | `Budget.preset(:rails_request\ | :sidekiq_job\ | :ci_strict)` |
| Sessions & scorecards | HeapScope.session / probe |
||
| Watch / monitor with alerts | heapscope watch / Monitor |
||
| Report packs | HeapScope.pack / heapscope pack |
||
| Baselines & schema validation | baseline / validate |
||
| Rails / Rack / Sidekiq / RSpec / Minitest | optional require paths |
Concepts
Ruby heap vs RSS
HeapScope records both Ruby heap populations and process RSS. RSS growth ≠ Ruby object leak — native extensions, allocators, mmap, and CoW matter.
Allocated vs retained
100,000 allocated + 99,500 freed → churn
100,000 allocated + 20,000 live → retention
Forced GC
Opt-in only (force_gc: true). Never enabled by surprise; refuse deep walks in production_safe.
Budgets & CI
HeapScope::Budget.preset(:rails_request)
HeapScope::Budget.preset(:sidekiq_job)
HeapScope::Budget.preset(:ci_strict)
# or hand-tuned
HeapScope::Budget.new(
max_retained_objects: 1_000,
max_rss_growth: 30 * 1024 * 1024,
severity_threshold: :high
)
heapscope baseline create report.json -o baseline.json
heapscope compare baseline.json current.json --threshold 0.5
Guide: docs/guides/ci-budgets.md
Monitoring
monitor = HeapScope::Monitor.start(interval: 10, mode: :lightweight, alert: true)
# ...
report = monitor.stop
heapscope watch --interval 10 --duration 600 -o monitor.json
heapscope flamegraph snapshot.json --format speedscope -o alloc.json
heapscope flamegraph turns captured allocation sites into folded stacks (inferno / flamegraph.pl) or Speedscope JSON. Capture with --track-allocations (or mode: :deep) so sites are present.
Alerts fire on RSS / live-slot spikes between samples (thresholds configurable).
Diagnostics
| Code | Name |
|---|---|
| HS001 | persistent_class_growth |
| HS002 | high_retention_ratio |
| HS003 | thread_local_retention |
| HS004 | unbounded_collection |
| HS005 | callback_accumulation |
| HS006 | closure_retention |
| HS007 | poor_gc_recovery |
| HS008 | baseline_regression |
| HS009 | high_allocation_pressure |
| HS010 | native_memory_mismatch |
heapscope codes
heapscope explain HS001
Encyclopedia: docs/diagnostics/
Configuration
HeapScope.configure do |config|
config.mode = :standard # lightweight | standard | deep | production_safe | development
config.track_allocations = false
config.ignore_patterns << /Zeitwerk/
config.inspect_values = false # privacy: off by default
end
heapscope doctor --fix --config-out heapscope.yml
heapscope --config heapscope.yml doctor
Branding / funding URLs live in HeapScope::Branding (single source of truth).
Reports
Text, Markdown, versioned JSON, and static HTML — all offline. HTML uses the same brand mark as the site and README. Reports include NEXT STEPS when findings warrant follow-up.
HeapScope.pack(report, "./pack")
Integrations
require "heapscope/middleware" # Rack sample_rate
require "heapscope/rails" # request_retention helpers
require "heapscope/sidekiq_middleware"
require "heapscope/minitest"
require "heapscope/rspec"
Core gem requires stdlib only (plus fiddle when available for Windows RSS).
Privacy
By default HeapScope:
- makes no network calls
- sends no telemetry / analytics
- does not serialize object values
- does not dump ENV, tokens, cookies, or request bodies
See SECURITY.md.
Examples
bundle exec ruby examples/healthy_churn.rb
bundle exec ruby examples/import_leak.rb
bundle exec ruby examples/showcase.rb
See examples/README.md.
Architecture
lib/heapscope.rb Public API
lib/heapscope/runtime/* Engine adapters + RSS
lib/heapscope/collector.rb Snapshot capture
lib/heapscope/diff.rb Population diffs
lib/heapscope/analyzer.rb Findings & suspects
lib/heapscope/findings.rb Codes + ranking/dedupe
lib/heapscope/suggest.rb Next steps + ignore hints
lib/heapscope/budget.rb CI budgets + presets
lib/heapscope/scorecard.rb Probe + executive scorecard
lib/heapscope/session.rb Named artifact sessions
lib/heapscope/report/* Text / HTML / Markdown
lib/heapscope/cli/ Modular CLI commands
Roadmap
See docs/ROADMAP.md. Current focus: deeper precision and optional CI marketplace packaging — not parallel product surfaces.
Contributing
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
bundle install
bundle exec rake test
bundle exec rubocop
gem build heapscope.gemspec
License
MIT © @theworker02 — see LICENSE.
Sponsor: GitHub Sponsors · thanks.dev/u/gh/theworker02
When top says memory is growing but profiling won’t say why — reach for HeapScope.