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
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 |
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.
Install from RubyGems (MRI Ruby ≥ 3.1 recommended):
gem install heapscopeOr add to your Gemfile:
# Gemfile
gem "heapscope", "~> 0.7"bundle add heapscopeConfirm the install:
heapscope about
heapscope doctorFrom source (development / contributing):
git clone https://github.com/theworker02/heapscope.git
cd heapscope
bundle install
bundle exec rake testRelease notes and source tags: GitHub Releases · narrative: docs/changelogs/0.7.0.md
| 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.capabilitiesrequire "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 aboutFull CLI: docs/cli.md
| 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 |
HeapScope records both Ruby heap populations and process RSS. RSS growth ≠ Ruby object leak — native extensions, allocators, mmap, and CoW matter.
100,000 allocated + 99,500 freed → churn
100,000 allocated + 20,000 live → retention
Opt-in only (force_gc: true). Never enabled by surprise; refuse deep walks in production_safe.
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.5Guide: docs/guides/ci-budgets.md
monitor = HeapScope::Monitor.start(interval: 10, mode: :lightweight, alert: true)
# ...
report = monitor.stopheapscope watch --interval 10 --duration 600 -o monitor.json
heapscope flamegraph snapshot.json --format speedscope -o alloc.jsonheapscope 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).
| 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 HS001Encyclopedia: docs/diagnostics/
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
endheapscope doctor --fix --config-out heapscope.yml
heapscope --config heapscope.yml doctorBranding / funding URLs live in HeapScope::Branding (single source of truth).
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")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).
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.
bundle exec ruby examples/healthy_churn.rb
bundle exec ruby examples/import_leak.rb
bundle exec ruby examples/showcase.rbSee examples/README.md.
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
See docs/ROADMAP.md. Current focus: deeper precision and optional CI marketplace packaging — not parallel product surfaces.
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
bundle install
bundle exec rake test
bundle exec rubocop
gem build heapscope.gemspecMIT © @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.