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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/ext/memory/profiler/allocations.c +3 -2
- data/ext/memory/profiler/capture.c +39 -7
- data/lib/memory/profiler/sampler.rb +16 -41
- data/lib/memory/profiler/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c70385bac57de768e1f6eb3ca9044942a357d77c97967b30636acd89b1f051f4
|
|
4
|
+
data.tar.gz: 8c7ae2a117684e61d34e09e9b2ebb4a99ab440b1b31fee364d2a92bc6caf5cac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
16
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
154
|
-
|
|
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
|
|
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
|
|
175
|
-
|
|
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
|
|
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) {
|
|
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
|
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.
|
|
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.
|
|
77
|
+
version: '3.3'
|
|
78
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - ">="
|
metadata.gz.sig
CHANGED
|
Binary file
|