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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/memory/graph.rb +141 -0
- data/lib/memory/usage.rb +42 -19
- data/lib/memory/version.rb +1 -1
- data/lib/memory.rb +2 -0
- data/readme.md +9 -0
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +4 -8
- 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: 610a559d7ecf64e0ec26f443e53492bcc8853f4c2a384ccb4384bf14855b9805
|
|
4
|
+
data.tar.gz: 53c34a593cf01eb64661d762b46a5a1e2d0b4ef3597f399cf6c2be76244ad2d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d014fe210c6b215773e275a7b9caa1ddcf346dae6fb21e06c637d01b638ff193154b77e3e4b54a6f9fca7a92bb9d18a8d56bf75ea0cc057d2749fe6b75b5d47
|
|
7
|
+
data.tar.gz: 7918321809f910e3979384a44863e02f6c37e797918caf6e1524103e4a808be847a2bec37ddc1a1cbf35e761e45c15aef2fb4f85d521828ae252e0a5042660d5
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/lib/memory/graph.rb
ADDED
|
@@ -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.
|
|
53
|
-
object
|
|
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
|
-
|
|
72
|
-
|
|
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
|
|
data/lib/memory/version.rb
CHANGED
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.
|
|
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:
|
|
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.
|
|
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
|