heap_dump 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/ext/heap_dump/extconf.rb +10 -0
- data/ext/heap_dump/heap_dump.c +641 -0
- data/ext/heap_dump/ruby_io.h +53 -0
- data/heap_dump.gemspec +26 -0
- data/lib/heap_dump/version.rb +3 -0
- data/lib/heap_dump.rb +15 -0
- metadata +91 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Vasily Fedoseyev
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# HeapDump
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'heap_dump'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install heap_dump
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
spec = Gem::Specification.find_by_name('ruby-internal', '~>0.8.5') #FIXME: DRY (see gemfile)
|
4
|
+
find_header('version.h', File.join(spec.gem_dir, 'ext', 'internal', 'yarv-headers'))
|
5
|
+
find_header('yarv-headers/node.h', File.join(spec.gem_dir, 'ext', 'internal'))
|
6
|
+
|
7
|
+
yajl = Gem::Specification.find_by_name('yajl-ruby', '~>1.1')
|
8
|
+
find_header('api/yajl_gen.h', File.join(yajl.gem_dir, 'ext', 'yajl'))
|
9
|
+
|
10
|
+
create_makefile('heap_dump')
|
@@ -0,0 +1,641 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
|
5
|
+
|
6
|
+
#include "yarv-headers/constant.h"
|
7
|
+
#include "yarv-headers/node.h"
|
8
|
+
#include "yarv-headers/vm_core.h"
|
9
|
+
|
10
|
+
//#undef RCLASS_IV_TBL
|
11
|
+
//#include "yarv-headers/internal.h"
|
12
|
+
#define RCLASS_EXT(c) (RCLASS(c)->ptr)
|
13
|
+
|
14
|
+
|
15
|
+
#include "yarv-headers/method.h"
|
16
|
+
|
17
|
+
#include "ruby_io.h" // need rb_io_t
|
18
|
+
|
19
|
+
#include "node/ruby_internal_node.h"
|
20
|
+
#include "node/node_type_descrip.c"
|
21
|
+
|
22
|
+
#include "api/yajl_gen.h"
|
23
|
+
|
24
|
+
#ifndef RUBY_VM
|
25
|
+
#error No RUBY_VM, old rubies not supported
|
26
|
+
#endif
|
27
|
+
|
28
|
+
// simple test - rake compile && bundle exec ruby -e 'require "heap_dump"; HeapDump.dump'
|
29
|
+
|
30
|
+
static VALUE rb_mHeapDumpModule;
|
31
|
+
|
32
|
+
static ID classid;
|
33
|
+
|
34
|
+
//shortcuts to yajl
|
35
|
+
#define yg_string(str,len) yajl_gen_string(ctx->yajl, str, len)
|
36
|
+
#define yg_cstring(str) yg_string(str, (unsigned int)strlen(str))
|
37
|
+
#define yg_rstring(str) yg_string(RSTRING_PTR(str), (unsigned int)RSTRING_LEN(str))
|
38
|
+
#define yg_int(i) yajl_gen_integer(ctx->yajl, i)
|
39
|
+
#define yg_double(d) yajl_gen_double(ctx->yajl, d)
|
40
|
+
|
41
|
+
//#define yg_id(obj) yg_int(NUM2LONG(rb_obj_id(obj)))
|
42
|
+
#define yg_id(obj) yg_id1(obj,ctx)
|
43
|
+
|
44
|
+
|
45
|
+
#define ygh_id(key,obj) {yg_cstring(key); yg_id(obj);}
|
46
|
+
#define ygh_int(key,i) {yg_cstring(key); yg_int((long int)(i));}
|
47
|
+
#define ygh_double(key,d) {yg_cstring(key); yg_double(d);}
|
48
|
+
#define ygh_string(key,str,len) {yg_cstring(key); yg_string(str,len);}
|
49
|
+
#define ygh_cstring(key,str) {yg_cstring(key); yg_cstring(str);}
|
50
|
+
#define ygh_rstring(key,str) {yg_cstring(key); yg_rstring(str);}
|
51
|
+
|
52
|
+
// context for objectspace_walker callback
|
53
|
+
typedef struct walk_ctx {
|
54
|
+
int walker_called;
|
55
|
+
int live_objects;
|
56
|
+
FILE* file;
|
57
|
+
|
58
|
+
yajl_gen yajl;
|
59
|
+
} walk_ctx_t;
|
60
|
+
|
61
|
+
static void flush_yajl(walk_ctx_t *ctx){
|
62
|
+
const unsigned char* buf;
|
63
|
+
unsigned int len;
|
64
|
+
if(yajl_gen_get_buf(ctx->yajl, &buf, &len) == yajl_gen_status_ok){
|
65
|
+
fwrite(buf, len, 1, ctx->file);
|
66
|
+
yajl_gen_clear(ctx->yajl);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
static inline const char* rb_builtin_type(VALUE obj){
|
71
|
+
switch(BUILTIN_TYPE(obj)){
|
72
|
+
#define T(t) case t: return #t;
|
73
|
+
T(T_NONE); T(T_NIL);
|
74
|
+
T(T_OBJECT); T(T_CLASS); T(T_ICLASS); T(T_MODULE);
|
75
|
+
T(T_SYMBOL); T(T_STRING); T(T_REGEXP); T(T_MATCH);
|
76
|
+
T(T_ARRAY); T(T_HASH); T(T_STRUCT);
|
77
|
+
|
78
|
+
T(T_FILE);
|
79
|
+
T(T_FIXNUM); T(T_BIGNUM); T(T_FLOAT); T(T_RATIONAL); T(T_COMPLEX);
|
80
|
+
|
81
|
+
T(T_TRUE); T(T_FALSE);
|
82
|
+
T(T_DATA);
|
83
|
+
T(T_UNDEF);
|
84
|
+
T(T_NODE); // code?
|
85
|
+
T(T_ZOMBIE);
|
86
|
+
#undef T
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
#define true 1
|
91
|
+
#define false 0
|
92
|
+
|
93
|
+
//FIXME: handle non-ids?
|
94
|
+
static void yg_id1(VALUE obj, walk_ctx_t* ctx){
|
95
|
+
if(!obj) return;
|
96
|
+
if (IMMEDIATE_P(obj)) {
|
97
|
+
if (FIXNUM_P(obj)) { /*ignore immediate fixnum*/ return; }
|
98
|
+
if (obj == Qtrue){ yajl_gen_bool(ctx->yajl, true); return; }
|
99
|
+
if (SYMBOL_P(obj)) {
|
100
|
+
//printf("symbol\n");
|
101
|
+
yg_cstring(rb_id2name(SYM2ID(obj)));
|
102
|
+
//printf("symbol %s\n", rb_id2name(SYM2ID(obj)));
|
103
|
+
return;
|
104
|
+
}
|
105
|
+
if (obj == Qundef) { yg_cstring("(undef)"); return; }
|
106
|
+
printf("immediate p\n");
|
107
|
+
} else /*non-immediate*/ if (!RTEST(obj)) {
|
108
|
+
if (obj == Qnil){
|
109
|
+
yajl_gen_null(ctx->yajl);
|
110
|
+
return;
|
111
|
+
}
|
112
|
+
if (obj == Qfalse) {
|
113
|
+
yajl_gen_bool(ctx->yajl, false);
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
if(BUILTIN_TYPE(obj) == T_STRING && (!(RBASIC(obj)->flags & RSTRING_NOEMBED))){
|
119
|
+
//printf("embedded string\n");
|
120
|
+
return;
|
121
|
+
}
|
122
|
+
yg_int(NUM2LONG(rb_obj_id(obj)));
|
123
|
+
}
|
124
|
+
|
125
|
+
|
126
|
+
static void dump_node_refs(NODE* obj, walk_ctx_t* ctx){
|
127
|
+
switch (nd_type(obj)) {
|
128
|
+
case NODE_IF: /* 1,2,3 */
|
129
|
+
case NODE_FOR:
|
130
|
+
case NODE_ITER:
|
131
|
+
case NODE_WHEN:
|
132
|
+
case NODE_MASGN:
|
133
|
+
case NODE_RESCUE:
|
134
|
+
case NODE_RESBODY:
|
135
|
+
case NODE_CLASS:
|
136
|
+
case NODE_BLOCK_PASS:
|
137
|
+
//gc_mark(objspace, (VALUE)obj->as.node.u2.node, lev);
|
138
|
+
yg_id((VALUE)obj->u2.node);
|
139
|
+
/* fall through */
|
140
|
+
case NODE_BLOCK: /* 1,3 */
|
141
|
+
case NODE_OPTBLOCK:
|
142
|
+
case NODE_ARRAY:
|
143
|
+
case NODE_DSTR:
|
144
|
+
case NODE_DXSTR:
|
145
|
+
case NODE_DREGX:
|
146
|
+
case NODE_DREGX_ONCE:
|
147
|
+
case NODE_ENSURE:
|
148
|
+
case NODE_CALL:
|
149
|
+
case NODE_DEFS:
|
150
|
+
case NODE_OP_ASGN1:
|
151
|
+
//gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev);
|
152
|
+
yg_id((VALUE)obj->u1.node);
|
153
|
+
/* fall through */
|
154
|
+
case NODE_SUPER: /* 3 */
|
155
|
+
case NODE_FCALL:
|
156
|
+
case NODE_DEFN:
|
157
|
+
case NODE_ARGS_AUX:
|
158
|
+
//ptr = (VALUE)obj->as.node.u3.node;
|
159
|
+
yg_id((VALUE)obj->u3.node);
|
160
|
+
return; //goto again;
|
161
|
+
|
162
|
+
case NODE_WHILE: /* 1,2 */
|
163
|
+
case NODE_UNTIL:
|
164
|
+
case NODE_AND:
|
165
|
+
case NODE_OR:
|
166
|
+
case NODE_CASE:
|
167
|
+
case NODE_SCLASS:
|
168
|
+
case NODE_DOT2:
|
169
|
+
case NODE_DOT3:
|
170
|
+
case NODE_FLIP2:
|
171
|
+
case NODE_FLIP3:
|
172
|
+
case NODE_MATCH2:
|
173
|
+
case NODE_MATCH3:
|
174
|
+
case NODE_OP_ASGN_OR:
|
175
|
+
case NODE_OP_ASGN_AND:
|
176
|
+
case NODE_MODULE:
|
177
|
+
case NODE_ALIAS:
|
178
|
+
case NODE_VALIAS:
|
179
|
+
case NODE_ARGSCAT:
|
180
|
+
//gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev);
|
181
|
+
yg_id((VALUE)obj->u1.node);
|
182
|
+
/* fall through */
|
183
|
+
case NODE_GASGN: /* 2 */
|
184
|
+
case NODE_LASGN:
|
185
|
+
case NODE_DASGN:
|
186
|
+
case NODE_DASGN_CURR:
|
187
|
+
case NODE_IASGN:
|
188
|
+
case NODE_IASGN2:
|
189
|
+
case NODE_CVASGN:
|
190
|
+
case NODE_COLON3:
|
191
|
+
case NODE_OPT_N:
|
192
|
+
case NODE_EVSTR:
|
193
|
+
case NODE_UNDEF:
|
194
|
+
case NODE_POSTEXE:
|
195
|
+
//ptr = (VALUE)obj->as.node.u2.node;
|
196
|
+
yg_id((VALUE)obj->u2.node);
|
197
|
+
return; //goto again;
|
198
|
+
|
199
|
+
case NODE_HASH: /* 1 */
|
200
|
+
case NODE_LIT:
|
201
|
+
case NODE_STR:
|
202
|
+
case NODE_XSTR:
|
203
|
+
case NODE_DEFINED:
|
204
|
+
case NODE_MATCH:
|
205
|
+
case NODE_RETURN:
|
206
|
+
case NODE_BREAK:
|
207
|
+
case NODE_NEXT:
|
208
|
+
case NODE_YIELD:
|
209
|
+
case NODE_COLON2:
|
210
|
+
case NODE_SPLAT:
|
211
|
+
case NODE_TO_ARY:
|
212
|
+
//ptr = (VALUE)obj->as.node.u1.node;
|
213
|
+
yg_id((VALUE)obj->u1.node);
|
214
|
+
return; //goto again;
|
215
|
+
|
216
|
+
case NODE_SCOPE: /* 2,3 */ //ANN("format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body");
|
217
|
+
//printf("node scope\n");
|
218
|
+
if(obj->nd_tbl){
|
219
|
+
ID *tbl = RNODE(obj)->nd_tbl;
|
220
|
+
int size = tbl[0];
|
221
|
+
tbl++;
|
222
|
+
int i = 0;
|
223
|
+
for (; i < size; i++) {
|
224
|
+
//TODO: dump local var names?
|
225
|
+
//printf("%s\n", rb_id2name(tbl[i]));
|
226
|
+
//yg_id(tbl[i]); //FIXME: these are ids, not values
|
227
|
+
}
|
228
|
+
}
|
229
|
+
case NODE_CDECL:
|
230
|
+
case NODE_OPT_ARG:
|
231
|
+
//gc_mark(objspace, (VALUE)obj->as.node.u3.node, lev);
|
232
|
+
//ptr = (VALUE)obj->as.node.u2.node;
|
233
|
+
//goto again;
|
234
|
+
yg_id((VALUE)obj->u3.node);
|
235
|
+
yg_id((VALUE)obj->u2.node);
|
236
|
+
return;
|
237
|
+
|
238
|
+
case NODE_ARGS: /* custom */
|
239
|
+
#if 0
|
240
|
+
//RUBY 1.9.3
|
241
|
+
{
|
242
|
+
struct rb_args_info *args = obj->u3.args;
|
243
|
+
if (args) {
|
244
|
+
if (args->pre_init) yg_id((VALUE)args->pre_init); //gc_mark(objspace, (VALUE)args->pre_init, lev);
|
245
|
+
if (args->post_init) yg_id((VALUE)args->post_init); //gc_mark(objspace, (VALUE)args->post_init, lev);
|
246
|
+
if (args->opt_args) yg_id((VALUE)args->opt_args); //gc_mark(objspace, (VALUE)args->opt_args, lev);
|
247
|
+
if (args->kw_args) yg_id((VALUE)args->kw_args); //gc_mark(objspace, (VALUE)args->kw_args, lev);
|
248
|
+
if (args->kw_rest_arg) yg_id((VALUE)args->kw_rest_arg); //gc_mark(objspace, (VALUE)args->kw_rest_arg, lev);
|
249
|
+
}
|
250
|
+
}
|
251
|
+
//ptr = (VALUE)obj->as.node.u2.node;
|
252
|
+
yg_id(obj->u2.node);
|
253
|
+
//goto again;
|
254
|
+
#endif
|
255
|
+
yg_id((VALUE)obj->u1.node);
|
256
|
+
return;
|
257
|
+
|
258
|
+
case NODE_ZARRAY: /* - */
|
259
|
+
case NODE_ZSUPER:
|
260
|
+
case NODE_VCALL:
|
261
|
+
case NODE_GVAR:
|
262
|
+
case NODE_LVAR:
|
263
|
+
case NODE_DVAR:
|
264
|
+
case NODE_IVAR:
|
265
|
+
case NODE_CVAR:
|
266
|
+
case NODE_NTH_REF:
|
267
|
+
case NODE_BACK_REF:
|
268
|
+
case NODE_REDO:
|
269
|
+
case NODE_RETRY:
|
270
|
+
case NODE_SELF:
|
271
|
+
case NODE_NIL:
|
272
|
+
case NODE_TRUE:
|
273
|
+
case NODE_FALSE:
|
274
|
+
case NODE_ERRINFO:
|
275
|
+
case NODE_BLOCK_ARG:
|
276
|
+
break;
|
277
|
+
case NODE_ALLOCA:
|
278
|
+
//mark_locations_array(objspace, (VALUE*)obj->as.node.u1.value, obj->as.node.u3.cnt); :
|
279
|
+
{
|
280
|
+
VALUE* x = (VALUE*)obj->u1.value;
|
281
|
+
unsigned long n = obj->u3.cnt;
|
282
|
+
while (n--) {
|
283
|
+
//v = *x;
|
284
|
+
// if (is_pointer_to_heap(objspace, (void *)v)) {
|
285
|
+
// //gc_mark(objspace, v, 0);
|
286
|
+
yg_id(*x);
|
287
|
+
// }
|
288
|
+
x++;
|
289
|
+
}
|
290
|
+
}
|
291
|
+
//ptr = (VALUE)obj->as.node.u2.node;
|
292
|
+
yg_id((VALUE)obj->u2.node);
|
293
|
+
//goto again;
|
294
|
+
return;
|
295
|
+
|
296
|
+
case NODE_MEMO:
|
297
|
+
yg_id((VALUE)obj->u1.node);
|
298
|
+
//printf("MEMO NODE: %p %p %p\n", obj->u1.node, obj->u2.node, obj->u3.node);
|
299
|
+
break;
|
300
|
+
|
301
|
+
//not implemented:
|
302
|
+
|
303
|
+
case NODE_CONST:
|
304
|
+
//no ref, just id
|
305
|
+
// if(n->nd_vid == 0)return Qfalse;
|
306
|
+
// else if(n->nd_vid == 1)return Qtrue;
|
307
|
+
// else return ID2SYM(n->nd_vid);
|
308
|
+
break;
|
309
|
+
case NODE_ATTRASGN:
|
310
|
+
//FIXME: may hold references!
|
311
|
+
break;
|
312
|
+
|
313
|
+
//iteration func - blocks,procs,lambdas etc:
|
314
|
+
case NODE_IFUNC: //NEN_CFNC, NEN_TVAL, NEN_STATE? / u2 seems to be data for func(context?)
|
315
|
+
printf("IFUNC NODE: %p %p %p\n", obj->nd_cfnc, obj->u2.node, obj->nd_aid /*u3 - aid id- - aka frame_this_func?*/);
|
316
|
+
//FIXME: closures may leak references?
|
317
|
+
break;
|
318
|
+
|
319
|
+
//empty:
|
320
|
+
case NODE_BEGIN: break;
|
321
|
+
default: /* unlisted NODE */
|
322
|
+
//FIXME: check pointers!
|
323
|
+
|
324
|
+
{const Node_Type_Descrip* descrip = node_type_descrips[nd_type(obj)];
|
325
|
+
|
326
|
+
printf("UNKNOWN NODE TYPE %d(%s): %p %p %p\n", nd_type(obj), descrip ? descrip->name : "unknown", obj->u1.node, obj->u2.node, obj->u3.node);
|
327
|
+
}
|
328
|
+
|
329
|
+
// if (is_pointer_to_heap(objspace, obj->as.node.u1.node)) { gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); }
|
330
|
+
// if (is_pointer_to_heap(objspace, obj->as.node.u2.node)) { gc_mark(objspace, (VALUE)obj->as.node.u2.node, lev); }
|
331
|
+
// if (is_pointer_to_heap(objspace, obj->as.node.u3.node)) { gc_mark(objspace, (VALUE)obj->as.node.u3.node, lev); }
|
332
|
+
|
333
|
+
//yg_id((VALUE)obj->u1.node);
|
334
|
+
//yg_id((VALUE)obj->u2.node);
|
335
|
+
//yg_id((VALUE)obj->u3.node);
|
336
|
+
}
|
337
|
+
}
|
338
|
+
|
339
|
+
static inline void dump_node(NODE* obj, walk_ctx_t *ctx){
|
340
|
+
const Node_Type_Descrip* descrip = node_type_descrips[nd_type(obj)]; // node_type_descrip(nd_type(obj)) raises exception on unknown 65, 66 and 92
|
341
|
+
|
342
|
+
ygh_int("nd_type", nd_type(obj));
|
343
|
+
ygh_cstring("nd_type_str", descrip ? descrip->name : "unknown");
|
344
|
+
|
345
|
+
yg_cstring("refs");
|
346
|
+
yajl_gen_array_open(ctx->yajl);
|
347
|
+
dump_node_refs(obj, ctx);
|
348
|
+
yajl_gen_array_close(ctx->yajl);
|
349
|
+
}
|
350
|
+
|
351
|
+
static int
|
352
|
+
dump_keyvalue(st_data_t key, st_data_t value, walk_ctx_t *ctx){
|
353
|
+
yg_id((VALUE)key);
|
354
|
+
yg_id((VALUE)value);
|
355
|
+
return ST_CONTINUE;
|
356
|
+
}
|
357
|
+
|
358
|
+
static void dump_hash(VALUE obj, walk_ctx_t* ctx){
|
359
|
+
yg_cstring("refs");
|
360
|
+
yajl_gen_array_open(ctx->yajl);
|
361
|
+
if(RHASH_SIZE(obj) > 0){
|
362
|
+
//TODO: mark keys and values separately?
|
363
|
+
st_foreach(RHASH(obj)->ntbl, dump_keyvalue, (st_data_t)ctx);
|
364
|
+
}
|
365
|
+
yajl_gen_array_close(ctx->yajl);
|
366
|
+
}
|
367
|
+
|
368
|
+
static int dump_method_entry_i(ID key, const rb_method_entry_t *me, st_data_t data){
|
369
|
+
walk_ctx_t *ctx = (void*)data;
|
370
|
+
if(key == ID_ALLOCATOR) {
|
371
|
+
yg_cstring("___allocator___");
|
372
|
+
} else {
|
373
|
+
yg_cstring(rb_id2name(key));
|
374
|
+
}
|
375
|
+
|
376
|
+
const rb_method_definition_t *def = me->def;
|
377
|
+
|
378
|
+
//gc_mark(objspace, me->klass, lev);?
|
379
|
+
if (!def) {
|
380
|
+
yajl_gen_null(ctx->yajl);
|
381
|
+
return ST_CONTINUE;
|
382
|
+
}
|
383
|
+
|
384
|
+
switch (def->type) {
|
385
|
+
case VM_METHOD_TYPE_ISEQ:
|
386
|
+
yg_id(def->body.iseq->self);
|
387
|
+
break;
|
388
|
+
case VM_METHOD_TYPE_CFUNC: yg_cstring("(CFUNC)"); break;
|
389
|
+
case VM_METHOD_TYPE_ATTRSET:
|
390
|
+
case VM_METHOD_TYPE_IVAR:
|
391
|
+
yg_id(def->body.attr.location);
|
392
|
+
break;
|
393
|
+
case VM_METHOD_TYPE_BMETHOD:
|
394
|
+
yg_id(def->body.proc);
|
395
|
+
break;
|
396
|
+
case VM_METHOD_TYPE_ZSUPER: yg_cstring("(ZSUPER)"); break;
|
397
|
+
case VM_METHOD_TYPE_UNDEF: yg_cstring("(UNDEF)"); break;
|
398
|
+
case VM_METHOD_TYPE_NOTIMPLEMENTED: yg_cstring("(NOTIMP)"); break;
|
399
|
+
case VM_METHOD_TYPE_OPTIMIZED: /* Kernel#send, Proc#call, etc */ yg_cstring("(OPTIMIZED)"); break;
|
400
|
+
case VM_METHOD_TYPE_MISSING: yg_cstring("(MISSING)"); break;
|
401
|
+
default:
|
402
|
+
yajl_gen_null(ctx->yajl);
|
403
|
+
break;
|
404
|
+
}
|
405
|
+
return ST_CONTINUE;
|
406
|
+
}
|
407
|
+
|
408
|
+
static int dump_iv_entry(st_data_t key, VALUE value, walk_ctx_t *ctx){
|
409
|
+
yg_id(value);
|
410
|
+
return ST_CONTINUE;
|
411
|
+
}
|
412
|
+
|
413
|
+
static int dump_const_entry_i(ID key, const rb_const_entry_t *ce, walk_ctx_t *ctx){
|
414
|
+
|
415
|
+
//was(ID key, VALUE value, walk_ctx_t *ctx){
|
416
|
+
|
417
|
+
printf("const entry\n");
|
418
|
+
|
419
|
+
VALUE value = ce->value;
|
420
|
+
//file = ce->file
|
421
|
+
|
422
|
+
printf("const key %p\n", (void*)key);
|
423
|
+
yg_cstring(rb_id2name(key));
|
424
|
+
yg_id(value);
|
425
|
+
return ST_CONTINUE;
|
426
|
+
}
|
427
|
+
|
428
|
+
static VALUE rb_class_real_checked(VALUE cl)
|
429
|
+
{
|
430
|
+
if (cl == 0)
|
431
|
+
return 0;
|
432
|
+
while ((RBASIC(cl)->flags & FL_SINGLETON) || BUILTIN_TYPE(cl) == T_ICLASS) {
|
433
|
+
if(RCLASS_EXT(cl) && RCLASS_SUPER(cl)){
|
434
|
+
cl = RCLASS_SUPER(cl);
|
435
|
+
} else {
|
436
|
+
return 0;
|
437
|
+
}
|
438
|
+
}
|
439
|
+
return cl;
|
440
|
+
}
|
441
|
+
|
442
|
+
static inline void walk_live_object(VALUE obj, walk_ctx_t *ctx){
|
443
|
+
ctx->live_objects++;
|
444
|
+
yajl_gen_map_open(ctx->yajl);
|
445
|
+
|
446
|
+
ygh_int("id", NUM2LONG(rb_obj_id(obj))); //TODO: object_id is value>>2 ?
|
447
|
+
ygh_cstring("bt", rb_builtin_type(obj));
|
448
|
+
|
449
|
+
switch(BUILTIN_TYPE(obj)){ // no need to call TYPE(), as value is on heap
|
450
|
+
case T_NODE:
|
451
|
+
dump_node(RNODE(obj), ctx);
|
452
|
+
break;
|
453
|
+
case T_STRING:
|
454
|
+
//TODO: limit string len!
|
455
|
+
ygh_string("val", RSTRING_PTR(obj), (unsigned int)RSTRING_LEN(obj));
|
456
|
+
break;
|
457
|
+
case T_SYMBOL:
|
458
|
+
ygh_cstring("val", rb_id2name(SYM2ID(obj)));
|
459
|
+
break;
|
460
|
+
case T_REGEXP:
|
461
|
+
ygh_string("val", RREGEXP_SRC_PTR(obj), (unsigned int)RREGEXP_SRC_LEN(obj));
|
462
|
+
break;
|
463
|
+
// T(T_MATCH);
|
464
|
+
|
465
|
+
case T_ARRAY:
|
466
|
+
// if (FL_TEST(obj, ELTS_SHARED)) ...
|
467
|
+
yg_cstring("refs");
|
468
|
+
yajl_gen_array_open(ctx->yajl);
|
469
|
+
{
|
470
|
+
long i, len = RARRAY_LEN(obj);
|
471
|
+
VALUE *ptr = RARRAY_PTR(obj);
|
472
|
+
for(i = 0; i < len; i++) yg_id(*ptr++);
|
473
|
+
}
|
474
|
+
yajl_gen_array_close(ctx->yajl);
|
475
|
+
break;
|
476
|
+
|
477
|
+
case T_STRUCT:
|
478
|
+
yg_cstring("refs"); //ivars
|
479
|
+
yajl_gen_array_open(ctx->yajl);
|
480
|
+
{
|
481
|
+
long len = RSTRUCT_LEN(obj);
|
482
|
+
VALUE *ptr = RSTRUCT_PTR(obj);
|
483
|
+
while (len--) yg_id(*ptr++);
|
484
|
+
}
|
485
|
+
yajl_gen_array_close(ctx->yajl);
|
486
|
+
break;
|
487
|
+
|
488
|
+
case T_HASH:
|
489
|
+
dump_hash(obj, ctx);
|
490
|
+
break;
|
491
|
+
|
492
|
+
case T_OBJECT:
|
493
|
+
yg_cstring("class");
|
494
|
+
yg_id(rb_class_of(obj));
|
495
|
+
yg_cstring("refs"); //ivars
|
496
|
+
yajl_gen_array_open(ctx->yajl);
|
497
|
+
{
|
498
|
+
long i, len = ROBJECT_NUMIV(obj);
|
499
|
+
VALUE *ptr = ROBJECT_IVPTR(obj);
|
500
|
+
for (i = 0; i < len; i++) yg_id(*ptr++);
|
501
|
+
}
|
502
|
+
yajl_gen_array_close(ctx->yajl);
|
503
|
+
break;
|
504
|
+
|
505
|
+
case T_ICLASS:
|
506
|
+
case T_CLASS:
|
507
|
+
case T_MODULE:
|
508
|
+
{
|
509
|
+
VALUE name = rb_ivar_get(obj, classid);
|
510
|
+
if (name != Qnil){
|
511
|
+
ygh_cstring("name", rb_id2name(SYM2ID(name)));
|
512
|
+
} else if(RCLASS_EXT(obj) && RCLASS_EXT(obj)->super){
|
513
|
+
// more expensive + allocates a string
|
514
|
+
VALUE path = rb_class_path(rb_class_real_checked(obj));
|
515
|
+
|
516
|
+
ygh_rstring("name", path);
|
517
|
+
}
|
518
|
+
|
519
|
+
yg_cstring("methods");
|
520
|
+
yajl_gen_map_open(ctx->yajl);
|
521
|
+
|
522
|
+
if(RCLASS_M_TBL(obj) && RCLASS_M_TBL(obj)->num_entries > 0){ // num check not necessary?
|
523
|
+
st_foreach(RCLASS_M_TBL(obj), dump_method_entry_i, (st_data_t)ctx);
|
524
|
+
}
|
525
|
+
yajl_gen_map_close(ctx->yajl);
|
526
|
+
|
527
|
+
if (RCLASS_EXT(obj)){
|
528
|
+
if(RCLASS_IV_TBL(obj) && RCLASS_IV_TBL(obj)->num_entries > 0){
|
529
|
+
yg_cstring("refs");
|
530
|
+
yajl_gen_array_open(ctx->yajl); //TODO: what are iv keys?
|
531
|
+
st_foreach(RCLASS_IV_TBL(obj), dump_iv_entry, (st_data_t)ctx);
|
532
|
+
yajl_gen_array_close(ctx->yajl);
|
533
|
+
}
|
534
|
+
|
535
|
+
#if 0
|
536
|
+
// this is for 1.9.3 or so - where rb_classext_t has const_tbl
|
537
|
+
if(RCLASS_CONST_TBL(obj)){
|
538
|
+
yg_cstring("consts");
|
539
|
+
yajl_gen_map_open(ctx->yajl);
|
540
|
+
flush_yajl(ctx); //for debug only
|
541
|
+
st_foreach(RCLASS_CONST_TBL(obj), dump_const_entry_i, (st_data_t)ctx);
|
542
|
+
yajl_gen_map_close(ctx->yajl);
|
543
|
+
}
|
544
|
+
#endif
|
545
|
+
|
546
|
+
ygh_id("super", RCLASS_SUPER(obj));
|
547
|
+
}
|
548
|
+
}
|
549
|
+
break;
|
550
|
+
|
551
|
+
case T_FILE:
|
552
|
+
yg_cstring("refs"); //ivars
|
553
|
+
yajl_gen_array_open(ctx->yajl);
|
554
|
+
if (RFILE(obj)->fptr) {
|
555
|
+
yg_id(RFILE(obj)->fptr->pathv);
|
556
|
+
yg_id(RFILE(obj)->fptr->tied_io_for_writing);
|
557
|
+
yg_id(RFILE(obj)->fptr->writeconv_asciicompat);
|
558
|
+
yg_id(RFILE(obj)->fptr->writeconv_pre_ecopts);
|
559
|
+
yg_id(RFILE(obj)->fptr->encs.ecopts);
|
560
|
+
yg_id(RFILE(obj)->fptr->write_lock);
|
561
|
+
}
|
562
|
+
yajl_gen_array_close(ctx->yajl);
|
563
|
+
break;
|
564
|
+
|
565
|
+
case T_FIXNUM:
|
566
|
+
ygh_int("val", NUM2LONG(obj));
|
567
|
+
break;
|
568
|
+
case T_FLOAT:
|
569
|
+
ygh_double("val", RFLOAT_VALUE(obj));
|
570
|
+
break;
|
571
|
+
// T(T_BIGNUM);
|
572
|
+
// T(T_RATIONAL); // refs too (num/den)...
|
573
|
+
// T(T_COMPLEX);
|
574
|
+
|
575
|
+
// T(T_DATA); // data of extensions, undumpable? maybe in some way mess with mark callback? (need to intercept rb_gc_mark :( )
|
576
|
+
// T(T_UNDEF);
|
577
|
+
default: break;
|
578
|
+
}
|
579
|
+
yajl_gen_map_close(ctx->yajl);
|
580
|
+
flush_yajl(ctx);
|
581
|
+
fprintf(ctx->file, "\n");
|
582
|
+
}
|
583
|
+
|
584
|
+
/*
|
585
|
+
* will be called several times (the number of heap slot, at current implementation) with:
|
586
|
+
* vstart: a pointer to the first living object of the heap_slot.
|
587
|
+
* vend: a pointer to next to the valid heap_slot area.
|
588
|
+
* stride: a distance to next VALUE.
|
589
|
+
*/
|
590
|
+
static int objspace_walker(void *vstart, void *vend, int stride, walk_ctx_t *ctx) {
|
591
|
+
ctx->walker_called++;
|
592
|
+
|
593
|
+
VALUE v = (VALUE)vstart;
|
594
|
+
for (; v != (VALUE)vend; v += stride) {
|
595
|
+
if (RBASIC(v)->flags) { // is live object
|
596
|
+
walk_live_object(v, ctx);
|
597
|
+
}
|
598
|
+
}
|
599
|
+
// return 1; //stop
|
600
|
+
return 0; // continue to iteration
|
601
|
+
}
|
602
|
+
|
603
|
+
|
604
|
+
static VALUE
|
605
|
+
rb_heapdump_dump(VALUE self, VALUE filename)
|
606
|
+
{
|
607
|
+
struct walk_ctx ctx;
|
608
|
+
memset(&ctx, 0, sizeof(ctx));
|
609
|
+
|
610
|
+
Check_Type(filename, T_STRING);
|
611
|
+
|
612
|
+
printf("Dump should go to %s\n", RSTRING_PTR(filename));
|
613
|
+
ctx.file = fopen(RSTRING_PTR(filename), "wt");
|
614
|
+
ctx.yajl = yajl_gen_alloc(NULL,NULL);
|
615
|
+
yajl_gen_array_open(ctx.yajl);
|
616
|
+
|
617
|
+
rb_objspace_each_objects(objspace_walker, &ctx);
|
618
|
+
|
619
|
+
yajl_gen_array_close(ctx.yajl);
|
620
|
+
flush_yajl(&ctx);
|
621
|
+
yajl_gen_free(ctx.yajl);
|
622
|
+
fclose(ctx.file);
|
623
|
+
|
624
|
+
printf("Walker called %d times, seen %d live objects.\n", ctx.walker_called, ctx.live_objects);
|
625
|
+
|
626
|
+
return Qnil;
|
627
|
+
}
|
628
|
+
|
629
|
+
|
630
|
+
void Init_heap_dump(){
|
631
|
+
printf("heap_dump extension loading\n");
|
632
|
+
//ruby-internal need to be required before linking us, but just in case..
|
633
|
+
rb_require("internal/node");
|
634
|
+
rb_require("yajl");
|
635
|
+
init_node_type_descrips();
|
636
|
+
|
637
|
+
CONST_ID(classid, "__classid__");
|
638
|
+
|
639
|
+
rb_mHeapDumpModule = rb_define_module("HeapDump");
|
640
|
+
rb_define_singleton_method(rb_mHeapDumpModule, "dump_ext", rb_heapdump_dump, 1);
|
641
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
//FIXME:!!!!! get this file from ruby source!
|
2
|
+
|
3
|
+
|
4
|
+
typedef struct {
|
5
|
+
char *ptr; /* off + len <= capa */
|
6
|
+
int off;
|
7
|
+
int len;
|
8
|
+
int capa;
|
9
|
+
} rb_io_buffer_t;
|
10
|
+
|
11
|
+
|
12
|
+
//#include "encoding.h" // and this also (rb_encoding + rb_econv_t)
|
13
|
+
//FIXME: nasty:
|
14
|
+
typedef int rb_encoding;
|
15
|
+
typedef struct rb_econv rb_econv_t;
|
16
|
+
|
17
|
+
typedef struct rb_io_t {
|
18
|
+
int fd; /* file descriptor */
|
19
|
+
FILE *stdio_file; /* stdio ptr for read/write if available */
|
20
|
+
int mode; /* mode flags: FMODE_XXXs */
|
21
|
+
rb_pid_t pid; /* child's pid (for pipes) */
|
22
|
+
int lineno; /* number of lines read */
|
23
|
+
VALUE pathv; /* pathname for file */
|
24
|
+
void (*finalize)(struct rb_io_t*,int); /* finalize proc */
|
25
|
+
|
26
|
+
rb_io_buffer_t wbuf, rbuf;
|
27
|
+
|
28
|
+
VALUE tied_io_for_writing;
|
29
|
+
|
30
|
+
/*
|
31
|
+
* enc enc2 read action write action
|
32
|
+
* NULL NULL force_encoding(default_external) write the byte sequence of str
|
33
|
+
* e1 NULL force_encoding(e1) convert str.encoding to e1
|
34
|
+
* e1 e2 convert from e2 to e1 convert str.encoding to e2
|
35
|
+
*/
|
36
|
+
struct rb_io_enc_t {
|
37
|
+
rb_encoding *enc;
|
38
|
+
rb_encoding *enc2;
|
39
|
+
int ecflags;
|
40
|
+
VALUE ecopts;
|
41
|
+
} encs;
|
42
|
+
|
43
|
+
rb_econv_t *readconv;
|
44
|
+
rb_io_buffer_t cbuf;
|
45
|
+
|
46
|
+
rb_econv_t *writeconv;
|
47
|
+
VALUE writeconv_asciicompat;
|
48
|
+
int writeconv_pre_ecflags;
|
49
|
+
VALUE writeconv_pre_ecopts;
|
50
|
+
int writeconv_initialized;
|
51
|
+
|
52
|
+
VALUE write_lock;
|
53
|
+
} rb_io_t;
|
data/heap_dump.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/heap_dump/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Vasily Fedoseyev"]
|
6
|
+
gem.email = ["vasilyfedoseyev@gmail.com"]
|
7
|
+
gem.description = %q{dump ruby 1.9 heap contents}
|
8
|
+
gem.summary = %q{dump heap to track reference leaks etc}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "heap_dump"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = HeapDump::VERSION
|
17
|
+
|
18
|
+
gem.required_ruby_version = '>=1.9.2'
|
19
|
+
#gem.platform = Gem::Platform::CURRENT # other than osx - maybe later
|
20
|
+
|
21
|
+
gem.extensions = "ext/heap_dump/extconf.rb"
|
22
|
+
|
23
|
+
gem.add_dependency "ruby-internal", '~>0.8.5'
|
24
|
+
gem.add_dependency 'yajl-ruby', '~>1.1'
|
25
|
+
gem.add_development_dependency "rake-compiler"
|
26
|
+
end
|
data/lib/heap_dump.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "heap_dump/version"
|
2
|
+
|
3
|
+
# need to require ruby-internal before our extension so that these extensions are loaded and linked
|
4
|
+
require 'internal/node'
|
5
|
+
require 'yajl'
|
6
|
+
|
7
|
+
#TODO: more cross-platform require for extension
|
8
|
+
require 'heap_dump.bundle'
|
9
|
+
|
10
|
+
module HeapDump
|
11
|
+
# Dumps ruby object space to file
|
12
|
+
def self.dump filename='dump.json'
|
13
|
+
return dump_ext(filename)
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heap_dump
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vasily Fedoseyev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-25 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ruby-internal
|
16
|
+
requirement: &70288827505440 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.5
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70288827505440
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yajl-ruby
|
27
|
+
requirement: &70288827504740 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.1'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70288827504740
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake-compiler
|
38
|
+
requirement: &70288827504140 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70288827504140
|
47
|
+
description: dump ruby 1.9 heap contents
|
48
|
+
email:
|
49
|
+
- vasilyfedoseyev@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions:
|
52
|
+
- ext/heap_dump/extconf.rb
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- ext/heap_dump/extconf.rb
|
61
|
+
- ext/heap_dump/heap_dump.c
|
62
|
+
- ext/heap_dump/ruby_io.h
|
63
|
+
- heap_dump.gemspec
|
64
|
+
- lib/heap_dump.rb
|
65
|
+
- lib/heap_dump/version.rb
|
66
|
+
homepage: ''
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.9.2
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.15
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: dump heap to track reference leaks etc
|
90
|
+
test_files: []
|
91
|
+
has_rdoc:
|