memory_profiler 1.0.0 → 1.0.2
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
- data/CHANGELOG.md +10 -0
- data/README.md +13 -4
- data/lib/memory_profiler/autorun.rb +17 -0
- data/lib/memory_profiler/cli.rb +24 -26
- data/lib/memory_profiler/helpers.rb +1 -1
- data/lib/memory_profiler/reporter.rb +7 -7
- data/lib/memory_profiler/results.rb +32 -22
- data/lib/memory_profiler/top_n.rb +24 -16
- data/lib/memory_profiler/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a33279b5aa11573ac4eb1c99c14d8ec0ac944827da175b6eef03d77bc57c652f
|
|
4
|
+
data.tar.gz: dc21db4963eb3840031d72de3b095a1a3414265dac0cd7df06de0c83ef0b95ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 421e253a198c2bc3881f57ac9f016bf69a5644f96eef211c1073442ce45b6a79021c3af2cdd77a8f916607080127d96a991c7c4fcd7b650ef107dd93161233ee
|
|
7
|
+
data.tar.gz: 8582b18f48088f242734d353ddd86a09ee9e2a1b2bb1ccdd1e066dd45ed29da2ef3417f2b1a82667c3863445358464f73fdc89e1e715c621d627469fc7b39be4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.2 - 17-06-2024
|
|
4
|
+
|
|
5
|
+
- Add ability to profile commands via CLI @fatkodima
|
|
6
|
+
|
|
7
|
+
## 1.0.1 - 23-10-2022
|
|
8
|
+
|
|
9
|
+
- Adapts tests to Ruby 3.0 / 3.1
|
|
10
|
+
- Lazy report evaluation
|
|
11
|
+
- Tested under Truffle Ruby
|
|
12
|
+
|
|
3
13
|
## 1.0.0 - 02-12-2020
|
|
4
14
|
|
|
5
15
|
- Added new CLI `ruby-memory-profiler` which can be used to profile scripts @fatkodima
|
data/README.md
CHANGED
|
@@ -32,14 +32,23 @@ There are two ways to use `memory_profiler`:
|
|
|
32
32
|
### Command Line
|
|
33
33
|
|
|
34
34
|
The easiest way to use memory_profiler is via the command line, which requires no modifications to your program. The basic usage is:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
$ ruby-memory-profiler [options] run [--] command [command-options]
|
|
35
38
|
```
|
|
36
|
-
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
$ ruby-memory-profiler --pretty run -- rubocop --cache false
|
|
44
|
+
|
|
45
|
+
$ ruby-memory-profiler --max=10 --pretty run -- ruby notify_users.rb 1 2 3 --quiet
|
|
37
46
|
```
|
|
38
|
-
Where `script.rb` is the program you want to profile.
|
|
39
47
|
|
|
40
48
|
For a full list of options, execute the following command:
|
|
49
|
+
|
|
41
50
|
```
|
|
42
|
-
ruby-memory-profiler -h
|
|
51
|
+
$ ruby-memory-profiler -h
|
|
43
52
|
```
|
|
44
53
|
|
|
45
54
|
### Convenience API
|
|
@@ -112,7 +121,7 @@ The `pretty_print` method can take a few options:
|
|
|
112
121
|
* `to_file`: a path to your log file - can be given a String
|
|
113
122
|
* `color_output`: a flag for whether to colorize output - can be given a Boolean
|
|
114
123
|
* `retained_strings`: how many retained strings to print - can be given an Integer
|
|
115
|
-
* `allocated_strings`: how many allocated strings to print - can be given a
|
|
124
|
+
* `allocated_strings`: how many allocated strings to print - can be given a Integer
|
|
116
125
|
* `detailed_report`: should report include detailed information - can be given a Boolean
|
|
117
126
|
* `scale_bytes`: flag to convert byte units (e.g. 183200000 is reported as 183.2 MB, rounds with a precision of 2 decimal digits) - can be given a Boolean
|
|
118
127
|
* `normalize_paths`: flag to remove a gem's directory path from printed locations - can be given a Boolean
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "memory_profiler"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
def deserialize_hash(data)
|
|
7
|
+
Marshal.load(Base64.urlsafe_decode64(data)) if data
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
options = deserialize_hash(ENV["MEMORY_PROFILER_OPTIONS"]) || {}
|
|
11
|
+
|
|
12
|
+
at_exit do
|
|
13
|
+
report = MemoryProfiler.stop
|
|
14
|
+
report.pretty_print(**options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
MemoryProfiler.start(options)
|
data/lib/memory_profiler/cli.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
+
require "base64"
|
|
4
5
|
|
|
5
6
|
module MemoryProfiler
|
|
6
7
|
class CLI
|
|
@@ -14,20 +15,6 @@ module MemoryProfiler
|
|
|
14
15
|
ignore_files: "memory_profiler/lib"
|
|
15
16
|
}.freeze
|
|
16
17
|
|
|
17
|
-
REPORTER_KEYS = [
|
|
18
|
-
:top, :trace, :ignore_files, :allow_files
|
|
19
|
-
].freeze
|
|
20
|
-
|
|
21
|
-
RESULTS_KEYS = [
|
|
22
|
-
:to_file, :color_output, :retained_strings, :allocated_strings,
|
|
23
|
-
:detailed_report, :scale_bytes, :normalize_paths
|
|
24
|
-
].freeze
|
|
25
|
-
|
|
26
|
-
private_constant :BIN_NAME, :VERSION_INFO,:STATUS_SUCCESS, :STATUS_ERROR,
|
|
27
|
-
:DEFAULTS, :REPORTER_KEYS, :RESULTS_KEYS
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
31
18
|
def run(argv)
|
|
32
19
|
options = {}
|
|
33
20
|
parser = option_parser(options)
|
|
@@ -43,17 +30,24 @@ module MemoryProfiler
|
|
|
43
30
|
return STATUS_ERROR
|
|
44
31
|
end
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
if script == "run"
|
|
34
|
+
# We are profiling a command.
|
|
35
|
+
profile_command(options, argv)
|
|
36
|
+
else
|
|
37
|
+
# We are profiling a ruby file.
|
|
38
|
+
begin
|
|
39
|
+
MemoryProfiler.start(options)
|
|
40
|
+
load(script)
|
|
41
|
+
ensure
|
|
42
|
+
report = MemoryProfiler.stop
|
|
43
|
+
report.pretty_print(**options)
|
|
44
|
+
end
|
|
45
|
+
STATUS_SUCCESS
|
|
46
|
+
end
|
|
50
47
|
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e
|
|
51
48
|
puts parser
|
|
52
49
|
puts e.message
|
|
53
50
|
STATUS_ERROR
|
|
54
|
-
ensure
|
|
55
|
-
report = MemoryProfiler.stop
|
|
56
|
-
report&.pretty_print(**results_options(options))
|
|
57
51
|
end
|
|
58
52
|
|
|
59
53
|
private
|
|
@@ -66,7 +60,7 @@ module MemoryProfiler
|
|
|
66
60
|
A Memory Profiler for Ruby
|
|
67
61
|
|
|
68
62
|
Usage:
|
|
69
|
-
#{BIN_NAME} [options]
|
|
63
|
+
#{BIN_NAME} [options] run [--] command [command-options]
|
|
70
64
|
BANNER
|
|
71
65
|
|
|
72
66
|
opts.separator ""
|
|
@@ -138,12 +132,16 @@ module MemoryProfiler
|
|
|
138
132
|
end
|
|
139
133
|
end
|
|
140
134
|
|
|
141
|
-
def
|
|
142
|
-
|
|
135
|
+
def profile_command(options, argv)
|
|
136
|
+
env = {}
|
|
137
|
+
env["MEMORY_PROFILER_OPTIONS"] = serialize_hash(options) if options.any?
|
|
138
|
+
gem_path = File.expand_path('../', __dir__)
|
|
139
|
+
env["RUBYOPT"] = "-I #{gem_path} -r memory_profiler/autorun #{ENV['RUBYOPT']}"
|
|
140
|
+
exec(env, *argv)
|
|
143
141
|
end
|
|
144
142
|
|
|
145
|
-
def
|
|
146
|
-
|
|
143
|
+
def serialize_hash(hash)
|
|
144
|
+
Base64.urlsafe_encode64(Marshal.dump(hash))
|
|
147
145
|
end
|
|
148
146
|
end
|
|
149
147
|
end
|
|
@@ -16,7 +16,7 @@ module MemoryProfiler
|
|
|
16
16
|
gemname
|
|
17
17
|
elsif /\/rubygems[\.\/]/ =~ path
|
|
18
18
|
"rubygems"
|
|
19
|
-
elsif /ruby
|
|
19
|
+
elsif /ruby\/\d\.[^\/]+\/(?<stdlib>[^\/\.]+)/ =~ path
|
|
20
20
|
stdlib
|
|
21
21
|
elsif /(?<app>[^\/]+\/(bin|app|lib))/ =~ path
|
|
22
22
|
app
|
|
@@ -34,8 +34,7 @@ module MemoryProfiler
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def start
|
|
37
|
-
GC.start
|
|
38
|
-
GC.start
|
|
37
|
+
3.times { GC.start }
|
|
39
38
|
GC.start
|
|
40
39
|
GC.disable
|
|
41
40
|
|
|
@@ -49,8 +48,11 @@ module MemoryProfiler
|
|
|
49
48
|
retained = StatHash.new.compare_by_identity
|
|
50
49
|
|
|
51
50
|
GC.enable
|
|
52
|
-
GC
|
|
53
|
-
|
|
51
|
+
# for whatever reason doing GC in a block is more effective at
|
|
52
|
+
# freeing objects.
|
|
53
|
+
# full_mark: true, immediate_mark: true, immediate_sweep: true are already default
|
|
54
|
+
3.times { GC.start }
|
|
55
|
+
# another start outside of the block to release the block
|
|
54
56
|
GC.start
|
|
55
57
|
|
|
56
58
|
# Caution: Do not allocate any new Objects between the call to GC.start and the completion of the retained
|
|
@@ -86,8 +88,6 @@ module MemoryProfiler
|
|
|
86
88
|
# Iterates through objects in memory of a given generation.
|
|
87
89
|
# Stores results along with meta data of objects collected.
|
|
88
90
|
def object_list(generation)
|
|
89
|
-
|
|
90
|
-
rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
|
|
91
91
|
helper = Helpers.new
|
|
92
92
|
|
|
93
93
|
result = StatHash.new.compare_by_identity
|
|
@@ -114,7 +114,7 @@ module MemoryProfiler
|
|
|
114
114
|
string = klass == String ? helper.lookup_string(obj) : nil
|
|
115
115
|
|
|
116
116
|
# compensate for API bug
|
|
117
|
-
memsize =
|
|
117
|
+
memsize = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] if memsize > 100_000_000_000
|
|
118
118
|
result[obj.__id__] = MemoryProfiler::Stat.new(class_name, gem, file, location, memsize, string)
|
|
119
119
|
rescue
|
|
120
120
|
# give up if any any error occurs inspecting the object
|
|
@@ -24,7 +24,16 @@ module MemoryProfiler
|
|
|
24
24
|
|
|
25
25
|
TYPES.each do |type|
|
|
26
26
|
METRICS.each do |metric|
|
|
27
|
-
|
|
27
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
28
|
+
def #{type}_#{metric}_by_#{name} # def allocated_memory_by_file
|
|
29
|
+
@#{type}_#{metric}_by ||= {} # @allocated_memory_by ||= {}
|
|
30
|
+
#
|
|
31
|
+
@#{type}_#{metric}_by['#{name}'] ||= begin # @allocated_memory_by['file'] ||= begin
|
|
32
|
+
_, stat_attribute = @@lookups.find { |(n, _stat_attribute)| n == '#{name}' } # _, stat_attribute = @@lookups.find { |(n, _stat_attribute)| n == 'file' }
|
|
33
|
+
@#{type}.top_n_#{metric}(@top, stat_attribute) # @allocated.top_n_memory(@top, stat_attribute)
|
|
34
|
+
end # end
|
|
35
|
+
end # end
|
|
36
|
+
RUBY
|
|
28
37
|
end
|
|
29
38
|
end
|
|
30
39
|
end
|
|
@@ -34,27 +43,20 @@ module MemoryProfiler
|
|
|
34
43
|
register_type 'location', :location
|
|
35
44
|
register_type 'class', :class_name
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
attr_writer :strings_retained, :strings_allocated
|
|
38
47
|
attr_accessor :total_retained, :total_allocated
|
|
39
48
|
attr_accessor :total_retained_memsize, :total_allocated_memsize
|
|
40
49
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
self.send("allocated_memory_by_#{name}=", memsize_results)
|
|
48
|
-
self.send("allocated_objects_by_#{name}=", count_results)
|
|
49
|
-
|
|
50
|
-
memsize_results, count_results = retained.top_n(top, stat_attribute)
|
|
51
|
-
|
|
52
|
-
self.send("retained_memory_by_#{name}=", memsize_results)
|
|
53
|
-
self.send("retained_objects_by_#{name}=", count_results)
|
|
54
|
-
end
|
|
50
|
+
def initialize
|
|
51
|
+
@allocated = StatHash.new
|
|
52
|
+
@retained = StatHash.new
|
|
53
|
+
@top = 50
|
|
54
|
+
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
def register_results(allocated, retained, top)
|
|
57
|
+
@allocated = allocated
|
|
58
|
+
@retained = retained
|
|
59
|
+
@top = top
|
|
58
60
|
|
|
59
61
|
self.total_allocated = allocated.size
|
|
60
62
|
self.total_allocated_memsize = total_memsize(allocated)
|
|
@@ -64,6 +66,14 @@ module MemoryProfiler
|
|
|
64
66
|
self
|
|
65
67
|
end
|
|
66
68
|
|
|
69
|
+
def strings_allocated
|
|
70
|
+
@strings_allocated ||= string_report(@allocated, @top)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def strings_retained
|
|
74
|
+
@strings_retained ||= string_report(@retained, @top)
|
|
75
|
+
end
|
|
76
|
+
|
|
67
77
|
def scale_bytes(bytes)
|
|
68
78
|
return "0 B" if bytes.zero?
|
|
69
79
|
|
|
@@ -139,10 +149,10 @@ module MemoryProfiler
|
|
|
139
149
|
end
|
|
140
150
|
end
|
|
141
151
|
end
|
|
142
|
-
end
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
io.puts
|
|
154
|
+
print_string_reports(io, options)
|
|
155
|
+
end
|
|
146
156
|
|
|
147
157
|
io.close if io.is_a? File
|
|
148
158
|
end
|
|
@@ -162,7 +172,7 @@ module MemoryProfiler
|
|
|
162
172
|
@normalize_path[path] ||= begin
|
|
163
173
|
if %r!(/gems/.*)*/gems/(?<gemname>[^/]+)(?<rest>.*)! =~ path
|
|
164
174
|
"#{gemname}#{rest}"
|
|
165
|
-
elsif %r!ruby
|
|
175
|
+
elsif %r!ruby/\d\.[^/]+/(?<stdlib>[^/.]+)(?<rest>.*)! =~ path
|
|
166
176
|
"ruby/lib/#{stdlib}#{rest}"
|
|
167
177
|
elsif %r!(?<app>[^/]+/(bin|app|lib))(?<rest>.*)! =~ path
|
|
168
178
|
"#{app}#{rest}"
|
|
@@ -5,32 +5,40 @@ module MemoryProfiler
|
|
|
5
5
|
# Fast approach for determining the top_n entries in a Hash of Stat objects.
|
|
6
6
|
# Returns results for both memory (memsize summed) and objects allocated (count) as a tuple.
|
|
7
7
|
def top_n(max, metric_method)
|
|
8
|
+
[
|
|
9
|
+
top_n_memory(max, metric_method),
|
|
10
|
+
top_n_objects(max, metric_method)
|
|
11
|
+
]
|
|
12
|
+
end
|
|
8
13
|
|
|
14
|
+
def top_n_memory(max, metric_method)
|
|
9
15
|
metric_memsize = Hash.new(0)
|
|
10
|
-
metric_objects_count = Hash.new(0)
|
|
11
16
|
|
|
12
17
|
each_value do |value|
|
|
13
18
|
metric = value.send(metric_method)
|
|
14
|
-
|
|
15
19
|
metric_memsize[metric] += value.memsize
|
|
16
|
-
metric_objects_count[metric] += 1
|
|
17
20
|
end
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
metric_memsize
|
|
23
|
+
.to_a
|
|
24
|
+
.sort_by! { |metric, memsize| [-memsize, metric] }
|
|
25
|
+
.take(max)
|
|
26
|
+
.map! { |metric, memsize| { data: metric, count: memsize } }
|
|
27
|
+
end
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
def top_n_objects(max, metric_method)
|
|
30
|
+
metric_objects_count = Hash.new(0)
|
|
31
|
+
|
|
32
|
+
each_value do |value|
|
|
33
|
+
metric = value.send(metric_method)
|
|
34
|
+
metric_objects_count[metric] += 1
|
|
35
|
+
end
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
metric_objects_count
|
|
38
|
+
.to_a
|
|
39
|
+
.sort_by! { |metric, count| [-count, metric] }
|
|
40
|
+
.take(max)
|
|
41
|
+
.map! { |metric, count| { data: metric, count: count } }
|
|
34
42
|
end
|
|
35
43
|
end
|
|
36
44
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: memory_profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-06-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Memory profiling routines for Ruby 2.5+
|
|
14
14
|
email:
|
|
@@ -23,6 +23,7 @@ files:
|
|
|
23
23
|
- README.md
|
|
24
24
|
- bin/ruby-memory-profiler
|
|
25
25
|
- lib/memory_profiler.rb
|
|
26
|
+
- lib/memory_profiler/autorun.rb
|
|
26
27
|
- lib/memory_profiler/cli.rb
|
|
27
28
|
- lib/memory_profiler/helpers.rb
|
|
28
29
|
- lib/memory_profiler/monochrome.rb
|
|
@@ -52,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
52
53
|
- !ruby/object:Gem::Version
|
|
53
54
|
version: '0'
|
|
54
55
|
requirements: []
|
|
55
|
-
rubygems_version: 3.
|
|
56
|
+
rubygems_version: 3.5.11
|
|
56
57
|
signing_key:
|
|
57
58
|
specification_version: 4
|
|
58
59
|
summary: Memory profiling routines for Ruby 2.5+
|