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 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: