memory_profiler 0.9.13 → 0.9.14
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|