rails_memory_profiler 0.2.0 → 0.3.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 +20 -0
- data/lib/generators/rails_memory_profiler/install/templates/initializer.rb +14 -0
- data/lib/rails_memory_profiler/configuration.rb +8 -3
- data/lib/rails_memory_profiler/middleware.rb +17 -0
- data/lib/rails_memory_profiler/notifiers/console.rb +36 -0
- data/lib/rails_memory_profiler/notifiers/file_logger.rb +16 -0
- data/lib/rails_memory_profiler/notifiers/logger.rb +9 -0
- data/lib/rails_memory_profiler/notifiers/stdout.rb +9 -0
- data/lib/rails_memory_profiler/notifiers.rb +19 -0
- data/lib/rails_memory_profiler/rspec_matchers.rb +20 -0
- data/lib/rails_memory_profiler/test_helper.rb +21 -0
- data/lib/rails_memory_profiler/version.rb +1 -1
- data/lib/rails_memory_profiler.rb +3 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da1c19f27cafbf571c7f5fe0e724c56e305d81307c99c1d970fdc96777459888
|
|
4
|
+
data.tar.gz: d95173a0c57ec1afefc7a13eed43e06b5fef53a8350a3b5b2955f42ad01c382f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c34cfa6505349cae21f6e808c55bd8c682456809ac3052ed2d67ed72099ec72f4cfd890653c36cf580d15f92c9988b1a94ce95dfa679c1cda1fb4f2fefa51747
|
|
7
|
+
data.tar.gz: e5e1c2f7194e48d82b0073aed382662f95ffb552e119993a25dd62a58285f5a89e31e600664928a7bcd515696a5abebc2f02d32d9a49df5a5124d7c38c5165b6
|
data/README.md
CHANGED
|
@@ -78,6 +78,9 @@ All options and their defaults:
|
|
|
78
78
|
| `ignore_controllers` | `[]` | Controller names to skip (e.g. `"rails/health"`) |
|
|
79
79
|
| `detailed_reports` | `false` | Capture full `MemoryProfiler.report` breakdowns; requires `gem "memory_profiler"` in your Gemfile |
|
|
80
80
|
| `detailed_sample_rate` | `10` | When `detailed_reports` is enabled, capture a full report every Nth profiled request |
|
|
81
|
+
| `notifiers` | `[]` | Array of objects responding to `#call(report)`; called after each report is stored |
|
|
82
|
+
| `log_file` | `nil` | Path to append JSON-serialized reports (one per line); shortcut for `Notifiers::FileLogger` |
|
|
83
|
+
| `raise_on_allocation_spike` | `nil` | Raise `AllocationSpikeError` when `allocated_objects` exceeds this threshold |
|
|
81
84
|
|
|
82
85
|
Example:
|
|
83
86
|
|
|
@@ -87,9 +90,26 @@ RailsMemoryProfiler.configure do |config|
|
|
|
87
90
|
config.min_allocated_objects = 1_000
|
|
88
91
|
config.ignore_paths = ["/rails/memory", "/up"]
|
|
89
92
|
config.ignore_controllers = ["rails/health"]
|
|
93
|
+
config.notifiers = [RailsMemoryProfiler::Notifiers::Console.new]
|
|
94
|
+
config.log_file = Rails.root.join("log/memory_profiler.jsonl")
|
|
90
95
|
end
|
|
91
96
|
```
|
|
92
97
|
|
|
98
|
+
### Test helpers
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
# Minitest / plain Ruby
|
|
102
|
+
require "rails_memory_profiler/test_helper"
|
|
103
|
+
|
|
104
|
+
count = RailsMemoryProfiler::TestHelper.capture_allocations { MyClass.new }
|
|
105
|
+
RailsMemoryProfiler::TestHelper.assert_allocations_below(500) { MyClass.new }
|
|
106
|
+
|
|
107
|
+
# RSpec
|
|
108
|
+
require "rails_memory_profiler/rspec_matchers"
|
|
109
|
+
|
|
110
|
+
expect { MyClass.new }.to allocate_fewer_than(500)
|
|
111
|
+
```
|
|
112
|
+
|
|
93
113
|
[↑ Back to top](#railsmemoryprofiler)
|
|
94
114
|
|
|
95
115
|
---
|
|
@@ -32,4 +32,18 @@ RailsMemoryProfiler.configure do |config|
|
|
|
32
32
|
|
|
33
33
|
# When detailed_reports is enabled, capture a full report every Nth profiled request.
|
|
34
34
|
# config.detailed_sample_rate = 10
|
|
35
|
+
|
|
36
|
+
# Notifiers — called after each report is stored. Each must respond to #call(report).
|
|
37
|
+
# Built-in options:
|
|
38
|
+
# RailsMemoryProfiler::Notifiers::Logger.new — writes to Rails.logger
|
|
39
|
+
# RailsMemoryProfiler::Notifiers::Stdout.new — writes to $stdout
|
|
40
|
+
# RailsMemoryProfiler::Notifiers::Console.new — colorized terminal output
|
|
41
|
+
# config.notifiers = [RailsMemoryProfiler::Notifiers::Logger.new]
|
|
42
|
+
|
|
43
|
+
# Append JSON-serialized reports to a file (one report per line).
|
|
44
|
+
# config.log_file = Rails.root.join("log/memory_profiler.log").to_s
|
|
45
|
+
|
|
46
|
+
# Raise AllocationSpikeError when a request exceeds this many allocated objects.
|
|
47
|
+
# Useful in test environments to catch unexpected memory spikes.
|
|
48
|
+
# config.raise_on_allocation_spike = nil
|
|
35
49
|
end
|
|
@@ -2,7 +2,9 @@ module RailsMemoryProfiler
|
|
|
2
2
|
class Configuration
|
|
3
3
|
attr_accessor :enabled, :sample_rate, :store_size, :dashboard_enabled,
|
|
4
4
|
:min_allocated_objects, :ignore_paths, :ignore_controllers,
|
|
5
|
-
:detailed_reports, :detailed_sample_rate
|
|
5
|
+
:detailed_reports, :detailed_sample_rate,
|
|
6
|
+
:raise_on_allocation_spike,
|
|
7
|
+
:notifiers, :log_file
|
|
6
8
|
|
|
7
9
|
def initialize
|
|
8
10
|
@enabled = Rails.env.development?
|
|
@@ -12,8 +14,11 @@ module RailsMemoryProfiler
|
|
|
12
14
|
@min_allocated_objects = 0
|
|
13
15
|
@ignore_paths = []
|
|
14
16
|
@ignore_controllers = []
|
|
15
|
-
@detailed_reports
|
|
16
|
-
@detailed_sample_rate
|
|
17
|
+
@detailed_reports = false
|
|
18
|
+
@detailed_sample_rate = 10
|
|
19
|
+
@raise_on_allocation_spike = nil
|
|
20
|
+
@notifiers = []
|
|
21
|
+
@log_file = nil
|
|
17
22
|
end
|
|
18
23
|
end
|
|
19
24
|
end
|
|
@@ -93,6 +93,23 @@ module RailsMemoryProfiler
|
|
|
93
93
|
payload[:detail] = detail if detail
|
|
94
94
|
|
|
95
95
|
ReportStore.push(payload)
|
|
96
|
+
notify!(payload)
|
|
97
|
+
check_spike!(allocated_objects, env)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def notify!(report)
|
|
101
|
+
RailsMemoryProfiler.config.notifiers.each { |n| n.call(report) }
|
|
102
|
+
if (path = RailsMemoryProfiler.config.log_file)
|
|
103
|
+
Notifiers::FileLogger.new(path).call(report)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def check_spike!(allocated_objects, env)
|
|
108
|
+
threshold = RailsMemoryProfiler.config.raise_on_allocation_spike
|
|
109
|
+
return unless threshold && allocated_objects > threshold
|
|
110
|
+
|
|
111
|
+
raise AllocationSpikeError,
|
|
112
|
+
"#{env['PATH_INFO']} allocated #{allocated_objects} objects (threshold: #{threshold})"
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
def serialize_stats(stats)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module RailsMemoryProfiler
|
|
2
|
+
module Notifiers
|
|
3
|
+
class Console
|
|
4
|
+
RESET = "\e[0m"
|
|
5
|
+
CYAN = "\e[36m"
|
|
6
|
+
DIM = "\e[2m"
|
|
7
|
+
GREEN = "\e[32m"
|
|
8
|
+
YELLOW = "\e[33m"
|
|
9
|
+
RED = "\e[31m"
|
|
10
|
+
|
|
11
|
+
def call(report)
|
|
12
|
+
$stdout.puts(colorize(report))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def colorize(report)
|
|
18
|
+
action = [report[:controller], report[:action]].compact.join("#")
|
|
19
|
+
alloc = report[:allocated_objects]
|
|
20
|
+
|
|
21
|
+
"#{DIM}[RMP]#{RESET} " \
|
|
22
|
+
"#{CYAN}#{report[:method]} #{report[:path]}#{RESET} " \
|
|
23
|
+
"#{DIM}(#{action})#{RESET} " \
|
|
24
|
+
"#{alloc_color(alloc)}#{Notifiers.thousands(alloc)} alloc#{RESET} " \
|
|
25
|
+
"#{DIM}#{report[:retained_objects]} retained #{report[:duration_ms]}ms#{RESET}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def alloc_color(count)
|
|
29
|
+
if count < 5_000 then GREEN
|
|
30
|
+
elsif count < 20_000 then YELLOW
|
|
31
|
+
else RED
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module RailsMemoryProfiler
|
|
4
|
+
module Notifiers
|
|
5
|
+
class FileLogger
|
|
6
|
+
def initialize(path)
|
|
7
|
+
@path = path
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(report)
|
|
11
|
+
serializable = report.merge(recorded_at: report[:recorded_at]&.iso8601)
|
|
12
|
+
File.open(@path, "a") { |f| f.puts(serializable.to_json) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "rails_memory_profiler/notifiers/logger"
|
|
2
|
+
require "rails_memory_profiler/notifiers/stdout"
|
|
3
|
+
require "rails_memory_profiler/notifiers/console"
|
|
4
|
+
require "rails_memory_profiler/notifiers/file_logger"
|
|
5
|
+
|
|
6
|
+
module RailsMemoryProfiler
|
|
7
|
+
module Notifiers
|
|
8
|
+
def self.format_line(report)
|
|
9
|
+
action = [report[:controller], report[:action]].compact.join("#")
|
|
10
|
+
"[RailsMemoryProfiler] #{report[:method]} #{report[:path]} (#{action})" \
|
|
11
|
+
" — #{thousands(report[:allocated_objects])} allocated," \
|
|
12
|
+
" #{report[:retained_objects]} retained, #{report[:duration_ms]}ms"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.thousands(n)
|
|
16
|
+
n.to_s.reverse.scan(/\d{1,3}/).join(",").reverse
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "rails_memory_profiler/test_helper"
|
|
2
|
+
|
|
3
|
+
if defined?(RSpec)
|
|
4
|
+
RSpec::Matchers.define :allocate_fewer_than do |expected|
|
|
5
|
+
supports_block_expectations
|
|
6
|
+
|
|
7
|
+
match do |block|
|
|
8
|
+
@count = RailsMemoryProfiler::TestHelper.capture_allocations(&block)
|
|
9
|
+
@count < expected
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
failure_message do
|
|
13
|
+
"expected block to allocate fewer than #{expected} objects, but got #{@count}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
failure_message_when_negated do
|
|
17
|
+
"expected block to allocate #{expected} or more objects, but got #{@count}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "rails_memory_profiler"
|
|
2
|
+
|
|
3
|
+
module RailsMemoryProfiler
|
|
4
|
+
module TestHelper
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
def capture_allocations
|
|
8
|
+
GC.start
|
|
9
|
+
before = GC.stat(:total_allocated_objects)
|
|
10
|
+
yield
|
|
11
|
+
GC.stat(:total_allocated_objects) - before
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def assert_allocations_below(threshold, &block)
|
|
15
|
+
count = capture_allocations(&block)
|
|
16
|
+
return if count < threshold
|
|
17
|
+
|
|
18
|
+
raise "Expected fewer than #{threshold} allocated objects but got #{count}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -2,10 +2,13 @@ require "rails_memory_profiler/version"
|
|
|
2
2
|
require "rails_memory_profiler/configuration"
|
|
3
3
|
require "rails_memory_profiler/request_context"
|
|
4
4
|
require "rails_memory_profiler/report_store"
|
|
5
|
+
require "rails_memory_profiler/notifiers"
|
|
5
6
|
require "rails_memory_profiler/middleware"
|
|
6
7
|
require "rails_memory_profiler/engine"
|
|
7
8
|
|
|
8
9
|
module RailsMemoryProfiler
|
|
10
|
+
class AllocationSpikeError < StandardError; end
|
|
11
|
+
|
|
9
12
|
class << self
|
|
10
13
|
def configure
|
|
11
14
|
yield config
|
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: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -96,8 +96,15 @@ files:
|
|
|
96
96
|
- lib/rails_memory_profiler/configuration.rb
|
|
97
97
|
- lib/rails_memory_profiler/engine.rb
|
|
98
98
|
- lib/rails_memory_profiler/middleware.rb
|
|
99
|
+
- lib/rails_memory_profiler/notifiers.rb
|
|
100
|
+
- lib/rails_memory_profiler/notifiers/console.rb
|
|
101
|
+
- lib/rails_memory_profiler/notifiers/file_logger.rb
|
|
102
|
+
- lib/rails_memory_profiler/notifiers/logger.rb
|
|
103
|
+
- lib/rails_memory_profiler/notifiers/stdout.rb
|
|
99
104
|
- lib/rails_memory_profiler/report_store.rb
|
|
100
105
|
- lib/rails_memory_profiler/request_context.rb
|
|
106
|
+
- lib/rails_memory_profiler/rspec_matchers.rb
|
|
107
|
+
- lib/rails_memory_profiler/test_helper.rb
|
|
101
108
|
- lib/rails_memory_profiler/version.rb
|
|
102
109
|
- lib/tasks/rails_memory_profiler_tasks.rake
|
|
103
110
|
homepage: https://github.com/eclectic-coding/rails_memory_profiler
|