memory 0.7.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f828b63a1b278be8fa689f21263c6850971984cb3de7d5743ea86b855c2585e
4
- data.tar.gz: 162f043ffe9de59804533cd33ba3140531d56b2e454ccf29cad5fe7698f92d56
3
+ metadata.gz: e3be5bbd2ea825e556faa064af09cfd77043d7ca04cc2767d7dffe9efa877b4b
4
+ data.tar.gz: 286b7693641ebf10c643e03baeb678dfa4e4135c4cd3b709b20b2dc608a61edd
5
5
  SHA512:
6
- metadata.gz: 48a61fdd8df592811bb547cff195b00b21abc470633c7c7706ae9f2d5fe29b069443c468268e27372e9d808bc0f72bf25b14f19cf86d9bfdc9d98bdbb8163fee
7
- data.tar.gz: 464a4ff8bdf9ef59afc9a0a18057b67862d467be36659b7a9efe1f35fda0c80bb1d14f23a3f570c733a7a68da4cb67b4e8b7a357e5dd9ca8a15f86243b84227e
6
+ metadata.gz: 2eddc8c03c592d5ef1a898af3b8fb72e71ed526473ad70cb74f4c905080be35c1d50ac3271e05da292e390e900a25fafe85a996d6f18b89016ca731d1f84ba4d
7
+ data.tar.gz: 535bcded6a5eb23536443d729c0a27034898b8240805b4d2180f9b18185d788b13572f9f73671be76fd47af50b6277659f9f5305ebba7a235d2440c595d6acf5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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 = Total.new
67
- @totals = Hash.new{|h,k| h[k] = Total.new}
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.memory += allocation.memsize
82
- total.count += 1
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.
@@ -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 = Aggregate::Total.new
33
- @total_retained = Aggregate::Total.new
32
+ @total_allocated = Usage.new
33
+ @total_retained = Usage.new
34
34
 
35
35
  @aggregates = aggregates
36
36
  end
@@ -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
@@ -7,5 +7,5 @@
7
7
  # Copyright, 2020-2025, by Samuel Williams.
8
8
 
9
9
  module Memory
10
- VERSION = "0.7.1"
10
+ VERSION = "0.8.0"
11
11
  end
data/readme.md CHANGED
@@ -94,6 +94,11 @@ 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
+
97
102
  ### v0.7.1
98
103
 
99
104
  - Ensure aggregate keys are safe for serialization (and printing).
data/releases.md CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  ## v0.7.1
4
9
 
5
10
  - Ensure aggregate keys are safe for serialization (and printing).
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.7.1
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