Skip to content

Commit 3a388a0

Browse files
Expose Capture#statistics for debugging.
1 parent 3871b43 commit 3a388a0

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

ext/memory/profiler/capture.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,57 @@ static VALUE Memory_Profiler_Capture_aref(VALUE self, VALUE klass) {
694694
return Qnil;
695695
}
696696

697+
// Struct to accumulate statistics during iteration
698+
struct Memory_Profiler_Allocations_Statistics {
699+
size_t total_tracked_objects;
700+
VALUE per_class_counts;
701+
};
702+
703+
// Iterator callback to count object_states per class
704+
static int Memory_Profiler_Capture_count_object_states(st_data_t key, st_data_t value, st_data_t argument) {
705+
struct Memory_Profiler_Allocations_Statistics *statistics = (struct Memory_Profiler_Allocations_Statistics *)argument;
706+
VALUE klass = (VALUE)key;
707+
VALUE allocations = (VALUE)value;
708+
struct Memory_Profiler_Capture_Allocations *record = Memory_Profiler_Allocations_get(allocations);
709+
710+
size_t object_states_count = record->object_states ? record->object_states->num_entries : 0;
711+
statistics->total_tracked_objects += object_states_count;
712+
713+
rb_hash_aset(statistics->per_class_counts, klass, SIZET2NUM(object_states_count));
714+
return ST_CONTINUE;
715+
}
716+
717+
// Get internal statistics for debugging
718+
// Returns hash with internal state sizes
719+
static VALUE Memory_Profiler_Capture_statistics(VALUE self) {
720+
struct Memory_Profiler_Capture *capture;
721+
TypedData_Get_Struct(self, struct Memory_Profiler_Capture, &Memory_Profiler_Capture_type, capture);
722+
723+
VALUE statistics = rb_hash_new();
724+
725+
// Tracked classes count
726+
rb_hash_aset(statistics, ID2SYM(rb_intern("tracked_classes_count")), SIZET2NUM(capture->tracked_classes->num_entries));
727+
728+
// Queue sizes
729+
rb_hash_aset(statistics, ID2SYM(rb_intern("newobj_queue_size")), SIZET2NUM(capture->newobj_queue.count));
730+
rb_hash_aset(statistics, ID2SYM(rb_intern("newobj_queue_capacity")), SIZET2NUM(capture->newobj_queue.capacity));
731+
rb_hash_aset(statistics, ID2SYM(rb_intern("freeobj_queue_size")), SIZET2NUM(capture->freeobj_queue.count));
732+
rb_hash_aset(statistics, ID2SYM(rb_intern("freeobj_queue_capacity")), SIZET2NUM(capture->freeobj_queue.capacity));
733+
734+
// Count object_states entries for each tracked class
735+
struct Memory_Profiler_Allocations_Statistics allocations_statistics = {
736+
.total_tracked_objects = 0,
737+
.per_class_counts = rb_hash_new()
738+
};
739+
740+
st_foreach(capture->tracked_classes, Memory_Profiler_Capture_count_object_states, (st_data_t)&allocations_statistics);
741+
742+
rb_hash_aset(statistics, ID2SYM(rb_intern("total_tracked_objects")), SIZET2NUM(allocations_statistics.total_tracked_objects));
743+
rb_hash_aset(statistics, ID2SYM(rb_intern("tracked_objects_per_class")), allocations_statistics.per_class_counts);
744+
745+
return statistics;
746+
}
747+
697748
void Init_Memory_Profiler_Capture(VALUE Memory_Profiler)
698749
{
699750
// Initialize event symbols
@@ -715,6 +766,7 @@ void Init_Memory_Profiler_Capture(VALUE Memory_Profiler)
715766
rb_define_method(Memory_Profiler_Capture, "each", Memory_Profiler_Capture_each, 0);
716767
rb_define_method(Memory_Profiler_Capture, "[]", Memory_Profiler_Capture_aref, 1);
717768
rb_define_method(Memory_Profiler_Capture, "clear", Memory_Profiler_Capture_clear, 0);
769+
rb_define_method(Memory_Profiler_Capture, "statistics", Memory_Profiler_Capture_statistics, 0);
718770

719771
// Initialize Allocations class
720772
Init_Memory_Profiler_Allocations(Memory_Profiler);

lib/memory/profiler/sampler.rb

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ def to_json(...)
8585
end
8686
end
8787

88-
attr_reader :depth
89-
9088
# Create a new memory sampler.
9189
#
9290
# @parameter depth [Integer] Number of stack frames to capture for call path analysis.
@@ -104,6 +102,30 @@ def initialize(depth: 4, filter: nil, increases_threshold: 10, prune_limit: 5, p
104102
@call_trees = {}
105103
@samples = {}
106104
end
105+
106+
# @attribute [Integer] The depth of the call tree.
107+
attr :depth
108+
109+
# @attribute [Proc] The filter to exclude frames from call paths.
110+
attr :filter
111+
112+
# @attribute [Integer] The number of increases before enabling detailed tracking.
113+
attr :increases_threshold
114+
115+
# @attribute [Integer] The number of insertions before auto-pruning (nil = no auto-pruning).
116+
attr :prune_limit
117+
118+
# @attribute [Integer | Nil] The number of insertions before auto-pruning (nil = no auto-pruning).
119+
attr :prune_threshold
120+
121+
# @attribute [Capture] The capture object.
122+
attr :capture
123+
124+
# @attribute [Hash] The call trees.
125+
attr :call_trees
126+
127+
# @attribute [Hash] The samples for each class being tracked.
128+
attr :samples
107129

108130
# Start capturing allocations.
109131
def start

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- Expose `Capture#statistics` for debugging internal memory tracking state.
6+
37
## v1.1.6
48

59
- Write barriers all the things.

0 commit comments

Comments
 (0)