heap-profiler 0.8.0.rc1-aarch64-linux
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 +7 -0
- data/.github/workflows/cibuildgem.yaml +87 -0
- data/.github/workflows/tests.yml +33 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +29 -0
- data/.ruby-version +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +291 -0
- data/Rakefile +17 -0
- data/TODO.md +3 -0
- data/benchmark/address-parsing.rb +15 -0
- data/benchmark/indexing.rb +17 -0
- data/bin/console +15 -0
- data/bin/generate-report +49 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/bin/testunit +9 -0
- data/dev.yml +20 -0
- data/exe/heap-profiler +5 -0
- data/ext/heap_profiler/extconf.rb +9 -0
- data/ext/heap_profiler/heap_profiler.cpp +335 -0
- data/ext/heap_profiler/simdjson.cpp +15047 -0
- data/ext/heap_profiler/simdjson.h +32071 -0
- data/heap-profiler.gemspec +31 -0
- data/lib/heap-profiler.rb +6 -0
- data/lib/heap_profiler/3.1/heap_profiler.so +0 -0
- data/lib/heap_profiler/3.2/heap_profiler.so +0 -0
- data/lib/heap_profiler/3.3/heap_profiler.so +0 -0
- data/lib/heap_profiler/3.4/heap_profiler.so +0 -0
- data/lib/heap_profiler/4.0/heap_profiler.so +0 -0
- data/lib/heap_profiler/analyzer.rb +232 -0
- data/lib/heap_profiler/cli.rb +140 -0
- data/lib/heap_profiler/diff.rb +39 -0
- data/lib/heap_profiler/dump.rb +97 -0
- data/lib/heap_profiler/full.rb +12 -0
- data/lib/heap_profiler/index.rb +89 -0
- data/lib/heap_profiler/monochrome.rb +19 -0
- data/lib/heap_profiler/parser.rb +83 -0
- data/lib/heap_profiler/polychrome.rb +93 -0
- data/lib/heap_profiler/reporter.rb +118 -0
- data/lib/heap_profiler/results.rb +256 -0
- data/lib/heap_profiler/runtime.rb +30 -0
- data/lib/heap_profiler/version.rb +4 -0
- metadata +94 -0
data/bin/rubocop
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
require "pathname"
|
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
13
|
+
Pathname.new(__FILE__).realpath)
|
|
14
|
+
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
|
16
|
+
|
|
17
|
+
if File.file?(bundle_binstub)
|
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
19
|
+
load(bundle_binstub)
|
|
20
|
+
else
|
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require "rubygems"
|
|
27
|
+
require "bundler/setup"
|
|
28
|
+
|
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/setup
ADDED
data/bin/testunit
ADDED
data/dev.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: heap-profiler
|
|
2
|
+
|
|
3
|
+
type: ruby
|
|
4
|
+
|
|
5
|
+
up:
|
|
6
|
+
- ruby
|
|
7
|
+
- bundler
|
|
8
|
+
|
|
9
|
+
commands:
|
|
10
|
+
console:
|
|
11
|
+
desc: 'start a console'
|
|
12
|
+
run: bin/console
|
|
13
|
+
run:
|
|
14
|
+
desc: 'start the application'
|
|
15
|
+
run: bin/run
|
|
16
|
+
test:
|
|
17
|
+
syntax:
|
|
18
|
+
argument: file
|
|
19
|
+
optional: args...
|
|
20
|
+
run: bin/testunit
|
data/exe/heap-profiler
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "ruby/encoding.h"
|
|
3
|
+
#include "simdjson.h"
|
|
4
|
+
#include <fstream>
|
|
5
|
+
|
|
6
|
+
using namespace simdjson;
|
|
7
|
+
|
|
8
|
+
static VALUE rb_eHeapProfilerError, rb_eHeapProfilerCapacityError, sym_type, sym_class,
|
|
9
|
+
sym_address, sym_value, sym_memsize, sym_imemo_type, sym_struct, sym_file,
|
|
10
|
+
sym_line, sym_shared, sym_references, sym_edge_name, id_uminus;
|
|
11
|
+
|
|
12
|
+
typedef struct {
|
|
13
|
+
dom::parser *parser;
|
|
14
|
+
} parser_t;
|
|
15
|
+
|
|
16
|
+
static void Parser_delete(void *ptr) {
|
|
17
|
+
parser_t *data = (parser_t*) ptr;
|
|
18
|
+
delete data->parser;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static size_t Parser_memsize(const void *parser) {
|
|
22
|
+
return sizeof(dom::parser); // TODO: low priority, figure the real size, e.g. internal buffers etc.
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static const rb_data_type_t parser_data_type = {
|
|
26
|
+
"Parser",
|
|
27
|
+
{ 0, Parser_delete, Parser_memsize, },
|
|
28
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
static VALUE parser_allocate(VALUE klass) {
|
|
32
|
+
parser_t *data;
|
|
33
|
+
VALUE obj = TypedData_Make_Struct(klass, parser_t, &parser_data_type, data);
|
|
34
|
+
data->parser = new dom::parser;
|
|
35
|
+
return obj;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static inline dom::parser * get_parser(VALUE self) {
|
|
39
|
+
parser_t *data;
|
|
40
|
+
TypedData_Get_Struct(self, parser_t, &parser_data_type, data);
|
|
41
|
+
return data->parser;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const uint64_t digittoval[256] = {
|
|
45
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
46
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
47
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
|
|
48
|
+
9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0,
|
|
49
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
50
|
+
0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
51
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
52
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
53
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
54
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
55
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
56
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
57
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
58
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
59
|
+
|
|
60
|
+
// Inspired by https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/
|
|
61
|
+
// Ruby addresses in heap dump are hexadecimal strings "0x000000000000"...0xffffffffffff".
|
|
62
|
+
// The format being fairly stable allow for faster parsing. It should be equivalent to String#to_i(16).
|
|
63
|
+
static inline uint64_t parse_address(const char * address, const long size) {
|
|
64
|
+
assert(address[0] == '0');
|
|
65
|
+
assert(address[1] == 'x');
|
|
66
|
+
|
|
67
|
+
uint64_t value = 0;
|
|
68
|
+
for (int index = 2; index < size; index++) {
|
|
69
|
+
value <<= 4;
|
|
70
|
+
value |= digittoval[address[index]];
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static inline int64_t parse_address(std::string_view address) {
|
|
76
|
+
return parse_address(address.data(), address.size());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static inline int64_t parse_dom_address(dom::element element) {
|
|
80
|
+
std::string_view address;
|
|
81
|
+
if (element.get(address)) {
|
|
82
|
+
return 0; // ROOT object
|
|
83
|
+
}
|
|
84
|
+
return parse_address(address);
|
|
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
|
+
|
|
105
|
+
static VALUE rb_heap_build_index(VALUE self, VALUE path, VALUE batch_size) {
|
|
106
|
+
Check_Type(path, T_STRING);
|
|
107
|
+
Check_Type(batch_size, T_FIXNUM);
|
|
108
|
+
dom::parser *parser = get_parser(self);
|
|
109
|
+
dom::document_stream objects;
|
|
110
|
+
|
|
111
|
+
VALUE string_index = rb_hash_new();
|
|
112
|
+
VALUE class_index = rb_hash_new();
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
auto error = parser->load_many(RSTRING_PTR(path), FIX2INT(batch_size)).get(objects);
|
|
116
|
+
if (error != SUCCESS) {
|
|
117
|
+
rb_raise(rb_eHeapProfilerError, "%s", error_message(error));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (dom::object object : objects) {
|
|
121
|
+
std::string_view type;
|
|
122
|
+
if (object["type"].get(type)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (type == "STRING") {
|
|
127
|
+
std::string_view value;
|
|
128
|
+
if (!object["value"].get(value)) {
|
|
129
|
+
VALUE address = INT2FIX(parse_dom_address(object["address"]));
|
|
130
|
+
VALUE string = make_string(value);
|
|
131
|
+
rb_hash_aset(string_index, address, string);
|
|
132
|
+
}
|
|
133
|
+
} else if (type == "CLASS" || type == "MODULE") {
|
|
134
|
+
VALUE address = INT2FIX(parse_dom_address(object["address"]));
|
|
135
|
+
VALUE class_name = Qfalse;
|
|
136
|
+
|
|
137
|
+
std::string_view name;
|
|
138
|
+
if (!object["name"].get(name)) {
|
|
139
|
+
class_name = dedup_string(name);
|
|
140
|
+
} else {
|
|
141
|
+
std::string_view file;
|
|
142
|
+
uint64_t line;
|
|
143
|
+
|
|
144
|
+
if (!object["file"].get(file) && !object["line"].get(line)) {
|
|
145
|
+
std::string buffer = "<Class ";
|
|
146
|
+
buffer += file;
|
|
147
|
+
buffer += ":";
|
|
148
|
+
buffer += std::to_string(line);
|
|
149
|
+
buffer += ">";
|
|
150
|
+
class_name = dedup_string(buffer);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (RTEST(class_name)) {
|
|
155
|
+
rb_hash_aset(class_index, address, class_name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (simdjson::simdjson_error error) {
|
|
160
|
+
if (error.error() == CAPACITY) {
|
|
161
|
+
rb_raise(rb_eHeapProfilerCapacityError, "The parser batch size is too small to parse this heap dump");
|
|
162
|
+
} else {
|
|
163
|
+
rb_raise(rb_eHeapProfilerError, "exc: %s", error.what());
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
VALUE return_value = rb_ary_new();
|
|
168
|
+
rb_ary_push(return_value, class_index);
|
|
169
|
+
rb_ary_push(return_value, string_index);
|
|
170
|
+
return return_value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static VALUE rb_heap_parse_address(VALUE self, VALUE address) {
|
|
174
|
+
Check_Type(address, T_STRING);
|
|
175
|
+
return INT2FIX(parse_address(RSTRING_PTR(address), RSTRING_LEN(address)));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static VALUE make_ruby_object(dom::object object)
|
|
179
|
+
{
|
|
180
|
+
VALUE hash = rb_hash_new();
|
|
181
|
+
|
|
182
|
+
std::string_view type;
|
|
183
|
+
if (!object["type"].get(type)) {
|
|
184
|
+
rb_hash_aset(hash, sym_type, make_symbol(type));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
std::string_view address;
|
|
188
|
+
if (!object["address"].get(address)) {
|
|
189
|
+
rb_hash_aset(hash, sym_address, INT2FIX(parse_address(address)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
std::string_view _class;
|
|
193
|
+
if (type != "IMEMO") {
|
|
194
|
+
// IMEMO "class" field can sometime be junk
|
|
195
|
+
if (!object["class"].get(_class)) {
|
|
196
|
+
rb_hash_aset(hash, sym_class, INT2FIX(parse_address(_class)));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
uint64_t memsize;
|
|
201
|
+
if (object["memsize"].get(memsize)) {
|
|
202
|
+
// ROOT object
|
|
203
|
+
rb_hash_aset(hash, sym_memsize, INT2FIX(0));
|
|
204
|
+
} else {
|
|
205
|
+
rb_hash_aset(hash, sym_memsize, INT2FIX(memsize));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (type == "IMEMO") {
|
|
209
|
+
std::string_view imemo_type;
|
|
210
|
+
if (!object["imemo_type"].get(imemo_type)) {
|
|
211
|
+
rb_hash_aset(hash, sym_imemo_type, make_symbol(imemo_type));
|
|
212
|
+
}
|
|
213
|
+
} else if (type == "DATA") {
|
|
214
|
+
std::string_view _struct;
|
|
215
|
+
if (!object["struct"].get(_struct)) {
|
|
216
|
+
rb_hash_aset(hash, sym_struct, make_symbol(_struct));
|
|
217
|
+
}
|
|
218
|
+
} else if (type == "STRING") {
|
|
219
|
+
std::string_view value;
|
|
220
|
+
if (!object["value"].get(value)) {
|
|
221
|
+
rb_hash_aset(hash, sym_value, make_string(value));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
bool shared;
|
|
225
|
+
if (!object["shared"].get(shared)) {
|
|
226
|
+
rb_hash_aset(hash, sym_shared, shared ? Qtrue : Qnil);
|
|
227
|
+
if (shared) {
|
|
228
|
+
VALUE references = rb_ary_new();
|
|
229
|
+
dom::array reference_elements(object["references"]);
|
|
230
|
+
for (dom::element reference_element : reference_elements) {
|
|
231
|
+
std::string_view reference;
|
|
232
|
+
if (!reference_element.get(reference)) {
|
|
233
|
+
rb_ary_push(references, INT2FIX(parse_address(reference)));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
rb_hash_aset(hash, sym_references, references);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} else if (type == "SHAPE") {
|
|
240
|
+
std::string_view edge_name;
|
|
241
|
+
if (!object["edge_name"].get(edge_name)) {
|
|
242
|
+
rb_hash_aset(hash, sym_edge_name, make_string(edge_name));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
std::string_view file;
|
|
247
|
+
if (!object["file"].get(file)) {
|
|
248
|
+
rb_hash_aset(hash, sym_file, dedup_string(file));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
uint64_t line;
|
|
252
|
+
if (!object["line"].get(line)) {
|
|
253
|
+
rb_hash_aset(hash, sym_line, INT2FIX(line));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return hash;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static VALUE rb_heap_load_many(VALUE self, VALUE arg, VALUE since, VALUE batch_size)
|
|
260
|
+
{
|
|
261
|
+
Check_Type(arg, T_STRING);
|
|
262
|
+
Check_Type(batch_size, T_FIXNUM);
|
|
263
|
+
|
|
264
|
+
dom::parser *parser = get_parser(self);
|
|
265
|
+
dom::document_stream objects;
|
|
266
|
+
try {
|
|
267
|
+
auto error = parser->load_many(RSTRING_PTR(arg), FIX2INT(batch_size)).get(objects);
|
|
268
|
+
if (error != SUCCESS) {
|
|
269
|
+
rb_raise(rb_eHeapProfilerError, "%s", error_message(error));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
int64_t generation = -1;
|
|
273
|
+
if (RTEST(since)) {
|
|
274
|
+
Check_Type(since, T_FIXNUM);
|
|
275
|
+
generation = FIX2INT(since);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (dom::element object : objects) {
|
|
279
|
+
int64_t object_generation;
|
|
280
|
+
if (generation > -1 && object["generation"].get(object_generation) || object_generation < generation) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
std::string_view property;
|
|
285
|
+
if (!object["file"].get(property) && property == "__hprof") {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!object["struct"].get(property) && property == "ObjectTracing/allocation_info_tracer") {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
rb_yield(make_ruby_object(object));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return Qnil;
|
|
296
|
+
} catch (simdjson::simdjson_error error) {
|
|
297
|
+
if (error.error() == CAPACITY) {
|
|
298
|
+
rb_raise(rb_eHeapProfilerCapacityError, "The parser batch size is too small to parse this heap dump");
|
|
299
|
+
} else {
|
|
300
|
+
rb_raise(rb_eHeapProfilerError, "exc: %s", error.what());
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
extern "C" {
|
|
306
|
+
void Init_heap_profiler(void) {
|
|
307
|
+
sym_type = ID2SYM(rb_intern("type"));
|
|
308
|
+
sym_class = ID2SYM(rb_intern("class"));
|
|
309
|
+
sym_address = ID2SYM(rb_intern("address"));
|
|
310
|
+
sym_edge_name = ID2SYM(rb_intern("edge_name"));
|
|
311
|
+
sym_value = ID2SYM(rb_intern("value"));
|
|
312
|
+
sym_memsize = ID2SYM(rb_intern("memsize"));
|
|
313
|
+
sym_struct = ID2SYM(rb_intern("struct"));
|
|
314
|
+
sym_imemo_type = ID2SYM(rb_intern("imemo_type"));
|
|
315
|
+
sym_file = ID2SYM(rb_intern("file"));
|
|
316
|
+
sym_line = ID2SYM(rb_intern("line"));
|
|
317
|
+
sym_shared = ID2SYM(rb_intern("shared"));
|
|
318
|
+
sym_references = ID2SYM(rb_intern("references"));
|
|
319
|
+
id_uminus = rb_intern("-@");
|
|
320
|
+
|
|
321
|
+
VALUE rb_mHeapProfiler = rb_const_get(rb_cObject, rb_intern("HeapProfiler"));
|
|
322
|
+
|
|
323
|
+
rb_eHeapProfilerError = rb_const_get(rb_mHeapProfiler, rb_intern("Error"));
|
|
324
|
+
rb_global_variable(&rb_eHeapProfilerError);
|
|
325
|
+
|
|
326
|
+
rb_eHeapProfilerCapacityError = rb_const_get(rb_mHeapProfiler, rb_intern("CapacityError"));
|
|
327
|
+
rb_global_variable(&rb_eHeapProfilerCapacityError);
|
|
328
|
+
|
|
329
|
+
VALUE rb_mHeapProfilerParserNative = rb_const_get(rb_const_get(rb_mHeapProfiler, rb_intern("Parser")), rb_intern("Native"));
|
|
330
|
+
rb_define_alloc_func(rb_mHeapProfilerParserNative, parser_allocate);
|
|
331
|
+
rb_define_method(rb_mHeapProfilerParserNative, "_build_index", reinterpret_cast<VALUE (*)(...)>(rb_heap_build_index), 2);
|
|
332
|
+
rb_define_method(rb_mHeapProfilerParserNative, "parse_address", reinterpret_cast<VALUE (*)(...)>(rb_heap_parse_address), 1);
|
|
333
|
+
rb_define_method(rb_mHeapProfilerParserNative, "_load_many", reinterpret_cast<VALUE (*)(...)>(rb_heap_load_many), 3);
|
|
334
|
+
}
|
|
335
|
+
}
|