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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6f3720c5cac0f1ec3299a0e54a621acb6c2f5881b8b76ca9b737df70cc29cad
4
- data.tar.gz: 7180cecd1e284b10521c1736c5f0aff6f4bc4811e3ac812a06d7d8e97d7bf4d0
3
+ metadata.gz: da1c19f27cafbf571c7f5fe0e724c56e305d81307c99c1d970fdc96777459888
4
+ data.tar.gz: d95173a0c57ec1afefc7a13eed43e06b5fef53a8350a3b5b2955f42ad01c382f
5
5
  SHA512:
6
- metadata.gz: e827b8dcbb33b723b2f557e93ef6b16dcbd062e18049e9ccb98bc0df3268318155f30594b124a6166547951eee3a79c1bff2d02a931c3193de6bd86940d98f6b
7
- data.tar.gz: c3522a8f0c3fe3a0d2c6fcb689f41203aa1267829509aa9ca123b185d9451f3347530724f33e17555736d91f8a5346e55a15834d4156c7f76a50030fd8100a5f
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 = false
16
- @detailed_sample_rate = 10
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,9 @@
1
+ module RailsMemoryProfiler
2
+ module Notifiers
3
+ class Logger
4
+ def call(report)
5
+ Rails.logger.info(Notifiers.format_line(report))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module RailsMemoryProfiler
2
+ module Notifiers
3
+ class Stdout
4
+ def call(report)
5
+ $stdout.puts(Notifiers.format_line(report))
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -1,3 +1,3 @@
1
1
  module RailsMemoryProfiler
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  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.2.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