memory_profiler 0.9.14 → 1.0.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +22 -3
- data/bin/ruby-memory-profiler +8 -0
- data/lib/memory_profiler/cli.rb +149 -0
- data/lib/memory_profiler/helpers.rb +30 -3
- data/lib/memory_profiler/polychrome.rb +2 -2
- data/lib/memory_profiler/reporter.rb +8 -12
- data/lib/memory_profiler/results.rb +52 -31
- data/lib/memory_profiler/top_n.rb +30 -17
- data/lib/memory_profiler/version.rb +1 -1
- data/lib/memory_profiler.rb +1 -0
- metadata +13 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4c8c5a12cd11ab3f9c5ab852e6bca0cb442ccc25f54997f49243a19aa6cef9b
|
|
4
|
+
data.tar.gz: 5c0ca217ca3cbc0edca725071512688443ac24a5d3e9f28980d93d3dd30a323f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11d3bf57b920c915a0a98f6cea8798349983b0c64047f481ab1227301ff100a8de16f858ee7d21028d32e3dd04015446f77555bfd050755637714da5d1cc08b3
|
|
7
|
+
data.tar.gz: 46d2df335163a6ddc35ea3a0eff97573dc7b8a50d671e9aa8940a5acb300ea342d7eb38e10f03f39f3705c4e9ed318a336c68f72ddf39a5cdf4f6bb81b9cc7e5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.1 - 23-10-2022
|
|
4
|
+
|
|
5
|
+
- Adapts tests to Ruby 3.0 / 3.1
|
|
6
|
+
- Lazy report evaluation
|
|
7
|
+
- Tested under Truffle Ruby
|
|
8
|
+
|
|
9
|
+
## 1.0.0 - 02-12-2020
|
|
10
|
+
|
|
11
|
+
- Added new CLI `ruby-memory-profiler` which can be used to profile scripts @fatkodima
|
|
12
|
+
- Reduced memory usage when generating reports
|
|
13
|
+
- Some optimizations for Ruby 2.7
|
|
14
|
+
- Remove EOL Rubies: 2.3 and 2.4 are no longer supported (use an earlier version of the gem if needed)
|
|
15
|
+
|
|
3
16
|
## 0.9.14 - 28-06-2019
|
|
4
17
|
|
|
5
18
|
- Pass 'normalize_path: true' to pretty_print to have locations stripped
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/SamSaffron/memory_profiler/actions?query=workflow%3ACI)
|
|
2
2
|
[](https://rubygems.org/gems/memory_profiler)
|
|
3
3
|
|
|
4
4
|
# MemoryProfiler
|
|
@@ -7,7 +7,7 @@ A memory profiler for Ruby
|
|
|
7
7
|
|
|
8
8
|
## Requirements
|
|
9
9
|
|
|
10
|
-
Ruby(MRI) Version 2.
|
|
10
|
+
Ruby(MRI) Version 2.5.0 and above.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -25,6 +25,25 @@ Or install it yourself as:
|
|
|
25
25
|
|
|
26
26
|
## Usage
|
|
27
27
|
|
|
28
|
+
There are two ways to use `memory_profiler`:
|
|
29
|
+
* command line
|
|
30
|
+
* convenience API
|
|
31
|
+
|
|
32
|
+
### Command Line
|
|
33
|
+
|
|
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
|
+
$ ruby-memory-profiler [options] <script.rb> [--] [script-options]
|
|
37
|
+
```
|
|
38
|
+
Where `script.rb` is the program you want to profile.
|
|
39
|
+
|
|
40
|
+
For a full list of options, execute the following command:
|
|
41
|
+
```
|
|
42
|
+
ruby-memory-profiler -h
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Convenience API
|
|
46
|
+
|
|
28
47
|
```ruby
|
|
29
48
|
require 'memory_profiler'
|
|
30
49
|
report = MemoryProfiler.report do
|
|
@@ -93,7 +112,7 @@ The `pretty_print` method can take a few options:
|
|
|
93
112
|
* `to_file`: a path to your log file - can be given a String
|
|
94
113
|
* `color_output`: a flag for whether to colorize output - can be given a Boolean
|
|
95
114
|
* `retained_strings`: how many retained strings to print - can be given an Integer
|
|
96
|
-
* `allocated_strings`: how many allocated strings to print - can be given a
|
|
115
|
+
* `allocated_strings`: how many allocated strings to print - can be given a Integer
|
|
97
116
|
* `detailed_report`: should report include detailed information - can be given a Boolean
|
|
98
117
|
* `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
|
|
99
118
|
* `normalize_paths`: flag to remove a gem's directory path from printed locations - can be given a Boolean
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module MemoryProfiler
|
|
6
|
+
class CLI
|
|
7
|
+
BIN_NAME = "ruby-memory-profiler"
|
|
8
|
+
VERSION_INFO = "#{BIN_NAME} #{MemoryProfiler::VERSION}"
|
|
9
|
+
|
|
10
|
+
STATUS_SUCCESS = 0
|
|
11
|
+
STATUS_ERROR = 1
|
|
12
|
+
|
|
13
|
+
DEFAULTS = {
|
|
14
|
+
ignore_files: "memory_profiler/lib"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
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
|
+
def run(argv)
|
|
32
|
+
options = {}
|
|
33
|
+
parser = option_parser(options)
|
|
34
|
+
parser.parse!(argv)
|
|
35
|
+
|
|
36
|
+
options = DEFAULTS.merge(options)
|
|
37
|
+
|
|
38
|
+
# Make sure the user specified at least one file
|
|
39
|
+
unless (script = argv.shift)
|
|
40
|
+
puts parser
|
|
41
|
+
puts ""
|
|
42
|
+
puts "#{VERSION_INFO} | ERROR: Must specify a script to run"
|
|
43
|
+
return STATUS_ERROR
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
MemoryProfiler.start(reporter_options(options))
|
|
47
|
+
load script
|
|
48
|
+
|
|
49
|
+
STATUS_SUCCESS
|
|
50
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e
|
|
51
|
+
puts parser
|
|
52
|
+
puts e.message
|
|
53
|
+
STATUS_ERROR
|
|
54
|
+
ensure
|
|
55
|
+
report = MemoryProfiler.stop
|
|
56
|
+
report&.pretty_print(**results_options(options))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def option_parser(options)
|
|
62
|
+
OptionParser.new do |opts|
|
|
63
|
+
opts.banner = <<~BANNER
|
|
64
|
+
|
|
65
|
+
#{VERSION_INFO}
|
|
66
|
+
A Memory Profiler for Ruby
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
#{BIN_NAME} [options] <script.rb> [--] [script-options]
|
|
70
|
+
BANNER
|
|
71
|
+
|
|
72
|
+
opts.separator ""
|
|
73
|
+
opts.separator "Options:"
|
|
74
|
+
|
|
75
|
+
# Reporter options
|
|
76
|
+
opts.on("-m", "--max=NUM", Integer, "Max number of entries to output. (Defaults to 50)") do |arg|
|
|
77
|
+
options[:top] = arg
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
opts.on("--classes=CLASSES", Array, "A class or list of classes you explicitly want to trace.") do |arg|
|
|
81
|
+
options[:trace] = arg.map { |klass| Object.const_get(klass) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
opts.on("--ignore-files=REGEXP", "A regular expression used to exclude certain files from tracing.") do |arg|
|
|
85
|
+
options[:ignore_files] = "#{arg}|memory_profiler/lib"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
opts.on("--allow-files=FILES", Array, "A string or list of strings to selectively include in tracing.") do |arg|
|
|
89
|
+
options[:allow_files] = arg
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
opts.separator ""
|
|
93
|
+
|
|
94
|
+
# Results options
|
|
95
|
+
opts.on("-o", "--out=FILE", "Write output to a file instead of STDOUT.") do |arg|
|
|
96
|
+
options[:to_file] = arg
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
opts.on("--[no-]color", "Force color output on or off. (Enabled by default)") do |arg|
|
|
100
|
+
options[:color_output] = arg
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
opts.on("--retained-strings=NUM", Integer, "How many retained strings to print.") do |arg|
|
|
104
|
+
options[:retained_strings] = arg
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
opts.on("--allocated-strings=NUM", Integer, "How many allocated strings to print.") do |arg|
|
|
108
|
+
options[:allocated_strings] = arg
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
opts.on("--[no-]detailed", "Print detailed information. (Enabled by default)") do |arg|
|
|
112
|
+
options[:detailed_report] = arg
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
opts.on("--scale-bytes", "Calculates unit prefixes for the numbers of bytes.") do
|
|
116
|
+
options[:scale_bytes] = true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
opts.on("--normalize-paths", "Print location paths relative to gem's source directory.") do
|
|
120
|
+
options[:normalize_paths] = true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
opts.on("--pretty", "Easily enable options 'scale-bytes' and 'normalize-paths'") do
|
|
124
|
+
options[:scale_bytes] = options[:normalize_paths] = true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
opts.separator ""
|
|
128
|
+
|
|
129
|
+
opts.on_tail("-h", "--help", "Show this help message.") do
|
|
130
|
+
puts opts
|
|
131
|
+
exit
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
opts.on_tail("-v", "--version", "Show program version.") do
|
|
135
|
+
puts VERSION_INFO
|
|
136
|
+
exit
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def reporter_options(options)
|
|
142
|
+
options.select { |k, _v| REPORTER_KEYS.include?(k) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def results_options(options)
|
|
146
|
+
options.select { |k, _v| RESULTS_KEYS.include?(k) }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
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
|
|
@@ -29,8 +29,35 @@ module MemoryProfiler
|
|
|
29
29
|
@location_cache[file][line] ||= "#{file}:#{line}"
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
KERNEL_CLASS_METHOD = Kernel.instance_method(:class)
|
|
33
|
+
if UnboundMethod.method_defined?(:bind_call)
|
|
34
|
+
def object_class(obj)
|
|
35
|
+
klass = obj.class rescue nil
|
|
36
|
+
unless Class === klass
|
|
37
|
+
# attempt to determine the true Class when .class returns something other than a Class
|
|
38
|
+
klass = KERNEL_CLASS_METHOD.bind_call(obj)
|
|
39
|
+
end
|
|
40
|
+
klass
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
def object_class(obj)
|
|
44
|
+
klass = obj.class rescue nil
|
|
45
|
+
unless Class === klass
|
|
46
|
+
# attempt to determine the true Class when .class returns something other than a Class
|
|
47
|
+
klass = KERNEL_CLASS_METHOD.bind(obj).call
|
|
48
|
+
end
|
|
49
|
+
klass
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if Object.name.frozen? # Since Ruby 2.7 Module#name no longer allocate a new string
|
|
54
|
+
def lookup_class_name(klass)
|
|
55
|
+
((klass.is_a?(Class) && klass.name) || '<<Unknown>>').to_s
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
def lookup_class_name(klass)
|
|
59
|
+
@class_name_cache[klass] ||= ((klass.is_a?(Class) && klass.name) || '<<Unknown>>').to_s
|
|
60
|
+
end
|
|
34
61
|
end
|
|
35
62
|
|
|
36
63
|
def lookup_string(obj)
|
|
@@ -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
|
|
@@ -99,11 +99,7 @@ module MemoryProfiler
|
|
|
99
99
|
next if @ignore_files && @ignore_files =~ file
|
|
100
100
|
next if @allow_files && !(@allow_files =~ file)
|
|
101
101
|
|
|
102
|
-
klass = obj
|
|
103
|
-
unless Class === klass
|
|
104
|
-
# attempt to determine the true Class when .class returns something other than a Class
|
|
105
|
-
klass = Kernel.instance_method(:class).bind(obj).call
|
|
106
|
-
end
|
|
102
|
+
klass = helper.object_class(obj)
|
|
107
103
|
next if @trace && !trace.include?(klass)
|
|
108
104
|
|
|
109
105
|
begin
|
|
@@ -118,7 +114,7 @@ module MemoryProfiler
|
|
|
118
114
|
string = klass == String ? helper.lookup_string(obj) : nil
|
|
119
115
|
|
|
120
116
|
# compensate for API bug
|
|
121
|
-
memsize =
|
|
117
|
+
memsize = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] if memsize > 100_000_000_000
|
|
122
118
|
result[obj.__id__] = MemoryProfiler::Stat.new(class_name, gem, file, location, memsize, string)
|
|
123
119
|
rescue
|
|
124
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,36 +43,37 @@ 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
|
-
self.total_allocated_memsize = allocated
|
|
62
|
+
self.total_allocated_memsize = total_memsize(allocated)
|
|
61
63
|
self.total_retained = retained.size
|
|
62
|
-
self.total_retained_memsize = retained
|
|
64
|
+
self.total_retained_memsize = total_memsize(retained)
|
|
63
65
|
|
|
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
|
|
|
@@ -73,19 +83,22 @@ module MemoryProfiler
|
|
|
73
83
|
end
|
|
74
84
|
|
|
75
85
|
def string_report(data, top)
|
|
76
|
-
grouped_strings =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
grouped_strings = Hash.new { |hash, key| hash[key] = [] }
|
|
87
|
+
data.each_value do |stat|
|
|
88
|
+
if stat.string_value
|
|
89
|
+
grouped_strings[stat.string_value.object_id] << stat
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
grouped_strings = grouped_strings.values
|
|
80
94
|
|
|
81
95
|
if grouped_strings.size > top
|
|
82
|
-
|
|
83
|
-
grouped_strings
|
|
96
|
+
grouped_strings.sort_by!(&:size)
|
|
97
|
+
grouped_strings = grouped_strings.drop(grouped_strings.size - top)
|
|
84
98
|
end
|
|
85
99
|
|
|
86
100
|
grouped_strings
|
|
87
101
|
.sort! { |a, b| a.size == b.size ? a[0].string_value <=> b[0].string_value : b.size <=> a.size }
|
|
88
|
-
.first(top)
|
|
89
102
|
.map! do |list|
|
|
90
103
|
# Return array of [string, [[location, count], [location, count], ...]
|
|
91
104
|
[
|
|
@@ -136,10 +149,10 @@ module MemoryProfiler
|
|
|
136
149
|
end
|
|
137
150
|
end
|
|
138
151
|
end
|
|
139
|
-
end
|
|
140
152
|
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
io.puts
|
|
154
|
+
print_string_reports(io, options)
|
|
155
|
+
end
|
|
143
156
|
|
|
144
157
|
io.close if io.is_a? File
|
|
145
158
|
end
|
|
@@ -159,7 +172,7 @@ module MemoryProfiler
|
|
|
159
172
|
@normalize_path[path] ||= begin
|
|
160
173
|
if %r!(/gems/.*)*/gems/(?<gemname>[^/]+)(?<rest>.*)! =~ path
|
|
161
174
|
"#{gemname}#{rest}"
|
|
162
|
-
elsif %r!ruby
|
|
175
|
+
elsif %r!ruby/\d\.[^/]+/(?<stdlib>[^/.]+)(?<rest>.*)! =~ path
|
|
163
176
|
"ruby/lib/#{stdlib}#{rest}"
|
|
164
177
|
elsif %r!(?<app>[^/]+/(bin|app|lib))(?<rest>.*)! =~ path
|
|
165
178
|
"#{app}#{rest}"
|
|
@@ -171,6 +184,14 @@ module MemoryProfiler
|
|
|
171
184
|
|
|
172
185
|
private
|
|
173
186
|
|
|
187
|
+
def total_memsize(stat_hash)
|
|
188
|
+
sum = 0
|
|
189
|
+
stat_hash.each_value do |stat|
|
|
190
|
+
sum += stat.memsize
|
|
191
|
+
end
|
|
192
|
+
sum
|
|
193
|
+
end
|
|
194
|
+
|
|
174
195
|
def print_title(io, title)
|
|
175
196
|
io.puts
|
|
176
197
|
io.puts title
|
|
@@ -5,27 +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
|
|
13
|
+
|
|
14
|
+
def top_n_memory(max, metric_method)
|
|
15
|
+
metric_memsize = Hash.new(0)
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
each_value do |value|
|
|
18
|
+
metric = value.send(metric_method)
|
|
19
|
+
metric_memsize[metric] += value.memsize
|
|
20
|
+
end
|
|
21
|
+
|
|
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
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.sort_by! { |metric, memsize, _count| [-memsize, metric] }
|
|
19
|
-
.take(max)
|
|
20
|
-
.map! { |metric, memsize, _count| { data: metric, count: memsize } }
|
|
29
|
+
def top_n_objects(max, metric_method)
|
|
30
|
+
metric_objects_count = Hash.new(0)
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.map! { |metric, _memsize, count| { data: metric, count: count } }
|
|
32
|
+
each_value do |value|
|
|
33
|
+
metric = value.send(metric_method)
|
|
34
|
+
metric_objects_count[metric] += 1
|
|
35
|
+
end
|
|
27
36
|
|
|
28
|
-
|
|
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 } }
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
44
|
end
|
data/lib/memory_profiler.rb
CHANGED
metadata
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: memory_profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-10-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description: Memory profiling routines for Ruby 2.
|
|
13
|
+
description: Memory profiling routines for Ruby 2.5+
|
|
14
14
|
email:
|
|
15
15
|
- sam.saffron@gmail.com
|
|
16
|
-
executables:
|
|
16
|
+
executables:
|
|
17
|
+
- ruby-memory-profiler
|
|
17
18
|
extensions: []
|
|
18
19
|
extra_rdoc_files: []
|
|
19
20
|
files:
|
|
20
21
|
- CHANGELOG.md
|
|
21
22
|
- LICENSE.txt
|
|
22
23
|
- README.md
|
|
24
|
+
- bin/ruby-memory-profiler
|
|
23
25
|
- lib/memory_profiler.rb
|
|
26
|
+
- lib/memory_profiler/cli.rb
|
|
24
27
|
- lib/memory_profiler/helpers.rb
|
|
25
28
|
- lib/memory_profiler/monochrome.rb
|
|
26
29
|
- lib/memory_profiler/polychrome.rb
|
|
@@ -34,7 +37,7 @@ homepage: https://github.com/SamSaffron/memory_profiler
|
|
|
34
37
|
licenses:
|
|
35
38
|
- MIT
|
|
36
39
|
metadata: {}
|
|
37
|
-
post_install_message:
|
|
40
|
+
post_install_message:
|
|
38
41
|
rdoc_options: []
|
|
39
42
|
require_paths:
|
|
40
43
|
- lib
|
|
@@ -42,15 +45,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
42
45
|
requirements:
|
|
43
46
|
- - ">="
|
|
44
47
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: 2.
|
|
48
|
+
version: 2.5.0
|
|
46
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
50
|
requirements:
|
|
48
51
|
- - ">="
|
|
49
52
|
- !ruby/object:Gem::Version
|
|
50
53
|
version: '0'
|
|
51
54
|
requirements: []
|
|
52
|
-
rubygems_version: 3.
|
|
53
|
-
signing_key:
|
|
55
|
+
rubygems_version: 3.3.20
|
|
56
|
+
signing_key:
|
|
54
57
|
specification_version: 4
|
|
55
|
-
summary: Memory profiling routines for Ruby 2.
|
|
58
|
+
summary: Memory profiling routines for Ruby 2.5+
|
|
56
59
|
test_files: []
|