heapscope 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +119 -0
  3. data/CHANGELOG.md +86 -0
  4. data/CODE_OF_CONDUCT.md +38 -0
  5. data/CONTRIBUTING.md +72 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.md +342 -0
  9. data/Rakefile +49 -0
  10. data/SECURITY.md +39 -0
  11. data/assets/heapscope-logo.png +0 -0
  12. data/assets/logo.svg +24 -0
  13. data/assets/wordmark.svg +18 -0
  14. data/docs/README.md +20 -0
  15. data/docs/ROADMAP.md +29 -0
  16. data/docs/adr/0001-evidence-over-certainty.md +24 -0
  17. data/docs/adr/0002-local-only-privacy.md +19 -0
  18. data/docs/adr/0003-runtime-adapters.md +25 -0
  19. data/docs/adr/README.md +7 -0
  20. data/docs/allocators.md +13 -0
  21. data/docs/api/overview.md +70 -0
  22. data/docs/changelogs/0.1.0.md +22 -0
  23. data/docs/changelogs/0.2.0.md +49 -0
  24. data/docs/changelogs/0.3.0.md +38 -0
  25. data/docs/changelogs/0.4.0.md +32 -0
  26. data/docs/changelogs/0.5.0.md +18 -0
  27. data/docs/changelogs/0.6.0.md +23 -0
  28. data/docs/cli.md +98 -0
  29. data/docs/diagnostics/HS001_persistent_class_growth.md +39 -0
  30. data/docs/diagnostics/HS002_high_retention_ratio.md +24 -0
  31. data/docs/diagnostics/HS003_thread_local_retention.md +24 -0
  32. data/docs/diagnostics/HS004_unbounded_collection.md +19 -0
  33. data/docs/diagnostics/HS005_callback_accumulation.md +14 -0
  34. data/docs/diagnostics/HS006_closure_retention.md +15 -0
  35. data/docs/diagnostics/HS007_poor_gc_recovery.md +18 -0
  36. data/docs/diagnostics/HS008_baseline_regression.md +15 -0
  37. data/docs/diagnostics/HS009_high_allocation_pressure.md +13 -0
  38. data/docs/diagnostics/HS010_native_memory_mismatch.md +19 -0
  39. data/docs/guides/ci-budgets.md +43 -0
  40. data/docs/guides/first-retention-experiment.md +25 -0
  41. data/docs/guides/production-safe.md +17 -0
  42. data/docs/index.md +38 -0
  43. data/examples/cache_vs_leak.rb +30 -0
  44. data/examples/closure_capture.rb +20 -0
  45. data/examples/healthy_churn.rb +16 -0
  46. data/examples/heapscope.yml +12 -0
  47. data/examples/import_leak.rb +19 -0
  48. data/examples/probe_and_session.rb +22 -0
  49. data/examples/thread_local_leak.rb +31 -0
  50. data/exe/heapscope +7 -0
  51. data/heapscope.gemspec +73 -0
  52. data/lib/heapscope/aging.rb +114 -0
  53. data/lib/heapscope/analyzer.rb +333 -0
  54. data/lib/heapscope/baseline.rb +77 -0
  55. data/lib/heapscope/branding.rb +98 -0
  56. data/lib/heapscope/budget.rb +108 -0
  57. data/lib/heapscope/capabilities.rb +42 -0
  58. data/lib/heapscope/catalog.rb +71 -0
  59. data/lib/heapscope/cli/color.rb +67 -0
  60. data/lib/heapscope/cli/commands/capture.rb +289 -0
  61. data/lib/heapscope/cli/commands/diffing.rb +157 -0
  62. data/lib/heapscope/cli/commands/meta.rb +244 -0
  63. data/lib/heapscope/cli/commands/reporting.rb +228 -0
  64. data/lib/heapscope/cli/completion.rb +72 -0
  65. data/lib/heapscope/cli/help.rb +226 -0
  66. data/lib/heapscope/cli/support.rb +126 -0
  67. data/lib/heapscope/cli.rb +165 -0
  68. data/lib/heapscope/closures.rb +102 -0
  69. data/lib/heapscope/collector.rb +167 -0
  70. data/lib/heapscope/config.rb +196 -0
  71. data/lib/heapscope/detectors.rb +131 -0
  72. data/lib/heapscope/diff.rb +130 -0
  73. data/lib/heapscope/dominators.rb +87 -0
  74. data/lib/heapscope/errors.rb +13 -0
  75. data/lib/heapscope/extrapolation.rb +51 -0
  76. data/lib/heapscope/findings.rb +149 -0
  77. data/lib/heapscope/globals.rb +108 -0
  78. data/lib/heapscope/graph.rb +218 -0
  79. data/lib/heapscope/growth.rb +108 -0
  80. data/lib/heapscope/middleware.rb +35 -0
  81. data/lib/heapscope/minitest.rb +47 -0
  82. data/lib/heapscope/monitor.rb +160 -0
  83. data/lib/heapscope/noise.rb +48 -0
  84. data/lib/heapscope/notifications.rb +79 -0
  85. data/lib/heapscope/pack.rb +55 -0
  86. data/lib/heapscope/paths.rb +64 -0
  87. data/lib/heapscope/rails.rb +71 -0
  88. data/lib/heapscope/report/html.rb +283 -0
  89. data/lib/heapscope/report/markdown.rb +78 -0
  90. data/lib/heapscope/report/text.rb +204 -0
  91. data/lib/heapscope/report.rb +308 -0
  92. data/lib/heapscope/retention.rb +91 -0
  93. data/lib/heapscope/rspec.rb +118 -0
  94. data/lib/heapscope/runtime/base.rb +130 -0
  95. data/lib/heapscope/runtime/jruby.rb +28 -0
  96. data/lib/heapscope/runtime/mri.rb +86 -0
  97. data/lib/heapscope/runtime/truffleruby.rb +40 -0
  98. data/lib/heapscope/runtime/windows_rss.rb +75 -0
  99. data/lib/heapscope/runtime.rb +33 -0
  100. data/lib/heapscope/schema.rb +28 -0
  101. data/lib/heapscope/scorecard.rb +85 -0
  102. data/lib/heapscope/session.rb +90 -0
  103. data/lib/heapscope/sidekiq_middleware.rb +36 -0
  104. data/lib/heapscope/snapshot.rb +165 -0
  105. data/lib/heapscope/suggest.rb +99 -0
  106. data/lib/heapscope/tables.rb +58 -0
  107. data/lib/heapscope/trend_store.rb +41 -0
  108. data/lib/heapscope/version.rb +6 -0
  109. data/lib/heapscope.rb +307 -0
  110. metadata +211 -0
@@ -0,0 +1,39 @@
1
+ # HS001 — persistent_class_growth
2
+
3
+ ## What it means
4
+
5
+ A class's live object count increased across the measured window or retention cycles.
6
+
7
+ ## Evidence used
8
+
9
+ - Class counts before/after (or multi-sample series)
10
+ - Growth pattern (`monotonic_growth`, `linear_growth`, `exponential_like`)
11
+ - Optional forced-GC between samples
12
+
13
+ ## Likely causes
14
+
15
+ - Unbounded registry / array / hash
16
+ - Request or job objects retained globally or in thread-locals
17
+ - Memoization without eviction
18
+ - Callbacks retaining contexts
19
+
20
+ ## False positives
21
+
22
+ - Cache warmup / lazy loading
23
+ - First-time autoload in development
24
+ - Batch size increases that are intentional
25
+ - Delayed GC (mitigate with `force_gc: true` in experiments)
26
+
27
+ ## How to investigate
28
+
29
+ 1. Run `HeapScope.retention_test` with forced GC
30
+ 2. Enable allocation tracing for the class's sites
31
+ 3. Inspect thread-locals, globals, and constants
32
+ 4. Use deep mode retention paths for sampled objects
33
+
34
+ ## Potential fixes
35
+
36
+ - Clear request/job context in `ensure`
37
+ - Bound caches / use TTL or LRU
38
+ - Unsubscribe listeners
39
+ - Avoid capturing large objects in long-lived closures
@@ -0,0 +1,24 @@
1
+ # HS002 — high_retention_ratio
2
+
3
+ ## What it means
4
+
5
+ A large fraction of objects allocated during the workload remained reachable afterward.
6
+
7
+ ## Evidence used
8
+
9
+ - `total_allocated_objects` / `total_freed_objects` deltas
10
+ - Surviving estimate and retention ratio
11
+
12
+ ## Likely causes
13
+
14
+ - Sticky object graphs retained by roots
15
+ - Accidental global retention of request data
16
+
17
+ ## False positives
18
+
19
+ - Workload that intentionally builds long-lived structures
20
+ - Insufficient GC between measurements
21
+
22
+ ## Investigation / fixes
23
+
24
+ Use class deltas + retention sessions; clear accidental roots; bound collections.
@@ -0,0 +1,24 @@
1
+ # HS003 — thread_local_retention
2
+
3
+ ## What it means
4
+
5
+ Retention appears consistent with thread-local (or fiber-local) storage holding growing state — common under Puma/Sidekiq thread reuse.
6
+
7
+ ## Evidence used
8
+
9
+ - Thread key inventory
10
+ - Growing populations reachable from thread contexts
11
+ - Persistence across request/job cycles
12
+
13
+ ## Likely causes
14
+
15
+ - `Thread.current[:context]` not cleared
16
+ - Middleware storing request state on the worker thread
17
+
18
+ ## False positives
19
+
20
+ - Intentional per-thread caches with bounds
21
+
22
+ ## Fixes
23
+
24
+ Clear thread/fiber locals in `ensure` after each request/job; avoid storing full request graphs.
@@ -0,0 +1,19 @@
1
+ # HS004 — unbounded_collection
2
+
3
+ ## What it means
4
+
5
+ An Array/Hash/Set-like structure grows across samples without observed shrinkage after GC.
6
+
7
+ ## Evidence used
8
+
9
+ - Size series across samples
10
+ - Owner class when identifiable
11
+
12
+ ## False positives
13
+
14
+ - Bounded caches still filling toward max
15
+ - Warmup periods
16
+
17
+ ## Fixes
18
+
19
+ Cap size, eviction, periodic compaction, or stop appending.
@@ -0,0 +1,14 @@
1
+ # HS005 — callback_accumulation
2
+
3
+ ## What it means
4
+
5
+ Listeners, subscribers, or observer lists grow over time (often repeated registration without unsubscribe).
6
+
7
+ ## Evidence used
8
+
9
+ - Listener count series (via adapters)
10
+ - Proc/Array growth near event buses
11
+
12
+ ## Fixes
13
+
14
+ Unsubscribe on teardown; register once at boot; use weak references where appropriate.
@@ -0,0 +1,15 @@
1
+ # HS006 — closure_retention
2
+
3
+ ## What it means
4
+
5
+ Long-lived `Proc` objects appear to retain large object graphs via captured bindings.
6
+
7
+ ## Evidence used
8
+
9
+ - Proc population growth
10
+ - Approximate retained size from bounded traversal
11
+ - Allocation site of the Proc when tracing is enabled
12
+
13
+ ## Caveats
14
+
15
+ Runtime APIs may not expose closure captures cleanly — treat as hypothesis.
@@ -0,0 +1,18 @@
1
+ # HS007 — poor_gc_recovery
2
+
3
+ ## What it means
4
+
5
+ After opt-in forced GC, live populations or RSS remain elevated versus baseline.
6
+
7
+ ## Evidence used
8
+
9
+ - Before / after GC / after idle phase snapshots
10
+
11
+ ## False positives
12
+
13
+ - Native memory / fragmentation (see HS010)
14
+ - Intentional caches filled during workload
15
+
16
+ ## Note
17
+
18
+ Forced GC distorts production behavior — experiments only.
@@ -0,0 +1,15 @@
1
+ # HS008 — baseline_regression
2
+
3
+ ## What it means
4
+
5
+ Current retention exceeds a stored baseline beyond a configured threshold.
6
+
7
+ ## Evidence used
8
+
9
+ - Baseline retained objects/bytes vs current report
10
+
11
+ ## Use in CI
12
+
13
+ ```bash
14
+ heapscope compare baseline.json current.json --threshold 0.5
15
+ ```
@@ -0,0 +1,13 @@
1
+ # HS009 — high_allocation_pressure
2
+
3
+ ## What it means
4
+
5
+ Many objects were allocated and mostly freed — **churn**, not necessarily a leak.
6
+
7
+ ## Evidence used
8
+
9
+ - High allocated Δ with high freed Δ / low retention ratio
10
+
11
+ ## Action
12
+
13
+ Optimize hot allocation sites if GC/CPU time matters; do not treat as retention failure.
@@ -0,0 +1,19 @@
1
+ # HS010 — native_memory_mismatch
2
+
3
+ ## What it means
4
+
5
+ Process RSS grew substantially while the Ruby heap (live slots / object estimates) did not.
6
+
7
+ ## Evidence used
8
+
9
+ - RSS Δ vs heap live slots Δ and shallow byte estimates
10
+
11
+ ## Likely causes
12
+
13
+ - Native extension allocations
14
+ - Allocator fragmentation (glibc / jemalloc / macOS)
15
+ - mmap / external buffers
16
+
17
+ ## Important
18
+
19
+ **RSS growth ≠ Ruby object leak.** Do not blame object retention from this signal alone.
@@ -0,0 +1,43 @@
1
+ # Guide: CI memory budgets
2
+
3
+ ## Presets (0.6+)
4
+
5
+ ```ruby
6
+ HeapScope.budget_preset(:rails_request) # typical request cycle
7
+ HeapScope.budget_preset(:sidekiq_job) # background job
8
+ HeapScope.budget_preset(:ci_strict) # tight PR gate
9
+ ```
10
+
11
+ ```ruby
12
+ report = HeapScope.check_budget(budget: HeapScope.budget_preset(:ci_strict)) { scenario }
13
+ raise report.budget_result[:violations].join("\n") unless report.passed_budget?
14
+ ```
15
+
16
+ ## Capture a baseline on `main`
17
+
18
+ ```bash
19
+ bundle exec ruby script/memory_scenario.rb # writes report.json
20
+ heapscope baseline create report.json -o baseline.json
21
+ git add baseline.json
22
+ ```
23
+
24
+ ## On PRs
25
+
26
+ ```bash
27
+ heapscope compare baseline.json report.json --threshold 0.5
28
+ ```
29
+
30
+ Exit code `1` means regression.
31
+
32
+ ## Hand-tuned budgets
33
+
34
+ ```ruby
35
+ budget = HeapScope::Budget.new(
36
+ max_retained_objects: 2_000,
37
+ max_rss_growth: 20 * 1024 * 1024,
38
+ severity_threshold: :high
39
+ )
40
+ report = HeapScope.check_budget(budget: budget) { scenario }
41
+ ```
42
+
43
+ Keep thresholds noisy-tolerant; prefer `experiment(runs: 5)` medians for flaky heaps.
@@ -0,0 +1,25 @@
1
+ # Guide: First retention experiment
2
+
3
+ Goal: prove whether a workload **retains** objects across GC, not merely allocates.
4
+
5
+ ## Steps
6
+
7
+ 1. Warm up the app (routes, autoload, DB connections).
8
+ 2. Capture a baseline mental model: what *should* survive?
9
+ 3. Run:
10
+
11
+ ```ruby
12
+ report = HeapScope.retention_test(cycles: 10, force_gc: true) do
13
+ 50.times { MyEndpoint.call }
14
+ end
15
+ puts report
16
+ report.save_html("retention.html")
17
+ ```
18
+
19
+ 4. Read findings in order: facts → derived → hypothesis → suspected cause.
20
+ 5. If HS001/HS002 fire, enable allocation tracing and re-run once.
21
+ 6. Inspect thread-locals if HS003 appears.
22
+
23
+ ## Interpreting HEALTHY
24
+
25
+ Temporary growth that reclaims after forced GC is success. Do not chase HS009 (churn) as a leak.
@@ -0,0 +1,17 @@
1
+ # Guide: Production-safe sampling
2
+
3
+ ```ruby
4
+ HeapScope.configure do |c|
5
+ c.mode = :production_safe
6
+ c.object_sample_rate = 0.05
7
+ end
8
+
9
+ use HeapScope::Middleware, sample_rate: 0.01, mode: :lightweight
10
+ ```
11
+
12
+ Rules of thumb:
13
+
14
+ - Never force GC in production paths
15
+ - Prefer `lightweight` snapshots
16
+ - Ship reports to local disk / log drains you already trust — HeapScope itself uploads nothing
17
+ - Treat HTML/JSON outputs as sensitive
data/docs/index.md ADDED
@@ -0,0 +1,38 @@
1
+ <p align="center">
2
+ <img src="../assets/wordmark.svg" alt="HeapScope" width="420"/>
3
+ </p>
4
+
5
+ # Documentation hub
6
+
7
+ **Website:** [theworker02.github.io/heapscope](https://theworker02.github.io/heapscope/) · **Sponsor:** [thanks.dev/u/gh/theworker02](https://thanks.dev/u/gh/theworker02)
8
+
9
+ ## Start here
10
+
11
+ | Doc | Purpose |
12
+ |-----|---------|
13
+ | [README](../README.md) | Product overview & quick start |
14
+ | [First retention experiment](guides/first-retention-experiment.md) | Hands-on workflow |
15
+ | [CI budgets](guides/ci-budgets.md) | Regression gates + presets |
16
+ | [Production-safe sampling](guides/production-safe.md) | Low-overhead prod use |
17
+ | [CLI reference](cli.md) | Command catalog |
18
+ | [API overview](api/overview.md) | Public Ruby API |
19
+ | [Diagnostic codes](diagnostics/) | HS001–HS010 encyclopedia |
20
+ | [Allocators](allocators.md) | RSS vs Ruby heap caveats |
21
+ | [Roadmap](ROADMAP.md) | What's next |
22
+ | [ADRs](adr/) | Architecture decisions |
23
+ | [Changelogs](changelogs/) | Release narratives |
24
+
25
+ ## Design pledges
26
+
27
+ 1. **Retention over allocation volume**
28
+ 2. **Evidence over certainty**
29
+ 3. **Local-only privacy by default**
30
+ 4. **Safe degradation across Ruby engines**
31
+
32
+ ## Version
33
+
34
+ Docs track **HeapScope 0.6.x**.
35
+
36
+ <p align="center">
37
+ <img src="../assets/logo.svg" alt="" width="48"/>
38
+ </p>
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "heapscope"
5
+
6
+ # Bounded cache vs unbounded registry — HeapScope should treat them differently.
7
+ CACHE = {}
8
+ CACHE_MAX = 100
9
+ REGISTRY = []
10
+
11
+ def fill_cache
12
+ 300.times { |i| CACHE[i % CACHE_MAX] = "v#{i}" }
13
+ end
14
+
15
+ def fill_registry
16
+ 150.times { REGISTRY << ("row-" + rand(10_000).to_s) }
17
+ end
18
+
19
+ puts "=== CACHE (expect plateau / low suspicion) ==="
20
+ cache_report = HeapScope.retention_test(cycles: 5, force_gc: true, mode: :lightweight) { fill_cache }
21
+ puts cache_report.summary.inspect
22
+ puts cache_report.findings.map { |f| "#{f.code}/#{f.severity}" }.inspect
23
+
24
+ puts
25
+ puts "=== REGISTRY (expect persistent growth) ==="
26
+ reg_report = HeapScope.retention_test(cycles: 5, force_gc: true, mode: :lightweight) { fill_registry }
27
+ puts reg_report.summary.inspect
28
+ puts reg_report.findings.map { |f| "#{f.code}/#{f.severity} #{f.subject}" }.inspect
29
+
30
+ reg_report.save_html("examples/cache_vs_leak.html")
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "heapscope"
5
+
6
+ # Closure capture demo — enable closure detection explicitly.
7
+ HOLDERS = []
8
+ big = Array.new(2_000) { "payload" }
9
+
10
+ HeapScope.configure do |c|
11
+ c.detect_closures = true
12
+ end
13
+
14
+ report = HeapScope.retention_test(cycles: 4, force_gc: true, mode: :lightweight) do
15
+ 30.times { HOLDERS << -> { big.size } }
16
+ end
17
+
18
+ puts report
19
+ puts "Closures sampled: #{Array(report.closures).size}"
20
+ report.save_markdown("examples/closure_report.md")
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "heapscope"
5
+
6
+ # Healthy temporary allocation — should reclaim after GC.
7
+ report = HeapScope.measure(force_gc: true, mode: :lightweight) do
8
+ data = []
9
+ 10_000.times { |i| data << { i: i, body: "x" * 20 } }
10
+ data.clear
11
+ nil
12
+ end
13
+
14
+ # Framework/Hash noise from the profiler itself is ignored in findings by default.
15
+ puts report
16
+ puts "Healthy?=#{report.summary[:healthy].inspect} findings=#{report.findings.map(&:code)}"
@@ -0,0 +1,12 @@
1
+ heapscope:
2
+ mode: standard
3
+ track_allocations: false
4
+ max_graph_depth: 6
5
+ max_objects: 100000
6
+ object_sample_rate: 1.0
7
+ ignore_patterns:
8
+ - "^Zeitwerk"
9
+ - "^Bootsnap"
10
+ - "^HeapScope::"
11
+ retention:
12
+ min_cycles: 3
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "heapscope"
5
+
6
+ # Demo: intentional leak via a growing array.
7
+ LEAK = []
8
+
9
+ puts "HeapScope #{HeapScope::VERSION}"
10
+ puts HeapScope.capabilities
11
+ puts
12
+
13
+ report = HeapScope.retention_test(cycles: 5, force_gc: true, mode: :lightweight) do
14
+ 200.times { LEAK << (+"item") << ("x" * 50) }
15
+ end
16
+
17
+ puts report
18
+ report.save("examples/import_leak_report.json")
19
+ puts "Wrote examples/import_leak_report.json"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "heapscope"
5
+
6
+ puts "Probe demo"
7
+ result = HeapScope.probe(title: "temporary objects", force_gc: true, mode: :lightweight) do
8
+ data = []
9
+ 5_000.times { |i| data << { i: i } }
10
+ data.clear
11
+ nil
12
+ end
13
+ puts result.scorecard.to_text
14
+
15
+ puts
16
+ puts "Session demo"
17
+ session = HeapScope.session("demo-probe")
18
+ session.measure(label: "churn", force_gc: true, mode: :lightweight) do
19
+ 1_000.times { "x" * 10 }
20
+ end
21
+ puts "Session artifacts in .heapscope/sessions/demo-probe/"
22
+ puts "Latest: #{session.latest.inspect}"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "heapscope"
5
+
6
+ # Demonstrates thread-local retention across "requests" on one thread.
7
+ HeapScopeFixtures = Module.new unless defined?(HeapScopeFixtures)
8
+
9
+ module ThreadLocalDemo
10
+ def self.handle_request
11
+ Thread.current[:request_context] ||= { presenters: [] }
12
+ 50.times do
13
+ Thread.current[:request_context][:presenters] << ("Presenter:" + ("x" * 40))
14
+ end
15
+ end
16
+
17
+ def self.clear
18
+ Thread.current[:request_context] = nil
19
+ end
20
+ end
21
+
22
+ ThreadLocalDemo.clear
23
+
24
+ report = HeapScope.retention_test(cycles: 6, force_gc: true, mode: :lightweight) do
25
+ ThreadLocalDemo.handle_request
26
+ end
27
+
28
+ puts report
29
+ report.save("examples/thread_local_report.json")
30
+ report.save_html("examples/thread_local_report.html")
31
+ puts "Wrote examples/thread_local_report.{json,html}"
data/exe/heapscope ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "heapscope"
5
+ require "heapscope/cli"
6
+
7
+ exit HeapScope::CLI.run(ARGV)
data/heapscope.gemspec ADDED
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/heapscope/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "heapscope"
7
+ spec.version = HeapScope::VERSION
8
+ spec.authors = ["theworker02"]
9
+ spec.email = ["matthewlooney5@gmail.com"]
10
+
11
+ spec.summary = "Ruby object retention, heap growth, and memory leak diagnostics"
12
+ spec.description = <<~DESC
13
+ HeapScope is a local, evidence-driven memory diagnostics toolkit for Ruby.
14
+ It helps identify object retention, abnormal heap growth, allocation hot spots,
15
+ long-lived objects, GC-surviving populations, and likely memory leaks in
16
+ long-running Ruby processes — without claiming a leak unless evidence is strong.
17
+ DESC
18
+ spec.homepage = "https://github.com/theworker02/heapscope"
19
+ spec.license = "MIT"
20
+ spec.required_ruby_version = ">= 3.1.0"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
24
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
25
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
26
+ spec.metadata["documentation_uri"] = "https://theworker02.github.io/heapscope/"
27
+ spec.metadata["funding_uri"] = "https://thanks.dev/u/gh/theworker02"
28
+ spec.metadata["rubygems_mfa_required"] = "true"
29
+
30
+ spec.post_install_message = <<~MSG
31
+
32
+ HeapScope #{HeapScope::VERSION} installed.
33
+ heapscope doctor
34
+ heapscope about
35
+ https://theworker02.github.io/heapscope/
36
+ https://thanks.dev/u/gh/theworker02
37
+
38
+ Local-only diagnostics. No telemetry. No object-value dumps by default.
39
+ MSG
40
+
41
+ spec.files = Dir.chdir(__dir__) do
42
+ `git ls-files -z`.split("\x0").reject do |f|
43
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github benchmark/ site/]) ||
44
+ f.end_with?(".gem")
45
+ end
46
+ end
47
+ # Ensure packaging works before first commit
48
+ if spec.files.empty?
49
+ spec.files = Dir[
50
+ "lib/**/*",
51
+ "exe/*",
52
+ "assets/**/*",
53
+ "docs/**/*",
54
+ "examples/**/*",
55
+ "LICENSE*",
56
+ "README*",
57
+ "CHANGELOG*",
58
+ "CONTRIBUTING*",
59
+ "CODE_OF_CONDUCT*",
60
+ "SECURITY*",
61
+ "heapscope.gemspec"
62
+ ]
63
+ end
64
+ spec.files.reject! { |f| f.start_with?("site/") }
65
+
66
+ spec.bindir = "exe"
67
+ spec.executables = ["heapscope"]
68
+ spec.require_paths = ["lib"]
69
+
70
+ spec.add_development_dependency "minitest", "~> 5.20"
71
+ spec.add_development_dependency "rake", "~> 13.0"
72
+ spec.add_development_dependency "rubocop", "~> 1.60"
73
+ end