memory-profiler 1.1.3 → 1.1.4

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: fdf7841a9d0249712c9bd140447e0063b5b543cae942bc1dedc09682472f3aaf
4
- data.tar.gz: 14f9521f93447843b93aff1dfe15b12fce5f9491bb6a6055caf1d90cce304f17
3
+ metadata.gz: c70385bac57de768e1f6eb3ca9044942a357d77c97967b30636acd89b1f051f4
4
+ data.tar.gz: 8c7ae2a117684e61d34e09e9b2ebb4a99ab440b1b31fee364d2a92bc6caf5cac
5
5
  SHA512:
6
- metadata.gz: 43ba6c482b4f9e6e80e5f3213435b158b954087790e19ac4effb77fe4d06d602027c6668f63aa0b68bd592c073ac112f98735da4249d68829913d1328bb21cb4
7
- data.tar.gz: b30e212a1d6a315a4fc94d9dfad066d0f1e2ec4f73dbf7f5bf757bd85e6279ddb088e87168cb6cc158b99fcb218df5ec6f3e7ca60254c8a071478959cbda4817
6
+ metadata.gz: 50d848d974700ab480c805c82decf0fa58806be6f087ab4a199fa6b88963c303e459ca7a3ea2dabfea9d05bc55972edc1912917f49b46dc2752f1ea8f6d23f4b
7
+ data.tar.gz: 248a8fded6707058ccea2f9e1ee8eef074af4f8afeb5abfa9e786ef0e8c05efbd12fc8139eb887a156a84eaa05aba35f4dec1907426153ba935d0339198a5532
checksums.yaml.gz.sig CHANGED
Binary file
@@ -12,8 +12,9 @@ static VALUE Memory_Profiler_Allocations = Qnil;
12
12
 
13
13
  // Helper to mark object_states table values
14
14
  static int Memory_Profiler_Allocations_object_states_mark(st_data_t key, st_data_t value, st_data_t arg) {
15
- VALUE object = (VALUE)key;
16
- rb_gc_mark_movable(object);
15
+ // Don't mark the object, we use it as a key but we never use it as an object, and we don't want to retain it.
16
+ // VALUE object = (VALUE)key;
17
+ // rb_gc_mark_movable(object);
17
18
 
18
19
  VALUE state = (VALUE)value;
19
20
  if (!NIL_P(state)) {
@@ -13,6 +13,8 @@
13
13
 
14
14
  enum {
15
15
  DEBUG = 0,
16
+ DEBUG_FREED_QUEUE = 0,
17
+ DEBUG_STATE = 0,
16
18
  };
17
19
 
18
20
  static VALUE Memory_Profiler_Capture = Qnil;
@@ -197,9 +199,7 @@ static void Memory_Profiler_Capture_process_freed_queue(void *arg) {
197
199
  struct Memory_Profiler_Capture *capture;
198
200
  TypedData_Get_Struct(self, struct Memory_Profiler_Capture, &Memory_Profiler_Capture_type, capture);
199
201
 
200
- if (DEBUG) {
201
- fprintf(stderr, "Processing freed queue with %zu entries\n", capture->freed_queue.count);
202
- }
202
+ if (DEBUG_FREED_QUEUE) fprintf(stderr, "Processing freed queue with %zu entries\n", capture->freed_queue.count);
203
203
 
204
204
  // Process all freed objects in the queue
205
205
  for (size_t i = 0; i < capture->freed_queue.count; i++) {
@@ -244,6 +244,9 @@ static void Memory_Profiler_Capture_newobj_handler(VALUE self, struct Memory_Pro
244
244
  if (!record->object_states) {
245
245
  record->object_states = st_init_numtable();
246
246
  }
247
+
248
+ if (DEBUG_STATE) fprintf(stderr, "Memory_Profiler_Capture_newobj_handler: Inserting state for object: %p (%s)\n", (void *)object, rb_class2name(klass));
249
+
247
250
  st_insert(record->object_states, (st_data_t)object, (st_data_t)state);
248
251
  // Notify GC about the state VALUE stored in the table
249
252
  RB_OBJ_WRITTEN(self, Qnil, state);
@@ -280,22 +283,29 @@ static void Memory_Profiler_Capture_freeobj_handler(VALUE self, struct Memory_Pr
280
283
 
281
284
  // If we have a callback and detailed tracking, queue the freeobj for later processing
282
285
  if (!NIL_P(record->callback) && record->object_states) {
286
+ if (DEBUG_STATE) fprintf(stderr, "Memory_Profiler_Capture_freeobj_handler: Looking up state for object: %p\n", (void *)object);
287
+
283
288
  // Look up state stored during NEWOBJ
284
289
  st_data_t state_data;
285
290
  if (st_delete(record->object_states, (st_data_t *)&object, &state_data)) {
291
+ if (DEBUG_STATE) fprintf(stderr, "Found state for object: %p\n", (void *)object);
286
292
  VALUE state = (VALUE)state_data;
287
293
 
288
294
  // Push a new item onto the queue (returns pointer to write to)
289
295
  // NOTE: realloc is safe during GC (doesn't trigger Ruby allocation)
290
296
  struct Memory_Profiler_Queue_Item *freed = Memory_Profiler_Queue_push(&capture->freed_queue);
291
297
  if (freed) {
298
+ if (DEBUG_FREED_QUEUE) fprintf(stderr, "Queued freed object, queue size now: %zu/%zu\n", capture->freed_queue.count, capture->freed_queue.capacity);
292
299
  // Write directly to the allocated space
293
300
  freed->klass = klass;
294
301
  freed->allocations = allocations;
295
302
  freed->state = state;
296
303
 
297
304
  // Trigger postponed job to process the queue after GC
305
+ if (DEBUG_FREED_QUEUE) fprintf(stderr, "Triggering postponed job to process the queue after GC\n");
298
306
  rb_postponed_job_trigger(capture->postponed_job_handle);
307
+ } else {
308
+ if (DEBUG_FREED_QUEUE) fprintf(stderr, "Failed to queue freed object, out of memory\n");
299
309
  }
300
310
  // If push failed (out of memory), silently drop this freeobj event
301
311
  }
@@ -358,7 +368,12 @@ static void Memory_Profiler_Capture_event_callback(VALUE data, void *ptr) {
358
368
  if (!klass) return;
359
369
 
360
370
  if (DEBUG) {
361
- const char *klass_name = rb_class2name(klass);
371
+ // In events other than NEWOBJ, we are unable to allocate objects (due to GC), so we simply say "ignored":
372
+ const char *klass_name = "ignored";
373
+ if (event_flag == RUBY_INTERNAL_EVENT_NEWOBJ) {
374
+ klass_name = rb_class2name(klass);
375
+ }
376
+
362
377
  fprintf(stderr, "Memory_Profiler_Capture_event_callback: %s, Object: %p, Class: %p (%s)\n", event_flag_name(event_flag), (void *)object, (void *)klass, klass_name);
363
378
  }
364
379
 
@@ -449,6 +464,7 @@ static VALUE Memory_Profiler_Capture_stop(VALUE self) {
449
464
  // Add a class to track with optional callback
450
465
  // Usage: track(klass) or track(klass) { |obj, klass| ... }
451
466
  // Callback can call caller_locations with desired depth
467
+ // Returns the Allocations object for the tracked class
452
468
  static VALUE Memory_Profiler_Capture_track(int argc, VALUE *argv, VALUE self) {
453
469
  struct Memory_Profiler_Capture *capture;
454
470
  TypedData_Get_Struct(self, struct Memory_Profiler_Capture, &Memory_Profiler_Capture_type, capture);
@@ -457,8 +473,10 @@ static VALUE Memory_Profiler_Capture_track(int argc, VALUE *argv, VALUE self) {
457
473
  rb_scan_args(argc, argv, "1&", &klass, &callback);
458
474
 
459
475
  st_data_t allocations_data;
476
+ VALUE allocations;
477
+
460
478
  if (st_lookup(capture->tracked_classes, (st_data_t)klass, &allocations_data)) {
461
- VALUE allocations = (VALUE)allocations_data;
479
+ allocations = (VALUE)allocations_data;
462
480
  struct Memory_Profiler_Capture_Allocations *record = Memory_Profiler_Allocations_get(allocations);
463
481
  RB_OBJ_WRITE(self, &record->callback, callback);
464
482
  } else {
@@ -469,7 +487,7 @@ static VALUE Memory_Profiler_Capture_track(int argc, VALUE *argv, VALUE self) {
469
487
  record->object_states = NULL;
470
488
 
471
489
  // Wrap the record in a VALUE
472
- VALUE allocations = Memory_Profiler_Allocations_wrap(record);
490
+ allocations = Memory_Profiler_Allocations_wrap(record);
473
491
 
474
492
  st_insert(capture->tracked_classes, (st_data_t)klass, (st_data_t)allocations);
475
493
  // Notify GC about the class VALUE stored as key in the table
@@ -482,7 +500,7 @@ static VALUE Memory_Profiler_Capture_track(int argc, VALUE *argv, VALUE self) {
482
500
  }
483
501
  }
484
502
 
485
- return self;
503
+ return allocations;
486
504
  }
487
505
 
488
506
  // Stop tracking a class
@@ -572,6 +590,19 @@ static VALUE Memory_Profiler_Capture_each(VALUE self) {
572
590
  return self;
573
591
  }
574
592
 
593
+ // Get allocations for a specific class
594
+ static VALUE Memory_Profiler_Capture_aref(VALUE self, VALUE klass) {
595
+ struct Memory_Profiler_Capture *capture;
596
+ TypedData_Get_Struct(self, struct Memory_Profiler_Capture, &Memory_Profiler_Capture_type, capture);
597
+
598
+ st_data_t allocations_data;
599
+ if (st_lookup(capture->tracked_classes, (st_data_t)klass, &allocations_data)) {
600
+ return (VALUE)allocations_data;
601
+ }
602
+
603
+ return Qnil;
604
+ }
605
+
575
606
  void Init_Memory_Profiler_Capture(VALUE Memory_Profiler)
576
607
  {
577
608
  // Initialize event symbols
@@ -591,6 +622,7 @@ void Init_Memory_Profiler_Capture(VALUE Memory_Profiler)
591
622
  rb_define_method(Memory_Profiler_Capture, "tracking?", Memory_Profiler_Capture_tracking_p, 1);
592
623
  rb_define_method(Memory_Profiler_Capture, "count_for", Memory_Profiler_Capture_count_for, 1);
593
624
  rb_define_method(Memory_Profiler_Capture, "each", Memory_Profiler_Capture_each, 0);
625
+ rb_define_method(Memory_Profiler_Capture, "[]", Memory_Profiler_Capture_aref, 1);
594
626
  rb_define_method(Memory_Profiler_Capture, "clear", Memory_Profiler_Capture_clear, 0);
595
627
 
596
628
  // Initialize Allocations class
@@ -141,7 +141,7 @@ module Memory
141
141
  if sample.sample!(count)
142
142
  # Check if we should enable detailed tracking
143
143
  if sample.increases >= @increases_threshold && !@call_trees.key?(klass)
144
- track_with_analysis_internal(klass, allocations)
144
+ track(klass, allocations)
145
145
  end
146
146
 
147
147
  # Notify about growth if block given
@@ -150,13 +150,19 @@ module Memory
150
150
  end
151
151
  end
152
152
 
153
- # Internal: Enable tracking with analysis using allocations object
154
- private def track_with_analysis_internal(klass, allocations)
153
+ # Start tracking with call path analysis.
154
+ #
155
+ # @parameter klass [Class] The class to track with detailed analysis.
156
+ def track(klass, allocations = nil)
157
+ # Track the class and get the allocations object
158
+ allocations ||= @capture.track(klass)
159
+
160
+ # Set up call tree for this class
155
161
  tree = @call_trees[klass] = CallTree.new
156
162
  depth = @depth
157
163
  filter = @filter
158
164
 
159
- # Register callback on allocations object with new signature:
165
+ # Register callback on allocations object:
160
166
  # - On :newobj - returns state (leaf node) which C extension stores
161
167
  # - On :freeobj - receives state back from C extension
162
168
  allocations.track do |klass, event, state|
@@ -166,43 +172,16 @@ module Memory
166
172
  locations = caller_locations(1, depth)
167
173
  filtered = locations.select(&filter)
168
174
  unless filtered.empty?
169
- # Record returns the leaf node - return it so C can store it
175
+ # Record returns the leaf node - return it so C can store it:
170
176
  tree.record(filtered)
171
177
  end
172
- # Return nil or the node - C will store whatever we return
178
+ # Return nil or the node - C will store whatever we return.
173
179
  when :freeobj
174
- # Decrement using the state (leaf node) passed back from C
175
- if state
176
- state.decrement_path!
177
- end
180
+ # Decrement using the state (leaf node) passed back from then native extension:
181
+ state&.decrement_path!
178
182
  end
179
183
  rescue Exception => error
180
- warn "Error in track_with_analysis_internal: #{error.message}\n#{error.backtrace.join("\n")}"
181
- end
182
- end
183
-
184
- # Start tracking allocations for a class (count only).
185
- def track(klass)
186
- return if @capture.tracking?(klass)
187
-
188
- @capture.track(klass)
189
- end
190
-
191
- # Start tracking with call path analysis.
192
- #
193
- # @parameter klass [Class] The class to track with detailed analysis.
194
- def track_with_analysis(klass)
195
- # Track the class if not already tracked
196
- unless @capture.tracking?(klass)
197
- @capture.track(klass)
198
- end
199
-
200
- # Enable analysis by setting callback on the allocations object
201
- @capture.each do |tracked_klass, allocations|
202
- if tracked_klass == klass
203
- track_with_analysis_internal(klass, allocations)
204
- break
205
- end
184
+ warn "Error in allocation tracking: #{error.message}\n#{error.backtrace.join("\n")}"
206
185
  end
207
186
  end
208
187
 
@@ -280,11 +259,7 @@ module Memory
280
259
  private
281
260
 
282
261
  def default_filter
283
- ->(location) {path = location.path
284
- !path.include?("/gems/") &&
285
- !path.include?("/ruby/") &&
286
- !path.start_with?("(eval)")
287
- }
262
+ ->(location) {!location.path.match?(%r{/(gems|ruby)/|\A\(eval\)})}
288
263
  end
289
264
  end
290
265
  end
@@ -7,7 +7,7 @@
7
7
  module Memory
8
8
  # @namespace
9
9
  module Profiler
10
- VERSION = "1.1.3"
10
+ VERSION = "1.1.4"
11
11
  end
12
12
  end
13
13
 
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.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -74,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - ">="
76
76
  - !ruby/object:Gem::Version
77
- version: '3.2'
77
+ version: '3.3'
78
78
  required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
metadata.gz.sig CHANGED
Binary file