heap_dump 0.0.1
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.
- 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:
|