rails_memory_profiler 0.5.0 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +13 -1
- data/app/controllers/rails_memory_profiler/reports_controller.rb +0 -5
- data/app/controllers/rails_memory_profiler/store_controller.rb +8 -0
- data/app/views/rails_memory_profiler/reports/index.html.erb +1 -1
- data/config/routes.rb +3 -6
- data/lib/rails_memory_profiler/configuration.rb +63 -0
- data/lib/rails_memory_profiler/engine.rb +22 -5
- data/lib/rails_memory_profiler/middleware.rb +13 -0
- data/lib/rails_memory_profiler/report_store.rb +33 -1
- data/lib/rails_memory_profiler/test_helper.rb +25 -0
- data/lib/rails_memory_profiler/version.rb +1 -1
- data/lib/rails_memory_profiler.rb +34 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0cfc3104434c10aa68686413abf2c879661158fde43cbe61c53cf31f45154b6f
|
|
4
|
+
data.tar.gz: 114b4471fca8c8a32c2d0ea00debec64f2e338d3b92268bff205ab1db871e73a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd622ed113b4cc6c1cf8bcebc612785bc5a6ee549daa6a83b7d74df236eefe9beffac0a81867ad54053b7a2761e24e7687e8de96d0004e11e94cc9a34050d39c
|
|
7
|
+
data.tar.gz: bfed9ad01ac4d045eb61d01bf4effbfa45b2c89839daa9faf58542afd5dbc2268d8c124f85c6d659e8d2d620ce9548a4c39d5d797fbf32ebe8f00e4c9092adf9
|
data/README.md
CHANGED
|
@@ -18,6 +18,7 @@ A Rack middleware captures object allocations for every request using `GC.stat`
|
|
|
18
18
|
- [Installation](#installation)
|
|
19
19
|
- [Dashboard](#dashboard)
|
|
20
20
|
- [Configuration](#configuration)
|
|
21
|
+
- [Compatibility](#compatibility)
|
|
21
22
|
- [Contributing](#contributing)
|
|
22
23
|
- [License](#license)
|
|
23
24
|
|
|
@@ -121,9 +122,20 @@ expect { MyClass.new }.to allocate_fewer_than(500)
|
|
|
121
122
|
|
|
122
123
|
---
|
|
123
124
|
|
|
125
|
+
## Compatibility
|
|
126
|
+
|
|
127
|
+
| | Ruby 3.3 | Ruby 3.4 | Ruby 4.0 |
|
|
128
|
+
|----------|:--------:|:--------:|:--------:|
|
|
129
|
+
| Rails 7.1 | ✓ | ✓ | ✓ |
|
|
130
|
+
| Rails 8.x | ✓ | ✓ | ✓ |
|
|
131
|
+
|
|
132
|
+
[↑ Back to top](#railsmemoryprofiler)
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
124
136
|
## Contributing
|
|
125
137
|
|
|
126
|
-
Bug reports and pull requests are welcome
|
|
138
|
+
Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
127
139
|
|
|
128
140
|
[↑ Back to top](#railsmemoryprofiler)
|
|
129
141
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<p><%= @reports.size %> request<%= "s" unless @reports.size == 1 %> captured</p>
|
|
6
6
|
</div>
|
|
7
7
|
<% unless @reports.empty? %>
|
|
8
|
-
<%= button_to "Clear All",
|
|
8
|
+
<%= button_to "Clear All", store_path, method: :delete,
|
|
9
9
|
class: "rmp-btn-danger",
|
|
10
10
|
form: { data: { turbo_confirm: "Clear all reports? This cannot be undone." } } %>
|
|
11
11
|
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
RailsMemoryProfiler::Engine.routes.draw do
|
|
2
|
-
resources :reports, only: [:index, :show]
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
end
|
|
6
|
-
end
|
|
7
|
-
resource :comparison, only: [:show]
|
|
2
|
+
resources :reports, only: [:index, :show]
|
|
3
|
+
resource :comparison, only: [:show]
|
|
4
|
+
resource :store, only: [:destroy], controller: "store"
|
|
8
5
|
end
|
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
module RailsMemoryProfiler
|
|
2
|
+
# Holds all user-facing settings. Obtain the shared instance via
|
|
3
|
+
# {RailsMemoryProfiler.config} or set values inside a {RailsMemoryProfiler.configure} block.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# RailsMemoryProfiler.configure do |config|
|
|
7
|
+
# config.sample_rate = 5
|
|
8
|
+
# config.min_allocated_objects = 1_000
|
|
9
|
+
# config.notifiers = [RailsMemoryProfiler::Notifiers::Console.new]
|
|
10
|
+
# end
|
|
2
11
|
class Configuration
|
|
12
|
+
# @!attribute enabled
|
|
13
|
+
# Enable or disable per-request profiling. Defaults to +true+ in development.
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
|
|
16
|
+
# @!attribute sample_rate
|
|
17
|
+
# Profile every Nth request. +1+ profiles every request.
|
|
18
|
+
# @return [Integer]
|
|
19
|
+
|
|
20
|
+
# @!attribute store_size
|
|
21
|
+
# Maximum number of reports kept in the ring buffer. Oldest are evicted when full.
|
|
22
|
+
# @return [Integer]
|
|
23
|
+
|
|
24
|
+
# @!attribute dashboard_enabled
|
|
25
|
+
# Enable the HTML/JSON dashboard endpoint. Defaults to +true+ in development.
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
|
|
28
|
+
# @!attribute min_allocated_objects
|
|
29
|
+
# Skip recording requests that allocate fewer objects than this threshold.
|
|
30
|
+
# @return [Integer]
|
|
31
|
+
|
|
32
|
+
# @!attribute ignore_paths
|
|
33
|
+
# Paths to skip entirely. Accepts strings (prefix match) or Regexps.
|
|
34
|
+
# The engine's own mount path is always ignored regardless of this setting.
|
|
35
|
+
# @return [Array<String, Regexp>]
|
|
36
|
+
|
|
37
|
+
# @!attribute ignore_controllers
|
|
38
|
+
# Controller names to skip (matched against the Rails controller name string).
|
|
39
|
+
# @return [Array<String>]
|
|
40
|
+
|
|
41
|
+
# @!attribute detailed_reports
|
|
42
|
+
# Capture full +MemoryProfiler.report+ breakdowns (by gem, class, file, location).
|
|
43
|
+
# Requires +gem "memory_profiler"+ in the host app's Gemfile.
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
|
|
46
|
+
# @!attribute detailed_sample_rate
|
|
47
|
+
# When {#detailed_reports} is enabled, capture a full report every Nth profiled request.
|
|
48
|
+
# @return [Integer]
|
|
49
|
+
|
|
50
|
+
# @!attribute raise_on_allocation_spike
|
|
51
|
+
# Raise {RailsMemoryProfiler::AllocationSpikeError} when allocated objects exceed this value.
|
|
52
|
+
# Set to +nil+ to disable. Intended for use in test environments.
|
|
53
|
+
# @return [Integer, nil]
|
|
54
|
+
|
|
55
|
+
# @!attribute notifiers
|
|
56
|
+
# Array of notifier objects, each responding to +#call(report)+. Called after
|
|
57
|
+
# each report is stored. Built-in options: {Notifiers::Logger}, {Notifiers::Stdout},
|
|
58
|
+
# {Notifiers::Console}, {Notifiers::FileLogger}.
|
|
59
|
+
# @return [Array<#call>]
|
|
60
|
+
|
|
61
|
+
# @!attribute log_file
|
|
62
|
+
# Path to a file where reports are appended as JSON lines. Shortcut for
|
|
63
|
+
# configuring {Notifiers::FileLogger} manually.
|
|
64
|
+
# @return [String, nil]
|
|
65
|
+
|
|
3
66
|
attr_accessor :enabled, :sample_rate, :store_size, :dashboard_enabled,
|
|
4
67
|
:min_allocated_objects, :ignore_paths, :ignore_controllers,
|
|
5
68
|
:detailed_reports, :detailed_sample_rate,
|
|
@@ -6,18 +6,35 @@ module RailsMemoryProfiler
|
|
|
6
6
|
isolate_namespace RailsMemoryProfiler
|
|
7
7
|
config.generators.api_only = true
|
|
8
8
|
|
|
9
|
+
MOUNT_PATH_MUTEX = Mutex.new
|
|
10
|
+
private_constant :MOUNT_PATH_MUTEX
|
|
11
|
+
|
|
9
12
|
class << self
|
|
13
|
+
# Returns the path at which the engine is mounted in the host application,
|
|
14
|
+
# e.g. +"/rails/memory"+. Detected lazily on first call by scanning
|
|
15
|
+
# +Rails.application.routes+, then cached. Returns +nil+ if the engine is
|
|
16
|
+
# not mounted.
|
|
17
|
+
#
|
|
18
|
+
# @return [String, nil]
|
|
10
19
|
def mount_path
|
|
11
|
-
@mount_path
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
return @mount_path if @mount_path
|
|
21
|
+
|
|
22
|
+
MOUNT_PATH_MUTEX.synchronize do
|
|
23
|
+
@mount_path ||= begin
|
|
24
|
+
mounted = Rails.application.routes.routes.find do |route|
|
|
25
|
+
route.app.app == self rescue false
|
|
26
|
+
end
|
|
27
|
+
mounted&.path&.spec&.to_s&.gsub(/\([^)]*\)/, "")
|
|
14
28
|
end
|
|
15
|
-
mounted&.path&.spec&.to_s&.gsub(/\([^)]*\)/, "")
|
|
16
29
|
end
|
|
17
30
|
end
|
|
18
31
|
|
|
32
|
+
# Clears the cached mount path so it will be re-detected on the next call.
|
|
33
|
+
# Primarily used in tests.
|
|
34
|
+
#
|
|
35
|
+
# @return [void]
|
|
19
36
|
def reset_mount_path!
|
|
20
|
-
@mount_path = nil
|
|
37
|
+
MOUNT_PATH_MUTEX.synchronize { @mount_path = nil }
|
|
21
38
|
end
|
|
22
39
|
end
|
|
23
40
|
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
module RailsMemoryProfiler
|
|
2
|
+
# Rack middleware that profiles each request and pushes a report into {ReportStore}.
|
|
3
|
+
# Registered automatically by the {Engine} initializer; you do not need to add it
|
|
4
|
+
# to the middleware stack manually.
|
|
5
|
+
#
|
|
6
|
+
# Profiling is skipped when:
|
|
7
|
+
# - {Configuration#enabled} is +false+
|
|
8
|
+
# - the request path matches {Configuration#ignore_paths} or the engine mount path
|
|
9
|
+
# - the request is not selected by {Configuration#sample_rate}
|
|
2
10
|
class Middleware
|
|
11
|
+
# @param app [#call] the next Rack application in the stack
|
|
3
12
|
def initialize(app)
|
|
4
13
|
@app = app
|
|
5
14
|
@request_count = 0
|
|
@@ -7,6 +16,10 @@ module RailsMemoryProfiler
|
|
|
7
16
|
@mutex = Mutex.new
|
|
8
17
|
end
|
|
9
18
|
|
|
19
|
+
# Profiles the request if applicable and delegates to the inner app.
|
|
20
|
+
#
|
|
21
|
+
# @param env [Hash] Rack environment
|
|
22
|
+
# @return [Array] Rack response triplet
|
|
10
23
|
def call(env)
|
|
11
24
|
return @app.call(env) unless RailsMemoryProfiler.config.enabled
|
|
12
25
|
return @app.call(env) if ignored_path?(env["PATH_INFO"])
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
module RailsMemoryProfiler
|
|
2
|
+
# Thread-safe circular buffer that holds per-request profiling reports.
|
|
3
|
+
# Capacity is controlled by {Configuration#store_size}. When full, the oldest
|
|
4
|
+
# report is evicted to make room for each new one.
|
|
5
|
+
#
|
|
6
|
+
# Each report is a Hash with the keys:
|
|
7
|
+
# - +:id+ — unique hex string assigned on push
|
|
8
|
+
# - +:path+, +:method+, +:controller+, +:action+
|
|
9
|
+
# - +:allocated_objects+, +:retained_objects+
|
|
10
|
+
# - +:duration_ms+
|
|
11
|
+
# - +:recorded_at+ — +Time+ of capture
|
|
12
|
+
# - +:detail+ — optional breakdown Hash (present when {Configuration#detailed_reports} is enabled)
|
|
2
13
|
module ReportStore
|
|
14
|
+
MUTEX = Mutex.new
|
|
15
|
+
private_constant :MUTEX
|
|
16
|
+
|
|
3
17
|
class << self
|
|
18
|
+
# Adds a report to the buffer, assigning a unique +:id+ key.
|
|
19
|
+
# Evicts the oldest entry when the buffer is at capacity.
|
|
20
|
+
#
|
|
21
|
+
# @param report [Hash] profiling data without an +:id+ key
|
|
22
|
+
# @return [void]
|
|
4
23
|
def push(report)
|
|
5
24
|
mutex.synchronize do
|
|
6
25
|
ensure_buffer_size
|
|
@@ -10,10 +29,17 @@ module RailsMemoryProfiler
|
|
|
10
29
|
end
|
|
11
30
|
end
|
|
12
31
|
|
|
32
|
+
# Returns the report with the given id, or +nil+ if not found or evicted.
|
|
33
|
+
#
|
|
34
|
+
# @param id [String]
|
|
35
|
+
# @return [Hash, nil]
|
|
13
36
|
def find(id)
|
|
14
37
|
all.find { |r| r[:id] == id }
|
|
15
38
|
end
|
|
16
39
|
|
|
40
|
+
# Returns all stored reports in insertion order (oldest first).
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<Hash>]
|
|
17
43
|
def all
|
|
18
44
|
mutex.synchronize do
|
|
19
45
|
stored = @stored || 0
|
|
@@ -27,10 +53,16 @@ module RailsMemoryProfiler
|
|
|
27
53
|
end
|
|
28
54
|
end
|
|
29
55
|
|
|
56
|
+
# Removes all stored reports.
|
|
57
|
+
#
|
|
58
|
+
# @return [void]
|
|
30
59
|
def clear
|
|
31
60
|
mutex.synchronize { reset! }
|
|
32
61
|
end
|
|
33
62
|
|
|
63
|
+
# Returns the number of reports currently stored.
|
|
64
|
+
#
|
|
65
|
+
# @return [Integer]
|
|
34
66
|
def size
|
|
35
67
|
mutex.synchronize { @stored || 0 }
|
|
36
68
|
end
|
|
@@ -38,7 +70,7 @@ module RailsMemoryProfiler
|
|
|
38
70
|
private
|
|
39
71
|
|
|
40
72
|
def mutex
|
|
41
|
-
|
|
73
|
+
MUTEX
|
|
42
74
|
end
|
|
43
75
|
|
|
44
76
|
def capacity
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
require "rails_memory_profiler"
|
|
2
2
|
|
|
3
3
|
module RailsMemoryProfiler
|
|
4
|
+
# Utility methods for asserting allocation counts in tests.
|
|
5
|
+
# Not auto-required — opt in with:
|
|
6
|
+
#
|
|
7
|
+
# require "rails_memory_profiler/test_helper"
|
|
8
|
+
#
|
|
9
|
+
# Works as a module-level call or mixed into a test class via +include+/+extend+:
|
|
10
|
+
#
|
|
11
|
+
# include RailsMemoryProfiler::TestHelper
|
|
12
|
+
#
|
|
13
|
+
# For RSpec block expectations see {file:lib/rails_memory_profiler/rspec_matchers.rb}.
|
|
14
|
+
# For Minitest assertions see {file:lib/rails_memory_profiler/minitest_matchers.rb}.
|
|
4
15
|
module TestHelper
|
|
5
16
|
extend self
|
|
6
17
|
|
|
18
|
+
# Runs the block and returns the number of Ruby objects allocated during it.
|
|
19
|
+
# Triggers a full GC before measuring to reduce noise from prior allocations.
|
|
20
|
+
#
|
|
21
|
+
# @yieldreturn [Object] the block's return value is discarded
|
|
22
|
+
# @return [Integer] number of objects allocated
|
|
7
23
|
def capture_allocations
|
|
8
24
|
GC.start
|
|
9
25
|
before = GC.stat(:total_allocated_objects)
|
|
@@ -11,6 +27,15 @@ module RailsMemoryProfiler
|
|
|
11
27
|
GC.stat(:total_allocated_objects) - before
|
|
12
28
|
end
|
|
13
29
|
|
|
30
|
+
# Asserts that the block allocates fewer than +threshold+ objects.
|
|
31
|
+
# Raises +Minitest::Assertion+ when Minitest is loaded (so the failure is
|
|
32
|
+
# reported as a test failure rather than an error), otherwise raises +RuntimeError+.
|
|
33
|
+
#
|
|
34
|
+
# @param threshold [Integer] maximum acceptable allocation count
|
|
35
|
+
# @yieldreturn [Object] the block's return value is discarded
|
|
36
|
+
# @raise [Minitest::Assertion] when Minitest is present and the threshold is exceeded
|
|
37
|
+
# @raise [RuntimeError] when Minitest is absent and the threshold is exceeded
|
|
38
|
+
# @return [void]
|
|
14
39
|
def assert_allocations_below(threshold, &block)
|
|
15
40
|
count = capture_allocations(&block)
|
|
16
41
|
return if count < threshold
|
|
@@ -5,20 +5,54 @@ require "rails_memory_profiler/notifiers"
|
|
|
5
5
|
require "rails_memory_profiler/middleware"
|
|
6
6
|
require "rails_memory_profiler/engine"
|
|
7
7
|
|
|
8
|
+
# RailsMemoryProfiler is a Rails engine that captures per-request object
|
|
9
|
+
# allocations via GC.stat diffs and serves them through a mountable dashboard.
|
|
10
|
+
#
|
|
11
|
+
# @example Mounting the engine
|
|
12
|
+
# # config/routes.rb
|
|
13
|
+
# mount RailsMemoryProfiler::Engine, at: "/rails/memory"
|
|
14
|
+
#
|
|
15
|
+
# @example Configuring the gem
|
|
16
|
+
# RailsMemoryProfiler.configure do |config|
|
|
17
|
+
# config.sample_rate = 5
|
|
18
|
+
# config.min_allocated_objects = 1_000
|
|
19
|
+
# end
|
|
8
20
|
module RailsMemoryProfiler
|
|
21
|
+
# Raised by the middleware when allocated objects exceed
|
|
22
|
+
# {Configuration#raise_on_allocation_spike}.
|
|
9
23
|
class AllocationSpikeError < StandardError; end
|
|
10
24
|
|
|
11
25
|
class << self
|
|
26
|
+
# Yields the {Configuration} instance for block-style setup.
|
|
27
|
+
#
|
|
28
|
+
# @yieldparam config [Configuration]
|
|
29
|
+
# @return [void]
|
|
12
30
|
def configure
|
|
13
31
|
yield config
|
|
14
32
|
end
|
|
15
33
|
|
|
34
|
+
# Returns the shared {Configuration} instance, creating it on first call.
|
|
35
|
+
#
|
|
36
|
+
# @return [Configuration]
|
|
16
37
|
def config
|
|
17
38
|
@config ||= Configuration.new
|
|
18
39
|
end
|
|
19
40
|
|
|
41
|
+
# Replaces the shared {Configuration} instance with a fresh default.
|
|
42
|
+
# Primarily used in tests.
|
|
43
|
+
#
|
|
44
|
+
# @return [Configuration]
|
|
20
45
|
def reset_config!
|
|
21
46
|
@config = Configuration.new
|
|
22
47
|
end
|
|
48
|
+
|
|
49
|
+
# Returns a memoized +ActiveSupport::Deprecation+ instance scoped to this
|
|
50
|
+
# gem. Use it to emit deprecation warnings for any future breaking changes
|
|
51
|
+
# rather than calling +warn+ directly.
|
|
52
|
+
#
|
|
53
|
+
# @return [ActiveSupport::Deprecation]
|
|
54
|
+
def deprecator
|
|
55
|
+
@deprecator ||= ActiveSupport::Deprecation.new("1.0.0", "RailsMemoryProfiler")
|
|
56
|
+
end
|
|
23
57
|
end
|
|
24
58
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_memory_profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -74,6 +74,7 @@ files:
|
|
|
74
74
|
- app/controllers/rails_memory_profiler/base_controller.rb
|
|
75
75
|
- app/controllers/rails_memory_profiler/comparisons_controller.rb
|
|
76
76
|
- app/controllers/rails_memory_profiler/reports_controller.rb
|
|
77
|
+
- app/controllers/rails_memory_profiler/store_controller.rb
|
|
77
78
|
- app/helpers/rails_memory_profiler/application_helper.rb
|
|
78
79
|
- app/javascript/rails_memory_profiler/application.js
|
|
79
80
|
- app/javascript/rails_memory_profiler/controllers/application.js
|