memory 0.8.4 → 0.10.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: cbe94b9b53feb63ebeef3069e3632f08084467a2670714f9680c881a88a3f133
4
- data.tar.gz: 95c23b5b6cd0be124e9370b7631118bf24e5fbcc0b19091588d01ff723769d06
3
+ metadata.gz: 610a559d7ecf64e0ec26f443e53492bcc8853f4c2a384ccb4384bf14855b9805
4
+ data.tar.gz: 53c34a593cf01eb64661d762b46a5a1e2d0b4ef3597f399cf6c2be76244ad2d4
5
5
  SHA512:
6
- metadata.gz: b94371ccdf66d56362320b83370c9b59d7db1d4b75d62c8506fa19392cf9da62b14eaecf8c981f518edf18c10bb65627c55687146c7fe9af0c4faa3a92f86982
7
- data.tar.gz: 764fc3157811e87be1a6d220425ee5c1c3135c9e47a3ce99066968d82948f8cbdf01dad47538cf22ac25a062c2d5c486bff024fa5ee862686779dade668dc046
6
+ metadata.gz: 3d014fe210c6b215773e275a7b9caa1ddcf346dae6fb21e06c637d01b638ff193154b77e3e4b54a6f9fca7a92bb9d18a8d56bf75ea0cc057d2749fe6b75b5d47
7
+ data.tar.gz: 7918321809f910e3979384a44863e02f6c37e797918caf6e1524103e4a808be847a2bec37ddc1a1cbf35e761e45c15aef2fb4f85d521828ae252e0a5042660d5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "set"
7
+
8
+ module Memory
9
+ # Tracks object traversal paths for memory analysis.
10
+ #
11
+ # The Graph class maintains a mapping of objects to their parent objects,
12
+ # allowing you to trace the reference path from any object back to its root.
13
+ class Graph
14
+ def initialize
15
+ @mapping = Hash.new.compare_by_identity
16
+ end
17
+
18
+ # The internal mapping of objects to their parents.
19
+ attr_reader :mapping
20
+
21
+ # Add a parent-child relationship to the via mapping.
22
+ #
23
+ # @parameter child [Object] The child object.
24
+ # @parameter parent [Object] The parent object that references the child.
25
+ def []=(child, parent)
26
+ @mapping[child] = parent
27
+ end
28
+
29
+ # Get the parent of an object.
30
+ #
31
+ # @parameter child [Object] The child object.
32
+ # @returns [Object | Nil] The parent object, or nil if not tracked.
33
+ def [](child)
34
+ @mapping[child]
35
+ end
36
+
37
+ # Check if an object is tracked in the via mapping.
38
+ #
39
+ # @parameter object [Object] The object to check.
40
+ # @returns [Boolean] True if the object is tracked.
41
+ def key?(object)
42
+ @mapping.key?(object)
43
+ end
44
+
45
+ # Find how a parent object references a child object.
46
+ #
47
+ # @parameter parent [Object] The parent object.
48
+ # @parameter child [Object] The child object to find.
49
+ # @returns [String | Nil] A human-readable description of the reference, or nil if not found.
50
+ def find_reference(parent, child)
51
+ # Check instance variables:
52
+ parent.instance_variables.each do |ivar|
53
+ value = parent.instance_variable_get(ivar)
54
+ if value.equal?(child)
55
+ return ivar.to_s
56
+ end
57
+ end
58
+
59
+ # Check array elements:
60
+ if parent.is_a?(Array)
61
+ parent.each_with_index do |element, index|
62
+ if element.equal?(child)
63
+ return "[#{index}]"
64
+ end
65
+ end
66
+ end
67
+
68
+ # Check hash keys and values:
69
+ if parent.is_a?(Hash)
70
+ parent.each do |key, value|
71
+ if value.equal?(child)
72
+ return "[#{key.inspect}]"
73
+ end
74
+ if key.equal?(child)
75
+ return "(key: #{key.inspect})"
76
+ end
77
+ end
78
+ end
79
+
80
+ # Check struct members:
81
+ if parent.is_a?(Struct)
82
+ parent.each_pair do |member, value|
83
+ if value.equal?(child)
84
+ return ".#{member}"
85
+ end
86
+ end
87
+ end
88
+
89
+ # Could not determine the reference:
90
+ return nil
91
+ end
92
+
93
+ # Construct a human-readable path from an object back to a root.
94
+ #
95
+ # @parameter object [Object] The object to trace back from.
96
+ # @parameter root [Object | Nil] The root object to trace to. If nil, traces to any root.
97
+ # @returns [Array(Array(Object), Array(String))] A tuple of [object_path, reference_path].
98
+ def path_to(object, root = nil)
99
+ # Build the object path by following via backwards:
100
+ object_path = [object]
101
+ current = object
102
+
103
+ while @mapping.key?(current)
104
+ parent = @mapping[current]
105
+ object_path << parent
106
+ current = parent
107
+
108
+ # Stop if we reached the specified root:
109
+ break if root && current.equal?(root)
110
+ end
111
+
112
+ # Reverse to get path from root to object:
113
+ object_path.reverse!
114
+
115
+ return object_path
116
+ end
117
+
118
+ # Format a human-readable path string.
119
+ #
120
+ # @parameter object [Object] The object to trace back from.
121
+ # @parameter root [Object | Nil] The root object to trace to. If nil, traces to any root.
122
+ # @returns [String] A formatted path string.
123
+ def path(object, root = nil)
124
+ object_path = path_to(object, root)
125
+
126
+ # Start with the root object description:
127
+ parts = ["#<#{object_path.first.class}:0x%016x>" % (object_path.first.object_id << 1)]
128
+
129
+ # Append each reference in the path:
130
+ (1...object_path.size).each do |i|
131
+ parent = object_path[i - 1]
132
+ child = object_path[i]
133
+
134
+ parts << (find_reference(parent, child) || "<??>")
135
+ end
136
+
137
+ return parts.join
138
+ end
139
+ end
140
+ end
141
+
data/lib/memory/usage.rb CHANGED
@@ -39,37 +39,60 @@ module Memory
39
39
  return self
40
40
  end
41
41
 
42
+ IGNORE = [
43
+ # Skip modules and symbols, they are usually "global":
44
+ Module,
45
+ # Note that `reachable_objects_from` does not include symbols, numbers, or other value types, AFAICT.
46
+
47
+ Proc,
48
+ Method,
49
+ UnboundMethod,
50
+ Binding,
51
+ TracePoint,
52
+
53
+ # We don't want to traverse into shared state:
54
+ Ractor,
55
+ Thread,
56
+ Fiber
57
+ ]
58
+
42
59
  # Compute the usage of an object and all reachable objects from it.
60
+ #
61
+ # The root is always visited even if it is in `seen`.
62
+ #
43
63
  # @parameter root [Object] The root object to start traversal from.
64
+ # @parameter seen [Hash(Object, Integer)] The seen objects (should be compare_by_identity).
65
+ # @parameter via [Hash(Object, Object) | Nil] The traversal path. The key object was seen via the value object.
44
66
  # @returns [Usage] The usage of the object and all reachable objects from it.
45
- def self.of(root)
46
- seen = Set.new.compare_by_identity
47
-
67
+ def self.of(root, seen: Set.new.compare_by_identity, ignore: IGNORE, via: nil)
48
68
  count = 0
49
69
  size = 0
50
70
 
51
71
  queue = [root]
52
- while queue.any?
53
- object = queue.shift
54
-
55
- # Skip modules and symbols, they are usually "global":
56
- next if object.is_a?(Module)
57
- # Note that `reachable_objects_from` does not include symbols, numbers, or other value types, AFAICT.
58
-
59
- # Skip internal objects - they don't behave correctly when added to `seen` and create unbounded recursion:
60
- next if object.is_a?(ObjectSpace::InternalObjectWrapper)
61
-
62
- # Skip objects we have already seen:
63
- next if seen.include?(object)
64
-
65
- # Add the object to the seen set and update the count and size:
72
+ while object = queue.shift
73
+ # Add the object to the seen set:
66
74
  seen.add(object)
75
+
76
+ # Update the count and size:
67
77
  count += 1
68
78
  size += ObjectSpace.memsize_of(object)
69
79
 
70
80
  # Add the object's reachable objects to the queue:
71
- if reachable_objects = ObjectSpace.reachable_objects_from(object)
72
- queue.concat(reachable_objects)
81
+ ObjectSpace.reachable_objects_from(object)&.each do |reachable_object|
82
+ # Skip ignored types:
83
+ next if ignore.any?{|type| reachable_object.is_a?(type)}
84
+
85
+ # Skip internal objects - they don't behave correctly when added to `seen` and create unbounded recursion:
86
+ next if reachable_object.is_a?(ObjectSpace::InternalObjectWrapper)
87
+
88
+ # Skip objects we have already seen:
89
+ next if seen.include?(reachable_object)
90
+
91
+ if via
92
+ via[reachable_object] ||= object
93
+ end
94
+
95
+ queue << reachable_object
73
96
  end
74
97
  end
75
98
 
@@ -7,5 +7,5 @@
7
7
  # Copyright, 2020-2025, by Samuel Williams.
8
8
 
9
9
  module Memory
10
- VERSION = "0.8.4"
10
+ VERSION = "0.10.0"
11
11
  end
data/lib/memory.rb CHANGED
@@ -11,6 +11,8 @@ require_relative "memory/version"
11
11
  require_relative "memory/cache"
12
12
  require_relative "memory/report"
13
13
  require_relative "memory/sampler"
14
+ require_relative "memory/usage"
15
+ require_relative "memory/graph"
14
16
 
15
17
  # Memory profiler for Ruby applications.
16
18
  # Provides tools to track and analyze memory allocations and retention.
data/readme.md CHANGED
@@ -94,6 +94,15 @@ end
94
94
 
95
95
  Please see the [project releases](https://socketry.github.io/memory/releases/index) for all releases.
96
96
 
97
+ ### v0.10.0
98
+
99
+ - Add support for `Memory::Usage.of(..., via:)` for tracking reachability of objects.
100
+ - Introduce `Memory::Graph` for computing paths between parent/child objects.
101
+
102
+ ### v0.9.0
103
+
104
+ - Explicit `ignore:` and `seen:` parameters for `Memory::Usage.of` to allow customization of ignored types and tracking of seen objects.
105
+
97
106
  ### v0.8.4
98
107
 
99
108
  - Fix bugs when printing reports due to interface mismatch with `Memory::Usage`.
data/releases.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Releases
2
2
 
3
+ ## v0.10.0
4
+
5
+ - Add support for `Memory::Usage.of(..., via:)` for tracking reachability of objects.
6
+ - Introduce `Memory::Graph` for computing paths between parent/child objects.
7
+
8
+ ## v0.9.0
9
+
10
+ - Explicit `ignore:` and `seen:` parameters for `Memory::Usage.of` to allow customization of ignored types and tracking of seen objects.
11
+
3
12
  ## v0.8.4
4
13
 
5
14
  - Fix bugs when printing reports due to interface mismatch with `Memory::Usage`.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
@@ -29,7 +29,6 @@ authors:
29
29
  - Olle Jonsson
30
30
  - Vasily Kolesnikov
31
31
  - William Tabi
32
- autorequire:
33
32
  bindir: bin
34
33
  cert_chain:
35
34
  - |
@@ -61,7 +60,7 @@ cert_chain:
61
60
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
62
61
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
63
62
  -----END CERTIFICATE-----
64
- date: 2025-10-29 00:00:00.000000000 Z
63
+ date: 1980-01-02 00:00:00.000000000 Z
65
64
  dependencies:
66
65
  - !ruby/object:Gem::Dependency
67
66
  name: bake
@@ -105,8 +104,6 @@ dependencies:
105
104
  - - ">="
106
105
  - !ruby/object:Gem::Version
107
106
  version: '0'
108
- description:
109
- email:
110
107
  executables: []
111
108
  extensions: []
112
109
  extra_rdoc_files: []
@@ -120,6 +117,7 @@ files:
120
117
  - lib/memory/cache.rb
121
118
  - lib/memory/deque.rb
122
119
  - lib/memory/format.rb
120
+ - lib/memory/graph.rb
123
121
  - lib/memory/report.rb
124
122
  - lib/memory/sampler.rb
125
123
  - lib/memory/usage.rb
@@ -133,7 +131,6 @@ licenses:
133
131
  metadata:
134
132
  documentation_uri: https://socketry.github.io/memory/
135
133
  source_code_uri: https://github.com/socketry/memory.git
136
- post_install_message:
137
134
  rdoc_options: []
138
135
  require_paths:
139
136
  - lib
@@ -148,8 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
145
  - !ruby/object:Gem::Version
149
146
  version: '0'
150
147
  requirements: []
151
- rubygems_version: 3.4.19
152
- signing_key:
148
+ rubygems_version: 3.7.2
153
149
  specification_version: 4
154
150
  summary: Memory profiling routines for Ruby 2.3+
155
151
  test_files: []
metadata.gz.sig CHANGED
Binary file