heap-profiler 0.2.1 → 0.3.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 +1 -1
- data/.rubocop.yml +3 -0
- data/Gemfile.lock +21 -21
- data/ext/heap_profiler/extconf.rb +2 -0
- data/ext/heap_profiler/heap_profiler.cpp +46 -9
- data/lib/heap_profiler/dump.rb +1 -1
- data/lib/heap_profiler/index.rb +9 -8
- data/lib/heap_profiler/parser.rb +15 -3
- data/lib/heap_profiler/reporter.rb +21 -12
- data/lib/heap_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: e84c345fb6a29dbb5da52ce051ae7ca87b220adaea020458b4fc44f9343ad2d2
|
4
|
+
data.tar.gz: ca8aa6b5a67209444ece911c598ff4d0fc075bc68274a2507273bf1f6b35ee8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd7ff25272e3ebf873603eb88db635772e0efc0642f6fde9addebb534a15c4ab9c4aa10271ebb4efed72be4c9dc0ee434e17d57fe2c5410d10353a1bf6c89ce1
|
7
|
+
data.tar.gz: '09bc159b740eb05dd2eb335308ab3f5b4b4842beef822014ac2525f7e7354cac607246cd6095d040853f96fc7171cca4bf4def21df044e4de2615af1a0525be9'
|
data/.github/workflows/tests.yml
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
heap-profiler (0.
|
4
|
+
heap-profiler (0.3.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.4.1)
|
10
|
-
benchmark-ips (2.8.
|
10
|
+
benchmark-ips (2.8.4)
|
11
11
|
byebug (11.1.3)
|
12
|
-
minitest (5.14.
|
13
|
-
parallel (1.
|
14
|
-
parser (
|
12
|
+
minitest (5.14.3)
|
13
|
+
parallel (1.20.1)
|
14
|
+
parser (3.0.0.0)
|
15
15
|
ast (~> 2.4.1)
|
16
16
|
rainbow (3.0.0)
|
17
|
-
rake (13.0.
|
17
|
+
rake (13.0.3)
|
18
18
|
rake-compiler (1.1.1)
|
19
19
|
rake
|
20
|
-
regexp_parser (
|
20
|
+
regexp_parser (2.0.3)
|
21
21
|
rexml (3.2.4)
|
22
|
-
rubocop (
|
22
|
+
rubocop (1.8.1)
|
23
23
|
parallel (~> 1.10)
|
24
|
-
parser (>=
|
24
|
+
parser (>= 3.0.0.0)
|
25
25
|
rainbow (>= 2.2.2, < 4.0)
|
26
|
-
regexp_parser (>= 1.
|
26
|
+
regexp_parser (>= 1.8, < 3.0)
|
27
27
|
rexml
|
28
|
-
rubocop-ast (>=
|
28
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
29
29
|
ruby-progressbar (~> 1.7)
|
30
|
-
unicode-display_width (>= 1.4.0, <
|
31
|
-
rubocop-ast (
|
32
|
-
parser (>= 2.7.
|
33
|
-
rubocop-shopify (1.0.
|
34
|
-
rubocop (
|
35
|
-
ruby-progressbar (1.
|
36
|
-
stackprof (0.2.
|
37
|
-
unicode-display_width (
|
30
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
31
|
+
rubocop-ast (1.4.0)
|
32
|
+
parser (>= 2.7.1.5)
|
33
|
+
rubocop-shopify (1.0.7)
|
34
|
+
rubocop (~> 1.4)
|
35
|
+
ruby-progressbar (1.11.0)
|
36
|
+
stackprof (0.2.16)
|
37
|
+
unicode-display_width (2.0.0)
|
38
38
|
|
39
39
|
PLATFORMS
|
40
|
-
|
40
|
+
x86_64-darwin-19
|
41
41
|
|
42
42
|
DEPENDENCIES
|
43
43
|
benchmark-ips
|
@@ -50,4 +50,4 @@ DEPENDENCIES
|
|
50
50
|
stackprof
|
51
51
|
|
52
52
|
BUNDLED WITH
|
53
|
-
2.
|
53
|
+
2.2.5
|
@@ -1,4 +1,5 @@
|
|
1
1
|
#include "ruby.h"
|
2
|
+
#include "ruby/encoding.h"
|
2
3
|
#include "simdjson.h"
|
3
4
|
#include <fstream>
|
4
5
|
|
@@ -6,7 +7,7 @@ using namespace simdjson;
|
|
6
7
|
|
7
8
|
static VALUE rb_eHeapProfilerError, sym_type, sym_class, sym_address, sym_value,
|
8
9
|
sym_memsize, sym_imemo_type, sym_struct, sym_file, sym_line, sym_shared,
|
9
|
-
sym_references;
|
10
|
+
sym_references, id_uminus;
|
10
11
|
|
11
12
|
typedef struct {
|
12
13
|
dom::parser *parser;
|
@@ -83,6 +84,24 @@ static inline int64_t parse_dom_address(dom::element element) {
|
|
83
84
|
return parse_address(address);
|
84
85
|
}
|
85
86
|
|
87
|
+
static inline VALUE make_symbol(std::string_view string) {
|
88
|
+
return ID2SYM(rb_intern2(string.data(), string.size()));
|
89
|
+
}
|
90
|
+
|
91
|
+
static inline VALUE make_string(std::string_view string) {
|
92
|
+
return rb_utf8_str_new(string.data(), string.size());
|
93
|
+
}
|
94
|
+
|
95
|
+
# ifdef HAVE_RB_ENC_INTERNED_STR
|
96
|
+
static inline VALUE dedup_string(std::string_view string) {
|
97
|
+
return rb_enc_interned_str(string.data(), string.size(), rb_utf8_encoding());
|
98
|
+
}
|
99
|
+
# else
|
100
|
+
static inline VALUE dedup_string(std::string_view string) {
|
101
|
+
return rb_funcall(make_string(string), id_uminus, 0);
|
102
|
+
}
|
103
|
+
# endif
|
104
|
+
|
86
105
|
static VALUE rb_heap_build_index(VALUE self, VALUE path, VALUE batch_size) {
|
87
106
|
Check_Type(path, T_STRING);
|
88
107
|
Check_Type(batch_size, T_FIXNUM);
|
@@ -107,14 +126,31 @@ static VALUE rb_heap_build_index(VALUE self, VALUE path, VALUE batch_size) {
|
|
107
126
|
std::string_view value;
|
108
127
|
if (!object["value"].get(value)) {
|
109
128
|
VALUE address = INT2FIX(parse_dom_address(object["address"]));
|
110
|
-
VALUE string =
|
129
|
+
VALUE string = make_string(value);
|
111
130
|
rb_hash_aset(string_index, address, string);
|
112
131
|
}
|
113
132
|
} else if (type == "CLASS" || type == "MODULE") {
|
133
|
+
VALUE address = INT2FIX(parse_dom_address(object["address"]));
|
134
|
+
VALUE class_name = Qfalse;
|
135
|
+
|
114
136
|
std::string_view name;
|
115
137
|
if (!object["name"].get(name)) {
|
116
|
-
|
117
|
-
|
138
|
+
class_name = dedup_string(name);
|
139
|
+
} else {
|
140
|
+
std::string_view file;
|
141
|
+
uint64_t line;
|
142
|
+
|
143
|
+
if (!object["file"].get(file) && !object["line"].get(line)) {
|
144
|
+
std::string buffer = "<Class ";
|
145
|
+
buffer += file;
|
146
|
+
buffer += ":";
|
147
|
+
buffer += std::to_string(line);
|
148
|
+
buffer += ">";
|
149
|
+
class_name = dedup_string(buffer);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
if (RTEST(class_name)) {
|
118
154
|
rb_hash_aset(class_index, address, class_name);
|
119
155
|
}
|
120
156
|
}
|
@@ -140,7 +176,7 @@ static VALUE make_ruby_object(dom::object object)
|
|
140
176
|
|
141
177
|
std::string_view type;
|
142
178
|
if (!object["type"].get(type)) {
|
143
|
-
rb_hash_aset(hash, sym_type,
|
179
|
+
rb_hash_aset(hash, sym_type, make_symbol(type));
|
144
180
|
}
|
145
181
|
|
146
182
|
std::string_view address;
|
@@ -164,17 +200,17 @@ static VALUE make_ruby_object(dom::object object)
|
|
164
200
|
if (type == "IMEMO") {
|
165
201
|
std::string_view imemo_type;
|
166
202
|
if (!object["imemo_type"].get(imemo_type)) {
|
167
|
-
rb_hash_aset(hash, sym_imemo_type,
|
203
|
+
rb_hash_aset(hash, sym_imemo_type, make_symbol(imemo_type));
|
168
204
|
}
|
169
205
|
} else if (type == "DATA") {
|
170
206
|
std::string_view _struct;
|
171
207
|
if (!object["struct"].get(_struct)) {
|
172
|
-
rb_hash_aset(hash, sym_struct,
|
208
|
+
rb_hash_aset(hash, sym_struct, make_symbol(_struct));
|
173
209
|
}
|
174
210
|
} else if (type == "STRING") {
|
175
211
|
std::string_view value;
|
176
212
|
if (!object["value"].get(value)) {
|
177
|
-
rb_hash_aset(hash, sym_value,
|
213
|
+
rb_hash_aset(hash, sym_value, make_string(value));
|
178
214
|
}
|
179
215
|
|
180
216
|
bool shared;
|
@@ -196,7 +232,7 @@ static VALUE make_ruby_object(dom::object object)
|
|
196
232
|
|
197
233
|
std::string_view file;
|
198
234
|
if (!object["file"].get(file)) {
|
199
|
-
rb_hash_aset(hash, sym_file,
|
235
|
+
rb_hash_aset(hash, sym_file, dedup_string(file));
|
200
236
|
}
|
201
237
|
|
202
238
|
uint64_t line;
|
@@ -261,6 +297,7 @@ extern "C" {
|
|
261
297
|
sym_line = ID2SYM(rb_intern("line"));
|
262
298
|
sym_shared = ID2SYM(rb_intern("shared"));
|
263
299
|
sym_references = ID2SYM(rb_intern("references"));
|
300
|
+
id_uminus = rb_intern("-@");
|
264
301
|
|
265
302
|
VALUE rb_mHeapProfiler = rb_const_get(rb_cObject, rb_intern("HeapProfiler"));
|
266
303
|
|
data/lib/heap_profiler/dump.rb
CHANGED
data/lib/heap_profiler/index.rb
CHANGED
@@ -34,7 +34,7 @@ module HeapProfiler
|
|
34
34
|
}.freeze
|
35
35
|
|
36
36
|
IMEMO_TYPES = Hash.new { |h, k| h[k] = "<#{k || 'unknown'}> (IMEMO)" }
|
37
|
-
DATA_TYPES = Hash.new { |h, k| h[k] = "<#{
|
37
|
+
DATA_TYPES = Hash.new { |h, k| h[k] = "<#{k || 'unknown'}> (DATA)" }
|
38
38
|
|
39
39
|
def guess_class(object)
|
40
40
|
type = object[:type]
|
@@ -44,14 +44,15 @@ module HeapProfiler
|
|
44
44
|
|
45
45
|
return IMEMO_TYPES[object[:imemo_type]] if type == :IMEMO
|
46
46
|
|
47
|
-
class_address = object[:class]
|
48
|
-
|
47
|
+
if (class_address = object[:class])
|
48
|
+
@classes.fetch(class_address) do
|
49
|
+
return DATA_TYPES[object[:struct]] if type == :DATA
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
$stderr.puts("WARNING: Couldn't infer class name of: #{object.inspect}")
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
elsif type == :DATA
|
55
|
+
DATA_TYPES[object[:struct]]
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
data/lib/heap_profiler/parser.rb
CHANGED
@@ -2,18 +2,28 @@
|
|
2
2
|
|
3
3
|
module HeapProfiler
|
4
4
|
module Parser
|
5
|
+
CLASS_DEFAULT_PROC = ->(_hash, key) { "<Class#0x#{key.to_s(16)}>" }
|
6
|
+
|
5
7
|
class Ruby
|
6
8
|
def build_index(path)
|
7
9
|
require 'json'
|
8
10
|
classes_index = {}
|
11
|
+
classes_index.default_proc = CLASS_DEFAULT_PROC
|
9
12
|
strings_index = {}
|
10
13
|
|
11
14
|
File.open(path).each_line do |line|
|
12
15
|
object = JSON.parse(line, symbolize_names: true)
|
13
16
|
case object[:type]
|
14
17
|
when 'MODULE', 'CLASS'
|
15
|
-
|
16
|
-
|
18
|
+
address = parse_address(object[:address])
|
19
|
+
|
20
|
+
name = object[:name]
|
21
|
+
name ||= if object[:file] && object[:line]
|
22
|
+
"<Class #{object[:file]}:#{object[:line]}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
if name
|
26
|
+
classes_index[address] = name
|
17
27
|
end
|
18
28
|
when 'STRING'
|
19
29
|
next if object[:shared]
|
@@ -35,7 +45,9 @@ module HeapProfiler
|
|
35
45
|
DEFAULT_BATCH_SIZE = 10_000_000 # 10MB
|
36
46
|
|
37
47
|
def build_index(path, batch_size: DEFAULT_BATCH_SIZE)
|
38
|
-
_build_index(path, batch_size)
|
48
|
+
indexes = _build_index(path, batch_size)
|
49
|
+
indexes.first.default_proc = CLASS_DEFAULT_PROC
|
50
|
+
indexes
|
39
51
|
end
|
40
52
|
|
41
53
|
def load_many(path, since: nil, batch_size: DEFAULT_BATCH_SIZE, &block)
|
@@ -9,8 +9,6 @@ module HeapProfiler
|
|
9
9
|
# So we name them at the start of the profile to avoid that.
|
10
10
|
#
|
11
11
|
# See: https://github.com/ruby/ruby/pull/3349
|
12
|
-
#
|
13
|
-
# TODO: Could we actually do the dump ourselves? objspace is a extension already.
|
14
12
|
if RUBY_VERSION < '2.8'
|
15
13
|
def name_anonymous_modules!
|
16
14
|
ObjectSpace.each_object(Module) do |mod|
|
@@ -56,10 +54,14 @@ module HeapProfiler
|
|
56
54
|
def stop
|
57
55
|
HeapProfiler.name_anonymous_modules!
|
58
56
|
ObjectSpace.trace_object_allocations_stop if @enable_tracing
|
57
|
+
|
58
|
+
# we can't use partial dump for allocated.heap, because we need old generations
|
59
|
+
# as well to build the classes and strings indexes.
|
59
60
|
dump_heap(@allocated_heap)
|
61
|
+
|
60
62
|
GC.enable
|
61
63
|
4.times { GC.start }
|
62
|
-
dump_heap(@retained_heap)
|
64
|
+
dump_heap(@retained_heap, partial: true)
|
63
65
|
@allocated_heap.close
|
64
66
|
@retained_heap.close
|
65
67
|
write_info("generation", @generation.to_s)
|
@@ -84,17 +86,24 @@ module HeapProfiler
|
|
84
86
|
File.write(File.join(@dir_path, "#{key}.info"), value)
|
85
87
|
end
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# property.
|
91
|
-
class_eval <<~RUBY, '__hprof', __LINE__
|
92
|
-
# frozen_string_literal: true
|
93
|
-
def dump_heap(file)
|
94
|
-
ObjectSpace.dump_all(output: file)
|
89
|
+
if RUBY_VERSION >= '3.0'
|
90
|
+
def dump_heap(file, partial: false)
|
91
|
+
ObjectSpace.dump_all(output: file, since: partial ? @generation : nil)
|
95
92
|
file.close
|
96
93
|
end
|
97
|
-
|
94
|
+
else
|
95
|
+
# ObjectSpace.dump_all does allocate a few objects in itself (https://bugs.ruby-lang.org/issues/17045)
|
96
|
+
# because of this even en empty block of code will report a handful of allocations.
|
97
|
+
# To filter them more easily we attribute call `dump_all` from a method with a very specific `file`
|
98
|
+
# property.
|
99
|
+
class_eval <<~RUBY, '__hprof', __LINE__
|
100
|
+
# frozen_string_literal: true
|
101
|
+
def dump_heap(file, partial: false)
|
102
|
+
ObjectSpace.dump_all(output: file)
|
103
|
+
file.close
|
104
|
+
end
|
105
|
+
RUBY
|
106
|
+
end
|
98
107
|
|
99
108
|
def open_heap(name)
|
100
109
|
File.open(File.join(@dir_path, "#{name}.heap"), 'w+')
|
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.3.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: 2021-01-
|
11
|
+
date: 2021-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Make several heap dumps and summarize allocated, retained memory
|
14
14
|
email:
|