heap-profiler 0.1.0 → 0.2.0
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/.github/workflows/tests.yml +2 -2
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.md +177 -169
- data/TODO.md +0 -6
- data/benchmark/address-parsing.rb +5 -2
- data/benchmark/indexing.rb +5 -2
- data/bin/generate-report +2 -0
- data/exe/heap-profiler +0 -1
- data/ext/heap_profiler/heap_profiler.cpp +95 -81
- data/heap-profiler.gemspec +1 -1
- data/lib/heap_profiler/analyzer.rb +95 -33
- data/lib/heap_profiler/diff.rb +13 -10
- data/lib/heap_profiler/dump.rb +2 -6
- data/lib/heap_profiler/full.rb +1 -1
- data/lib/heap_profiler/index.rb +27 -33
- data/lib/heap_profiler/{native.rb → parser.rb} +34 -19
- data/lib/heap_profiler/results.rb +7 -7
- data/lib/heap_profiler/version.rb +1 -1
- metadata +5 -5
data/lib/heap_profiler/diff.rb
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
module HeapProfiler
|
4
4
|
class Diff
|
5
|
+
class DumpSubset
|
6
|
+
def initialize(path, generation)
|
7
|
+
@path = path
|
8
|
+
@generation = generation
|
9
|
+
end
|
10
|
+
|
11
|
+
def each_object(&block)
|
12
|
+
Parser.load_many(@path, since: @generation, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
5
16
|
attr_reader :allocated
|
6
17
|
|
7
18
|
def initialize(report_directory)
|
@@ -11,23 +22,15 @@ module HeapProfiler
|
|
11
22
|
end
|
12
23
|
|
13
24
|
def allocated_diff
|
14
|
-
@allocated_diff ||=
|
25
|
+
@allocated_diff ||= DumpSubset.new(@allocated.path, @generation)
|
15
26
|
end
|
16
27
|
|
17
28
|
def retained_diff
|
18
|
-
@retained_diff ||=
|
29
|
+
@retained_diff ||= DumpSubset.new(open_dump('retained').path, @generation)
|
19
30
|
end
|
20
31
|
|
21
32
|
private
|
22
33
|
|
23
|
-
def build_diff(name, base)
|
24
|
-
diff = open_dump(name)
|
25
|
-
unless diff.exist?
|
26
|
-
base.filter(File.join(@report_directory, "#{name}.heap"), since: @generation)
|
27
|
-
end
|
28
|
-
diff
|
29
|
-
end
|
30
|
-
|
31
34
|
def open_dump(name)
|
32
35
|
Dump.open(@report_directory, name)
|
33
36
|
end
|
data/lib/heap_profiler/dump.rb
CHANGED
@@ -61,12 +61,8 @@ module HeapProfiler
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
def
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
def each_object(&block)
|
69
|
-
Native.load_many(path, batch_size: 10_000_000, &block)
|
64
|
+
def each_object(since: 0, &block)
|
65
|
+
Parser.load_many(path, since: since, batch_size: 10_000_000, &block)
|
70
66
|
end
|
71
67
|
|
72
68
|
def stats
|
data/lib/heap_profiler/full.rb
CHANGED
data/lib/heap_profiler/index.rb
CHANGED
@@ -11,26 +11,26 @@ module HeapProfiler
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def build!
|
14
|
-
@classes, @strings =
|
14
|
+
@classes, @strings = Parser.build_index(@heap.path)
|
15
15
|
self
|
16
16
|
end
|
17
17
|
|
18
18
|
BUILTIN_CLASSES = {
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
FILE: "File",
|
20
|
+
ICLASS: "ICLASS",
|
21
|
+
COMPLEX: "Complex",
|
22
|
+
RATIONAL: "Rational",
|
23
|
+
BIGNUM: "Bignum",
|
24
|
+
FLOAT: "Float",
|
25
|
+
ARRAY: "Array",
|
26
|
+
STRING: "String",
|
27
|
+
HASH: "Hash",
|
28
|
+
SYMBOL: "Symbol",
|
29
|
+
MODULE: "Module",
|
30
|
+
CLASS: "Class",
|
31
|
+
REGEXP: "Regexp",
|
32
|
+
MATCH: "MatchData",
|
33
|
+
ROOT: "<VM Root>",
|
34
34
|
}.freeze
|
35
35
|
|
36
36
|
IMEMO_TYPES = Hash.new { |h, k| h[k] = "<#{k || 'unknown'}> (IMEMO)" }
|
@@ -42,20 +42,17 @@ module HeapProfiler
|
|
42
42
|
return class_name
|
43
43
|
end
|
44
44
|
|
45
|
-
return IMEMO_TYPES[object[:imemo_type]] if type ==
|
46
|
-
return DATA_TYPES[object[:struct]] if type == 'DATA'
|
45
|
+
return IMEMO_TYPES[object[:imemo_type]] if type == :IMEMO
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
return unless class_address
|
47
|
+
class_address = object[:class]
|
48
|
+
return unless class_address
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
nil
|
55
|
-
end
|
56
|
-
end
|
50
|
+
@classes.fetch(class_address) do
|
51
|
+
return DATA_TYPES[object[:struct]] if type == :DATA
|
57
52
|
|
58
|
-
|
53
|
+
$stderr.puts("WARNING: Couldn't infer class name of: #{object.inspect}")
|
54
|
+
nil
|
55
|
+
end
|
59
56
|
end
|
60
57
|
|
61
58
|
def string_value(object)
|
@@ -63,15 +60,12 @@ module HeapProfiler
|
|
63
60
|
return value if value
|
64
61
|
|
65
62
|
if object[:shared]
|
66
|
-
@strings[
|
63
|
+
@strings[Native.parse_address(object[:references].first)]
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
70
|
-
def
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
def guess_gem(path)
|
67
|
+
def guess_gem(object)
|
68
|
+
path = object[:file]
|
75
69
|
@gems[path] ||=
|
76
70
|
if %r{(/gems/.*)*/gems/(?<gemname>[^/]+)} =~ path
|
77
71
|
gemname
|
@@ -1,22 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HeapProfiler
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
def build_index(path, batch_size: DEFAULT_BATCH_SIZE)
|
8
|
-
_build_index(path, batch_size)
|
9
|
-
end
|
10
|
-
|
11
|
-
def load_many(path, batch_size: DEFAULT_BATCH_SIZE, &block)
|
12
|
-
_load_many(path, batch_size, &block)
|
13
|
-
end
|
14
|
-
|
15
|
-
def filter_heap(source_path, destination_path, since:)
|
16
|
-
_filter_heap(source_path, destination_path, since)
|
17
|
-
end
|
18
|
-
|
19
|
-
def ruby_build_index(path)
|
4
|
+
module Parser
|
5
|
+
class Ruby
|
6
|
+
def build_index(path)
|
20
7
|
require 'json'
|
21
8
|
classes_index = {}
|
22
9
|
strings_index = {}
|
@@ -26,12 +13,12 @@ module HeapProfiler
|
|
26
13
|
case object[:type]
|
27
14
|
when 'MODULE', 'CLASS'
|
28
15
|
if (name = object[:name])
|
29
|
-
classes_index[
|
16
|
+
classes_index[parse_address(object[:address])] = name
|
30
17
|
end
|
31
18
|
when 'STRING'
|
32
19
|
next if object[:shared]
|
33
20
|
if (value = object[:value])
|
34
|
-
strings_index[
|
21
|
+
strings_index[parse_address(object[:address])] = value
|
35
22
|
end
|
36
23
|
end
|
37
24
|
end
|
@@ -39,10 +26,38 @@ module HeapProfiler
|
|
39
26
|
[classes_index, strings_index]
|
40
27
|
end
|
41
28
|
|
42
|
-
def
|
29
|
+
def parse_address(address)
|
43
30
|
address.to_i(16)
|
44
31
|
end
|
45
32
|
end
|
33
|
+
|
34
|
+
class Native
|
35
|
+
DEFAULT_BATCH_SIZE = 10_000_000 # 10MB
|
36
|
+
|
37
|
+
def build_index(path, batch_size: DEFAULT_BATCH_SIZE)
|
38
|
+
_build_index(path, batch_size)
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_many(path, since: nil, batch_size: DEFAULT_BATCH_SIZE, &block)
|
42
|
+
_load_many(path, since, batch_size, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
def build_index(path)
|
48
|
+
current.build_index(path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_many(path, **kwargs, &block)
|
52
|
+
current.load_many(path, **kwargs, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def current
|
58
|
+
Thread.current[:HeapProfilerParser] ||= Native.new
|
59
|
+
end
|
60
|
+
end
|
46
61
|
end
|
47
62
|
require "heap_profiler/heap_profiler"
|
48
63
|
end
|
@@ -78,8 +78,8 @@ module HeapProfiler
|
|
78
78
|
analyzer = Analyzer.new(heap, index)
|
79
79
|
dimensions = analyzer.run(@metrics, @groupings)
|
80
80
|
|
81
|
-
io.puts "Total: #{scale_bytes(dimensions['
|
82
|
-
"(#{dimensions['
|
81
|
+
io.puts "Total: #{scale_bytes(dimensions['total'].memory)} " \
|
82
|
+
"(#{dimensions['total'].objects} objects)"
|
83
83
|
|
84
84
|
@metrics.each do |metric|
|
85
85
|
next if metric == "strings"
|
@@ -95,7 +95,7 @@ module HeapProfiler
|
|
95
95
|
|
96
96
|
def dump_data(io, dimensions, metric, grouping, options)
|
97
97
|
print_title io, "#{metric} by #{grouping}"
|
98
|
-
data = dimensions[
|
98
|
+
data = dimensions[grouping].top_n(metric, options.fetch(:top, 50))
|
99
99
|
|
100
100
|
scale_data = metric == "memory" && options[:scale_bytes]
|
101
101
|
normalize_paths = options[:normalize_paths]
|
@@ -141,7 +141,7 @@ module HeapProfiler
|
|
141
141
|
|
142
142
|
def pretty_print(io = $stdout, **options)
|
143
143
|
diff = Diff.new(@directory)
|
144
|
-
heaps = @types.
|
144
|
+
heaps = @types.each_with_object({}) { |t, h| h[t] = diff.public_send("#{t}_diff") }
|
145
145
|
index = Index.new(diff.allocated)
|
146
146
|
|
147
147
|
color_output = options.fetch(:color_output) { io.respond_to?(:isatty) && io.isatty }
|
@@ -154,8 +154,8 @@ module HeapProfiler
|
|
154
154
|
end
|
155
155
|
|
156
156
|
dimensions.each do |type, metrics|
|
157
|
-
io.puts "Total #{type}: #{scale_bytes(metrics['
|
158
|
-
"(#{metrics['
|
157
|
+
io.puts "Total #{type}: #{scale_bytes(metrics['total'].memory)} " \
|
158
|
+
"(#{metrics['total'].objects} objects)"
|
159
159
|
end
|
160
160
|
|
161
161
|
@types.each do |type|
|
@@ -176,7 +176,7 @@ module HeapProfiler
|
|
176
176
|
|
177
177
|
def dump_data(io, dimensions, type, metric, grouping, options)
|
178
178
|
print_title io, "#{type} #{metric} by #{grouping}"
|
179
|
-
data = dimensions[type][
|
179
|
+
data = dimensions[type][grouping].top_n(metric, options.fetch(:top, 50))
|
180
180
|
|
181
181
|
scale_data = metric == "memory" && options[:scale_bytes]
|
182
182
|
normalize_paths = options[:normalize_paths]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heap-profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Make several heap dumps and summarize allocated, retained memory
|
14
14
|
email:
|
@@ -51,7 +51,7 @@ files:
|
|
51
51
|
- lib/heap_profiler/full.rb
|
52
52
|
- lib/heap_profiler/index.rb
|
53
53
|
- lib/heap_profiler/monochrome.rb
|
54
|
-
- lib/heap_profiler/
|
54
|
+
- lib/heap_profiler/parser.rb
|
55
55
|
- lib/heap_profiler/polychrome.rb
|
56
56
|
- lib/heap_profiler/reporter.rb
|
57
57
|
- lib/heap_profiler/results.rb
|
@@ -72,14 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 2.
|
75
|
+
version: 2.5.0
|
76
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
77
|
requirements:
|
78
78
|
- - ">="
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: '0'
|
81
81
|
requirements: []
|
82
|
-
rubygems_version: 3.
|
82
|
+
rubygems_version: 3.1.2
|
83
83
|
signing_key:
|
84
84
|
specification_version: 4
|
85
85
|
summary: Ruby heap profiling tool
|