memory_profiler 0.9.13 → 0.9.14
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 +5 -0
- data/README.md +3 -0
- data/lib/memory_profiler.rb +3 -5
- data/lib/memory_profiler/helpers.rb +2 -2
- data/lib/memory_profiler/monochrome.rb +1 -1
- data/lib/memory_profiler/reporter.rb +2 -2
- data/lib/memory_profiler/results.rb +105 -54
- data/lib/memory_profiler/top_n.rb +17 -8
- data/lib/memory_profiler/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 187acbd9601ade1bb6ccdb60bfb4c305ada15097f02278529f8f6a6fc7d68666
|
4
|
+
data.tar.gz: 33d797db658d4a4507247a0ae9c4b92da568b86e9246f4571a94774b62285c6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 977f958e6b9f3292053ccef51bcef01828cda7d29760f131687d21b00222950fbf1ec4629a7021bfdc4665e137c3f75cfd729a29db60b879a5c30f08da0de097
|
7
|
+
data.tar.gz: 1cc221f3bf20520a3063031a4d9830e3303d9d9182cafb746aa77d0059888a4a640f2d6dd39a925e31398664018c98e9324565247046310c15ef823419bb6baf
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -96,6 +96,9 @@ The `pretty_print` method can take a few options:
|
|
96
96
|
* `allocated_strings`: how many allocated strings to print - can be given a String
|
97
97
|
* `detailed_report`: should report include detailed information - can be given a Boolean
|
98
98
|
* `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
|
+
* `normalize_paths`: flag to remove a gem's directory path from printed locations - can be given a Boolean
|
100
|
+
*Note: normalized path of a "location" from Ruby's stdlib will be prefixed with `ruby/lib/`. e.g.: `ruby/lib/set.rb`, `ruby/lib/pathname.rb`, etc.*
|
101
|
+
|
99
102
|
|
100
103
|
Check out `Results#pretty_print` for more details.
|
101
104
|
|
data/lib/memory_profiler.rb
CHANGED
@@ -11,11 +11,11 @@ require "memory_profiler/results"
|
|
11
11
|
require "memory_profiler/reporter"
|
12
12
|
|
13
13
|
module MemoryProfiler
|
14
|
-
def self.report(opts={}
|
15
|
-
Reporter.report(opts
|
14
|
+
def self.report(opts = {}, &block)
|
15
|
+
Reporter.report(opts, &block)
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.start(opts={})
|
18
|
+
def self.start(opts = {})
|
19
19
|
unless Reporter.current_reporter
|
20
20
|
Reporter.current_reporter = Reporter.new(opts)
|
21
21
|
Reporter.current_reporter.start
|
@@ -28,5 +28,3 @@ module MemoryProfiler
|
|
28
28
|
Reporter.current_reporter = nil
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
32
|
-
|
@@ -5,7 +5,7 @@ module MemoryProfiler
|
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
@gem_guess_cache = Hash.new
|
8
|
-
@location_cache = Hash.new { |h,k| h[k] = Hash.new.compare_by_identity }
|
8
|
+
@location_cache = Hash.new { |h, k| h[k] = Hash.new.compare_by_identity }
|
9
9
|
@class_name_cache = Hash.new.compare_by_identity
|
10
10
|
@string_cache = Hash.new
|
11
11
|
end
|
@@ -37,7 +37,7 @@ module MemoryProfiler
|
|
37
37
|
# This string is shortened to 200 characters which is what the string report shows
|
38
38
|
# The string report can still list unique strings longer than 200 characters
|
39
39
|
# separately because the object_id of the shortened string will be different
|
40
|
-
@string_cache[obj] ||= String.new << obj[0,200]
|
40
|
+
@string_cache[obj] ||= String.new << obj[0, 200]
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -29,7 +29,7 @@ module MemoryProfiler
|
|
29
29
|
# @option opts :ignore_files a regular expression used to exclude certain files from tracing
|
30
30
|
# @option opts :allow_files a string or array of strings to selectively include in tracing
|
31
31
|
# @return [MemoryProfiler::Results]
|
32
|
-
def self.report(opts={}, &block)
|
32
|
+
def self.report(opts = {}, &block)
|
33
33
|
self.new(opts).run(&block)
|
34
34
|
end
|
35
35
|
|
@@ -115,7 +115,7 @@ module MemoryProfiler
|
|
115
115
|
# we do memsize first to avoid freezing as a side effect and shifting
|
116
116
|
# storage to the new frozen string, this happens on @hash[s] in lookup_string
|
117
117
|
memsize = ObjectSpace.memsize_of(obj)
|
118
|
-
string
|
118
|
+
string = klass == String ? helper.lookup_string(obj) : nil
|
119
119
|
|
120
120
|
# compensate for API bug
|
121
121
|
memsize = rvalue_size if memsize > 100_000_000_000
|
@@ -14,12 +14,18 @@ module MemoryProfiler
|
|
14
14
|
24 => 'YB'
|
15
15
|
}.freeze
|
16
16
|
|
17
|
+
TYPES = ["allocated", "retained"].freeze
|
18
|
+
METRICS = ["memory", "objects"].freeze
|
19
|
+
NAMES = ["gem", "file", "location", "class"].freeze
|
20
|
+
|
17
21
|
def self.register_type(name, stat_attribute)
|
18
22
|
@@lookups ||= []
|
19
23
|
@@lookups << [name, stat_attribute]
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
TYPES.each do |type|
|
26
|
+
METRICS.each do |metric|
|
27
|
+
attr_accessor "#{type}_#{metric}_by_#{name}"
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
|
@@ -47,7 +53,6 @@ module MemoryProfiler
|
|
47
53
|
self.send("retained_objects_by_#{name}=", count_results)
|
48
54
|
end
|
49
55
|
|
50
|
-
|
51
56
|
self.strings_allocated = string_report(allocated, top)
|
52
57
|
self.strings_retained = string_report(retained, top)
|
53
58
|
|
@@ -64,30 +69,33 @@ module MemoryProfiler
|
|
64
69
|
|
65
70
|
scale = Math.log10(bytes).div(3) * 3
|
66
71
|
scale = 24 if scale > 24
|
67
|
-
"#{(bytes / 10.0**scale)
|
72
|
+
"%.2f #{UNIT_PREFIXES[scale]}" % (bytes / 10.0**scale)
|
68
73
|
end
|
69
74
|
|
70
75
|
def string_report(data, top)
|
71
|
-
grouped_strings = data.values
|
72
|
-
keep_if
|
73
|
-
group_by { |stat| stat.string_value.object_id }
|
74
|
-
values
|
76
|
+
grouped_strings = data.values
|
77
|
+
.keep_if { |stat| stat.string_value }
|
78
|
+
.group_by { |stat| stat.string_value.object_id }
|
79
|
+
.values
|
75
80
|
|
76
81
|
if grouped_strings.size > top
|
77
82
|
cutoff = grouped_strings.sort_by!(&:size)[-top].size
|
78
83
|
grouped_strings.keep_if { |list| list.size >= cutoff }
|
79
84
|
end
|
80
85
|
|
81
|
-
grouped_strings
|
82
|
-
sort! { |a, b| a.size == b.size ? a[0].string_value <=> b[0].string_value : b.size <=> a.size }
|
83
|
-
first(top)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
grouped_strings
|
87
|
+
.sort! { |a, b| a.size == b.size ? a[0].string_value <=> b[0].string_value : b.size <=> a.size }
|
88
|
+
.first(top)
|
89
|
+
.map! do |list|
|
90
|
+
# Return array of [string, [[location, count], [location, count], ...]
|
91
|
+
[
|
92
|
+
list[0].string_value,
|
93
|
+
list.group_by { |stat| stat.location }
|
94
|
+
.map { |location, stat_list| [location, stat_list.size] }
|
95
|
+
.sort_by!(&:last)
|
96
|
+
.reverse!
|
97
|
+
]
|
98
|
+
end
|
91
99
|
end
|
92
100
|
|
93
101
|
# Output the results of the report
|
@@ -98,6 +106,7 @@ module MemoryProfiler
|
|
98
106
|
# @option opts [Integer] :allocated_strings how many allocated strings to print
|
99
107
|
# @option opts [Boolean] :detailed_report should report include detailed information
|
100
108
|
# @option opts [Boolean] :scale_bytes calculates unit prefixes for the numbers of bytes
|
109
|
+
# @option opts [Boolean] :normalize_paths print location paths relative to gem's source directory.
|
101
110
|
def pretty_print(io = $stdout, **options)
|
102
111
|
# Handle the special case that Ruby PrettyPrint expects `pretty_print`
|
103
112
|
# to be a customized pretty printing function for a class
|
@@ -110,72 +119,114 @@ module MemoryProfiler
|
|
110
119
|
|
111
120
|
if options[:scale_bytes]
|
112
121
|
total_allocated_output = scale_bytes(total_allocated_memsize)
|
113
|
-
total_retained_output
|
122
|
+
total_retained_output = scale_bytes(total_retained_memsize)
|
114
123
|
else
|
115
124
|
total_allocated_output = "#{total_allocated_memsize} bytes"
|
116
|
-
total_retained_output
|
125
|
+
total_retained_output = "#{total_retained_memsize} bytes"
|
117
126
|
end
|
118
127
|
|
119
128
|
io.puts "Total allocated: #{total_allocated_output} (#{total_allocated} objects)"
|
120
129
|
io.puts "Total retained: #{total_retained_output} (#{total_retained} objects)"
|
121
130
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
.
|
126
|
-
|
127
|
-
.each do |(type, metric), name|
|
128
|
-
scale_data = metric == "memory" && options[:scale_bytes]
|
129
|
-
dump "#{type} #{metric} by #{name}", self.send("#{type}_#{metric}_by_#{name}"), io, scale_data
|
131
|
+
unless options[:detailed_report] == false
|
132
|
+
TYPES.each do |type|
|
133
|
+
METRICS.each do |metric|
|
134
|
+
NAMES.each do |name|
|
135
|
+
dump_data(io, type, metric, name, options)
|
130
136
|
end
|
137
|
+
end
|
138
|
+
end
|
131
139
|
end
|
132
140
|
|
133
141
|
io.puts
|
134
|
-
|
135
|
-
io.puts
|
136
|
-
dump_strings(io, "Retained", strings_retained, limit: options[:retained_strings])
|
142
|
+
print_string_reports(io, options)
|
137
143
|
|
138
144
|
io.close if io.is_a? File
|
139
145
|
end
|
140
146
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
strings = strings[0...limit]
|
147
|
+
def print_string_reports(io, options)
|
148
|
+
TYPES.each do |type|
|
149
|
+
dump_opts = {
|
150
|
+
normalize_paths: options[:normalize_paths],
|
151
|
+
limit: options["#{type}_strings".to_sym]
|
152
|
+
}
|
153
|
+
dump_strings(io, type, dump_opts)
|
149
154
|
end
|
155
|
+
end
|
150
156
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
+
def normalize_path(path)
|
158
|
+
@normalize_path ||= {}
|
159
|
+
@normalize_path[path] ||= begin
|
160
|
+
if %r!(/gems/.*)*/gems/(?<gemname>[^/]+)(?<rest>.*)! =~ path
|
161
|
+
"#{gemname}#{rest}"
|
162
|
+
elsif %r!ruby/2\.[^/]+/(?<stdlib>[^/.]+)(?<rest>.*)! =~ path
|
163
|
+
"ruby/lib/#{stdlib}#{rest}"
|
164
|
+
elsif %r!(?<app>[^/]+/(bin|app|lib))(?<rest>.*)! =~ path
|
165
|
+
"#{app}#{rest}"
|
166
|
+
else
|
167
|
+
path
|
157
168
|
end
|
158
|
-
io.puts
|
159
169
|
end
|
160
|
-
nil
|
161
170
|
end
|
162
171
|
|
163
|
-
|
164
|
-
|
172
|
+
private
|
173
|
+
|
174
|
+
def print_title(io, title)
|
175
|
+
io.puts
|
176
|
+
io.puts title
|
165
177
|
io.puts @colorize.line("-----------------------------------")
|
178
|
+
end
|
179
|
+
|
180
|
+
def print_output(io, topic, detail)
|
181
|
+
io.puts "#{@colorize.path(topic.to_s.rjust(10))} #{detail}"
|
182
|
+
end
|
183
|
+
|
184
|
+
def dump_data(io, type, metric, name, options)
|
185
|
+
print_title io, "#{type} #{metric} by #{name}"
|
186
|
+
data = self.send "#{type}_#{metric}_by_#{name}"
|
187
|
+
|
188
|
+
scale_data = metric == "memory" && options[:scale_bytes]
|
189
|
+
normalize_paths = options[:normalize_paths]
|
190
|
+
|
166
191
|
if data && !data.empty?
|
167
192
|
data.each do |item|
|
168
|
-
|
169
|
-
|
193
|
+
count = scale_data ? scale_bytes(item[:count]) : item[:count]
|
194
|
+
value = normalize_paths ? normalize_path(item[:data]) : item[:data]
|
195
|
+
print_output io, count, value
|
170
196
|
end
|
171
197
|
else
|
172
198
|
io.puts "NO DATA"
|
173
199
|
end
|
174
|
-
|
200
|
+
|
201
|
+
nil
|
175
202
|
end
|
176
203
|
|
177
|
-
|
204
|
+
def dump_strings(io, type, options)
|
205
|
+
strings = self.send("strings_#{type}") || []
|
206
|
+
return if strings.empty?
|
178
207
|
|
179
|
-
|
208
|
+
options = {} unless options.is_a?(Hash)
|
209
|
+
|
210
|
+
if (limit = options[:limit])
|
211
|
+
return if limit == 0
|
212
|
+
strings = strings[0...limit]
|
213
|
+
end
|
180
214
|
|
215
|
+
normalize_paths = options[:normalize_paths]
|
181
216
|
|
217
|
+
print_title(io, "#{type.capitalize} String Report")
|
218
|
+
strings.each do |string, stats|
|
219
|
+
print_output io, (stats.reduce(0) { |a, b| a + b[1] }), @colorize.string(string.inspect)
|
220
|
+
stats.sort_by { |x, y| [-y, x] }.each do |location, count|
|
221
|
+
location = normalize_path(location) if normalize_paths
|
222
|
+
print_output io, count, location
|
223
|
+
end
|
224
|
+
io.puts
|
225
|
+
end
|
226
|
+
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
@@ -6,17 +6,26 @@ module MemoryProfiler
|
|
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
8
|
|
9
|
-
stat_totals =
|
10
|
-
|
11
|
-
|
9
|
+
stat_totals =
|
10
|
+
self.values
|
11
|
+
.group_by(&metric_method)
|
12
|
+
.map do |metric, stats|
|
13
|
+
[metric, stats.reduce(0) { |sum, stat| sum + stat.memsize }, stats.size]
|
14
|
+
end
|
12
15
|
|
13
|
-
stats_by_memsize =
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
stats_by_memsize =
|
17
|
+
stat_totals
|
18
|
+
.sort_by! { |metric, memsize, _count| [-memsize, metric] }
|
19
|
+
.take(max)
|
20
|
+
.map! { |metric, memsize, _count| { data: metric, count: memsize } }
|
17
21
|
|
18
|
-
|
22
|
+
stats_by_count =
|
23
|
+
stat_totals
|
24
|
+
.sort_by! { |metric, _memsize, count| [-count, metric] }
|
25
|
+
.take(max)
|
26
|
+
.map! { |metric, _memsize, count| { data: metric, count: count } }
|
19
27
|
|
28
|
+
[stats_by_memsize, stats_by_count]
|
20
29
|
end
|
21
30
|
end
|
22
31
|
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: 0.9.
|
4
|
+
version: 0.9.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Memory profiling routines for Ruby 2.3+
|
14
14
|
email:
|