memory_profiler 0.9.4 → 0.9.5
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/README.md +11 -10
- data/lib/memory_profiler/helpers.rb +22 -27
- data/lib/memory_profiler/reporter.rb +46 -49
- data/lib/memory_profiler/results.rb +43 -63
- data/lib/memory_profiler/stat.rb +16 -1
- data/lib/memory_profiler/top_n.rb +11 -44
- data/lib/memory_profiler/version.rb +1 -1
- metadata +8 -26
- data/.gitignore +0 -17
- data/.travis.yml +0 -4
- data/Gemfile +0 -4
- data/Guardfile +0 -7
- data/Rakefile +0 -8
- data/memory_profiler.gemspec +0 -26
- data/test/test_helper.rb +0 -3
- data/test/test_helpers.rb +0 -32
- data/test/test_reporter.rb +0 -101
- data/test/test_results.rb +0 -9
- data/test/test_stat_hash.rb +0 -6
- data/test/test_top_n.rb +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7e1c880efe5b674c1d8281a7f8659ca224abafe
|
|
4
|
+
data.tar.gz: 37e0a418e082d1fd1af188a0966efdc9e97c92d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e900a6abc28f73ceffc12dae74ba7cb416c53930b5eb9703d00b25a075afda2f9137f6a4f52f6958b91b9afa057c9fc4845227517c147226a1ab99de7dc384c3
|
|
7
|
+
data.tar.gz: 915bd319992cbfa7ba1f38a64ae8a5e83358a1bb390f67ac283dfe70cab03463e1698e869a73641f563c4a9aaa29d5b0732049e29341d46c4de1d61f042b2089
|
data/README.md
CHANGED
|
@@ -35,7 +35,14 @@ report.pretty_print
|
|
|
35
35
|
|
|
36
36
|
## Options
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
The report method can take a few options:
|
|
39
|
+
|
|
40
|
+
* `top`: maximum number of entries to display in a report (default is 50)
|
|
41
|
+
* `allow_files`: include only certain files from tracing - can be given as a String, Regexp, or array of Strings
|
|
42
|
+
* `ignore_files`: exclude certain files from tracing - can be given as a String or Regexp
|
|
43
|
+
* `trace`: an array of classes for which you explicitly want to trace object allocations
|
|
44
|
+
|
|
45
|
+
Check out `Reporter#new` for more details.
|
|
39
46
|
|
|
40
47
|
```
|
|
41
48
|
pry> require 'memory_profiler'
|
|
@@ -77,14 +84,6 @@ rubygems x 305879
|
|
|
77
84
|
. . .
|
|
78
85
|
```
|
|
79
86
|
|
|
80
|
-
Other options include:
|
|
81
|
-
|
|
82
|
-
* `top`: maximum number of entries to display in a report
|
|
83
|
-
* `trace`: an array of classes for which you explicitly want to trace object allocations
|
|
84
|
-
* `ignore_files`: a regular expression used to exclude certain files from tracing (opposite of `allow_files`)
|
|
85
|
-
|
|
86
|
-
Check out `Reporter#new` for more details.
|
|
87
|
-
|
|
88
87
|
## Example Session
|
|
89
88
|
|
|
90
89
|
You can easily use memory_profiler to profile require impact of a gem, for example:
|
|
@@ -366,6 +365,9 @@ Memory profiler also performs some String analysis to help you find strings that
|
|
|
366
365
|
|
|
367
366
|
## Changelog
|
|
368
367
|
|
|
368
|
+
### 0.9.5
|
|
369
|
+
- Improved stability and performance @dgynn
|
|
370
|
+
|
|
369
371
|
### 0.9.4
|
|
370
372
|
- FIX: remove incorrect RVALUE offset on 2.2 @dgynn
|
|
371
373
|
- FEATURE: add total memory usage @dgynn
|
|
@@ -380,7 +382,6 @@ Memory profiler also performs some String analysis to help you find strings that
|
|
|
380
382
|
- This is quite stable, upping version to reflect
|
|
381
383
|
- Fixed bug where it would crash when location was nil for some reason
|
|
382
384
|
|
|
383
|
-
|
|
384
385
|
### 0.0.4
|
|
385
386
|
- Added compatibility with released version of Ruby 2.1.0
|
|
386
387
|
- Cleanup to use latest APIs available in 2.1.0
|
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
module MemoryProfiler
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
elsif /(?<app>[^\/]+\/(bin|app|lib))/ =~ path
|
|
9
|
-
app
|
|
10
|
-
else
|
|
11
|
-
"other"
|
|
12
|
-
end
|
|
2
|
+
class Helpers
|
|
3
|
+
|
|
4
|
+
def initialize
|
|
5
|
+
@gem_guess_cache = Hash.new
|
|
6
|
+
@location_cache = Hash.new { |h,k| h[k] = Hash.new.compare_by_identity }
|
|
7
|
+
@class_name_cache = Hash.new.compare_by_identity
|
|
13
8
|
end
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
def guess_gem(path)
|
|
11
|
+
@gem_guess_cache[path] ||=
|
|
12
|
+
if /(\/gems\/.*)*\/gems\/(?<gemname>[^\/]+)/ =~ path
|
|
13
|
+
gemname
|
|
14
|
+
elsif /\/rubygems[\.\/]/ =~ path
|
|
15
|
+
"rubygems".freeze
|
|
16
|
+
elsif /(?<app>[^\/]+\/(bin|app|lib))/ =~ path
|
|
17
|
+
app
|
|
18
|
+
else
|
|
19
|
+
"other".freeze
|
|
20
|
+
end
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
if !old || count < old
|
|
24
|
-
count
|
|
25
|
-
else
|
|
26
|
-
nil
|
|
27
|
-
end
|
|
23
|
+
def lookup_location(file, line)
|
|
24
|
+
@location_cache[file][line] ||= "#{file}:#{line}"
|
|
28
25
|
end
|
|
29
26
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
ObjectSpace.each_object do |obj|
|
|
33
|
-
i += 1
|
|
34
|
-
end
|
|
27
|
+
def lookup_class_name(klass)
|
|
28
|
+
@class_name_cache[klass] ||= klass.name || '<<Unknown>>'
|
|
35
29
|
end
|
|
30
|
+
|
|
36
31
|
end
|
|
37
32
|
end
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
require 'objspace'
|
|
2
2
|
module MemoryProfiler
|
|
3
|
-
# Reporter is the top level
|
|
3
|
+
# Reporter is the top level API used for generating memory reports.
|
|
4
4
|
#
|
|
5
5
|
# @example Measure object allocation in a block
|
|
6
|
-
#
|
|
7
6
|
# report = Reporter.report(top: 50) do
|
|
8
7
|
# 5.times { "foo" }
|
|
9
8
|
# end
|
|
@@ -12,84 +11,71 @@ module MemoryProfiler
|
|
|
12
11
|
|
|
13
12
|
def initialize(opts = {})
|
|
14
13
|
@top = opts[:top] || 50
|
|
15
|
-
@trace = opts[:trace]
|
|
16
|
-
@ignore_files = opts[:ignore_files]
|
|
17
|
-
@allow_files
|
|
14
|
+
@trace = opts[:trace] && Array(opts[:trace])
|
|
15
|
+
@ignore_files = opts[:ignore_files] && Regexp.new(opts[:ignore_files])
|
|
16
|
+
@allow_files = opts[:allow_files] && /#{Array(opts[:allow_files]).join('|')}/
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
# Helper for generating new reporter and running against block
|
|
19
|
+
# Helper for generating new reporter and running against block.
|
|
20
|
+
# @param [Hash] opts the options to create a report with
|
|
21
|
+
# @option opts :top max number of entries to output
|
|
22
|
+
# @option opts :trace a class or an array of classes you explicitly want to trace
|
|
23
|
+
# @option opts :ignore_files a regular expression used to exclude certain files from tracing
|
|
24
|
+
# @option opts :allow_files a string or array of strings to selectively include in tracing
|
|
25
|
+
# @return [MemoryProfiler::Results]
|
|
21
26
|
def self.report(opts={}, &block)
|
|
22
27
|
self.new(opts).run(&block)
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
# Collects object allocation and memory of ruby code inside of passed block.
|
|
26
|
-
#
|
|
27
|
-
# @param [Hash] opts the options to create a message with.
|
|
28
|
-
# @option opts [Fixnum] :top max number of entries to output in report
|
|
29
|
-
# @option opts [Array <Class>] :trace an array of classes you explicitly want to trace
|
|
30
|
-
# @return [MemoryProfiler::Results]
|
|
31
31
|
def run(&block)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
|
|
36
|
-
Helpers.full_gc
|
|
33
|
+
GC.start
|
|
37
34
|
GC.disable
|
|
38
35
|
|
|
36
|
+
generation = GC.count
|
|
39
37
|
ObjectSpace.trace_object_allocations do
|
|
40
|
-
generation = GC.count
|
|
41
38
|
block.call
|
|
42
|
-
allocated = object_list(generation, rvalue_size)
|
|
43
39
|
end
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
results.strings_allocated = results.string_report(allocated, top)
|
|
40
|
+
allocated = object_list(generation)
|
|
41
|
+
retained = StatHash.new.compare_by_identity
|
|
47
42
|
|
|
48
43
|
GC.enable
|
|
44
|
+
GC.start
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
# Caution: Do not allocate any new Objects between the call to GC.start and the completion of the retained
|
|
47
|
+
# lookups. It is likely that a new Object would reuse an object_id from a GC'd object.
|
|
51
48
|
|
|
52
|
-
retained = StatHash.new
|
|
53
49
|
ObjectSpace.each_object do |obj|
|
|
50
|
+
next unless ObjectSpace.allocation_generation(obj) == generation
|
|
54
51
|
begin
|
|
55
52
|
found = allocated[obj.__id__]
|
|
56
53
|
retained[obj.__id__] = found if found
|
|
57
54
|
rescue
|
|
58
55
|
# __id__ is not defined on BasicObject, skip it
|
|
59
|
-
# we can probably
|
|
56
|
+
# we can probably transplant the object_id at this point,
|
|
60
57
|
# but it is quite rare
|
|
61
58
|
end
|
|
62
59
|
end
|
|
60
|
+
ObjectSpace.trace_object_allocations_clear
|
|
63
61
|
|
|
62
|
+
results = Results.new
|
|
64
63
|
results.register_results(allocated, retained, top)
|
|
65
64
|
results
|
|
66
65
|
end
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
!trace
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def ignore_file?(file)
|
|
73
|
-
return true if file == __FILE__
|
|
74
|
-
@ignore_files && @ignore_files =~ file
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def allow_file?(file)
|
|
78
|
-
return true if @allow_files.empty?
|
|
79
|
-
!/#{@allow_files.join('|')}/.match(file).to_s.empty?
|
|
80
|
-
end
|
|
67
|
+
private
|
|
81
68
|
|
|
82
69
|
# Iterates through objects in memory of a given generation.
|
|
83
70
|
# Stores results along with meta data of objects collected.
|
|
84
|
-
def object_list(generation
|
|
71
|
+
def object_list(generation)
|
|
85
72
|
|
|
86
|
-
results = StatHash.new
|
|
87
73
|
objs = []
|
|
88
74
|
|
|
89
75
|
ObjectSpace.each_object do |obj|
|
|
90
|
-
next unless
|
|
76
|
+
next unless ObjectSpace.allocation_generation(obj) == generation
|
|
91
77
|
begin
|
|
92
|
-
if
|
|
78
|
+
if !trace || trace.include?(obj.class)
|
|
93
79
|
objs << obj
|
|
94
80
|
end
|
|
95
81
|
rescue
|
|
@@ -97,29 +83,40 @@ module MemoryProfiler
|
|
|
97
83
|
end
|
|
98
84
|
end
|
|
99
85
|
|
|
86
|
+
rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
|
|
87
|
+
rvalue_size_adjustment = RUBY_VERSION < '2.2' ? rvalue_size : 0
|
|
88
|
+
helper = Helpers.new
|
|
89
|
+
|
|
90
|
+
result = StatHash.new.compare_by_identity
|
|
91
|
+
|
|
100
92
|
objs.each do |obj|
|
|
101
|
-
file = ObjectSpace.allocation_sourcefile(obj)
|
|
102
|
-
next if
|
|
93
|
+
file = ObjectSpace.allocation_sourcefile(obj) || "(no name)".freeze
|
|
94
|
+
next if @ignore_files && @ignore_files =~ file
|
|
95
|
+
next if @allow_files && !(@allow_files =~ file)
|
|
103
96
|
|
|
104
97
|
line = ObjectSpace.allocation_sourceline(obj)
|
|
105
|
-
|
|
106
|
-
|
|
98
|
+
location = helper.lookup_location(file, line)
|
|
99
|
+
klass = obj.class
|
|
100
|
+
class_name = helper.lookup_class_name(klass)
|
|
101
|
+
gem = helper.guess_gem(file)
|
|
102
|
+
string = '' << obj if klass == String
|
|
107
103
|
|
|
108
|
-
class_name = obj.class.name rescue "BasicObject"
|
|
109
104
|
begin
|
|
110
105
|
object_id = obj.__id__
|
|
111
106
|
|
|
112
|
-
memsize = ObjectSpace.memsize_of(obj)
|
|
113
|
-
memsize += rvalue_size if RUBY_VERSION < '2.2'.freeze
|
|
107
|
+
memsize = ObjectSpace.memsize_of(obj) + rvalue_size_adjustment
|
|
114
108
|
# compensate for API bug
|
|
115
109
|
memsize = rvalue_size if memsize > 100_000_000_000
|
|
116
|
-
|
|
110
|
+
result[object_id] = MemoryProfiler::Stat.new(class_name, gem, file, location, memsize, string)
|
|
117
111
|
rescue
|
|
118
112
|
# __id__ is not defined, give up
|
|
119
113
|
end
|
|
120
114
|
end
|
|
121
115
|
|
|
122
|
-
|
|
116
|
+
# Although `objs` will go out of scope, clear the array so objects can definitely be GCd
|
|
117
|
+
objs.clear
|
|
118
|
+
|
|
119
|
+
result
|
|
123
120
|
end
|
|
124
121
|
end
|
|
125
122
|
end
|
|
@@ -1,89 +1,69 @@
|
|
|
1
1
|
module MemoryProfiler
|
|
2
2
|
class Results
|
|
3
3
|
|
|
4
|
-
def self.register_type(name,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_accessor full_name
|
|
8
|
-
|
|
9
|
-
@@lookups ||= []
|
|
10
|
-
mapped = lookup
|
|
11
|
-
|
|
12
|
-
if metric == "memory"
|
|
13
|
-
mapped = lambda { |stat|
|
|
14
|
-
[lookup.call(stat), stat.memsize]
|
|
15
|
-
}
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
@@lookups << [full_name, mapped]
|
|
4
|
+
def self.register_type(name, stat_attribute)
|
|
5
|
+
@@lookups ||= []
|
|
6
|
+
@@lookups << [name, stat_attribute]
|
|
19
7
|
|
|
8
|
+
["allocated", "retained"].product(["objects", "memory"]).each do |type, metric|
|
|
9
|
+
attr_accessor "#{type}_#{metric}_by_#{name}"
|
|
20
10
|
end
|
|
21
11
|
end
|
|
22
12
|
|
|
23
|
-
register_type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
register_type :file, lambda { |stat|
|
|
28
|
-
stat.file || "(no name)"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
register_type :location, lambda { |stat|
|
|
32
|
-
"#{stat.file}:#{stat.line}"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
register_type :class, lambda { |stat|
|
|
36
|
-
"#{stat.class_name}"
|
|
37
|
-
}
|
|
13
|
+
register_type 'gem', :gem
|
|
14
|
+
register_type 'file', :file
|
|
15
|
+
register_type 'location', :location
|
|
16
|
+
register_type 'class', :class_name
|
|
38
17
|
|
|
39
18
|
attr_accessor :strings_retained, :strings_allocated
|
|
40
19
|
attr_accessor :total_retained, :total_allocated
|
|
41
20
|
attr_accessor :total_retained_memsize, :total_allocated_memsize
|
|
42
21
|
|
|
43
|
-
def self.from_raw(allocated, retained, top)
|
|
44
|
-
self.new.register_results(allocated, retained, top)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
22
|
def register_results(allocated, retained, top)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
self.send "#{name}=", result
|
|
23
|
+
|
|
24
|
+
@@lookups.each do |name, stat_attribute|
|
|
25
|
+
|
|
26
|
+
memsize_results, count_results = allocated.top_n(top, stat_attribute)
|
|
27
|
+
|
|
28
|
+
self.send("allocated_memory_by_#{name}=", memsize_results)
|
|
29
|
+
self.send("allocated_objects_by_#{name}=", count_results)
|
|
30
|
+
|
|
31
|
+
memsize_results, count_results = retained.top_n(top, stat_attribute)
|
|
32
|
+
|
|
33
|
+
self.send("retained_memory_by_#{name}=", memsize_results)
|
|
34
|
+
self.send("retained_objects_by_#{name}=", count_results)
|
|
61
35
|
end
|
|
62
36
|
|
|
37
|
+
|
|
38
|
+
self.strings_allocated = string_report(allocated, top)
|
|
63
39
|
self.strings_retained = string_report(retained, top)
|
|
64
40
|
|
|
65
|
-
self.total_allocated = allocated.
|
|
66
|
-
self.total_allocated_memsize = allocated.values.map(&:memsize).inject(:+)
|
|
67
|
-
self.total_retained = retained.
|
|
68
|
-
self.total_retained_memsize = retained.values.map(&:memsize).inject(:+)
|
|
41
|
+
self.total_allocated = allocated.size
|
|
42
|
+
self.total_allocated_memsize = allocated.values.map!(&:memsize).inject(0, :+)
|
|
43
|
+
self.total_retained = retained.size
|
|
44
|
+
self.total_retained_memsize = retained.values.map!(&:memsize).inject(0, :+)
|
|
69
45
|
|
|
70
46
|
self
|
|
71
47
|
end
|
|
72
48
|
|
|
73
|
-
StringStat = Struct.new(:string, :count, :location)
|
|
74
|
-
|
|
75
49
|
def string_report(data, top)
|
|
76
|
-
data
|
|
77
|
-
.
|
|
78
|
-
.map { |
|
|
79
|
-
.group_by { |string,
|
|
80
|
-
.sort_by {
|
|
50
|
+
data.values
|
|
51
|
+
.keep_if { |stat| stat.string_value }
|
|
52
|
+
.map! { |stat| [stat.string_value, stat.location] }
|
|
53
|
+
.group_by { |string, _location| string }
|
|
54
|
+
.sort_by {|string, list| [-list.size, string] }
|
|
81
55
|
.first(top)
|
|
82
|
-
.map { |string, list| [string, list.group_by { |
|
|
83
|
-
|
|
56
|
+
.map { |string, list| [string, list.group_by { |_string, location| location }
|
|
57
|
+
.map { |location, locations| [location, locations.size] }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
84
60
|
end
|
|
85
61
|
|
|
86
|
-
|
|
62
|
+
# Output the results of the report
|
|
63
|
+
# @param [Hash] options the options for output
|
|
64
|
+
# @option opts [String] :to_file a path to your log file
|
|
65
|
+
# @option opts [Boolean] :color_output a flag for whether to colorize output
|
|
66
|
+
def pretty_print(io = STDOUT, options = {})
|
|
87
67
|
io = File.open(options[:to_file], "w") if options[:to_file]
|
|
88
68
|
|
|
89
69
|
color_output = options.fetch(:color_output) { io.respond_to?(:isatty) && io.isatty }
|
|
@@ -116,7 +96,7 @@ module MemoryProfiler
|
|
|
116
96
|
io.puts @colorize.line("-----------------------------------")
|
|
117
97
|
strings.each do |string, stats|
|
|
118
98
|
io.puts "#{stats.reduce(0) { |a, b| a + b[1] }.to_s.rjust(10)} #{@colorize.string((string[0..200].inspect))}"
|
|
119
|
-
stats.sort_by { |x, y| -y }.each do |location, count|
|
|
99
|
+
stats.sort_by { |x, y| [-y, x] }.each do |location, count|
|
|
120
100
|
io.puts "#{@colorize.path(count.to_s.rjust(10))} #{location}"
|
|
121
101
|
end
|
|
122
102
|
io.puts
|
|
@@ -127,7 +107,7 @@ module MemoryProfiler
|
|
|
127
107
|
def dump(description, data, io)
|
|
128
108
|
io.puts description
|
|
129
109
|
io.puts @colorize.line("-----------------------------------")
|
|
130
|
-
if data
|
|
110
|
+
if data && !data.empty?
|
|
131
111
|
data.each do |item|
|
|
132
112
|
io.puts "#{item[:count].to_s.rjust(10)} #{item[:data]}"
|
|
133
113
|
end
|
data/lib/memory_profiler/stat.rb
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
module MemoryProfiler
|
|
2
|
-
Stat
|
|
2
|
+
class Stat
|
|
3
|
+
|
|
4
|
+
attr_reader :class_name, :gem, :file, :location, :memsize, :string_value
|
|
5
|
+
|
|
6
|
+
def initialize(class_name, gem, file, location, memsize, string_value)
|
|
7
|
+
@class_name = class_name
|
|
8
|
+
@gem = gem
|
|
9
|
+
|
|
10
|
+
@file = file
|
|
11
|
+
@location = location
|
|
12
|
+
|
|
13
|
+
@memsize = memsize
|
|
14
|
+
@string_value = string_value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
3
18
|
end
|
|
@@ -1,54 +1,21 @@
|
|
|
1
1
|
module MemoryProfiler
|
|
2
2
|
module TopN
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
def top_n(max
|
|
3
|
+
# Fast approach for determining the top_n entries in a Hash of Stat objects.
|
|
4
|
+
# Returns results for both memory (memsize summed) and objects allocated (count) as a tuple.
|
|
5
|
+
def top_n(max, metric)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
if block_given?
|
|
9
|
-
self.map { |row|
|
|
10
|
-
yield(row)
|
|
11
|
-
}
|
|
12
|
-
else
|
|
13
|
-
self.dup
|
|
14
|
-
end
|
|
7
|
+
stats_by_metric = self.values.map! { |stat| [stat.send(metric), stat.memsize] }
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
stat_totals = stats_by_metric.group_by { |metric_value, _memsize| metric_value }.
|
|
10
|
+
map { |key, values| [key, values.reduce(0) { |sum, item| _key, memsize = item ; sum + memsize }, values.size] }
|
|
18
11
|
|
|
19
|
-
|
|
12
|
+
stats_by_memsize = stat_totals.sort_by! { |metric, memsize, _count| [-memsize, metric] }.first(max).
|
|
13
|
+
map! { |metric, memsize, _count| { data: metric, count: memsize } }
|
|
14
|
+
stats_by_count = stat_totals.sort_by! { |metric, _memsize, count| [-count, metric] }.first(max).
|
|
15
|
+
map! { |metric, _memsize, count| { data: metric, count: count } }
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
count = 0
|
|
23
|
-
lowest_count = 0
|
|
17
|
+
[stats_by_memsize, stats_by_count]
|
|
24
18
|
|
|
25
|
-
sorted << nil
|
|
26
|
-
|
|
27
|
-
sorted.each do |row|
|
|
28
|
-
|
|
29
|
-
current_item, current_count = row
|
|
30
|
-
|
|
31
|
-
unless current_item == last
|
|
32
|
-
if count > lowest_count
|
|
33
|
-
found << {data: last, count: count}
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
if found.length > max
|
|
37
|
-
found.sort!{|x,y| x[:count] <=> y[:count] }
|
|
38
|
-
found.delete_at(0)
|
|
39
|
-
lowest_count = found[0][:count]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
count = 0
|
|
43
|
-
last = current_item
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
count += (current_count || 1) unless row.nil?
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
found
|
|
50
|
-
.sort!{|x,y| x[:count] <=> y[:count] }
|
|
51
|
-
.reverse
|
|
52
19
|
end
|
|
53
20
|
end
|
|
54
21
|
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.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-
|
|
11
|
+
date: 2015-10-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -80,20 +80,15 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
|
-
description: Memory profiling routines for Ruby
|
|
83
|
+
description: Memory profiling routines for Ruby 2.1+
|
|
84
84
|
email:
|
|
85
85
|
- sam.saffron@gmail.com
|
|
86
86
|
executables: []
|
|
87
87
|
extensions: []
|
|
88
88
|
extra_rdoc_files: []
|
|
89
89
|
files:
|
|
90
|
-
- ".gitignore"
|
|
91
|
-
- ".travis.yml"
|
|
92
|
-
- Gemfile
|
|
93
|
-
- Guardfile
|
|
94
90
|
- LICENSE.txt
|
|
95
91
|
- README.md
|
|
96
|
-
- Rakefile
|
|
97
92
|
- lib/memory_profiler.rb
|
|
98
93
|
- lib/memory_profiler/helpers.rb
|
|
99
94
|
- lib/memory_profiler/monochrome.rb
|
|
@@ -104,14 +99,7 @@ files:
|
|
|
104
99
|
- lib/memory_profiler/stat_hash.rb
|
|
105
100
|
- lib/memory_profiler/top_n.rb
|
|
106
101
|
- lib/memory_profiler/version.rb
|
|
107
|
-
|
|
108
|
-
- test/test_helper.rb
|
|
109
|
-
- test/test_helpers.rb
|
|
110
|
-
- test/test_reporter.rb
|
|
111
|
-
- test/test_results.rb
|
|
112
|
-
- test/test_stat_hash.rb
|
|
113
|
-
- test/test_top_n.rb
|
|
114
|
-
homepage: ''
|
|
102
|
+
homepage: https://github.com/SamSaffron/memory_profiler
|
|
115
103
|
licenses:
|
|
116
104
|
- MIT
|
|
117
105
|
metadata: {}
|
|
@@ -123,7 +111,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
111
|
requirements:
|
|
124
112
|
- - ">="
|
|
125
113
|
- !ruby/object:Gem::Version
|
|
126
|
-
version:
|
|
114
|
+
version: 2.1.0
|
|
127
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
116
|
requirements:
|
|
129
117
|
- - ">="
|
|
@@ -131,14 +119,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
131
119
|
version: '0'
|
|
132
120
|
requirements: []
|
|
133
121
|
rubyforge_project:
|
|
134
|
-
rubygems_version: 2.4.5
|
|
122
|
+
rubygems_version: 2.4.5.1
|
|
135
123
|
signing_key:
|
|
136
124
|
specification_version: 4
|
|
137
|
-
summary: Memory profiling routines for Ruby
|
|
138
|
-
test_files:
|
|
139
|
-
- test/test_helper.rb
|
|
140
|
-
- test/test_helpers.rb
|
|
141
|
-
- test/test_reporter.rb
|
|
142
|
-
- test/test_results.rb
|
|
143
|
-
- test/test_stat_hash.rb
|
|
144
|
-
- test/test_top_n.rb
|
|
125
|
+
summary: Memory profiling routines for Ruby 2.1+
|
|
126
|
+
test_files: []
|
data/.gitignore
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Guardfile
DELETED
data/Rakefile
DELETED
data/memory_profiler.gemspec
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require 'memory_profiler/version'
|
|
5
|
-
|
|
6
|
-
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name = "memory_profiler"
|
|
8
|
-
spec.version = MemoryProfiler::VERSION
|
|
9
|
-
spec.authors = ["Sam Saffron"]
|
|
10
|
-
spec.email = ["sam.saffron@gmail.com"]
|
|
11
|
-
spec.description = %q{Memory profiling routines for Ruby Head}
|
|
12
|
-
spec.summary = %q{Memory profiling routines for Ruby Head}
|
|
13
|
-
spec.homepage = ""
|
|
14
|
-
spec.license = "MIT"
|
|
15
|
-
|
|
16
|
-
spec.files = `git ls-files`.split($/)
|
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
-
spec.require_paths = ["lib"]
|
|
20
|
-
|
|
21
|
-
spec.add_development_dependency "bundler", "~> 1.3"
|
|
22
|
-
spec.add_development_dependency "rake"
|
|
23
|
-
spec.add_development_dependency "minitest"
|
|
24
|
-
spec.add_development_dependency "guard"
|
|
25
|
-
spec.add_development_dependency "guard-minitest"
|
|
26
|
-
end
|
data/test/test_helper.rb
DELETED
data/test/test_helpers.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
require_relative 'test_helper'
|
|
2
|
-
|
|
3
|
-
module MemoryProfiler
|
|
4
|
-
|
|
5
|
-
class TestHelpers < Minitest::Test
|
|
6
|
-
def assert_gem_parse(expected, path)
|
|
7
|
-
assert_equal(expected, Helpers.guess_gem(path))
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def test_rubygems_parse
|
|
11
|
-
assert_gem_parse( "rubygems",
|
|
12
|
-
"/home/sam/.rbenv/versions/ruby-head/lib/ruby/2.1.0/rubygems/version.rb")
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def test_standard_parse
|
|
16
|
-
assert_gem_parse( "rails_multisite",
|
|
17
|
-
"/home/sam/Source/discourse/vendor/gems/rails_multisite/lib")
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def test_another_standard_parse
|
|
21
|
-
assert_gem_parse( "activesupport-3.2.12",
|
|
22
|
-
"/home/sam/.rbenv/versions/ruby-head/lib/ruby/gems/2.1.0/gems/activesupport-3.2.12/lib/active_support/dependencies.rb")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def test_app_path_parse
|
|
26
|
-
assert_gem_parse( "discourse/app",
|
|
27
|
-
"/home/sam/Source/discourse/app/assets")
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
end
|
data/test/test_reporter.rb
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
require_relative 'test_helper'
|
|
2
|
-
|
|
3
|
-
class TestReporter < Minitest::Test
|
|
4
|
-
|
|
5
|
-
class Foo; end
|
|
6
|
-
|
|
7
|
-
def allocate_strings(n)
|
|
8
|
-
n.times do
|
|
9
|
-
""
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def test_counts
|
|
14
|
-
a = nil
|
|
15
|
-
result = MemoryProfiler::Reporter.report do
|
|
16
|
-
allocate_strings(10)
|
|
17
|
-
a = "hello"
|
|
18
|
-
end
|
|
19
|
-
assert_equal(11, result.total_allocated)
|
|
20
|
-
assert_equal(1, result.total_retained)
|
|
21
|
-
assert_equal(1, result.retained_objects_by_location.length)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def test_class_tracing
|
|
25
|
-
result = MemoryProfiler::Reporter.report(:trace => [Foo]) do
|
|
26
|
-
"hello"
|
|
27
|
-
"hello"
|
|
28
|
-
Foo.new
|
|
29
|
-
end
|
|
30
|
-
assert_equal(1, result.total_allocated)
|
|
31
|
-
assert_equal(0, result.total_retained)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def test_ignore_file
|
|
35
|
-
result = MemoryProfiler::Reporter.report(:ignore_files => /test_reporter\.rb/) do
|
|
36
|
-
"hello"
|
|
37
|
-
"hello"
|
|
38
|
-
Foo.new
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
assert_equal(0, result.total_allocated)
|
|
42
|
-
assert_equal(0, result.total_retained)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def test_no_color_output
|
|
46
|
-
report = MemoryProfiler::Reporter.report do
|
|
47
|
-
allocate_strings(10)
|
|
48
|
-
end
|
|
49
|
-
io = StringIO.new
|
|
50
|
-
report.pretty_print io, color_output: false
|
|
51
|
-
assert(!io.string.include?("\033"), 'excludes color information')
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def test_color_output
|
|
55
|
-
report = MemoryProfiler::Reporter.report do
|
|
56
|
-
allocate_strings(10)
|
|
57
|
-
end
|
|
58
|
-
io = StringIO.new
|
|
59
|
-
report.pretty_print io, color_output: true
|
|
60
|
-
assert(io.string.include?("\033"), 'includes color information')
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
class StdoutMock < StringIO
|
|
64
|
-
def isatty
|
|
65
|
-
true
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def test_color_output_defaults_to_true_when_run_from_tty
|
|
70
|
-
report = MemoryProfiler::Reporter.report do
|
|
71
|
-
allocate_strings(10)
|
|
72
|
-
end
|
|
73
|
-
io = StdoutMock.new
|
|
74
|
-
report.pretty_print io
|
|
75
|
-
assert(io.string.include?("\033"), 'includes color information')
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def test_mono_output_defaults_to_true_when_not_run_from_tty
|
|
79
|
-
report = MemoryProfiler::Reporter.report do
|
|
80
|
-
allocate_strings(10)
|
|
81
|
-
end
|
|
82
|
-
io = StringIO.new
|
|
83
|
-
report.pretty_print io
|
|
84
|
-
assert(!io.string.include?("\033"), 'excludes color information')
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def test_reports_can_be_reused_with_different_color_options
|
|
88
|
-
report = MemoryProfiler::Reporter.report do
|
|
89
|
-
allocate_strings(10)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
io = StringIO.new
|
|
93
|
-
report.pretty_print io, color_output: true
|
|
94
|
-
assert(io.string.include?("\033"), 'includes color information')
|
|
95
|
-
|
|
96
|
-
io = StringIO.new
|
|
97
|
-
report.pretty_print io, color_output: false
|
|
98
|
-
assert(!io.string.include?("\033"), 'excludes color information')
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
end
|
data/test/test_results.rb
DELETED
data/test/test_stat_hash.rb
DELETED
data/test/test_top_n.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
require_relative 'test_helper'
|
|
2
|
-
|
|
3
|
-
class ArrayWithTopN < Array
|
|
4
|
-
include MemoryProfiler::TopN
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
class TestTopN < Minitest::Test
|
|
8
|
-
|
|
9
|
-
def tn(*vals)
|
|
10
|
-
ArrayWithTopN.new.concat(vals)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def test_top_n
|
|
14
|
-
data = tn( 7,1,2,2,3,3,99,3 )
|
|
15
|
-
results = data.top_n(2)
|
|
16
|
-
|
|
17
|
-
assert_equal([{data: 3, count: 3}, {data: 2, count: 2}], results)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def test_top_n_with_block
|
|
21
|
-
data = tn( 0,3,6,1,4,2 )
|
|
22
|
-
|
|
23
|
-
results = data.top_n(2) do |r|
|
|
24
|
-
r%3
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
assert_equal([{data: 0, count: 3}, {data: 1, count: 2}], results)
|
|
28
|
-
end
|
|
29
|
-
def test_top_n_with_block_and_size
|
|
30
|
-
data = tn( [1,100], [1,10], [2,1], [2,1], [2,1],[3,100] )
|
|
31
|
-
|
|
32
|
-
results = data.top_n(2) do |r|
|
|
33
|
-
r
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
assert_equal([{data: 1, count: 110}, {data: 3, count: 100}], results)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
end
|