memory 0.6.1 → 0.7.1

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: 8ab40ebc992286597b6a01b5157fca5393b29563c7131483d2e7839f15172541
4
- data.tar.gz: d5b9b73ea305840435c242a6e34bf755d94e77350846f6618467fef4fa7561ff
3
+ metadata.gz: 8f828b63a1b278be8fa689f21263c6850971984cb3de7d5743ea86b855c2585e
4
+ data.tar.gz: 162f043ffe9de59804533cd33ba3140531d56b2e454ccf29cad5fe7698f92d56
5
5
  SHA512:
6
- metadata.gz: 5a10c287ab778c7deb2e12b5a04e7214107ae7afbdf12e57af3423128fe0dc9c233735892ae97ec18d90f47ba46856f08ab5624389a1cb61dbdc4d9af3733bf3
7
- data.tar.gz: db0d83a8c69a4bdf37e058d6a9e304a7b0608791f8a1f89ad1f2864f2d4119aa8c61c9726b813800e4b45506e1cfa2eec7227ca8eeed99170b931927878e4efb
6
+ metadata.gz: 48a61fdd8df592811bb547cff195b00b21abc470633c7c7706ae9f2d5fe29b069443c468268e27372e9d808bc0f72bf25b14f19cf86d9bfdc9d98bdbb8163fee
7
+ data.tar.gz: 464a4ff8bdf9ef59afc9a0a18057b67862d467be36659b7a9efe1f35fda0c80bb1d14f23a3f570c733a7a68da4cb67b4e8b7a357e5dd9ca8a15f86243b84227e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -5,7 +5,7 @@
5
5
 
6
6
  def initialize(...)
7
7
  super
8
-
8
+
9
9
  require_relative "../../lib/memory"
10
10
  end
11
11
 
@@ -26,6 +26,6 @@ def print(input:)
26
26
  end
27
27
 
28
28
  report.print($stderr)
29
-
29
+
30
30
  return report
31
31
  end
@@ -6,7 +6,7 @@
6
6
  def initialize(...)
7
7
  super
8
8
 
9
- require_relative '../../lib/memory'
9
+ require_relative "../../lib/memory"
10
10
  end
11
11
 
12
12
  # Load a sampler from one or more .mprof files.
@@ -27,7 +27,7 @@ def load(paths:)
27
27
  paths.each do |path|
28
28
  Console.logger.info(sampler, "Loading #{path}, #{Memory.formatted_bytes File.size(path)}")
29
29
 
30
- File.open(path, 'r', encoding: Encoding::BINARY) do |io|
30
+ File.open(path, "r", encoding: Encoding::BINARY) do |io|
31
31
  unpacker = wrapper.unpacker(io)
32
32
  count = unpacker.read_array_header
33
33
 
@@ -56,7 +56,7 @@ end
56
56
  # @parameter output [String] Path to write the .mprof file.
57
57
  # @returns [Memory::Sampler] The input sampler.
58
58
  def dump(path, input:)
59
- File.open(path, 'w', encoding: Encoding::BINARY) do |io|
59
+ File.open(path, "w", encoding: Encoding::BINARY) do |io|
60
60
  input.dump(io)
61
61
  end
62
62
 
@@ -76,7 +76,7 @@ def load_object_space_dump(path)
76
76
  Console.logger.info(self, "Loading heap dump from #{path} (#{Memory.formatted_bytes(file_size)})")
77
77
 
78
78
  sampler = nil
79
- File.open(path, 'r') do |io|
79
+ File.open(path, "r") do |io|
80
80
  sampler = Memory::Sampler.load_object_space_dump(io) do |line_count, object_count|
81
81
  # Update progress based on bytes read:
82
82
  progress.increment(io.pos - progress.current)
@@ -129,7 +129,7 @@ module Memory
129
129
  @title = title
130
130
  @metric = block
131
131
 
132
- @aggregates = Hash.new{|h,k| h[k] = Aggregate.new(k.inspect, &@metric)}
132
+ @aggregates = Hash.new{|h,k| h[k] = Aggregate.new(safe_key(k.inspect), &@metric)}
133
133
  end
134
134
 
135
135
  attr :title
@@ -169,10 +169,14 @@ module Memory
169
169
  # @parameter options [Hash | Nil] Optional JSON serialization options.
170
170
  # @returns [Hash] JSON-compatible representation.
171
171
  def as_json(options = nil)
172
- {
172
+ result = {
173
173
  title: @title,
174
- aggregates: @aggregates.map{|k, v| [k, v.as_json]}
174
+ aggregates: @aggregates.map{|k, v| [safe_key(k), v.as_json]}
175
175
  }
176
176
  end
177
+
178
+ private def safe_key(key)
179
+ key.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
180
+ end
177
181
  end
178
182
  end
data/lib/memory/report.rb CHANGED
@@ -90,16 +90,16 @@ module Memory
90
90
  }
91
91
  end
92
92
 
93
- # Convert this report to a JSON string.
94
- # @returns [String] JSON representation of this report.
95
- def to_json(...)
96
- as_json.to_json(...)
97
- end
98
-
99
- # Generate a human-readable representation of this report.
100
- # @returns [String] Summary showing allocated and retained totals.
101
- def inspect
102
- "#<#{self.class}: #{@total_allocated} allocated, #{@total_retained} retained>"
93
+ # Convert this report to a JSON string.
94
+ # @returns [String] JSON representation of this report.
95
+ def to_json(...)
96
+ as_json.to_json(...)
97
+ end
98
+
99
+ # Generate a human-readable representation of this report.
100
+ # @returns [String] Summary showing allocated and retained totals.
101
+ def inspect
102
+ "#<#{self.class}: #{@total_allocated} allocated, #{@total_retained} retained>"
103
+ end
103
104
  end
104
105
  end
105
- end
@@ -66,72 +66,72 @@ module Memory
66
66
  # end
67
67
  # ~~~
68
68
  class Sampler
69
- # Load allocations from an ObjectSpace heap dump.
70
- #
71
- # If a block is given, it will be called periodically with progress information.
72
- #
73
- # @parameter io [IO] The IO stream containing the heap dump JSON.
74
- # @yields [line_count, object_count] Progress callback with current line and object counts.
75
- # @returns [Sampler] A new sampler populated with allocations from the heap dump.
76
- def self.load_object_space_dump(io, &block)
77
- sampler = new
78
- cache = sampler.cache
79
-
80
- line_count = 0
81
- object_count = 0
82
- report_interval = 10000
83
-
84
- io.each_line do |line|
85
- line_count += 1
86
-
87
- begin
88
- object = JSON.parse(line)
89
- rescue JSON::ParserError
90
- # Skip invalid JSON lines
91
- next
92
- end
93
-
94
- # Skip non-object entries (ROOT, SHAPE, etc.)
95
- next unless object['address']
69
+ # Load allocations from an ObjectSpace heap dump.
70
+ #
71
+ # If a block is given, it will be called periodically with progress information.
72
+ #
73
+ # @parameter io [IO] The IO stream containing the heap dump JSON.
74
+ # @yields [line_count, object_count] Progress callback with current line and object counts.
75
+ # @returns [Sampler] A new sampler populated with allocations from the heap dump.
76
+ def self.load_object_space_dump(io, &block)
77
+ sampler = new
78
+ cache = sampler.cache
96
79
 
97
- # Get allocation information (may be nil if tracing wasn't enabled)
98
- file = object['file'] || '(unknown)'
99
- line_number = object['line'] || 0
80
+ line_count = 0
81
+ object_count = 0
82
+ report_interval = 10000
100
83
 
101
- # Get object type/class
102
- type = object['type'] || 'unknown'
103
-
104
- # Get memory size
105
- memsize = object['memsize'] || 0
106
-
107
- # Get value for strings
108
- value = object['value']
109
-
110
- allocation = Allocation.new(
111
- cache,
112
- type, # class_name
113
- file, # file
114
- line_number, # line
115
- memsize, # memsize
116
- value, # value (for strings)
117
- true # retained (all objects in heap dump are live)
118
- )
84
+ io.each_line do |line|
85
+ line_count += 1
86
+
87
+ begin
88
+ object = JSON.parse(line)
89
+ rescue JSON::ParserError
90
+ # Skip invalid JSON lines
91
+ next
92
+ end
93
+
94
+ # Skip non-object entries (ROOT, SHAPE, etc.)
95
+ next unless object["address"]
96
+
97
+ # Get allocation information (may be nil if tracing wasn't enabled)
98
+ file = object["file"] || "(unknown)"
99
+ line_number = object["line"] || 0
100
+
101
+ # Get object type/class
102
+ type = object["type"] || "unknown"
103
+
104
+ # Get memory size
105
+ memsize = object["memsize"] || 0
106
+
107
+ # Get value for strings
108
+ value = object["value"]
109
+
110
+ allocation = Allocation.new(
111
+ cache,
112
+ type, # class_name
113
+ file, # file
114
+ line_number, # line
115
+ memsize, # memsize
116
+ value, # value (for strings)
117
+ true # retained (all objects in heap dump are live)
118
+ )
119
+
120
+ sampler.allocated << allocation
121
+ object_count += 1
122
+
123
+ # Report progress periodically
124
+ if block && (object_count % report_interval == 0)
125
+ block.call(line_count, object_count)
126
+ end
127
+ end
119
128
 
120
- sampler.allocated << allocation
121
- object_count += 1
129
+ # Final progress report
130
+ block.call(line_count, object_count) if block
122
131
 
123
- # Report progress periodically
124
- if block && (object_count % report_interval == 0)
125
- block.call(line_count, object_count)
126
- end
132
+ return sampler
127
133
  end
128
134
 
129
- # Final progress report
130
- block.call(line_count, object_count) if block
131
-
132
- return sampler
133
- end
134
-
135
135
  # Initialize a new sampler.
136
136
  # @parameter filter [Block | Nil] Optional filter block to select which allocations to track.
137
137
  def initialize(&filter)
@@ -231,6 +231,22 @@ module Memory
231
231
  return report
232
232
  end
233
233
 
234
+ # Convert this sampler to a JSON-compatible summary.
235
+ # Returns the allocation count without iterating through all allocations.
236
+ # @parameter options [Hash | Nil] Optional JSON serialization options.
237
+ # @returns [Hash] JSON-compatible summary of sampler data.
238
+ def as_json(options = nil)
239
+ {
240
+ allocations: @allocated.size
241
+ }
242
+ end
243
+
244
+ # Convert this sampler to a JSON string.
245
+ # @returns [String] JSON representation of this sampler summary.
246
+ def to_json(...)
247
+ as_json.to_json(...)
248
+ end
249
+
234
250
  # Collects object allocation and memory of ruby code inside of passed block.
235
251
  def run(&block)
236
252
  start
@@ -4,8 +4,8 @@
4
4
  # Copyright, 2013-2019, by Sam Saffron.
5
5
  # Copyright, 2015-2016, by Dave Gynn.
6
6
  # Copyright, 2018, by Jonas Peschla.
7
- # Copyright, 2020-2024, by Samuel Williams.
7
+ # Copyright, 2020-2025, by Samuel Williams.
8
8
 
9
9
  module Memory
10
- VERSION = "0.6.1"
10
+ VERSION = "0.7.1"
11
11
  end
data/readme.md CHANGED
@@ -94,6 +94,14 @@ end
94
94
 
95
95
  Please see the [project releases](https://socketry.github.io/memory/releases/index) for all releases.
96
96
 
97
+ ### v0.7.1
98
+
99
+ - Ensure aggregate keys are safe for serialization (and printing).
100
+
101
+ ### v0.7.0
102
+
103
+ - Add `Memory::Sampler#as_json` and `#to_json`.
104
+
97
105
  ### v0.6.0
98
106
 
99
107
  - Add agent context.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.7.1
4
+
5
+ - Ensure aggregate keys are safe for serialization (and printing).
6
+
7
+ ## v0.7.0
8
+
9
+ - Add `Memory::Sampler#as_json` and `#to_json`.
10
+
3
11
  ## v0.6.0
4
12
 
5
13
  - Add agent context.
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.6.1
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
metadata.gz.sig CHANGED
Binary file