memory-profiler 1.1.14 → 1.2.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: c26d5bc8b4215de3776e42ec9cd8daaf54a3cba9d55449f6b570ba7ee5ef9c91
4
- data.tar.gz: 6668320402b35e3633eff016bd79b34c6b69952cb03120fc7f04886782bacfe3
3
+ metadata.gz: 44131407c83cd30a000572173db2bd27a1b2cc226090fe5b2ab5b9452671676a
4
+ data.tar.gz: 76c488be0ca9902ac543a21941b3ebd4d5fd5ea807e958289452044021ba8f0c
5
5
  SHA512:
6
- metadata.gz: 68404ae44a442584084727cc854ed81abc36964c5621b93361cde70c6ae80f71ad21c7a45caf049c5906846cec8dff23ccbddb2e021eedf137e8ba7d57c78363
7
- data.tar.gz: 406d8caac175e30907d752cec211d066a8c7ed5bf260c4e4eba6304f73b60d7c47be6d68d8b6cc4e738cb8fe19ebadeea0f251c7b2df888923b86abdaae2867e
6
+ metadata.gz: 98eba2b7e667ad5d9e9ea01a528de98a4525074580e66178b91f246932183557d0a826a8693e773147977e694f7ef9a15af47d0d8f954c4352bb77ada9d5e558
7
+ data.tar.gz: 71424607c4a188ef16da5436c4bd7b0ebc6640b3bbc1b182f0a9cc64c15b940bb4cf81a3f1cc47f00cfb5b7dc596565a3f22df88cd6c9ccb80b7866122c6bb53
checksums.yaml.gz.sig CHANGED
Binary file
@@ -298,21 +298,6 @@ static void Memory_Profiler_Capture_event_callback(VALUE data, void *ptr) {
298
298
  VALUE klass = rb_class_of(object);
299
299
  if (!klass) return;
300
300
 
301
- // Check if class is already freed (T_NONE). This can happen when:
302
- // 1. Object was allocated before capture started (no NEWOBJ event queued).
303
- // 2. Anonymous class (Class.new) was freed before its instances.
304
- //
305
- // If the class is T_NONE, it means:
306
- // - It's NOT in tracked_classes (would retain it via GC mark callback).
307
- // - It's NOT in any pending NEWOBJ event (would retain it via event queue marking).
308
- //
309
- // Therefore process_freeobj() would skip this event anyway (class lookup fails at line 200).
310
- // We must skip enqueueing to avoid attempting to mark a T_NONE object during GC,
311
- // which can cause: [BUG] try to mark T_NONE object.
312
- if (rb_type(klass) == T_NONE) {
313
- return;
314
- }
315
-
316
301
  if (DEBUG) {
317
302
  const char *klass_name = "(ignored)";
318
303
  if (event_flag == RUBY_INTERNAL_EVENT_NEWOBJ) {
@@ -325,6 +310,7 @@ static void Memory_Profiler_Capture_event_callback(VALUE data, void *ptr) {
325
310
  // Skip NEWOBJ if disabled (during callback) to prevent infinite recursion
326
311
  if (capture->paused) return;
327
312
 
313
+ // Check if class is already freed (T_NONE). This can happen when:
328
314
  // It's safe to unconditionally call here:
329
315
  object = rb_obj_id(object);
330
316
 
@@ -332,6 +318,10 @@ static void Memory_Profiler_Capture_event_callback(VALUE data, void *ptr) {
332
318
  } else if (event_flag == RUBY_INTERNAL_EVENT_FREEOBJ) {
333
319
  // We only care about objects that have been seen before (i.e. have an object ID):
334
320
  if (RB_FL_TEST(object, FL_SEEN_OBJ_ID)) {
321
+ // For freeobj, we only care about klasses that we are tracking.
322
+ // This prevents us from enqueuing klass objects that might be freed.
323
+ if (!st_lookup(capture->tracked_classes, (st_data_t)klass, NULL)) return;
324
+
335
325
  // It's only safe to call here if the object already has an object ID.
336
326
  object = rb_obj_id(object);
337
327
 
@@ -93,12 +93,15 @@ module Memory
93
93
  # @parameter increases_threshold [Integer] Number of increases before enabling detailed tracking.
94
94
  # @parameter prune_limit [Integer] Keep only top N children per node during pruning (default: 5).
95
95
  # @parameter prune_threshold [Integer] Number of insertions before auto-pruning (nil = no auto-pruning).
96
- def initialize(depth: 4, filter: nil, increases_threshold: 10, prune_limit: 5, prune_threshold: nil)
96
+ # @parameter gc [Hash | Nil] Run GC with these options before each sample (nil = don't run GC).
97
+ def initialize(depth: 4, filter: nil, increases_threshold: 10, prune_limit: 5, prune_threshold: nil, gc: nil)
97
98
  @depth = depth
98
99
  @filter = filter || default_filter
99
100
  @increases_threshold = increases_threshold
100
101
  @prune_limit = prune_limit
101
102
  @prune_threshold = prune_threshold
103
+ @gc = gc
104
+
102
105
  @capture = Capture.new
103
106
  @call_trees = {}
104
107
  @samples = {}
@@ -150,6 +153,9 @@ module Memory
150
153
  while true
151
154
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
152
155
 
156
+ # Optional garbage collection before sampling can help reduce noise:
157
+ GC.start(**@gc) if @gc
158
+
153
159
  sample!(&block)
154
160
 
155
161
  # Log capture statistics to detect issues like missing FREEOBJ events:
@@ -169,15 +175,22 @@ module Memory
169
175
  @capture.each do |klass, allocations|
170
176
  count = allocations.retained_count
171
177
  sample = @samples[klass] ||= Sample.new(klass, count)
178
+ increased = false
172
179
 
173
180
  if sample.sample!(count)
181
+ increased = true
182
+
174
183
  # Check if we should enable detailed tracking
175
- if sample.increases >= @increases_threshold && !@call_trees.key?(klass)
176
- track(klass, allocations)
184
+ if sample.increases >= @increases_threshold
185
+ # Start tracking with call path analysis if not already doing so:
186
+ unless tracking?(klass)
187
+ track(klass, allocations)
188
+ end
177
189
  end
178
-
179
- # Notify about growth if block given
180
- yield sample if block_given?
190
+ end
191
+
192
+ if block_given?
193
+ yield sample, increased
181
194
  end
182
195
  end
183
196
 
@@ -188,14 +201,12 @@ module Memory
188
201
  # Start tracking with call path analysis.
189
202
  #
190
203
  # @parameter klass [Class] The class to track with detailed analysis.
191
- def track(klass, allocations = nil)
204
+ def track(klass, allocations = nil, filter: @filter, depth: @depth)
192
205
  # Track the class and get the allocations object
193
206
  allocations ||= @capture.track(klass)
194
207
 
195
208
  # Set up call tree for this class
196
209
  tree = @call_trees[klass] = CallTree.new
197
- depth = @depth
198
- filter = @filter
199
210
 
200
211
  # Register callback on allocations object:
201
212
  # - On :newobj - returns state (leaf node) which C extension stores
@@ -293,8 +304,9 @@ module Memory
293
304
 
294
305
  private
295
306
 
307
+ # Default filter to include all locations.
296
308
  def default_filter
297
- ->(location) {!location.path.match?(%r{/(gems|ruby)/|\A\(eval\)})}
309
+ ->(location) {true}
298
310
  end
299
311
 
300
312
  def prune_call_trees!
@@ -7,7 +7,7 @@
7
7
  module Memory
8
8
  # @namespace
9
9
  module Profiler
10
- VERSION = "1.1.14"
10
+ VERSION = "1.2.0"
11
11
  end
12
12
  end
13
13
 
data/readme.md CHANGED
@@ -22,6 +22,17 @@ Please see the [project documentation](https://socketry.github.io/memory-profile
22
22
 
23
23
  Please see the [project releases](https://socketry.github.io/memory-profiler/releases/index) for all releases.
24
24
 
25
+ ### v1.2.0
26
+
27
+ - Enable custom `depth:` and `filter:` options to `Sampler#track`.
28
+ - Change default filter to no-op.
29
+ - Add option to run GC with custom options before each sample to reduce noise.
30
+ - Always report sampler statistics after each sample.
31
+
32
+ ### v1.1.15
33
+
34
+ - Ignore `freeobj` for classes that are not being tracked.
35
+
25
36
  ### v1.1.14
26
37
 
27
38
  - Ignore `freeobj` events for objects with anonymous classes that are not tracked (and thus become `T_NONE`).
@@ -60,16 +71,6 @@ Please see the [project releases](https://socketry.github.io/memory-profiler/rel
60
71
 
61
72
  - Expose `Capture#statistics` for debugging internal memory tracking state.
62
73
 
63
- ### v1.1.6
64
-
65
- - Write barriers all the things.
66
- - Better state handling and object increment/decrement counting.
67
- - Better call tree handling - including support for `prune!`.
68
-
69
- ### v1.1.5
70
-
71
- - Use queue for `newobj` too to avoid invoking user code during object allocation.
72
-
73
74
  ## Contributing
74
75
 
75
76
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Releases
2
2
 
3
+ ## v1.2.0
4
+
5
+ - Enable custom `depth:` and `filter:` options to `Sampler#track`.
6
+ - Change default filter to no-op.
7
+ - Add option to run GC with custom options before each sample to reduce noise.
8
+ - Always report sampler statistics after each sample.
9
+
10
+ ## v1.1.15
11
+
12
+ - Ignore `freeobj` for classes that are not being tracked.
13
+
3
14
  ## v1.1.14
4
15
 
5
16
  - Ignore `freeobj` events for objects with anonymous classes that are not tracked (and thus become `T_NONE`).
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memory-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.14
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file