heap_dump 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.dll
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in heap_dump.gemspec
4
+ gemspec
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,6 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+
5
+ require 'rake/extensiontask'
6
+ Rake::ExtensionTask.new('heap_dump')
@@ -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
@@ -0,0 +1,3 @@
1
+ module HeapDump
2
+ VERSION = "0.0.1"
3
+ 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: