memory 0.7.0 → 0.8.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/aggregate.rb +13 -59
- data/lib/memory/format.rb +29 -0
- data/lib/memory/report.rb +2 -2
- data/lib/memory/usage.rb +81 -0
- data/lib/memory/version.rb +1 -1
- data/readme.md +9 -0
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +3 -2
- metadata.gz.sig +0 -0
- data/lib/memory/rspec/profiler.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3be5bbd2ea825e556faa064af09cfd77043d7ca04cc2767d7dffe9efa877b4b
|
|
4
|
+
data.tar.gz: 286b7693641ebf10c643e03baeb678dfa4e4135c4cd3b709b20b2dc608a61edd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2eddc8c03c592d5ef1a898af3b8fb72e71ed526473ad70cb74f4c905080be35c1d50ac3271e05da292e390e900a25fafe85a996d6f18b89016ca731d1f84ba4d
|
|
7
|
+
data.tar.gz: 535bcded6a5eb23536443d729c0a27034898b8240805b4d2180f9b18185d788b13572f9f73671be76fd47af50b6277659f9f5305ebba7a235d2440c595d6acf5
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/lib/memory/aggregate.rb
CHANGED
|
@@ -3,59 +3,12 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2020-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
|
+
require_relative "usage"
|
|
7
|
+
|
|
6
8
|
module Memory
|
|
7
|
-
UNITS = {
|
|
8
|
-
0 => "B",
|
|
9
|
-
3 => "KiB",
|
|
10
|
-
6 => "MiB",
|
|
11
|
-
9 => "GiB",
|
|
12
|
-
12 => "TiB",
|
|
13
|
-
15 => "PiB",
|
|
14
|
-
18 => "EiB",
|
|
15
|
-
21 => "ZiB",
|
|
16
|
-
24 => "YiB"
|
|
17
|
-
}.freeze
|
|
18
|
-
|
|
19
|
-
# Format bytes into human-readable units.
|
|
20
|
-
# @parameter bytes [Integer] The number of bytes to format.
|
|
21
|
-
# @returns [String] Formatted string with appropriate unit (e.g., "1.50 MiB").
|
|
22
|
-
def self.formatted_bytes(bytes)
|
|
23
|
-
return "0 B" if bytes.zero?
|
|
24
|
-
|
|
25
|
-
scale = Math.log2(bytes).div(10) * 3
|
|
26
|
-
scale = 24 if scale > 24
|
|
27
|
-
"%.2f #{UNITS[scale]}" % (bytes / 10.0**scale)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
9
|
# Aggregates memory allocations by a given metric.
|
|
31
10
|
# Groups allocations and tracks totals for memory usage and allocation counts.
|
|
32
11
|
class Aggregate
|
|
33
|
-
Total = Struct.new(:memory, :count) do
|
|
34
|
-
def initialize
|
|
35
|
-
super(0, 0)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def << allocation
|
|
39
|
-
self.memory += allocation.memsize
|
|
40
|
-
self.count += 1
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def formatted_memory
|
|
44
|
-
self.memory
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def to_s
|
|
48
|
-
"(#{Memory.formatted_bytes memory} in #{count} allocations)"
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def as_json(options = nil)
|
|
52
|
-
{
|
|
53
|
-
memory: memory,
|
|
54
|
-
count: count
|
|
55
|
-
}
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
12
|
# Initialize a new aggregate with a title and metric block.
|
|
60
13
|
# @parameter title [String] The title for this aggregate.
|
|
61
14
|
# @parameter block [Block] A block that extracts the metric from an allocation.
|
|
@@ -63,8 +16,8 @@ module Memory
|
|
|
63
16
|
@title = title
|
|
64
17
|
@metric = block
|
|
65
18
|
|
|
66
|
-
@total =
|
|
67
|
-
@totals = Hash.new{|h,k| h[k] =
|
|
19
|
+
@total = Usage.new
|
|
20
|
+
@totals = Hash.new{|h,k| h[k] = Usage.new}
|
|
68
21
|
end
|
|
69
22
|
|
|
70
23
|
attr :title
|
|
@@ -78,11 +31,8 @@ module Memory
|
|
|
78
31
|
metric = @metric.call(allocation)
|
|
79
32
|
total = @totals[metric]
|
|
80
33
|
|
|
81
|
-
total
|
|
82
|
-
total
|
|
83
|
-
|
|
84
|
-
@total.memory += allocation.memsize
|
|
85
|
-
@total.count += 1
|
|
34
|
+
total << allocation
|
|
35
|
+
@total << allocation
|
|
86
36
|
end
|
|
87
37
|
|
|
88
38
|
# Sort totals by a given key.
|
|
@@ -129,7 +79,7 @@ module Memory
|
|
|
129
79
|
@title = title
|
|
130
80
|
@metric = block
|
|
131
81
|
|
|
132
|
-
@aggregates = Hash.new{|h,k| h[k] = Aggregate.new(k.inspect, &@metric)}
|
|
82
|
+
@aggregates = Hash.new{|h,k| h[k] = Aggregate.new(safe_key(k.inspect), &@metric)}
|
|
133
83
|
end
|
|
134
84
|
|
|
135
85
|
attr :title
|
|
@@ -169,10 +119,14 @@ module Memory
|
|
|
169
119
|
# @parameter options [Hash | Nil] Optional JSON serialization options.
|
|
170
120
|
# @returns [Hash] JSON-compatible representation.
|
|
171
121
|
def as_json(options = nil)
|
|
172
|
-
{
|
|
122
|
+
result = {
|
|
173
123
|
title: @title,
|
|
174
|
-
aggregates: @aggregates.map{|k, v| [k, v.as_json]}
|
|
124
|
+
aggregates: @aggregates.map{|k, v| [safe_key(k), v.as_json]}
|
|
175
125
|
}
|
|
176
126
|
end
|
|
127
|
+
|
|
128
|
+
private def safe_key(key)
|
|
129
|
+
key.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
130
|
+
end
|
|
177
131
|
end
|
|
178
132
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Memory
|
|
7
|
+
UNITS = {
|
|
8
|
+
0 => "B",
|
|
9
|
+
3 => "KiB",
|
|
10
|
+
6 => "MiB",
|
|
11
|
+
9 => "GiB",
|
|
12
|
+
12 => "TiB",
|
|
13
|
+
15 => "PiB",
|
|
14
|
+
18 => "EiB",
|
|
15
|
+
21 => "ZiB",
|
|
16
|
+
24 => "YiB"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# Format bytes into human-readable units.
|
|
20
|
+
# @parameter bytes [Integer] The number of bytes to format.
|
|
21
|
+
# @returns [String] Formatted string with appropriate unit (e.g., "1.50 MiB").
|
|
22
|
+
def self.formatted_bytes(bytes)
|
|
23
|
+
return "0 B" if bytes.zero?
|
|
24
|
+
|
|
25
|
+
scale = Math.log2(bytes).div(10) * 3
|
|
26
|
+
scale = 24 if scale > 24
|
|
27
|
+
"%.2f #{UNITS[scale]}" % (bytes / 10.0**scale)
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/memory/report.rb
CHANGED
|
@@ -29,8 +29,8 @@ module Memory
|
|
|
29
29
|
def initialize(aggregates, retained_only: true)
|
|
30
30
|
@retained_only = retained_only
|
|
31
31
|
|
|
32
|
-
@total_allocated =
|
|
33
|
-
@total_retained =
|
|
32
|
+
@total_allocated = Usage.new
|
|
33
|
+
@total_retained = Usage.new
|
|
34
34
|
|
|
35
35
|
@aggregates = aggregates
|
|
36
36
|
end
|
data/lib/memory/usage.rb
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "format"
|
|
7
|
+
|
|
8
|
+
require "set"
|
|
9
|
+
require "objspace"
|
|
10
|
+
|
|
11
|
+
module Memory
|
|
12
|
+
class Usage
|
|
13
|
+
def initialize(size = 0, count = 0)
|
|
14
|
+
@size = size
|
|
15
|
+
@count = count
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @attribute size [Integer] The total size of the usage in bytes.
|
|
19
|
+
attr_accessor :size
|
|
20
|
+
|
|
21
|
+
alias memsize size
|
|
22
|
+
|
|
23
|
+
# @attribute count [Integer] The total count of the usage in object instances.
|
|
24
|
+
attr_accessor :count
|
|
25
|
+
|
|
26
|
+
# Add an allocation to this usage.
|
|
27
|
+
# @parameter allocation [Allocation] The allocation to add.
|
|
28
|
+
def << allocation
|
|
29
|
+
self.size += allocation.memsize
|
|
30
|
+
self.count += 1
|
|
31
|
+
|
|
32
|
+
return self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Compute the usage of an object and all reachable objects from it.
|
|
36
|
+
# @parameter root [Object] The root object to start traversal from.
|
|
37
|
+
# @returns [Usage] The usage of the object and all reachable objects from it.
|
|
38
|
+
def self.of(root)
|
|
39
|
+
seen = Set.new.compare_by_identity
|
|
40
|
+
|
|
41
|
+
count = 0
|
|
42
|
+
size = 0
|
|
43
|
+
|
|
44
|
+
queue = [root]
|
|
45
|
+
while queue.any?
|
|
46
|
+
object = queue.shift
|
|
47
|
+
# Skip modules and symbols, they are usually "global":
|
|
48
|
+
next if object.is_a?(Module)
|
|
49
|
+
# Note that `reachable_objects_from` does not include symbols, numbers, or other value types, AFAICT.
|
|
50
|
+
|
|
51
|
+
# Skip objects we have already seen:
|
|
52
|
+
next if seen.include?(object)
|
|
53
|
+
|
|
54
|
+
# Add the object to the seen set and update the count and size:
|
|
55
|
+
seen.add(object)
|
|
56
|
+
count += 1
|
|
57
|
+
size += ObjectSpace.memsize_of(object)
|
|
58
|
+
|
|
59
|
+
# Add the object's reachable objects to the queue:
|
|
60
|
+
queue.concat(ObjectSpace.reachable_objects_from(object))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
return new(size, count)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def as_json(...)
|
|
67
|
+
{
|
|
68
|
+
size: @size,
|
|
69
|
+
count: @count
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_json(...)
|
|
74
|
+
as_json.to_json(...)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_s
|
|
78
|
+
"(#{Memory.formatted_bytes(memory)} in #{count} allocations)"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/memory/version.rb
CHANGED
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.8.0
|
|
98
|
+
|
|
99
|
+
- Removed old `RSpec` integration.
|
|
100
|
+
- Introduced `Memory::Usage` and `Memory::Usage.of(object)` which recursively computes memory usage of an object and its contents.
|
|
101
|
+
|
|
102
|
+
### v0.7.1
|
|
103
|
+
|
|
104
|
+
- Ensure aggregate keys are safe for serialization (and printing).
|
|
105
|
+
|
|
97
106
|
### v0.7.0
|
|
98
107
|
|
|
99
108
|
- Add `Memory::Sampler#as_json` and `#to_json`.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.8.0
|
|
4
|
+
|
|
5
|
+
- Removed old `RSpec` integration.
|
|
6
|
+
- Introduced `Memory::Usage` and `Memory::Usage.of(object)` which recursively computes memory usage of an object and its contents.
|
|
7
|
+
|
|
8
|
+
## v0.7.1
|
|
9
|
+
|
|
10
|
+
- Ensure aggregate keys are safe for serialization (and printing).
|
|
11
|
+
|
|
3
12
|
## v0.7.0
|
|
4
13
|
|
|
5
14
|
- Add `Memory::Sampler#as_json` and `#to_json`.
|
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
@@ -116,9 +116,10 @@ files:
|
|
|
116
116
|
- lib/memory/aggregate.rb
|
|
117
117
|
- lib/memory/cache.rb
|
|
118
118
|
- lib/memory/deque.rb
|
|
119
|
+
- lib/memory/format.rb
|
|
119
120
|
- lib/memory/report.rb
|
|
120
|
-
- lib/memory/rspec/profiler.rb
|
|
121
121
|
- lib/memory/sampler.rb
|
|
122
|
+
- lib/memory/usage.rb
|
|
122
123
|
- lib/memory/version.rb
|
|
123
124
|
- license.md
|
|
124
125
|
- readme.md
|
metadata.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-2025, by Samuel Williams.
|
|
5
|
-
|
|
6
|
-
require_relative "../sampler"
|
|
7
|
-
|
|
8
|
-
module Memory
|
|
9
|
-
# @namespace
|
|
10
|
-
module RSpec
|
|
11
|
-
# Integration with RSpec for memory profiling test suites.
|
|
12
|
-
# Profiles memory allocations for RSpec example groups and generates reports.
|
|
13
|
-
module Profiler
|
|
14
|
-
# Profile memory allocations for RSpec examples.
|
|
15
|
-
# @parameter scope [Object] The RSpec scope to apply profiling hooks to.
|
|
16
|
-
def self.profile(scope)
|
|
17
|
-
memory_sampler = nil
|
|
18
|
-
|
|
19
|
-
scope.before(:all) do |example_group|
|
|
20
|
-
name = example_group.class.description.gsub(/[^\w]+/, "-")
|
|
21
|
-
path = "#{name}.mprof"
|
|
22
|
-
|
|
23
|
-
skip if File.exist?(path)
|
|
24
|
-
|
|
25
|
-
memory_sampler = Memory::Sampler.new
|
|
26
|
-
memory_sampler.start
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
scope.after(:all) do |example_group|
|
|
30
|
-
name = example_group.class.description.gsub(/[^\w]+/, "-")
|
|
31
|
-
path = "#{name}.mprof"
|
|
32
|
-
|
|
33
|
-
if memory_sampler
|
|
34
|
-
memory_sampler.stop
|
|
35
|
-
|
|
36
|
-
File.open(path, "w", encoding: Encoding::BINARY) do |io|
|
|
37
|
-
memory_sampler.dump(io)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
memory_sampler = nil
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
scope.after(:suite) do
|
|
45
|
-
memory_sampler = Memory::Sampler.new
|
|
46
|
-
|
|
47
|
-
Dir.glob("*.mprof") do |path|
|
|
48
|
-
memory_sampler.load(File.read(
|
|
49
|
-
path,
|
|
50
|
-
encoding: Encoding::BINARY,
|
|
51
|
-
))
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
memory_sampler.report.print
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|